9
Dez.
Off

(English) On databinding

Leider ist der Eintrag nur auf Amerikanisches Englisch verfügbar. Der Inhalt wird unten in einer verfügbaren Sprache angezeigt. Klicken Sie auf den Link, um die aktuelle Sprache zu ändern.

Legacy WinForms with tons of code in the eventhandler methods

You start a developing a simple WinForm, you don’t like databinding because you don’t feel you have a tight grip on format, timing and updates.
The more complex it gets the more you throw code in the eventhandlers to update other related controls etc.
You lose grip on where you defined requirements and what is the data model.

Databinding and MVVM

I find it quite usefull to use MVVM when refactoring an old WinForms application. The clarification on actions,  the separation of the viewmodel effects and the relevant model are quite helpful.

It even helps when moving from WinForms to WPF or MVC, especially when using knockout.js in conjunction with WebAPI.

But real elegance takes further efforts:

Where to define databinding

Separation of concerns
In my opinion databinding is functional code to be performed by the frontend developer. The UX designer should only desing features that are visible.

I prefer to do this in code-behind in a separate binding method.

How to define databinding

The standard for WinForm and WPF databinding is that you use the designer to map viewmodel fields to your control properties etc.
Doing so you map string constants to control properties

Strings don’t support reference lookup, refactoring and compile time validation very well.

What I want is to map the observable viewmodel property to the control property direktly.

Something like

myTextBox.Bind(() => myTextBox.Enabled, () => myViewModel.EditingAllowed);

instead of the base databing method

myTextBox.DataBindings.Add("Enabled", theViewModel, "EditingAllowed")

Here is an extension class for WinForms for Visual Studio 2013

Starting with Visual Studio 2015 you can use the nameof() operator to help you.

public static class BindingExtension
{
    /// <summary>
    /// Extension Method for Winform controls
    /// that binds a control property to a ViewModel property
    /// </summary>
    /// <typeparam name="C">any control derived from System.Windows.Forms.Control</typeparam>
    /// <typeparam name="P">the type of the properties, must be the same for the control as for ViewModel property</typeparam>
    /// <param name="winFormControl">the control to be bound</param>
    /// <param name="controlPropertyLambda">an System.Linq.Expressions.Expression defining the control property</param>
    /// <param name="viewModelPropertyLambda">an System.Linq.Expressions.Expression defining the ViewModel property</param>
    /// <example>myTextBox.Bind(() => myTextBox.Enabled, () => myViewModel.EditingAllowed)</example>
    public static void Bind<C,P>(
        this C winFormControl,
        Expression<Func<P>> controlPropertyLambda, 
        Expression<Func<P>> viewModelPropertyLambda) where C: Control
    {
        var controlPropertyName = PropertyName(controlPropertyLambda);
        var viewModelPropertyName = PropertyName(viewModelPropertyLambda);
        var viewModel = GetViewModelInstance(viewModelPropertyLambda);
        winFormControl.DataBindings.Add(controlPropertyName, viewModel, viewModelPropertyName);
    }
 
    /// <summary>
    /// Resolves the name of the property in an expression
    /// </summary>
    /// <typeparam name="T">delegate type of the expression</typeparam>
    /// <param name="propertyLambda">an System.Linq.Expressions.Expression defining the property of an object</param>
    /// <returns>name of the Property</returns>
    /// <example>PropertyName(() => myObject.MyProperty)</example>
    private static String PropertyName<T>(Expression<T> propertyLambda)
    {
        if (propertyLambda.Body.NodeType == ExpressionType.MemberAccess)
        {
            var memberExpression = propertyLambda.Body as MemberExpression;
            if (memberExpression == null)
                return null;
            return memberExpression.Member.Name;
        }
        return null;
    }
 
    /// <summary>
    /// Analyze the expression and use reflection to get the object
    /// method was derived from 
    /// http://stackoverflow.com/questions/1613239/getting-the-object-out-of-a-memberexpression
    /// </summary>
    /// <typeparam name="T">delegate type of the generic expression</typeparam>
    /// <param name="lambda">expression to be analyzed</param>
    /// <returns>the runtime object of the expression</returns>
    /// <example>GetViewModelInstance(() => myObject.MyProperty)</example>
    private static object GetViewModelInstance<T>(Expression<T> lambda)
    {
        var memberInfos = new Stack<MemberInfo>();
        var expr = lambda.Body as Expression;
        // "descend" towards the root object reference:
        while (expr is MemberExpression)
        {
            var memberExpr = expr as MemberExpression;
            memberInfos.Push(memberExpr.Member);
            expr = memberExpr.Expression;
        }
        var constExpr = expr as ConstantExpression;
        // fetch the root object reference:
        
        var objReference = constExpr.Value;
 
        object resultObject = null;
 
        if (memberInfos.Count > 0)
        {
            var mi = memberInfos.Pop();
 
            if (mi.MemberType == MemberTypes.Property)
            {
                resultObject = objReference.GetType()
                                           .GetProperty(mi.Name)
                                           .GetValue(objReference, null);
            }
            else if (mi.MemberType == MemberTypes.Field)
            {
                resultObject = objReference.GetType()
                                           .GetField(mi.Name)
                                           .GetValue(objReference);
            }
        }
        return resultObject;
    }
 
}

Performance considerations

LINQ Expressions are performing well, reflection is used once per binding for the sake of elegance, the ViewModel could be an additional Parameter for binding.
Remember that binding is done only once when calling the form.

Don’t optimize too early. Yet I had no performance issues with databinding.

It’s more important not to freeze the GUI thread. For this purpose use async/await for databinding. The WinForm starts fast and data are filled into controls when the ViewModel is loaded and bound.

An additional benefit is that command actions can be bound after control properties so that your actions on the GUI won’t trigger any Actions before loading is complete.

Command action binding

button1.Click += (sender, eventArgs) => { myViewModel.ExecuteCommand(); };

Here we Register a new eventhandler, get rid of the sender and eventArgs during binding and use a lambda that calls a method in the ViewModel

 

Remaining uglyness

In WinForm and WPF there are still some ugly remains to be coded in code-behind or in the ViewModel. An example is a changing background color for a TextBox. This should be done by the desinger but neither WinForm nor WPF allow proper abstraction for Enums like ’negative‘,’positive‘,’empty‘.

WPF has dynamic ressources which can be defined in the XAML and assigned to the brush of a UI control but the name of the ressource cannot be bound.

The Beauty of the web

When using Knockout.js only the Model is delivered via WebApi from the Server,
the ViewModel is defined via Typescript/Javascript
and the Binding is defined in attributes whitin HTML:

<input id="MyCheckbox" type="checkbox" data-bind="checked: myViewModel.EntryAllowed" />

The UX designer can ignore the attribute „data-bind“. The page is rendered even without WebApi, Knockout.js or an existing ViewModel.
The frontend developer can use IntelliSense to bind ViewModel properties and can use expressions to set the style etc.:

<input id="SaldoText" type="text" data-bind="value: Saldo, style: { 'background-color': (isNaN(myViewModel.Saldo())) ? 'white' : ((parseFloat(myViewModel.Saldo()) < 0) ? 'red' : 'aqua')}, enable: myViewModel.EntryAllowed" />

 

Schlagwörter: ,