【问题标题】:Update ViewModel from View with an attached property使用附加属性从 View 更新 ViewModel
【发布时间】:2021-02-03 08:54:31
【问题描述】:

我想在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 更改并相应地调用SetColumnOrder 。您的绑定怎么会知道您从未更改附加属性的值?
  • 看起来怎么样?您的意思是在附加的属性类中监听诸如 ColumnDisplayIndexChanged 之类的事件并在其中调用 SetColumnOrder 吗?
  • 是的,应该就是这样
  • @chris 在这种情况下你想做什么,更新现有的集合还是每次都创建一个新的集合?另一个问题是,如何初始化集合,代码仅在字符串集合为空或具有 for all 列的项目时才有效。说到集合,为什么显示索引是int时使用StringCollection

标签: c# wpf xaml data-binding


【解决方案1】:

您的附加行为必须监听附加的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
  }
}

【讨论】:

    【解决方案2】:

    您的附加属性实现仅对集合的更改做出反应。为了对用户界面的变化做出反应,您必须订阅DataGrid.ColumnDisplayIndexChanged 事件。

    以下是如何从您的代码开始执行此操作的示例。您没有指定是否要在每次显示索引更改时创建一个新集合,因此在下文中,我重用该集合。

    请注意,如果您绑定的集合为空或没有all 列的项目,则集合现在从DataGrid 初始化,这很容易出错。该集合在 Loaded 处理程序事件中初始化,因为在完全加载 DataGrid 之前,这些列会将 DisplayIndex 设置为 -1

    DisplayIndex 属性在添加到 DataGrid.Columns 集合之前的默认值为 -1。当列添加到 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&lt;DataGrid&gt; 而不是附加的属性类,因为它已经提供了对关联对象和其他有用方法的访问,所以它更容易编写。为此,您可以安装Microsoft.Xaml.Behaviors.Wpf NuGet 包。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      相关资源
      最近更新 更多