【问题标题】:Accessing a component of a column in a DataGrid访问 DataGrid 中列的组件
【发布时间】:2014-09-02 13:47:41
【问题描述】:

我是一名专业的 Java 开发人员,并在 .NET 中作为试点项目完成了一些任务。

这是一个小型的发票应用程序,需要使用 WPF 和 EntityFramework 开发。

我的一项任务是在窗口中显示发票列表,并在单击任何发票的“编辑”时,我应该显示该发票的详细信息以及分配给该发票的发票项目。

以下是我显示发票项目的 XAML 代码片段。

<DataGrid x:Name="ProductGrid" AutoGenerateColumns="False" HorizontalAlignment="Stretch" 
            HorizontalContentAlignment="Stretch" ColumnWidth="*" Height="464" VerticalAlignment="Top" Margin="444,16,10,0" CanUserAddRows="false">
    <DataGrid.Columns>
        <DataGridTemplateColumn Width="55" Header="Selected">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox Margin="2,0,2,0" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                Checked="Product_Selected" Unchecked="Product_Deselected" IsChecked="{Binding Path=selected}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Width="60" Header="Quantity">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <xctk:IntegerUpDown x:Name="UPDOWN" Increment="1" Minimum="0" HorizontalAlignment="Center" ValueChanged="Quantity_Changed" 
                                        VerticalAlignment="Center" Width="50" Value="{Binding productQuantity, Mode=TwoWay}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Header="Product Name" Width="250" Binding="{Binding Path=productName}"/>
        <DataGridTextColumn Header="Weight" Binding="{Binding Path=productWeight}"/>
        <DataGridTextColumn Header="Size" Binding="{Binding Path=productSize}"/>
        <DataGridTextColumn Header="Sale price" Binding="{Binding Path=productSalePrice}"/>
    </DataGrid.Columns>
</DataGrid>

现在,我需要实现的是,当我选择一个复选框时,后面的代码应该自动将 IntegerUpDown 组件的值增加到 1。另外,如果我取消选择一个复选框,后面的代码应该自动重置IntegerUpDown 组件为 0。

以下是我的 Product_Selected 事件的代码片段。

private void Product_Selected(object sender, RoutedEventArgs e)
{
    var currRow = ProductGrid.CurrentItem; // Current row

    InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT
    if (sel != null)
    {
        if (sel.productQuantity == 0) // The user is trying to assign a new item to the invoice
        {
            int currentRowIndex = ProductGrid.Items.IndexOf(currRow); // Current row index
            DataGridRow currentRow = ProductGrid.ItemContainerGenerator.ContainerFromIndex(currentRowIndex) as DataGridRow;

            IntegerUpDown prop = ProductGrid.Columns[1].GetCellContent(currentRow) as IntegerUpDown; // Here I get a NULL for "prop"..!! :(
            prop.Value = 1; // Automatically increase the value of IntegerUpDown from zero to one
        }
    }
}

为此,我需要访问所选行的 IntegerUpDown 组件。不幸的是,我不知道这样做。

我希望你们中的一些 .NET 天才可以在这件事上帮助我。

非常感谢。

请注意, 阿塞拉。

【问题讨论】:

    标签: c# wpf xaml datagrid integerupdown


    【解决方案1】:

    好的,我已经有一段时间没有在这里回答任何问题了,但你的问题绝对值得关注。

    首先,关于这个:

    我的职业是 Java 开发人员

    忘记 java。

    您可能在 java 中习惯的大多数(如果不是全部)(相当繁琐和过于冗长)模式和范例在 C# 和 WPF 中几乎没有用处或根本没有用处。

    这是因为,与 java 相比,C# 是一种现代的专业级语言,具有许多语言特性,可轻松开发并大大减少样板代码。

    除此之外,WPF 是一个高级的专业级 UI 框架,具有大量高级功能(最著名的是 Data BindingData Templating),允许您创建高级抽象并完全分离您的代码和来自 UI 组件的应用程序逻辑,在不引入讨厌的结构或不必要的耦合的情况下实现最大的灵活性。

    这种抽象是通过实现称为MVVM 的模式来实现的。这种模式在大多数(如果不是全部)现代 UI 技术(包括 Web 和非 Web)中相当普遍,但在 Java 世界中除外,Java 世界似乎(毫不奇怪)认为仍然是 1990 年。

    因此,我建议您花点时间了解并接受The WPF Mentality,而不是试图从遗留技术中锤炼概念并使它们以某种方式融入 WPF。

    现在,我发现您的代码中存在一些缺陷,无论是在代码本身方面,还是在您用于编写代码的理念/方法方面。

    首先,在 XAML 中出现 Height="464" Margin="444,16,10,0" 等内容表明您使用 Visual Studio 设计器来构建此类 UI。这作为一种学习练习很有用,但对于生产代码来说非常不鼓励,原因是 here

    我建议您花时间正确学习 XAML 并查看 this tutorial 以了解 WPF 布局系统的工作原理,以及如何编写与分辨率无关、可自动调整的 WPF UI,而不是固定大小、固定-即使在调整包含窗口大小时也无法正确调整的位置布局。

    同样,开发人员在使用任何其他技术时在 WPF 中犯的典型错误在于他们如何处理事物,而不是他们如何编码。让我们分析一下您的代码:

     var currRow = ProductGrid.CurrentItem; // Current row
    
     InvoiceItemsDTO sel = (InvoiceItemsDTO)currRow; // Current row DTO OBJECT
    
     if (sel != null)
     {
        //...
     }
    

    乍一看,这段代码(除了它可以被缩短)还不错。您正在检索基础数据对象,而不是试图弄乱 UI 元素。这是 WPF 中的正确方法。您需要操作您的数据项,而不是 UI。

    让我们把它改写成更像 C# 的方式:

    var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
    if (row != null)
    {
       //...
    }
    

    注意: 上面的代码展示了 C# 语言级别的特性(在本例中为 as operator)如何帮助减少样板代码的示例(我们现在有 2 行代码而不是 3 行)通过允许漂亮的代码,否则在 Java 等劣质技术中需要 bunch of horrible hacks

    好的,到目前为止一切都很好,但是你会从这种以数据为中心的想法溜走,因为某种原因试图操纵 UI。

    考虑一下:您正在尝试“更新对应于当前选定行的IntegerUpDownValue 属性”

    但是,您的 XAML 显示 IntegerUpDownValue 属性实际上是通过双向数据绑定绑定到一个底层数据项中名为productQuantity的属性。

    所以,基本上,您的代码会产生如下结果:

    get Data Item -> get UI item -> update UI item -> DataBinding updates Data Item.
    

    看到了吗?您正在创建一个完全不必要的间接。相反,只需对数据项而不是 UI 进行操作,然后让双向数据绑定处理其余的工作。这就是 WPF 的理念。

    var row = ProductGrid.CurrentItem as InvoiceItemsDTO;
    if (row != null)
    {
        row.productQuantity++;
    }
    

    看看当您使用现代技术时,生活变得多么轻松?

    但它甚至还没有结束。

    您的 XAML 还显示您正在处理的 CheckBoxIsChecked 属性绑定到基础数据项中名为 selected 的属性:

    <CheckBox [...] IsChecked="{Binding Path=selected}"/>
    

    这意味着你的InvoiceItemsDTO 类有一个public bool selected {...} 属性,对吗?所以,与其在 UI 级别处理事件,(再次),为什么不简单地把逻辑放在它真正属于的地方,并摆脱 UI 依赖,有效地让你的代码更可测试、更清洁、更漂亮?

    public class InvoiceItemsDTO
    {
        private bool _selected;
        public bool Selected
        {
            get { return _selected; }
            set 
            {
                _selected = value;
    
                //This is where your code should be.
                if (value)
                   ProductQuantity++;
                else
                   ProductQuantity--;
            }
        }
    }
    

    顺便说一句,请注意使用正确的外壳。 camelCasing 很糟糕,因此仅在 C# 中为private 成员保留。不是public 的。

    看到了吗?简单、干净、可测试且美观,而且它只是有效

    但是,如何它是如何工作的?

    1 - 当用户单击 Checkbox 时,IsChecked 值被更新。

    2 - WPF 的 DataBinding 将 InvoiceItemsDTO.Selected 属性的值更新为 true

    3 - 您的代码将 +1 添加到 ProductQuantity 属性。

    4 - WPF 的 DataBinding 反映 UI 中的 ProductQuantity 更改,前提是您有正确的 Implemented INotifyPropertyChange

    取消选中复选框时会发生相同的工作流程,但具有false 值。

    这消除了对事件处理程序、强制转换、代码隐藏和其他需要无用样板并引入不必要的、不希望的耦合的繁琐方法的需求。

    底线:C# Rocks。 WPF 摇滚。 java 是传统的。

    如果您需要进一步的帮助,请告诉我。

    【讨论】:

    • 感谢您出色而详细的回答。它真的帮助了我,我很快就解决了我的问题。
    猜你喜欢
    • 2016-04-18
    • 2014-10-28
    • 2013-10-24
    • 2012-11-18
    • 1970-01-01
    • 2015-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多