1

我已扩展 XamDataGrid 以支持使用名为 ColumnSource 的 DependencyProperty 生成 DynamicColumn。因此,网格现在将根据名为“ColumnSource”的依赖属性动态生成列。这个想法的灵感来自我之前使用的 DevExpress WPF Grid。

话虽如此,我需要提一下,我在扩展控件中使用 Field(不是 UnBoundField)来生成列并将它们绑定到 ViewModel 对象。对于我到目前为止的要求,它工作得很好。

现在我有一个需要动态属性的 ViewModel 的情况。显然,我有 ICustomTypeDescriptor 的想法。我只是好奇是否可以在 XamDataGrid 中查看数据,但有以下限制:

  1. .Net 4.0
  2. ICustomTypeDescriptor
  3. 使用字段而不是 UnboundField 类来生成列。
  4. 显示的数据应该是两种可绑定的,即单元格数据的更改应该更改适当的 ViewModel 属性。

我在这里粘贴扩展控件的代码。它很长,所以我会尽量减少负责其他功能的代码。

public class AdvancedXamDataGrid : XamDataGrid

 {

        #region Static Constructor

        static AdvancedXamDataGrid()
        {
          //Dependency properties overrides if any to be done here.
          DataSourceProperty.OverrideMetadata(typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, DataSourcePropetyChanged));
        }

        #endregion

    #region Dependency Properties

        /// <summary>
        /// Dependency proeprty for Columns List shown in the Grid Header
        /// </summary>
        public static readonly DependencyProperty ColumnsSourceProperty = DependencyProperty.Register("ColumnsSource", typeof(IEnumerable),
          typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, OnColumnsSourceChanged));

        /// <summary>
        /// Gets or sets the <see cref="ColumnsSource"/>.
        /// This is a Dependency Property.
        /// </summary>
        public IEnumerable ColumnsSource
        {
          get { return GetValue(ColumnsSourceProperty) as IEnumerable; }
          set { SetValue(ColumnsSourceProperty, value); }
        }    

        #endregion


        #region Dependency Property Property Changed Handlers (static).

        /// <summary>
        /// The handler is fired when the <see cref="ColumnsSource"/> is changed.
        /// </summary>
        /// <param name="sender">The dependency object that raises the event.</param>
        /// <param name="e">The event argument</param>
        private static void OnColumnsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
          var control = sender as AdvancedXamDataGrid;
          if (null != control)
          {
            if (null != control._fieldAdornerSettings)
              control.DetachAdorner();
            control._fieldAdornerSettings = new FieldAdornerSettings();
            control._fieldAdornerList = new List<FieldAdorner>();

            var oldValue = e.OldValue as IEnumerable;
            var newValue = e.NewValue as IEnumerable;
            if (BindingOperations.IsDataBound(sender, ColumnsSourceProperty))
              control.ColumnsSourceChanged(oldValue, newValue);
          }
        }


        /// <summary>
        /// This handler is fired when the data source property changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void DataSourcePropetyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
          var control = sender as AdvancedXamDataGrid;
          if (null != control)
          {    
            var dataSource = e.NewValue as IEnumerable;
            control.DataSource = dataSource;
          }
        }

        #endregion


        #region Instance Properties and Event Handlers

        /// <summary>
        /// Handles when the <see cref="ColumnsSource"/> is changed.
        /// </summary>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        private void ColumnsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
          if (null != oldValue)
            //I could never figure out why I need this check. But this check is requred for consistent laytout for first time load.Funny I know!
            FieldLayouts.Clear(); //Clear the existing columns.

          var oldColSource = oldValue as INotifyCollectionChanged;
          if (null != oldColSource)
          {
            oldColSource.CollectionChanged -= oldColSource_CollectionChanged;
            //Remove the columns first.
            foreach (IGridColumn column in oldValue)
            {
              RemoveField(column);
            }
          }

          var newColSource = newValue as INotifyCollectionChanged;
          if (null != newColSource)
          {
            newColSource.CollectionChanged += oldColSource_CollectionChanged;
          }

          if (null != newValue)
          {
            var fieldLayout = new FieldLayout {IsDefault = true, Key = Convert.ToString(Guid.NewGuid())};

            FieldLayouts.Add(fieldLayout);
            foreach (IGridColumn col in newValue)
            {
              AddField(col);
            }
            DefaultFieldLayout = fieldLayout;
          }
        }

        /// <summary>
        /// Fires when the ColumnsSource Collection changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void oldColSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
          //Remove old Items.
          foreach (IGridColumn col in e.OldItems)
          {
            RemoveField(col);
          }
          //Add new items.
          foreach (IGridColumn col in e.NewItems)
          {
            AddField(col);
          }
        }

        /// <summary>
        /// Adds a Field to the wrapped grids FiledCollection.
        /// </summary>
        /// <param name="column"></param>
        private void AddField(IGridColumn column)
        {
          if (FieldLayouts.Count > 0)
          {
            var fieldLayout = FieldLayouts[0];
            var field = new Field {Name = column.Name, Label = column.DisplayName.ToUpper(), ToolTip = column.ToolTip};
            switch (column.ColumnType)
            {
                //  case GridColumnType.Text:
                //    field.DataType = typeof(string);
                //    break;
              case GridColumnType.Boolean:
                var style = new Style(typeof (XamCheckEditor));
                style.Setters.Add(new Setter(XamCheckEditor.IsCheckedProperty,
                                             new Binding()
                                               {
                                                 Path = new PropertyPath(string.Concat("DataItem.", column.Name)),
                                                 UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                                                 Mode = BindingMode.TwoWay
                                               }));
                field.Settings.EditorType = typeof (XamCheckEditor);
                field.Settings.EditorStyle = style;
                break;
            }
            if (column.ColumnType == GridColumnType.Combo)
            {
              var style = new Style(typeof (XamComboEditor));
              style.Setters.Add(new Setter(XamComboEditor.ItemsSourceProperty,
                                           new Binding() {Path = new PropertyPath(column.ItemsSource)}));
              style.Setters.Add(new Setter(XamComboEditor.SelectedItemProperty,
                                           new Binding(column.SelectedItemPropertyName) {Mode = BindingMode.TwoWay}));
              style.Setters.Add(new Setter(XamComboEditor.DisplayMemberPathProperty, column.DisplayMemberPath));
              style.Setters.Add(new Setter(XamComboEditor.ValuePathProperty, column.ValueMemberPath));
              field.Settings.EditorType = typeof (XamComboEditor);
              field.Settings.EditorStyle = style;
            }

            if (column.IsReadOnly)
              field.Settings.AllowEdit = false;
            if (!column.IsVisible)
              field.Visibility = Visibility.Collapsed;
            fieldLayout.Fields.Add(field);

            if (!string.IsNullOrEmpty(column.TemplateKey))
              _fieldAdornerList.Add(new FieldAdorner()
                {
                  Name = column.Name,
                  BindToParentSource = column.BindToParent,
                  TemplateKey = column.TemplateKey
                });

            //Register to the property changed notofication.
            var propertyNotifier = column as INotifyPropertyChanged;
            propertyNotifier.PropertyChanged += propertyNotifier_PropertyChanged;
          }
        }

        /// <summary>
        /// Removes a field 
        /// </summary>
        /// <param name="column"></param>
        private void RemoveField(IGridColumn column)
        {
          if (FieldLayouts.Count > 0)
          {
            var fieldLayout = FieldLayouts[0];
            var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
            if (null != field)
              fieldLayout.Fields.Remove(field);
            var propertyNotifier = column as INotifyPropertyChanged;
            propertyNotifier.PropertyChanged -= propertyNotifier_PropertyChanged;
          }
        }

        /// <summary>
        /// Event handler for handling property notification.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void propertyNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
          var column = sender as IGridColumn;
          if (null != column)
          {
            var fieldLayout = FieldLayouts[0];
            var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
            if (e.PropertyName.Equals("IsVisible"))
            {
              if (field != null)
                field.Visibility = column.IsVisible ? Visibility.Visible : Visibility.Collapsed;
            }
            if (e.PropertyName.Equals("IsReadOnly"))
            {
              if (field != null)
                field.Settings.AllowEdit = !column.IsReadOnly;
            }
          }
        }    
        #endregion    
      }

这是 IGridColumn 合同:

/// <summary>
  /// A contract that need to be implemented by an item that needs to participate in ColumnSource binding.
  /// </summary>
  public interface IGridColumn : INotifyPropertyChanged
  {
    /// <summary>
    /// Gets or sets the PropertyName to which the Column would bind.
    /// </summary>
    string Name { get; set; }

    /// <summary>
    /// Gets or sets the Display Text that will be visible in the column header.
    /// </summary>
    string DisplayName { get; set; }

    /// <summary>
    /// Gets the type of the property that gets bound to this column.
    /// </summary>
    GridColumnType ColumnType { get; }

    /// <summary>
    /// Gets or sets if the column is read-only.
    /// </summary>
    bool IsReadOnly { get; set; }

    /// <summary>
    /// Gets or sets if the column is visible.
    /// </summary>
    bool IsVisible { get; set; }


    #region For Combo Columns

    /// <summary>
    /// Gets or sets the Items source of the combo editor.
    /// </summary>
    string ItemsSource { get; set; }

    /// <summary>
    /// Gets or sets the SelectedItem propertyName.
    /// </summary>
    string SelectedItemPropertyName { get; set; }

    /// <summary>
    /// Gets or sets the name of the property that be the display item of the combo.
    /// </summary>
    string DisplayMemberPath { get; set; }

    /// <summary>
    /// Gets or sets the name of the property that be the value item of the combo.
    /// </summary>
    string ValueMemberPath { get; set; }

    /// <summary>
    /// Gets or sets the tool tip on the column.
    /// </summary>
    string ToolTip { get; set; }

    /// <summary>
    /// Gets or sets the Template Key for the adorner.
    /// </summary>
    string TemplateKey { get; set; }

    /// <summary>
    /// Gets or sets if the smart tag, would be bound to the view model of the grid.
    /// <remarks>
    /// Note: By default it would be bound to an item of the grid.
    /// </remarks>
    /// </summary>
    bool BindToParent { get; set; }

    /// <summary>
    /// Gets or sets the caption for the smart tag.
    /// </summary>
    string SmartTagCaption { get; set; }

    #endregion
  }

  /// <summary>
  /// An enumeration offering various types of Grid Column types.
  /// </summary>
  public enum GridColumnType
  {
    Text=0,
    Boolean,
    Integer,
    Double,
    Decimal,
    Combo
  } ;

我计划为 Grid 填充 ColumnSource 并将它们绑定到 ViewModel 的 ICustomTypeDescriptor 实例,其动态属性名称将与 IGridColumn 名称匹配。

4

0 回答 0