Reputation: 46098
I've got a read-only property on a model object, but want to bind two-way to it. When the binding source should get updated, I want the update to instead be 'redirected' to a separate method that plugs into our controller infrastructure to perform the update and generate a new model with the change property. Simply using a Binding on a read-only property and adding a handler to the Binding.TargetUpdated
event doesn't work (throws an InvalidOperationException, specifying the property should be read-only).
There's a simple solution to this (create a copy of the model, with read-write properties that do the redirect for me) but I don't really want to have to copy all my model objects. Is there a way of doing this programatically?
Upvotes: 0
Views: 727
Reputation: 46098
I found a solution using ExpandoObject
- given a read-only model object, this generates a read-write model at runtime that calls the controller method whenever any property changes:
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);
}
Upvotes: 0
Reputation: 754
This solution is based on custom markup extension which setups a two way binding for some dependency property. The binding uses some kind of wrapper with writable property as a source. The wrapper calls the infrastructure code to update and generate new model after property changed.
Below example with hard coded scenario, but I think the idea is quite clear.
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
<StackPanel>
<StackPanel.DataContext>
<MyApp:MyViewModel />
<StackPanel.DataContext>
<TextBox Text="{MyApp:CustomBinding}" />
<StackPanel />
Upvotes: 1
Reputation: 310897
I don't think you'll get away without binding to a new intermediate object. Some might call it a view model. I was wondering whether a custom IValueConverter
might be able to intercept the write operation as you need in ConvertBack
, but I'm reasonably sure that the binding system won't even try to call the converter if the source property isn't writable.
Upvotes: 0