1

Allow me to first introduce the classes that I am using:

public class Assessment
{
    public List<NormDefinition> Definitions {get;set;}
    public List<Parameter> Parameters {get;set;}
}

public class NormDefinition
{
    public string ColumnName {get;set;}
}

public class Parameter
{
    public string Name {get;set;}
    public List<NormValue> Norms {get;set;}
}

public class NormValue
{
    public NormDefinition Definition {get;set;}
    public string Value {get;set;}
}

The user is navigating through a sort of wizard and has to complete some tasks to create an Assessment.

The first step is defining one or more NormDefinitions. It has some more settings than shown above but the others are irrelevant to the problem.

The second step is to define one or more Parameters. However, the user can also add some values for the previously defined NormDefinitions. I am trying to do this by creating a DataGrid with ItemsSource = {Binding Assessment.Parameters}, so I know that the source for each binding is the Parameter object that is bound to that row.

When the second screen gets focus, some code adds a column for each NormDefinition where the user might fill in the value. The creation of the columns looks like this:

private void CreateNormDefinitionColumns()
{
    foreach (var definition in Assessment.Definitions)
    {
        DataGridTextColumn column = new DataGridTextColumn();
        column.Header = definition.ColumnName;
        column.Binding = new Binding()
        {
            // Add a binding to:
            // Parameter.Norms.Single(norm => norm.Definition == definition).Value;
        }
        ParameterDataGrid.Columns.Add(column);
    }
}

Please help me with a solution to set up this binding.

To take it one step further, in case the .Single() does not return a result, I would like the NormValue object to be created and added to the parameter.

  • I had the same problem previously and I have solved the problem with following links: http://stackoverflow.com/questions/320089/how-do-i-bind-a-wpf-datagrid-to-a-variable-number-of-columns , http://julmar.com/blog/programming/dynamic-type-binding-in-wpf-4-5/ , http://www.shujaat.net/2012/09/dynamically-generated-properties-using.html – Ugur Jul 17 '15 at 13:12
  • So basically, you will create new columns with new bindings and you should add new maybe new properties to your data class. – Ugur Jul 17 '15 at 13:13

2 Answers2

0

For such a case I use my GenericRow and GenericTable classes:

    public class GenericRow : CustomTypeDescriptor, INotifyPropertyChanged
    {

        #region Private Fields
        List<PropertyDescriptor> _property_list = new List<PropertyDescriptor>();
        #endregion

        #region INotifyPropertyChange Implementation

        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion INotifyPropertyChange Implementation

        #region Public Methods

        public void SetPropertyValue<T>(string propertyName, T propertyValue)
        {
            var properties = this.GetProperties()
                                    .Cast<PropertyDescriptor>()
                                    .Where(prop => prop.Name.Equals(propertyName));

            if (properties == null || properties.Count() != 1)
            {
                throw new Exception("The property doesn't exist.");
            }

            var property = properties.First();
            property.SetValue(this, propertyValue);

            OnPropertyChanged(propertyName);
        }

        public T GetPropertyValue<T>(string propertyName)
        {
            var properties = this.GetProperties()
                                .Cast<PropertyDescriptor>()
                                .Where(prop => prop.Name.Equals(propertyName));

            if (properties == null || properties.Count() != 1)
            {
                throw new Exception("The property doesn't exist.");
            }

            var property = properties.First();
            return (T)property.GetValue(this);
        }

        public void AddProperty<T, U>(string propertyName) where U : GenericRow
        {
            var customProperty =
                    new CustomPropertyDescriptor<T>(
                                            propertyName,
                                            typeof(U));

            _property_list.Add(customProperty);
        }

        #endregion

        #region Overriden Methods

        public override PropertyDescriptorCollection GetProperties()
        {
            var properties = base.GetProperties();
            return new PropertyDescriptorCollection(
                                properties.Cast<PropertyDescriptor>()
                                          .Concat(_property_list).ToArray());
        }

        #endregion






    }

and:

 public class GenericTable
{

    private string tableName = "";
    public string TableName
    {
        get { return tableName; }
        set { tableName = value; }
    }

    private ObservableCollection<DataGridColumn> columnCollection;
    public ObservableCollection<DataGridColumn> ColumnCollection
    {
        get { return columnCollection; }
        private set { columnCollection = value; }
    }

    private ObservableCollection<GenericRow> genericRowCollection;
    public ObservableCollection<GenericRow> GenericRowCollection
    {
        get { return genericRowCollection; }
        set { genericRowCollection = value; }
    }




    public GenericTable(string tableName)
    {
        this.TableName = tableName;
        ColumnCollection = new ObservableCollection<DataGridColumn>();
        GenericRowCollection = new ObservableCollection<GenericRow>(); 
    }

    /// <summary>
    /// ColumnName is also binding property name
    /// </summary>
    /// <param name="columnName"></param>
    public void AddColumn(string columnName)
    {
        DataGridTextColumn column = new DataGridTextColumn();
        column.Header = columnName;
        column.Binding = new Binding(columnName);
        ColumnCollection.Add(column);
    }



    public override string ToString()
    {
        return TableName; 
    }


}
Ugur
  • 1,257
  • 2
  • 20
  • 30
0

In my opinion you just need to modify your Parameter class in this way:

public class Parameter
{
    public string Name { get; set; }
    public List<NormValue> Norms { get; set; }

    public NormValue this[string columnName]
    {
        get
        {
            /* You can use Linq if you prefer */
            foreach (NormValue normValue in Norms)
            {
                if (StringComparer.OrdinalIgnoreCase.Compare(normValue.Definition.ColumnName, columnName) == 0)
                {
                    return normValue;
                }
            }

            NormValue newNormValue = new NormValue();
            newNormValue.Definition = new NormDefinition();
            newNormValue.Definition.ColumnName = columnName;
            newNormValue.Value = "Data is not available";

            Norms.Add(newNormValue);

            return newNormValue;
        }
    }
}

Then you can build the DataGrid's columns in this way:

private void CreateNormDefinitionColumns()
{
    foreach (var definition in Assessment.Definitions)
    {
        Binding binding = new Binding(String.Format("[{0}].Value", definition.ColumnName));
        binding.Mode = BindingMode.TwoWay;

        DataGridTextColumn column = new DataGridTextColumn();
        column.Header = definition.ColumnName;
        column.Binding = binding;
        ParameterDataGrid.Columns.Add(column);
    }
}

Of course your classes should implement INotifyPropertyChanged, if you want that changes are reflected to the UI. For the same reason Lists should be replaced by ObservableCollections.

I hope this hint can help you.

Il Vic
  • 5,576
  • 4
  • 26
  • 37