.net 双向绑定到只读属性

.net 双向绑定到只读属性,.net,wpf,data-binding,.net,Wpf,Data Binding,我在模型对象上有一个只读属性,但希望双向绑定到它。当绑定源应该更新时,我希望将更新“重定向”到一个单独的方法,该方法插入到控制器基础结构中,以执行更新并生成具有change属性的新模型。简单地在只读属性上使用绑定并将处理程序添加到Binding.TargetUpdated事件不起作用(引发InvalidOperationException,指定属性应为只读) 有一个简单的解决方案(创建一个模型副本,使用读写属性为我重定向),但我并不想复制所有的模型对象。有没有一种方法可以通过编程来实现这一点?我

我在模型对象上有一个只读属性,但希望双向绑定到它。当绑定源应该更新时,我希望将更新“重定向”到一个单独的方法,该方法插入到控制器基础结构中,以执行更新并生成具有change属性的新模型。简单地在只读属性上使用绑定并将处理程序添加到
Binding.TargetUpdated
事件不起作用(引发InvalidOperationException,指定属性应为只读)


有一个简单的解决方案(创建一个模型副本,使用读写属性为我重定向),但我并不想复制所有的模型对象。有没有一种方法可以通过编程来实现这一点?

我认为如果不绑定到新的中间对象,您将无法逃脱。有些人可能会称之为视图模型。我想知道定制的
IValueConverter
是否能够根据您在
ConvertBack
中的需要拦截写入操作,但我可以合理地确定,如果源属性不可写,绑定系统甚至不会尝试调用转换器。

此解决方案基于自定义标记扩展,它为某些依赖属性设置双向绑定。绑定使用某种具有可写属性的包装器作为源。包装器在属性更改后调用基础结构代码来更新和生成新模型

下面是一个硬编码场景的示例,但我认为这个想法非常清楚

namespace MyApp
{

public class MyModel
{
    //readonly property
    public string Name { get; private set; }

    public MyModel(string name)
    {
        Name = name;
    }
}

public class MyViewModel
{
    public MyModel Model { get; set; }

    public MyViewModel()
    {
        Model = new MyModel("default");
    }
}

public class Wrapper
{
    public MyViewModel ViewModel { get; set; }

    //writable property to enable two-way binding
    public object Value
    {
        get
        {
            return ViewModel.Model.Name;
        }
        set
        {
            //call your infrastructure method to 
            //update and generate new model
            ViewModel.Model = new MyModel((string)value);
        }
    }
}

[MarkupExtensionReturnType(typeof(Object))]
public class CustomBinding : MarkupExtension
{
    //you can add any properties here for your infrastructure method call
    //public string PropertyName { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        var binding = new Binding()
        {
            //get whatever you need from target element to setup the binding and wrapper
            Source = new Wrapper()
            {
                ViewModel = (provideValueTarget.TargetObject as FrameworkElement).DataContext as MyViewModel
            },
            Path = new PropertyPath("Value")
        };
        var result = binding.ProvideValue(serviceProvider);
        return result;
    }
}

}
XAML
我找到了一个使用
ExpandoObject
的解决方案-给定一个只读模型对象,这将在运行时生成一个读写模型,在任何属性更改时调用控制器方法:

public static dynamic GetAdapterFor(IController controller, object modelObj)
{
    if (modelObj == null)
        return null;

    ExpandoObject obj = new ExpandoObject();

    // add all the properties in the model
    foreach (var prop in modelObj.GetType().GetProperties())
    {
        ((IDictionary<string, object>)obj).Add(prop.Name, prop.GetValue(modelObj, null));
    }

    // add the handler to update the controller when a property changes
    ((INotifyPropertyChanged)obj).PropertyChanged += (s, e) => UpdateController(controller, e.PropertyName, ((IDictionary<string, object>)s)[e.PropertyName]);

    return obj;
}

private static void UpdateController(IController controller, string propertyName, object propertyValue)
{
    controller.SetPropertyValue(propertyName, propertyValue);
}
公共静态动态GetAdapter(IController控制器,对象模型OBJ)
{
if(modelObj==null)
返回null;
ExpandoObject obj=新的ExpandoObject();
//在模型中添加所有属性
foreach(modelObj.GetType().GetProperties()中的var prop)
{
添加(prop.Name,prop.GetValue(modelObj,null));
}
//添加处理程序以在属性更改时更新控制器
((INotifyPropertyChanged)obj).PropertyChanged+=(s,e)=>UpdateController(controller,e.PropertyName,((IDictionary)s)[e.PropertyName]);
返回obj;
}
私有静态void UpdateController(IController控制器、字符串propertyName、对象propertyValue)
{
controller.SetPropertyValue(propertyName,propertyValue);
}

我不明白。您是否将视图直接绑定到模型,而不使用中间层?您从何处获取组合框的ItemsSource(例如)?组合框的所有值都由控制器生成。我通常会在任何集合和
DataContext
之间放置一个
ICollectionView
,这样我就可以收到更改通知并回拨控制器。
public static dynamic GetAdapterFor(IController controller, object modelObj)
{
    if (modelObj == null)
        return null;

    ExpandoObject obj = new ExpandoObject();

    // add all the properties in the model
    foreach (var prop in modelObj.GetType().GetProperties())
    {
        ((IDictionary<string, object>)obj).Add(prop.Name, prop.GetValue(modelObj, null));
    }

    // add the handler to update the controller when a property changes
    ((INotifyPropertyChanged)obj).PropertyChanged += (s, e) => UpdateController(controller, e.PropertyName, ((IDictionary<string, object>)s)[e.PropertyName]);

    return obj;
}

private static void UpdateController(IController controller, string propertyName, object propertyValue)
{
    controller.SetPropertyValue(propertyName, propertyValue);
}