2
\$\begingroup\$

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…

\$\endgroup\$
  • 2
    \$\begingroup\$ IMO XAML basically is the first step in the 2 step view. It feels like you now have a 3 step view, which is overly abstracted and over engineered. FWIW. \$\endgroup\$ – RubberDuck Jul 6 '16 at 20:45
  • \$\begingroup\$ Yep, something like this - but we always have it in MVVM (M-V-VM). I just do not see how XAML defines a provable contract, which is an idea behind the pattern. Compiler does not check bindings as far as I know. I am also looking for a way to easily introduce calculable bindings. \$\endgroup\$ – Dmitry Nogin Jul 6 '16 at 21:02
  • \$\begingroup\$ the compiler won't enforce the binding, but it will warn you if it can't resolve. Crank up your compiler warnings so they're errors instead. Just a thought. \$\endgroup\$ – RubberDuck Jul 6 '16 at 21:03
  • \$\begingroup\$ Yep, will do, thanks. BTW, I still think that I need to data-bind VM to M, and the simplest way would probably be to inherit VM from 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:16
  • 1
    \$\begingroup\$ Oops, got it. Data-binding works with DependencyObject using third party class, it is not necessary to inherit huge FrameworkElement. \$\endgroup\$ – Dmitry Nogin Jul 6 '16 at 21:50
1
\$\begingroup\$

Here is how I redefined it now. Demo:

public MainWindow()
{
    InitializeComponent();

    var dvm = new DemoViewModel();
    var dm = new DemoModel();
    dvm.Text = dm.Select(m => m.Text); // bidirectional
    dvm.Text2 = dm.Select(m => m.Text); // bidirectional
    dvm.IsReadOnly = dm.Select(m => m.Text.Length > 4); // calculable
    DataContext = dvm;
}

Where:

class DemoModel : Model
{
    string _text = "";

    public string Text
    {
        get { return _text; }
        set { Set(() => Text, ref _text, value); }
    }
}

And (too verbose, but it is a stupid WPF thing):

class DemoViewModel : ViewModel
{
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(DemoViewModel));

    public static readonly DependencyProperty Text2Property =
        DependencyProperty.Register("Text2", typeof(string), typeof(DemoViewModel));

    public static readonly DependencyProperty IsReadOnlyProperty =
        DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(DemoViewModel));

    public Binding<string> Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetBinding(TextProperty, value); }
    }

    public Binding<string> Text2
    {
        get { return (string)GetValue(Text2Property); }
        set { SetBinding(Text2Property, value); }
    }

    public Binding<bool> IsReadOnly
    {
        get { return (bool)GetValue(IsReadOnlyProperty); }
        set { SetBinding(IsReadOnlyProperty, value); }
    }
}

Library classes:

class Model : ObservableObject
{
}

And:

Outdated - see below:

class ViewModel : FrameworkElement
{
    protected BindingExpressionBase SetBinding<T>
        (DependencyProperty dp, Binding<T> property)
    {
        var binding = new Binding("Value") 
            { Source = property, Mode = BindingMode.TwoWay };

        return SetBinding(dp, binding);
    }
}

Now

public class ViewModel : DependencyObject
{
    protected BindingExpressionBase SetBinding<T>(DependencyProperty dp, Binding<T> property)
    {
        var binding = new Binding("Value")
        {
            Source = property,
            Mode = BindingMode.TwoWay
        };

        return BindingOperations.SetBinding(this, dp, binding);
    }
}

And:

public static class ModelBinding
{
    public static Binding<T> Select<TM, T>(this TM m, Expression<Func<TM, T>> mapper)
    {
        Mapping<TM, T> mapping = mapper;
        return new Binding<T>(() => mapping[m], v => mapping[m] = v, m);
    }
}

And:

public class Binding<T> : ObservableObject
{
    public static implicit operator Binding<T>(T value) => new Binding<T>(() => value);        
    public static implicit operator T(Binding<T> property) => property.Value;

    public Binding(Func<T> getter, params object[] dataSources)
        : this(getter, v => { throw new NotSupportedException(); }, dataSources)
    {
    }

    public Binding(Func<T> getter, Action<T> setter, params object[] dataSources)
    {
        Getter = getter;
        Setter = setter;            
        foreach (var npc in dataSources.OfType<INotifyPropertyChanged>())
            npc.PropertyChanged += (s, e) => RaisePropertyChanged("Value");
    }

    public T Value
    {
        get { return Getter(); }
        set { Setter(value); }
    }

    Func<T> Getter { get; }
    Action<T> Setter { get; }
}

And:

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)
    {
        Getter = mapper.Compile().Invoke;

        try
        {
            var newValue = System.Linq.Expressions.Expression.Parameter(mapper.Body.Type);
            var assign = System.Linq.Expressions.Expression.Lambda<Action<TSource, TTarget>>(
                System.Linq.Expressions.Expression.Assign(mapper.Body, newValue),
                mapper.Parameters[0], newValue);

            Setter = assign.Compile().Invoke;
        }
        catch (ArgumentException)
        {
        }
    }

    public TTarget this[TSource source]
    {
        get { return Getter(source); }
        set { Setter(source, value); }
    }

    Func<TSource, TTarget> Getter { get; }
    Action<TSource, TTarget> Setter { get; }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.