C# WPF双向绑定不';控件修改后才能工作
我正在尝试使用数据中的集合同步DataGrid中的选择。我有一个小怪癖 当我在DataGrid中更改选择时,更改会写入我的数据集合,到目前为止效果良好。然后,如果数据收集更改,则会按照预期更新我的DataGrid中的选择。但是,如果在修改DataGrid之前修改数据,则DataGrid选择不会更新 第一个工作案例的示例 第二个非工作案例的示例 代码C# WPF双向绑定不';控件修改后才能工作,c#,wpf,data-binding,C#,Wpf,Data Binding,我正在尝试使用数据中的集合同步DataGrid中的选择。我有一个小怪癖 当我在DataGrid中更改选择时,更改会写入我的数据集合,到目前为止效果良好。然后,如果数据收集更改,则会按照预期更新我的DataGrid中的选择。但是,如果在修改DataGrid之前修改数据,则DataGrid选择不会更新 第一个工作案例的示例 第二个非工作案例的示例 代码 使用系统集合; 使用System.Collections.ObjectModel; 使用System.Windows; 使用System.Win
使用系统集合;
使用System.Collections.ObjectModel;
使用System.Windows;
使用System.Windows.Controls;
命名空间测试床
{
公共类小部件
{
公共字符串名称{get;set;}
}
公共类数据
{
公共静态数据实例{get;}=new Data();
公共ObservableCollection小部件{get;set;}=new ObservableCollection();
公共IList SelectedWidgets{get;set;}=new ObservableCollection();
数据()
{
添加(newwidget(){Name=“Widget 1”});
添加(newwidget(){Name=“Widget 2”});
添加(newwidget(){Name=“Widget 3”});
}
};
公共类BindableDataGrid:DataGrid
{
公共静态只读DependencyProperty SelectedItemsProperty=DependencyProperty.Register(
“SelectedItems”,
类型(IList),
类型(BindableDataGrid),
新属性元数据(默认值(IList));
公共新IList SelectedItems
{
获取{return(IList)GetValue(SelectedItemsProperty);}
set{SetValue(selecteditemsprroperty,value);}
}
选择更改时受保护的覆盖无效(SelectionChangedEventArgs e)
{
基础。选举变更(e);
SetCurrentValue(SelectedItemsProperty,base.SelectedItems);
}
}
公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
}
私有无效按钮1_单击(对象发送者,路由目标){按钮_单击(0);}
私有无效按钮2_单击(对象发送者,路由目标){按钮_单击(1);}
私有无效按钮3_单击(对象发送者,路由目标){按钮_单击(2);}
已单击专用无效按钮(整数索引)
{
Data=Data.Instance;
Widget Widget=data.Widgets[index];
if(data.SelectedWidgets.Contains(widget))
{
data.SelectedWidgets.Remove(widget);
}
其他的
{
data.SelectedWidgets.Add(widget);
}
}
}
}
和标记
<Window
x:Class="Testbed.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Testbed"
Title="MainWindow"
Height="480" Width="640"
DataContext="{Binding Source={x:Static test:Data.Instance}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="210" />
<ColumnDefinition Width="5" />
<ColumnDefinition MinWidth="210" />
<ColumnDefinition Width="5" />
<ColumnDefinition MinWidth="210" />
</Grid.ColumnDefinitions>
<!-- Change selection through data -->
<StackPanel Grid.Column="0">
<Button Content="Select Widget 1" Click="Button1_Click"/>
<Button Content="Select Widget 2" Click="Button2_Click"/>
<Button Content="Select Widget 3" Click="Button3_Click"/>
</StackPanel>
<!-- Current selection in data -->
<DataGrid Grid.Column="2"
ItemsSource="{Binding SelectedWidgets}"
IsReadOnly="true">
</DataGrid>
<!-- Change selection through UI -->
<test:BindableDataGrid Grid.Column="4"
SelectionMode="Extended"
ColumnWidth="*"
ItemsSource="{Binding Widgets}"
SelectedItems="{Binding SelectedWidgets, Mode=TwoWay}"
IsReadOnly="true">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="CornflowerBlue"/>
</Style.Resources>
</Style>
</DataGrid.RowStyle>
</test:BindableDataGrid>
</Grid>
</Window>
发生此问题的原因是您没有处理BindableDataGrid.SelectedItems集合的通知。 在第一种情况下,您不需要手动处理它们,因为您实际上从基本DataGrid类获取SelectedItems集合,并通过OnSelectionChanged方法调用将其传递给视图模型。基本DataGrid本身处理此集合的通知 但是,如果先单击按钮,SelectedItems属性将获得一个新集合,而基本DataGrid对此一无所知。 我认为您需要处理propertyChangedCallback,并处理所提供集合的通知以手动更新网格中的选择。请参阅以下演示该概念的代码。请注意,为了简单起见,我已经重命名了该属性,但仍然没有调试它
public static readonly DependencyProperty SelectedItemsNewProperty = DependencyProperty.Register(
"SelectedItemsNew",
typeof(IList),
typeof(BindableDataGrid), new PropertyMetadata(OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
BindableDataGrid bdg = (BindableDataGrid)d;
if (e.OldValue as INotifyCollectionChanged != null)
(e.NewValue as INotifyCollectionChanged).CollectionChanged -= bdg.BindableDataGrid_CollectionChanged;
if (Object.ReferenceEquals(e.NewValue, bdg.SelectedItems))
return;
if( e.NewValue as INotifyCollectionChanged != null )
(e.NewValue as INotifyCollectionChanged).CollectionChanged += bdg.BindableDataGrid_CollectionChanged;
bdg.SynchronizeSelection(e.NewValue as IList);
}
private void BindableDataGrid_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
SynchronizeSelection((IList)sender);
}
private void SynchronizeSelection( IList collection) {
SelectedItems.Clear();
if (collection != null)
foreach (var item in collection)
SelectedItems.Add(item);
}
这是因为新的
SelectedItems
属性在设置时从不更新基本SelectedItems
。当然,问题是,MultiSelector.SelectedItems
是只读的。它是专门设计为不可设置的,但它也是设计为可更新的
代码之所以有效,是因为当您通过BindableDataGrid
更改选择时,SelectedWidgets
会被DataGrid
的内部SelectedItemsCollection
替换。在此之后,您将添加和删除该集合,因此它将更新DataGrid
当然,如果您还没有更改选择,这将不起作用,因为OnSelectionChanged
在此之前不会运行,因此SetCurrentValue
从未被调用,因此绑定从未更新SelectedWidgets
。但这很好,您所要做的就是调用SetCurrentValue
,作为BindableDataGrid
初始化的一部分
将此添加到BindableDataGrid
:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
SetCurrentValue(SelectedItemsProperty, base.SelectedItems);
}
不过要小心,因为如果在初始化后的某个时间尝试设置SelectedItems
,此选项仍然会中断。如果您可以将其设置为只读,那就太好了,但这会阻止它在数据绑定中使用。因此,请确保您的绑定使用单向源,而不是双向源:
<test:BindableDataGrid Grid.Column="4"
SelectionMode="Extended"
ColumnWidth="*"
ItemsSource="{Binding Widgets}"
SelectedItems="{Binding SelectedWidgets, Mode=OneWayToSource}"
IsReadOnly="true">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="CornflowerBlue"/>
</Style.Resources>
</Style>
</DataGrid.RowStyle>
</test:BindableDataGrid>
@雷默博士的回答为我指明了正确的方向。然而,它归结为接受以下事实:源数据集合正被DataGrid.SelectedItems集合所取代。在第一次修改之后,它会绕过
OnPropertyChanged
,因为绑定的两端实际上是同一个对象
我不想替换源集合,所以我找到了另一个同步集合内容的解决方案。它的好处是更直接
Wh
<test:BindableDataGrid Grid.Column="4"
SelectionMode="Extended"
ColumnWidth="*"
ItemsSource="{Binding Widgets}"
SelectedItems="{Binding SelectedWidgets, Mode=OneWayToSource}"
IsReadOnly="true">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="CornflowerBlue"/>
</Style.Resources>
</Style>
</DataGrid.RowStyle>
</test:BindableDataGrid>
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(IList),
typeof(BindableDataGrid),
new PropertyMetadata(default(IList), null, (o, v) => ((BindableDataGrid)o).CoerceBindableSelectedItems(v)));
protected object CoerceBindableSelectedItems(object baseValue)
{
return base.SelectedItems;
}
public class BindableDataGrid : DataGrid
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof(IList),
typeof(BindableDataGrid),
new PropertyMetadata(OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindableDataGrid bdg = (BindableDataGrid) d;
if (bdg.initialized) return;
bdg.initialized = true;
bdg.source = (IList) e.NewValue;
bdg.target = ((DataGrid) bdg).SelectedItems;
((INotifyCollectionChanged) e.NewValue).CollectionChanged += bdg.OnCollectionChanged;
}
public new IList SelectedItems
{
get { return (IList) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
IList source;
IList target;
bool synchronizing;
bool initialized;
private void OnSourceChanged()
{
if (synchronizing) return;
synchronizing = true;
target.Clear();
foreach (var item in source)
target.Add(item);
synchronizing = false;
}
private void OnTargetChanged()
{
if (synchronizing) return;
synchronizing = true;
source.Clear();
foreach (var item in target)
source.Add(item);
synchronizing = false;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnSourceChanged();
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
OnTargetChanged();
}
}