IMultiValueConverter在DataGridTemplateColumn
使用时我具有被绑定到DataGrid在可观察到的集合对象的一个嵌套一束从限值接收空字符串。 IMultiValueConverter用于从两个属性收集信息;当我在DataGridTextColumn中这样做,但在DataGridTemplateColumn中失败时,这是有效的。这是一个复杂的情况,所以我会进一步分解它并发布我的代码的简化版本。IMultiValueConverter在DataGridTemplateColumn
每个列表项的嵌套如下: User_Ext类继承具有User_Rank类的属性的User类,该类又具有User类的属性。不幸的是,这种嵌套对于程序设置的方式是必需的。
还有一个Rank对象的单独列表,绑定为DataGridTemplateColumn中ComboBox的选项,它将从ObservableCollection中的项目切换等级。
秩有一个布尔属性Require_License和用户有一个字符串属性许可证。如果许可证是空白并且Require_License是真的,那么这个想法是使用IMultiValueConverter的单元格的亮点。
我已经在我的示例代码既包括一个DataGridTextColumn和DataGridTemplateColumn这里更容易表现出发生了什么。
对于绑定到许可证的DataGridTextColumn,转换器会在我编辑Rank单元格的ComboBox选项或许可证文本的内容后立即触发,并且所有信息都会传送。
对于DataGridTemplateColumn势必许可,转换器仅火灾时,我改变了组合框的选择,而不是当我编辑的许可证文本。最重要的是,当转换器捕获组合框更改时,许可证的值是一个空字符串(不是UnsetValue)而不是单元格的内容,而第二个绑定值(Rank选择)是正确的。我还应该在这里提到,所做的任何更改都正确更新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年7月25日
我已经玩了更多,我我发现我和DataGridCheckboxColumn有同样的问题。现在,我不太了解控件的内部功能,但这是我观察到的。
- IMultiValueConverter确实可以看到单元的初始值。
- 当在DataGrid中使用带有TextBox的DataGridTemplateColumn时,在DataGrid中进行更改时绑定到DataGrid的ItemsSource,UserImport不会更新。但是,当绑定的许可证更改时,IMultiValueConverter不会触发。它在绑定的user_Rank.rank发生更改(在DataGridComboBoxColumn中)时执行,但即便如此,许可证更改也未反映出来。
- 如果我尝试使用DataGridCheckBoxColumn,情况也是如此。
- 如果我单击列标题,导致发生排序列,IMultiValueConverter将在排序时选取License的值,但之后不会更新。
- 如果我在其中使用带有DatePicker的DataGridTemplateColumn,则具有与其他情况相同的问题:IMultiValueConverter不会接受更改...除了当它发生更改时。如果我疯狂点击,在文本区域内,在日期选择器按钮上,在日期选择器中选择一个日期,点击日期选择器按钮右侧的小空间,然后点击离开框,我发现那有时 IMultiValueConverter会触发。有时是在DatePicker中点击日期,有时候是点击按钮旁边的空间,有时候是当点击另一个单元后点击了该按钮旁边的空间。
因此,我在单元格中更新了一个值,在绑定对象中进行了更新,但在某些情况下除了IMultiValueConverter没有被拾取之外。就好像数据存储的第三个点一样。我想知道(再次,没有控制的内在知识)当单击时,某些单元格内容是否只更新单元格内的控件,而不更新单元格本身。
单元格中的控件是否可能具有与单元格分开测量的“值”,直到它“更新”单元格的“值”?如果是这样,并且控件正在更新绑定对象而不更新单元格,并且IMultiValueConverter正在查看单元格,而不是单元格中的绑定对象或控件......也许这会是我的问题?
请别人告诉我我是多么的错,接下来是对这种现象的解释。 :)
编辑 我找到了我要发布的解决方案。
我找到了解决方案。
尽管我不确定为什么它能够正确地找到user_Rank.rank而不是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。
我还没有完全解决这个谜,但我想出了一些东西,以便我可以继续我的程序。如果有人有更明智的答案,请随时为我们安排。 :)
这是因为您的许可证属性在更改时不会引发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的的。
试过,但仍然是同样的问题。我不得不改变行> to >因为它给了我一个错误,否则。我认为ObservableCollection可以处理很多属性更改通知,因为大多数其他属性更改会在不实现INotifyPropertyChanged的情况下继续进行,并且假定它在DataGridTextColumn中工作,但不在DataGridTemplateColumn中工作。 – Pierre
@Pierre,如果您已经做出更改以确保PropertyChanged事件在正确拼写属性名称时正确解锁,则无论何时更改许可证,我都无法在此处找到该错误。你的XAML是完美的。 – hoodaticus
@Pierre,ObservableCollection仅在项目被插入,删除或替换时通知UI。它不会跟踪它包含的对象的属性。只有PropertyChanged可以做到这一点。我知道这是因为我从头开始写了很多我自己的免费线程ObservableCollection替代品。你不相信你的问题的唯一正确答案。 – hoodaticus