简体   繁体   English

绑定到WPF DataGrid的DataTable不会更新

[英]DataTable bound to WPF DataGrid does not update

I have a WPF DataGrid that I bind to a DataTable. 我有绑定到DataTable的WPF DataGrid。 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. 我不喜欢这样做,但是数据来自定界的文本文件,并且我不知道该表将包含多少个字段(列)。从编程上讲,这似乎是完成此操作的最简单方法(使用MVVM并避免后面的代码),但是鉴于我想要两种方式的绑定,也许这行不通。

The DataGrid is defined like this in the view: 在视图中,DataGrid的定义如下:

        <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. ViewModel通过读取文本文件来设置数据表,并在每行的末尾添加两个布尔值。 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. 我希望布尔值映射到DataGrid中的复选框,但它们不这样做,并且当更改值时,在ViewModel中没有任何事件。 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. 我认为我需要更改数据表,如在其他相关问题中所见,但是它们都响应于更改视图的viewmodel(例如添加列的按钮),而不是使更改来自于内部的datagrid。视图。

For context here is the FileTable member in my ViewModel: 对于上下文,这是我的ViewModel中的FileTable成员:

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 您需要使用行为动态创建DatagridColumns

    /// <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 ViewModel属性如下

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

While I try to avoid code behind, I think my use in this solution is acceptable, as the view is simply preparing to display the ViewModel which has a dynamic size (or shape). 虽然我尽量避免使用代码,但我认为我在此解决方案中的使用是可以接受的,因为视图只是准备显示具有动态大小(或形状)的ViewModel。

Instead of reading to a DataTable, I read to an object that contains a list of strings and two booleans. 我没有读取DataTable,而是读取了一个包含字符串列表和两个布尔值的对象。 In my ViewModel I have an observable collection of this object. 在我的ViewModel中,我有一个可观察到的对象集合。 The datagrid is initialized in the code behind like this (just the list of strings shown, the two checkbox columns don't require the loop): 像这样在后面的代码中初始化datagrid(只是显示的字符串列表,两个复选框列不需要循环):

    public MainWindow(FileParametersViewModel vm)
    {
        InitializeComponent();
        DataContext = vm;
        dataGrid.ItemsSource = vm.lParams;
        for (int i = 0; i < vm.ParamNames.Count(); i++)
        {
            DataGridTextColumn col = new DataGridTextColumn();
            col.Header = vm.ParamNames[i];
            string path = String.Format("pArray[{0}]", i);
            col.Binding = new Binding(path);
            dataGrid.Columns.Add(col);
        }
    }

And my collection object: 和我的收藏对象:

public class FileSheetParameters
{
    public FileSheetParameters()
    {
        SheetExists = false;
        IsPlaceholder = false;
        pArray = new List<string>();
    }

    public bool SheetExists { get; set; }
    public bool IsPlaceholder { get; set; }
    public List<string> pArray { get; set; }
}

This seems to me to be the simplest method . 在我看来,这是最简单的方法。 . . I have not fully tested it but it appears to work so far. 我尚未对其进行全面测试,但到目前为止它似乎可以正常工作。 Will update if I find something else not working . 如果发现其他问题将更新。 . .

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM