0

我在绑定到 DataGrid 的 Observable 集合中有一组嵌套的对象。IMul​​tiValueConverter 用于从两个属性收集信息;这在我在 DataGridTextColumn 中这样做时有效,但在 DataGridTemplateColumn 中失败。这是一个复杂的情况,所以我将进一步分解并发布我的代码的简化版本。

每个列表项的嵌套如下: User_Ext 类继承了 User 类,该类具有 User_Rank 类的属性,而 User_Rank 类又具有 User 类的属性。不幸的是,这种嵌套对于程序的设置方式是必要的。

还有一个单独的 Rank 对象列表,它被绑定为 DataGridTemplateColumn 中的 ComboBox 的选项,它将从 ObservableCollection 中的项目切换 Rank。

Rank 有一个布尔属性 Require_License,而 User 有一个字符串属性 License。这个想法是使用 IMultiValueConverter 突出显示单元格,如果许可证为空白且 Require_License 为真。

我在这里的示例代码中包含了 DataGridTextColumn 和 DataGridTemplateColumn,以便更轻松地演示正在发生的事情。

对于绑定到 License 的 DataGridTextColumn,只要我编辑 Rank 单元格的 ComboBox 选项或 License 文本的内容,转换器就会触发,并且所有信息都会继续。

对于绑定到许可证的 DataGridTemplateColumn,转换器仅在我更改 ComboBox 选项时触发,而不是在我编辑许可证文本时触发。最重要的是,当转换器捕获 ComboBox 更改时,许可证的值是一个空字符串(不是 UnsetValue)而不是单元格的内容,而第二个绑定值(排名选择)是正确的。我还应该在这里提到,所做的任何更改都会正确更新 ObservableCollection 中的项目,因此绑定的这方面工作正常。

我已经尽我所能在这里进行搜索,但我似乎无法找到解决这个问题的方法。

如果有任何问题或被遗忘,我会提前道歉,但我不得不去掉我作品的识别标记,并希望尽可能多地包括在内,因为我不确定问题出在哪里。但是,如果我的代码有助于将其复制到项目中并对其进行测试,那么我的代码是可操作的。如果我过于冗长,我也很抱歉;这是我在这里的第一个问题,我不确定用多少措辞来描述这种情况是合适的。

至于为什么我不只使用功能性 DataGridTextColumn,还有更多的事情我需要落实到位,我需要 DataGridTemplateColumn 的灵活性。

这是我的 XAML:

<Window x:Class="Tool.Transfer"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:Tool"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        >
    <Window.Resources>
        <src:MatchMultiCellColourConverter x:Key="MatchMultiCellColourConverter"/>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding UserImport, Mode=TwoWay}" AutoGenerateColumns="False">
            <DataGrid.Resources>
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black"/>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Header="User" Binding="{Binding User_Code}"/>
                <DataGridComboBoxColumn Header="Rank" DisplayMemberPath="Desc" SelectedValuePath="Code" SelectedItemBinding="{Binding user_Rank.rank}">
                    <DataGridComboBoxColumn.ElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=TargetRanks, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
                            <Setter Property="DisplayMemberPath" Value="Desc"/>
                            <Setter Property="Background" Value="White"/>
                        </Style>
                    </DataGridComboBoxColumn.ElementStyle>
                    <DataGridComboBoxColumn.EditingElementStyle>
                        <Style TargetType="{x:Type ComboBox}">
                            <Setter Property="ItemsSource" Value="{Binding Path=TargetRanks, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
                            <Setter Property="DisplayMemberPath" Value="Desc"/>
                        </Style>
                    </DataGridComboBoxColumn.EditingElementStyle>
                </DataGridComboBoxColumn>

                <DataGridTextColumn Header="TextColumn License" Binding="{Binding License}">
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Style.Setters>
                                <Setter Property="Background">
                                    <Setter.Value>
                                        <MultiBinding Converter="{StaticResource MatchMultiCellColourConverter}">
                                            <Binding Path="License"/>
                                            <Binding Path="user_Rank.rank"/>
                                        </MultiBinding>
                                    </Setter.Value>
                                </Setter>
                            </Style.Setters>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

                <DataGridTemplateColumn Header="TemplateColumn License">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding License, UpdateSourceTrigger=PropertyChanged}">
                                <TextBox.Style>
                                    <Style TargetType="{x:Type TextBox}">
                                        <Style.Setters>
                                            <Setter Property="Background">
                                                <Setter.Value>
                                                    <MultiBinding Converter="{StaticResource MatchMultiCellColourConverter}">
                                                        <Binding Path="License"/>
                                                        <Binding Path="user_Rank.rank"/>
                                                    </MultiBinding>
                                                </Setter.Value>
                                            </Setter>
                                        </Style.Setters>
                                    </Style>
                                </TextBox.Style>
                            </TextBox>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>

        </DataGrid>
    </Grid>
</Window>

还有我的 C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace Tool
{
    public partial class Transfer
    {
        private ObservableCollection<User_Ext> _userImport = null;
        public ObservableCollection<User_Ext> UserImport
        {
            get
            {
                if (_userImport == null)
                {
                    _userImport = new ObservableCollection<User_Ext>();
                }
                return _userImport;
            }
            set { _userImport = value; }
        }

        private ObservableCollection<Rank> _targetRanks = null;
        public ObservableCollection<Rank> TargetRanks
        {
            get
            {
                if (_targetRanks == null)
                {
                    _targetRanks = new ObservableCollection<Rank>();
                }
                return _targetRanks;
            }
            set { _targetRanks = value; }
        }

        public Transfer()
        {
            Rank r1 = new Rank(); r1.Code = "R1"; r1.Desc = "Rank1"; r1.Require_License = false;
            Rank r2 = new Rank(); r2.Code = "R2"; r2.Desc = "Rank2"; r2.Require_License = true;

            User a = new User(); a.User_Code = "A"; a.License = ""; a.user_Rank = new User_Rank(); a.user_Rank.rank = r1;
            User b = new User(); b.User_Code = "B"; b.License = ""; b.user_Rank = new User_Rank(); b.user_Rank.rank = r2;


            TargetRanks.Add(r1); TargetRanks.Add(r2);
            UserImport.Add(new User_Ext(a)); UserImport.Add(new User_Ext(b));

            InitializeComponent();
        }
    }

    public class MatchMultiCellColourConverter : IMultiValueConverter
    {
        #region IValueConverter Members

        public object Convert(object[] value, Type targetRank, object parameter, System.Globalization.CultureInfo culture)
        {
            if (targetRank != typeof(Brush))
                throw new InvalidOperationException("The target must be a Brush");

            bool pass = false;

            if ( value[0] != DependencyProperty.UnsetValue && value[1] != DependencyProperty.UnsetValue)
            {
                String l = (String)value[0];

                Rank r = (Rank)value[1];
                pass = !((l ?? "") == "" && r.Require_License);
            }
            return pass ? Brushes.White : Brushes.Pink;
        }

        public object[] ConvertBack(object value, Type[] targetRank, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    public class User_Ext : User, INotifyPropertyChanged
    {
        private bool _isComplete;
        public bool IsComplete
        {
            get { return _isComplete; }
            set
            {
                _isComplete = value;
                NotifyPropertyChanged("IsComplete");
            }
        }

        public User_Ext(User u) : base(u)
        {
            IsComplete = false;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }

    public class User
    {
        public string User_Code { get; set; }
        public string License { get; set; }
        public User_Rank user_Rank { get; set; }

        public User() { }

        public User(User u) 
        {
            User_Code = u.User_Code;
            License = u.License;
            user_Rank = u.user_Rank;
        }
    }

    public class User_Rank
    {
        public Rank rank { get; set; }
    }

    public class Rank
    {
        public string Code { get; set; }
        public string Desc { get; set; }
        public bool Require_License { get; set; }
    }

}

编辑2017-07-25

我一直在玩它,我发现 DataGridCheckboxColumn 也有同样的问题。现在,我对控件的内部功能知之甚少,但这就是我所观察到的。

- IMultiValueConverter 确实看到了单元格的初始值。

-当使用带有 TextBox 的 DataGridTemplateColumn 时,绑定到 DataGrid 的 ItemsSource UserImport 会在 DataGrid 中进行更改时得到更新。但是,当绑定的许可证更改时,IMultiValueConverter 不会触发。当绑定的 user_Rank.rank 更改(在 DataGridComboBoxColumn 中)时,它会这样做,但即便如此,许可证更改也不会反映。

- 如果我尝试使用 DataGridCheckBoxColumn,也是如此。

- 如果我单击列标题,导致发生列排序,IMultiValueConverter 将在排序时获取许可证的值,但之后不会更新。

- 如果我在其中使用带有 DatePicker 的 DataGridTemplateColumn,我会遇到与其他情况相同的问题:IMultiValueConverter 不会接受更改……除非它这样做。如果我在文本区域内疯狂单击日期选择器按钮,在日期选择器中选择一个日期,单击日期选择器按钮右侧的小空间,然后单击远离框,我发现有时IMultiValueConverter 会触发。有时是在我单击 DatePicker 中的日期时,有时是在我单击按钮旁边的那个空间时,有时是在我单击按钮旁边的那个空间单击另一个单元格时。

所以,我在单元格中更新了一个值,在绑定对象中更新,但不知何故没有被 IMultiValueConverter 拾取,除非在某些情况下。就好像数据被存储在第三个位置一样。我想知道(再次,没有控件的内部知识)当您单击离开时,某些单元格内容是否仅更新单元格内的控件而不更新单元格本身。

单元格内的控件是否有可能将其“值”与单元格分开测量,直到它“更新”单元格的“值”?如果是这种情况,并且控件正在更新绑定对象而不更新单元格,并且 IMultiValueConverter 正在查看单元格而不是绑定对象或单元格内的控件......也许这将是我的问题?

请有人告诉我我错了,然后解释这种现象。:)

编辑 我找到了一个我将发布的解决方案。

4

2 回答 2

0

我找到了解决方案。

虽然我不确定为什么它能够正确找到 user_Rank.rank 而不是 License,但由于它们绑定到同一个对象,似乎在寻找 License 时迷路了。

如果我让它查看它自己的内容,无论如何它都绑定到对象,它可以将它正确地传送到 IMultiValueConverter。

我稍微更改了 DataGridTemplateColumn 代码来做到这一点:

<DataGridTemplateColumn Header="TemplateColumn License">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBox Text="{Binding License}">
                <TextBox.Style>
                    <Style TargetType="{x:Type TextBox}">
                        <Style.Setters>
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource MatchMultiCellColourConverter}">
                                        <Binding Path="Text" RelativeSource="{RelativeSource Self}"/>
                                        <Binding Path="user_Rank.rank"/>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style.Setters>
                    </Style>
                </TextBox.Style>
            </TextBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

现在 IMultiValueConverter 立即获取每个更改。

这可以应用于我提供的其他示例。对于 DataGridCheckBoxColum,我用于样式的 TargetType 是 DataGridCell,所以我使用 Path="Content.IsChecked" 来访问 CheckBox。

我还没有完全解开这个谜团,但我想出了一些办法,这样我就可以继续我的程序了。如果有人有更聪明的答案,请随时为我们提供。:)

于 2017-07-25T20:07:37.070 回答
0

这是因为您的 License 属性在更改时不会引发 PropertyChanged 事件。

改变这个:

public class User
{
    public string User_Code { get; set; }
    public string License { get; set; }
    public User_Rank user_Rank { get; set; }

    public User() { }

    public User(User u) 
    {
        User_Code = u.User_Code;
        License = u.License;
        user_Rank = u.user_Rank;
    }
}

对此:

public class User : INotifyPropertyChanged
{
    public string User_Code { get; set; }
    string _license;
    public string License 
    { 
       get { return _license; }; 
       set 
       {  
           _license = value;
           var handler = PropertyChanged;
           if (handler != null) 
              handler(this, new PropertyChangedEventArgs("License"));
       } 
    }
    public User_Rank user_Rank { get; set; }

    public User() { }

    public event PropertyChangedEventHandler PropertyChanged;

    public User(User u) 
    {
        User_Code = u.User_Code;
        License = u.License;
        user_Rank = u.user_Rank;
    }
}

然后清理派生类中 INotifyPropertyChanged 的​​重新实现。

于 2017-07-21T15:53:59.773 回答