使用附加 属性 从 View 更新 ViewModel
Update ViewModel from View with an attached property
我想save/load列订单上DataGrid
。我正在尝试在我的 ViewModel 和 View 之间绑定一个包含 DataGrid
列索引的 StringCollection
。我可以在使用附加属性更新视图的 ViewModel 中成功设置 StringCollection
,但是当我使用 UI 更改视图中的列顺序时,ViewModel 不会更新以反映新的列顺序。如何让我的 ViewModel 监听 View 的变化?
我的xaml
<DataGrid ItemsSource="{Binding MyObservableCollection}"
attach:DataGridColumnChanger.ColumnOrder="{Binding ColumnOrders, Mode=TwoWay}">
</DataGrid>
我的附件属性
public class DataGridColumnChanger : DependencyObject
{
#region dependency properties
public static StringCollection GetColumnOrder(DependencyObject obj)
{
return (StringCollection)obj.GetValue(ColumnOrderProperty);
}
public static void SetColumnOrder(DependencyObject obj, StringCollection value)
{
obj.SetValue(ColumnOrderProperty, value);
}
public static readonly DependencyProperty ColumnOrderProperty = DependencyProperty.RegisterAttached("ColumnOrder",
typeof(StringCollection), typeof(DataGridColumnChanger), new UIPropertyMetadata(new StringCollection(), new PropertyChangedCallback(OnColumnOrderChange)));
#endregion
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as DataGrid;
if (element != null)
{
try
{
StringCollection collection = (StringCollection)e.NewValue;
if (collection != null && collection.Count > 0)
{
for (int j = 0; j <= element.Columns.Count - 1; j++)
{
int index = Convert.ToInt32(collection[j]);
element.Columns[j].DisplayIndex = index;
}
}
}
catch
{
Console.WriteLine("Error");
}
}
}
}
我的视图模型
private StringCollection columnOrders = new StringCollection();
public System.Collections.Specialized.StringCollection ColumnOrders
{
get => this.columnOrders;
set
{
this.columnOrders = value;
this.RaisePropertyChanged("ColumnOrders");
}
}
您的附加行为必须监听附加的 DataGrid.ColumnDisplayIndexChanged
事件。处理此事件并同步附加 DataGrid
:
的源集合
private static ConditionalWeakTable<DataGrid, DataGrid> RegisteredInstances { get; } = new ConditionalWeakTable<DataGrid, DataGrid>();
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (element is DataGrid dataDrid)
{
if (!RegisteredInstances.TryGetValue(dataDrid, out _))
{
RegisteredInstances.Add(dataDrid, dataDrid);
dataDrid.ColumnDisplayIndexChanged += OnDataGridColumnOrderChanged;
}
try
{
...
}
catch
{
...
}
}
}
private static void OnDataGridColumnOrderChanged(object sender, DataGridColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
if (RegisteredInstances.TryGetValue(dataDrid, out _))
{
StringCollection newColumnOrderCollection = new StringCollection();
// TODO::Populate newColumnOrderCollection using e.Column.DisplayIndex
// and raise property changed by assigning the collection to the OrderCollection property
}
}
您附加的 属性 实现仅对集合的更改做出反应。为了对用户界面的变化做出反应,您必须订阅 DataGrid.ColumnDisplayIndexChanged
事件。
下面是如何从您的代码开始执行此操作的示例。你没有指定,如果你想在每次显示索引改变时创建一个新的集合,所以在下面,我重新使用这个集合。
请注意,如果您的绑定集合为空或没有 all 列的项目,则集合现在从 DataGrid
初始化,这将容易出错。该集合在 Loaded
处理程序事件中初始化,因为在 DataGrid
完全加载之前,列将 DisplayIndex
设置为 -1
。
The DisplayIndex property has a default value of -1 before it is added to the DataGrid.Columns collection. This value is updated when the column is added to the DataGrid.
public class DataGridColumnChanger : DependencyObject
{
#region dependency properties
public static StringCollection GetColumnOrder(DependencyObject obj)
{
return (StringCollection)obj.GetValue(ColumnOrderProperty);
}
public static void SetColumnOrder(DependencyObject obj, StringCollection value)
{
obj.SetValue(ColumnOrderProperty, value);
}
public static readonly DependencyProperty ColumnOrderProperty = DependencyProperty.RegisterAttached("ColumnOrder",
typeof(StringCollection), typeof(DataGridColumnChanger), new UIPropertyMetadata(new StringCollection(), OnColumnOrderChange));
#endregion
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var dataGrid = (DataGrid)obj;
if (e.NewValue == null)
{
// No collection set, unsubscribe from the event.
dataGrid.ColumnDisplayIndexChanged -= OnDisplayIndexChanged;
}
else
{
// A new collection was set, subscribe to the event to sync it with user driven reordering.
dataGrid.ColumnDisplayIndexChanged += OnDisplayIndexChanged;
var columnOrder = (StringCollection)e.NewValue;
// The collection is not fully initialized.
if (columnOrder == null || columnOrder.Count <= dataGrid.Columns.Count)
{
// Must be updated after load, otherwise the indices are -1.
dataGrid.Loaded += OnDataGridLoaded;
return;
}
try
{
for (var i = 0; i <= dataGrid.Columns.Count - 1; i++)
{
var index = Convert.ToInt32(columnOrder[i]);
dataGrid.Columns[i].DisplayIndex = index;
}
}
catch
{
Console.WriteLine("Error");
}
}
}
private static void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
var dataGrid = (DataGrid)sender;
var columnOrder = GetColumnOrder(dataGrid);
// Since the collection is not fully initialized, clear it as it could be invalid.
columnOrder.Clear();
// Initialize the collection with the current column display indices.
foreach (var column in dataGrid.Columns)
columnOrder.Add(column.DisplayIndex.ToString());
dataGrid.Loaded -= OnDataGridLoaded;
}
private static void OnDisplayIndexChanged(object sender, DataGridColumnEventArgs e)
{
var dataGrid = (DataGrid)sender;
var columnOrder = GetColumnOrder(dataGrid);
var columnIndex = dataGrid.Columns.IndexOf(e.Column);
// Sync the changed column display index.
columnOrder[columnIndex] = e.Column.DisplayIndex.ToString();
}
}
我也想推荐你使用 int
的集合而不是 StringCollection
,因为显示索引是 int
类型并且代码中的转换是不必要的.
继续前进,您可能有兴趣创建一个 Behavior<DataGrid>
instead of an attached property class, which is easier to write, as it already provides access to the associated object and other useful methods. For this, you can install the Microsoft.Xaml.Behaviors.Wpf NuGet 包。
我想save/load列订单上DataGrid
。我正在尝试在我的 ViewModel 和 View 之间绑定一个包含 DataGrid
列索引的 StringCollection
。我可以在使用附加属性更新视图的 ViewModel 中成功设置 StringCollection
,但是当我使用 UI 更改视图中的列顺序时,ViewModel 不会更新以反映新的列顺序。如何让我的 ViewModel 监听 View 的变化?
我的xaml
<DataGrid ItemsSource="{Binding MyObservableCollection}"
attach:DataGridColumnChanger.ColumnOrder="{Binding ColumnOrders, Mode=TwoWay}">
</DataGrid>
我的附件属性
public class DataGridColumnChanger : DependencyObject
{
#region dependency properties
public static StringCollection GetColumnOrder(DependencyObject obj)
{
return (StringCollection)obj.GetValue(ColumnOrderProperty);
}
public static void SetColumnOrder(DependencyObject obj, StringCollection value)
{
obj.SetValue(ColumnOrderProperty, value);
}
public static readonly DependencyProperty ColumnOrderProperty = DependencyProperty.RegisterAttached("ColumnOrder",
typeof(StringCollection), typeof(DataGridColumnChanger), new UIPropertyMetadata(new StringCollection(), new PropertyChangedCallback(OnColumnOrderChange)));
#endregion
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as DataGrid;
if (element != null)
{
try
{
StringCollection collection = (StringCollection)e.NewValue;
if (collection != null && collection.Count > 0)
{
for (int j = 0; j <= element.Columns.Count - 1; j++)
{
int index = Convert.ToInt32(collection[j]);
element.Columns[j].DisplayIndex = index;
}
}
}
catch
{
Console.WriteLine("Error");
}
}
}
}
我的视图模型
private StringCollection columnOrders = new StringCollection();
public System.Collections.Specialized.StringCollection ColumnOrders
{
get => this.columnOrders;
set
{
this.columnOrders = value;
this.RaisePropertyChanged("ColumnOrders");
}
}
您的附加行为必须监听附加的 DataGrid.ColumnDisplayIndexChanged
事件。处理此事件并同步附加 DataGrid
:
private static ConditionalWeakTable<DataGrid, DataGrid> RegisteredInstances { get; } = new ConditionalWeakTable<DataGrid, DataGrid>();
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (element is DataGrid dataDrid)
{
if (!RegisteredInstances.TryGetValue(dataDrid, out _))
{
RegisteredInstances.Add(dataDrid, dataDrid);
dataDrid.ColumnDisplayIndexChanged += OnDataGridColumnOrderChanged;
}
try
{
...
}
catch
{
...
}
}
}
private static void OnDataGridColumnOrderChanged(object sender, DataGridColumnEventArgs e)
{
var dataGrid = sender as DataGrid;
if (RegisteredInstances.TryGetValue(dataDrid, out _))
{
StringCollection newColumnOrderCollection = new StringCollection();
// TODO::Populate newColumnOrderCollection using e.Column.DisplayIndex
// and raise property changed by assigning the collection to the OrderCollection property
}
}
您附加的 属性 实现仅对集合的更改做出反应。为了对用户界面的变化做出反应,您必须订阅 DataGrid.ColumnDisplayIndexChanged
事件。
下面是如何从您的代码开始执行此操作的示例。你没有指定,如果你想在每次显示索引改变时创建一个新的集合,所以在下面,我重新使用这个集合。
请注意,如果您的绑定集合为空或没有 all 列的项目,则集合现在从 DataGrid
初始化,这将容易出错。该集合在 Loaded
处理程序事件中初始化,因为在 DataGrid
完全加载之前,列将 DisplayIndex
设置为 -1
。
The DisplayIndex property has a default value of -1 before it is added to the DataGrid.Columns collection. This value is updated when the column is added to the DataGrid.
public class DataGridColumnChanger : DependencyObject
{
#region dependency properties
public static StringCollection GetColumnOrder(DependencyObject obj)
{
return (StringCollection)obj.GetValue(ColumnOrderProperty);
}
public static void SetColumnOrder(DependencyObject obj, StringCollection value)
{
obj.SetValue(ColumnOrderProperty, value);
}
public static readonly DependencyProperty ColumnOrderProperty = DependencyProperty.RegisterAttached("ColumnOrder",
typeof(StringCollection), typeof(DataGridColumnChanger), new UIPropertyMetadata(new StringCollection(), OnColumnOrderChange));
#endregion
private static void OnColumnOrderChange(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var dataGrid = (DataGrid)obj;
if (e.NewValue == null)
{
// No collection set, unsubscribe from the event.
dataGrid.ColumnDisplayIndexChanged -= OnDisplayIndexChanged;
}
else
{
// A new collection was set, subscribe to the event to sync it with user driven reordering.
dataGrid.ColumnDisplayIndexChanged += OnDisplayIndexChanged;
var columnOrder = (StringCollection)e.NewValue;
// The collection is not fully initialized.
if (columnOrder == null || columnOrder.Count <= dataGrid.Columns.Count)
{
// Must be updated after load, otherwise the indices are -1.
dataGrid.Loaded += OnDataGridLoaded;
return;
}
try
{
for (var i = 0; i <= dataGrid.Columns.Count - 1; i++)
{
var index = Convert.ToInt32(columnOrder[i]);
dataGrid.Columns[i].DisplayIndex = index;
}
}
catch
{
Console.WriteLine("Error");
}
}
}
private static void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
var dataGrid = (DataGrid)sender;
var columnOrder = GetColumnOrder(dataGrid);
// Since the collection is not fully initialized, clear it as it could be invalid.
columnOrder.Clear();
// Initialize the collection with the current column display indices.
foreach (var column in dataGrid.Columns)
columnOrder.Add(column.DisplayIndex.ToString());
dataGrid.Loaded -= OnDataGridLoaded;
}
private static void OnDisplayIndexChanged(object sender, DataGridColumnEventArgs e)
{
var dataGrid = (DataGrid)sender;
var columnOrder = GetColumnOrder(dataGrid);
var columnIndex = dataGrid.Columns.IndexOf(e.Column);
// Sync the changed column display index.
columnOrder[columnIndex] = e.Column.DisplayIndex.ToString();
}
}
我也想推荐你使用 int
的集合而不是 StringCollection
,因为显示索引是 int
类型并且代码中的转换是不必要的.
继续前进,您可能有兴趣创建一个 Behavior<DataGrid>
instead of an attached property class, which is easier to write, as it already provides access to the associated object and other useful methods. For this, you can install the Microsoft.Xaml.Behaviors.Wpf NuGet 包。