WPF 绑定值不会更新回模型

本文关键字:更新 模型 绑定 WPF | 更新日期: 2023-09-27 18:37:24

>我创建了一个派生自DataGrid的类,以便我可以在DataGrid.AutoGenerateColumn设置为True时覆盖用于列类型的模板。这是我的 DataGrid 类:

public class DataGridEx : DataGrid
{
    protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
    {
        base.OnAutoGeneratingColumn(e);
        Type colDataType = e.PropertyType;
        if (colDataType == typeof(DateTime))
        {
            // Create a new template column.
            var templateColumn = new DataGridTemplateColumnEx();
            templateColumn.Header = e.Column.Header;
            templateColumn.CellTemplate = (DataTemplate)Resources["DateTimePickerCellTemplate"];
            templateColumn.CellEditingTemplate = (DataTemplate)Resources["DateTimePickerCellEditingTemplate"];
            templateColumn.SortMemberPath = e.Column.SortMemberPath;
            templateColumn.ColumnName = e.PropertyName;
            // Replace the auto-generated column with new template column
            e.Column = templateColumn;
        }
    }
}

但是,这导致新DataGridTemplateColumnDataContext绑定到行项,因此我必须从DataGridTemplateColumn派生另一个类并重写GenerateElement()GenerateEditingElement()函数,以便能够将模板内容绑定回行项的列的目标属性。

public class DataGridTemplateColumnEx : DataGridTemplateColumn
{
    public string ColumnName { get; set; }
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
        var cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
        // Reset the Binding to the specific column. The default binding is to the DataRowView.
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
        return cp;
    }
    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
        var cp = (ContentPresenter)base.GenerateEditingElement(cell, dataItem);
        // Reset the Binding to the specific column. The default binding is to the DataRowView.
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
        return cp;
    }
}

这是我视图中显示的控件:

<c:DataGridEx AutoGenerateColumns="True" ItemsSource="{Binding}">
    <c:DataGridEx.Resources>
        <DataTemplate x:Key="DateTimePickerCellTemplate">
            <TextBlock Text="{Binding}"/>
        </DataTemplate>
        <DataTemplate x:Key="DateTimePickerCellEditingTemplate">
            <!-- Needed to specify Path=. or else got error about two-way binding requiring a path or xpath. -->
            <DatePicker Text="{Binding Path=., Mode=TwoWay}"/>
        </DataTemplate>
    </c:DataGridEx.Resources>
</c:DataGridEx>

只要TextBlock正确显示行项上的属性值,这似乎有效。但是CellEditingTemplate当我尝试编辑单元格并具有正确的初始值时,会出现DatePicker控件,但是当我更改更改未保存更改的日期时,源值未更新。

为什么此处不更新源?

WPF 绑定值不会更新回模型

关于需要路径的双向绑定的错误是因为您无法将一个对象更改为另一个对象......

在您的情况下,您的日期选取器绑定到".",这是日期时间的特定实例。当日期选取器的 SelectedDate 发生更改时,绑定无法将日期时间的初始实例更改为日期时间的另一个实例。

通过设置"Path=.",您可以胜过引发错误的代码,但其原因仍然适用。

要做你想做的事情,你必须设置 Path=SomeProperty。我假设您正在使用这种方法,因为直到运行时您才知道属性名称。对此的解决方案是使用一种具有已知属性名称的代理对象。在 XAML 绑定中使用该属性名称。此代理需要将属性的值与实际数据项同步。

下面是我刚刚为您制作的一个这样的实现。将 XAML 更改为以下内容:

<local:DataGridEx.Resources>
    <DataTemplate x:Key="DateTimePickerCellTemplate">
        <TextBlock Text="{Binding .}"/>
    </DataTemplate>
    <DataTemplate x:Key="DateTimePickerCellEditingTemplate">
        <DatePicker SelectedDate="{Binding Value}" />
    </DataTemplate>
</local:DataGridEx.Resources>

将 DataGridTemplateColumnEx 类更改为:

class DataGridTemplateColumnEx : DataGridTemplateColumn
{
    public string ColumnName { get; set; }
    protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
        var cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
        // Reset the Binding to the specific column. The default binding is to the DataRowView.
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
        return cp;
    }
    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // Create our ObjectProxy that will update our dataItem's ColumnName property
        var op = new ObjectProxy(dataItem, ColumnName);
        // Generate the editing element using our ObjectProxy
        var cp = (ContentPresenter)base.GenerateEditingElement(cell, op);
        // Reset the Binding to our ObjectProxy
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(".") { Source = op });
        return cp;
    }
}

ObjectProxy类是这样的:

public class ObjectProxy : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private object dataItem;
    private System.Reflection.PropertyInfo prop;
    private object val;
    public object Value
    {
        get { return val; }
        set
        {
            if (val != value)
            {
                val = value;
                OnPropertyChanged("Value");
            }
        }
    }
    public ObjectProxy(object DataItem, string propertyName)
    {
        this.dataItem = DataItem;
        if (dataItem != null)
        {
            prop = dataItem.GetType().GetProperty(propertyName);
            if (prop != null)
            {
                val = prop.GetValue(dataItem);
            }
        }
    }
    private void OnPropertyChanged(string name)
    {
        if (prop != null)
            prop.SetValue(dataItem, val);
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

下面是支持源项的 INotifyPropertyChanged 接口的 ObjectProxy 版本:

public class ObjectProxy : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler PropertyChanged;
    private object dataItem;
    private System.Reflection.PropertyInfo prop;
    private object val;
    public object Value
    {
        get { return val; }
        set
        {
            if (!Object.Equals(val, value))
            {
                val = value;
                OnPropertyChanged("Value");
            }
        }
    }
    public ObjectProxy(object DataItem, string propertyName)
    {
        this.dataItem = DataItem;
        if (dataItem != null)
        {
            prop = dataItem.GetType().GetProperty(propertyName);
            if (prop != null)
            {
                val = prop.GetValue(dataItem);
                // Sync from dataItem to ObjectProxy
                if (dataItem is INotifyPropertyChanged)
                {
                    INotifyPropertyChanged pc = dataItem as INotifyPropertyChanged;
                    pc.PropertyChanged += DataItemPropertyChanged;
                }
            }
        }
    }
    private void OnPropertyChanged(string name)
    {
        if (prop != null)
            prop.SetValue(dataItem, val);
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
    // The source item changed - Update our Value
    private void DataItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (prop != null && e.PropertyName == prop.Name)
        {
            Value = prop.GetValue(dataItem);
        }
    }
    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls
    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                if (dataItem is INotifyPropertyChanged)
                {
                    var pc = dataItem as INotifyPropertyChanged;
                    pc.PropertyChanged -= DataItemPropertyChanged;
                }
            }
            disposedValue = true;
        }
    }
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion
}

需要更新 DataGridTemplateColumnEx 类,以便在取消或提交编辑时释放 ObjectProxy。否则,将继续调用 PropertyChanged 事件处理程序。

class DataGridTemplateColumnEx : DataGridTemplateColumn
{
    public string ColumnName { get; set; }
    protected override System.Windows.FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
        var cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
        // Reset the Binding to the specific column. The default binding is to the DataRowView.
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
        return cp;
    }
    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        // Create our ObjectProxy that will update our dataItem's ColumnName property
        var op = new ObjectProxy(dataItem, ColumnName);
        // Generate the editing element using our ObjectProxy
        var cp = (ContentPresenter)base.GenerateEditingElement(cell, op);
        // Reset the Binding to our ObjectProxy
        BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(".") { Source = op });
        return cp;
    }
    private void DisposeOfProxyObject(FrameworkElement editingElement)
    {
        var cp = editingElement as ContentPresenter;
        if (cp != null)
        {
            var op = cp.Content as ObjectProxy;
            if (op != null)
                op.Dispose();
        }
    }
    protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
    {
        DisposeOfProxyObject(editingElement);
        base.CancelCellEdit(editingElement, uneditedValue);
    }
    protected override bool CommitCellEdit(FrameworkElement editingElement)
    {
        DisposeOfProxyObject(editingElement);
        return base.CommitCellEdit(editingElement);
    }
}
相关文章: