WPF绑定值未更新回模型

时间:2016-03-03 16:31:40

标签: c# wpf xaml data-binding datagrid

我创建了一个派生自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;
        }
    }
}

但是,这会导致新DataContext的{​​{1}}绑定到行项,因此我必须从DataGridTemplateColumn派生另一个类并覆盖DataGridTemplateColumn和{ {1}}函数能够将模板内容绑定回列项的目标属性。

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控件出现,但是当我更改日期时未更改更改时,源值不会更新。

为什么源不在这里更新?

1 个答案:

答案 0 :(得分:1)

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

在您的情况下,您的DatePicker绑定到“。”这是日期时间的特定实例。当DatePicker的SelectedDate更改时,绑定不能将datetime的初始实例更改为另一个datetime实例。

通过设置“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类更新为Dispose of 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);
    }
}