DataTable Linked to WPG DataGrid Does Not Update

advertisements

I have a WPF DataGrid that I bind to a DataTable. I don't like doing this but the data is coming from a delimited text file, and I don't know how many fields (columns) that the table will contain going in. Programatically this seems to be the easiest way to accomplish this (using MVVM and avoiding code behind), but given that I want two way binding, perhaps this won't work.

The DataGrid is defined like this in the view:

        <DataGrid x:Name="dataGrid" ItemsSource="{Binding FileTable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
              HorizontalAlignment="Stretch" Margin="0,60,0,0" VerticalAlignment="Stretch">
    </DataGrid>

The ViewModel sets up the datatable by reading a text file, and adds two boolean values to the end of each row. I want the boolean values to map to the checkboxes in the DataGrid, but they don't, and I don't get any events in the viewmodel when the values are changed. I am thinking that I need to change out the datatable as seen in other related questions, but they are all responding to the viewmodel changing the view (like a button that adds a column), rather than having the change come from the datagrid within the view.

For context here is the FileTable member in my ViewModel:

private DataTable _fileTable;
public DataTable FileTable
{
    get
    {
        return _fileTable;
    }
    set
    {
        if (value != _fileTable)
        {
            _fileTable = value;
            NotifyPropertyChanged("FileTable");
        }
    }
}

And here is the code that creates the datatable from a text file:

public DataTable ParseFileToTable(Document doc, string PlaceHolders)
{
    if (dt == null)
    {
        dt = new DataTable();
    }
    else dt.Clear();

    if (filepath == null)
    {
        OpenFileDialog dlg = new OpenFileDialog();
        dlg.DefaultExt = ".txt"; // Default file extension
        dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension

        Nullable<bool> result = dlg.ShowDialog();
        if (result != true) return null;

        filepath = dlg.FileName;
        StreamReader r = new StreamReader(filepath);
        string line = r.ReadLine(); // First Line is Column Names
        string[] h_line = line.Split('\t'); // tab delimeter is hardcoded for now
        for(int i = 0; i < h_line.Count(); i++)
        {
            dt.Columns.Add(h_line[i]);
        }
        dt.Columns.Add(new DataColumn("Exists", typeof(bool)));
        dt.Columns.Add(new DataColumn("Placeholder", typeof(bool)));

        //read the rest of the file
        while (!r.EndOfStream)
        {
            line = r.ReadLine();
            string [] a_line = line.Split('\t');
            DataRow nRow = dt.NewRow();
            for(int i = 0; i < h_line.Count(); i++)
            {
                nRow[h_line[i]] = a_line[i];
            }
            nRow["Exists"] = DoesSheetExist(doc, h_line[0], a_line[0]);
            nRow["Placeholder"] = IsAPlaceholder(a_line[0], PlaceHolders);
            dt.Rows.Add(nRow);
        }
    }
    return dt;
}


You need to create DatagridColumns dynamically using Behavior

    /// <summary>
    /// Creating dymanic columns to the datagrid
    /// </summary>
    public class ColumnsBindingBehaviour : Behavior<DataGrid>
    {
        public ObservableCollection<DataGridColumn> Columns
        {
            get { return (ObservableCollection<DataGridColumn>)base.GetValue(ColumnsProperty); }
            set { base.SetValue(ColumnsProperty, value); }
        }
        public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns",
            typeof(ObservableCollection<DataGridColumn>), typeof(ColumnsBindingBehaviour),
                new PropertyMetadata(OnDataGridColumnsPropertyChanged));
        private static void OnDataGridColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            var context = source as ColumnsBindingBehaviour;
            var oldItems = e.OldValue as ObservableCollection<DataGridColumn>;
            if (oldItems != null)
            {
                foreach (var one in oldItems)
                    context._datagridColumns.Remove(one);
                oldItems.CollectionChanged -= context.collectionChanged;
            }
            var newItems = e.NewValue as ObservableCollection<DataGridColumn>;
            if (newItems != null)
            {
                foreach (var one in newItems)
                    context._datagridColumns.Add(one);
                newItems.CollectionChanged += context.collectionChanged;
            }
        }
        private ObservableCollection<DataGridColumn> _datagridColumns = new ObservableCollection<DataGridColumn>();
        protected override void OnAttached()
        {
            base.OnAttached();
            this._datagridColumns = AssociatedObject.Columns;
        }
        private void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewItems != null)
                        foreach (DataGridColumn one in e.NewItems)
                            _datagridColumns.Add(one);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldItems != null)
                        foreach (DataGridColumn one in e.OldItems)
                            _datagridColumns.Remove(one);
                    break;
                case NotifyCollectionChangedAction.Move:
                    _datagridColumns.Move(e.OldStartingIndex, e.NewStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    _datagridColumns.Clear();
                    if (e.NewItems != null)
                        foreach (DataGridColumn one in e.NewItems)
                            _datagridColumns.Add(one);
                    break;
            }
        }
    }

ViewModel Property as follows

        //Datagrid Column collection in Viewmodel
        private ObservableCollection<DataGridColumn> dataGridColumns;
        public ObservableCollection<DataGridColumn> DataGridColumns
        {
            get
            {
                return dataGridColumns;
            }
            set
            {
                dataGridColumns = value;
                OnPropertyChanged();
            }
        }

And Create datatable,Bind to it as follows,

    //Getting column names from datatable
    string[] columnNames = (from dc in dt.Columns.Cast<DataColumn>() select dc.ColumnName).ToArray();

    //Add two of your columns
    dt.Columns.Add(new DataColumn("Exists", typeof(bool)));
    dt.Columns.Add(new DataColumn("Placeholder", typeof(bool)));

    //Create DataGrid Column and bind datatable fields
    foreach (string item in columnNames)
    {
                        if (item.Equals("your Normal Column"))
                        {
                            DataGridColumns.Add(new DataGridTextColumn() { Header = "Normal Column", Binding = new Binding("Normal Column Name"), Visibility = Visibility.Visible});
                        }

                        else if (!item.Contains("your Bool column"))
                        {
                            //Creating checkbox control 

                          FrameworkElementFactory checkBox = new FrameworkElementFactory(typeof(CheckBox));
                         checkBox.SetValue(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center);

                            checkBox.Name = "Dynamic name of your check box";
                            //Creating binding
                            Binding PermissionID = new Binding(item); 

                            PermissionID.Mode = BindingMode.TwoWay;

                            PermissionID.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;

                            checkBox.SetBinding(CheckBox.TagProperty, BindingVal);

                            DataTemplate d = new DataTemplate();
                            d.VisualTree = checkBox;
                            DataGridTemplateColumn dgTemplate = new DataGridTemplateColumn();
                            dgTemplate.Header = item;
                            dgTemplate.CellTemplate = d;
                            DataGridColumns.Add(dgTemplate);
                        }

                    }

Finally Namespace in Xaml

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:YourProject.ViewModels"
<DataGrid  AutoGenerateColumns="False"
                      ItemsSource="{Binding Datatable,
 UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,IsAsync=True}">
                <i:Interaction.Behaviors>
                    <vm:ColumnsBindingBehaviour Columns="{Binding DataContext.DataGridColumns, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
                </i:Interaction.Behaviors>
            </DataGrid>