I am trying to use Two Step View pattern in WPF. The problem is about keeping things in sync – I need a way to data-bind models to view models. Are there any good existing solutions? My approach is to introduce Property<T>
to be used this way:
public abstract class TextField : ObservableObject
{
public Property<bool> IsEnabled { get; set; } = true;
public Property<string> Text { get; set; } = false;
// etc.
}
Where property supports databinding updates:
public class Property<T> : ObservableObject
{
public static implicit operator Property<T>(T value) => new Property<T>(() => value);
public static implicit operator T(Property<T> property) => property.Value;
public Property(Func<T> getter, params INotifyPropertyChanged[] dataSources)
: this(getter, v => { throw new NotSupportedException(); }, dataSources)
{
}
public Property(Func<T> getter, Action<T> setter, params INotifyPropertyChanged[] dataSources)
{
Getter = getter;
Setter = setter;
foreach (var dataSource in dataSources)
dataSource.PropertyChanged += (s, e) => RaisePropertyChanged("Value");
}
public T Value
{
get { return Getter(); }
set { Setter(value); }
}
Func<T> Getter { get; }
Action<T> Setter { get; }
}
I use helper method Bind in the following way for view model construction:
var model = new DocumentModel();
var viewModel = new TextField
{
Text = model.Bind(m => m.FullName),
IsEnabled = model.Bind(m => m.FullName != "")
};
Where bind defined as:
public static class Binding
{
public static Property<TTarget> Bind<TSource, TTarget>(this TSource source, Expression<Func<TSource, TTarget>> mapper)
where TSource : INotifyPropertyChanged
{
Mapping<TSource, TTarget> mapping = mapper;
return new Property<TTarget>(() => mapping[source], v => mapping[source] = v, source);
}
}
Where Mapping
compiles getter and setter from the expression:
class Mapping<TSource, TTarget>
{
public static implicit operator Mapping<TSource, TTarget>(Expression<Func<TSource, TTarget>> mapping) =>
new Mapping<TSource, TTarget>(mapping);
[DebuggerHidden]
public Mapping(Expression<Func<TSource, TTarget>> mapper)
{
Field = mapper.Parameters[0].Name;
Getter = mapper.Compile().Invoke;
try
{
var newValue = Expression.Parameter(mapper.Body.Type);
var assign = Expression.Lambda<Action<TSource, TTarget>>(
Expression.Assign(mapper.Body, newValue),
mapper.Parameters[0], newValue);
Setter = assign.Compile().Invoke;
}
catch(ArgumentException)
{
}
}
public string Field { get; }
public TTarget this[TSource source]
{
get { return Getter(source); }
set { Setter(source, value); }
}
Func<TSource, TTarget> Getter { get; }
Action<TSource, TTarget> Setter { get; }
}
It works somehow:
<DataTemplate DataType="{x:Type f:TextField}">
<TextBox
Text="{Binding Path=Text.Value, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding Path=IsEnabled.Value}"/>
</DataTemplate>
Does it look ugly? Two Step View helps me a lot…
FrameworkElement
(please see below). Is it supposed to be done this way in WPF? Are there any known side effects of using none CLR properties for VM? \$\endgroup\$ – Dmitry Nogin Jul 6 '16 at 21:16DependencyObject
using third party class, it is not necessary to inherit hugeFrameworkElement
. \$\endgroup\$ – Dmitry Nogin Jul 6 '16 at 21:50