ICustomTypeDescriptor wth XamDataGrid

496 Views Asked by At

I have extended the XamDataGrid to support DynamicColumn generation using a DependencyProperty called ColumnSource. Thus the gird now will generate columns dynamically based on a dependency property called "ColumnSource". this idea was inspired from the DevExpress WPF Grid I had used before.

Having said that, I need to mention that I am using Field (not UnBoundField) inside the extended control to generate the columns and binding them to the ViewModel objects. It has worked fine for requirement I had till now.

Now I have a situation where I have a ViewModel that needs to have dynamic properties. Obviously I have ICustomTypeDescriptor in mind.I just am curious is it possible to view data in the XamDataGrid with the following limitations:

  1. .Net 4.0
  2. ICustomTypeDescriptor
  3. Use of field and not UnboundField class for column generations.
  4. Data shown should be two way bindable, that is change in cell data should change appropriate ViewModel property.

I am pasting the Extended control's code here. It is very long so I will try to curtail the code responsible for other functionalities.

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    
      }

Here is the IGridColumn contract:

/// <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
  } ;

I had the plan to populating the ColumnSource for the Grid and bind them to ICustomTypeDescriptor instances of ViewModels whose Dynamic property names would match with the IGridColumn Names.

0

There are 0 best solutions below