Robert Važan

Expose WPF control to view model III

Gain access to WPF controls from view models where needed. While admittedly an ugly practice, it is sometimes easier that fixing the offending controls.

Although accessing WPF controls from view models is a big no-no in unit-tested view models, it's often a necessary evil when working with poorly designed controls. Moreover, in many WPF programming styles, exposing selected controls to view models is not only allowed but heartily encouraged, because view models are often a favorable replacement for messy code-behind logic. I will now show you how to expose WPF controls to view models cleanly and easily.

I've already tried to expose WPF controls via attached property, but that approach failed due to oddly (mis)behaving OneWayToSource bindings in WPF.

Filled with mistrust for anything to do with attached properties, I resorted to a single call in user control constructor that can lift any control into the view model. That technique was working reasonably well while user controls seemed like the way to go. Then I ran into performance issues that forced me to replace all user controls with templates. My user control constructor was gone and I had to look for yet another solution.

After reviewing approaches by other developers, which I have summed up in my last post, and after having two wrong designs already, I sat down and designed something much simpler that will (hopefully) work flawlessly forever.

All you have to do is to add single property to the exposed control and bind it to the view model property that will carry the control reference:

<PasswordBox jc:ExposeControl.As="{Binding Password}" />

On the view model side, you just declare Password property like this:

public PasswordBox Password { get; set; }

Done! Now your view model methods can access Password property that will be initialized to view's PasswordBox for you.

Update: ExposeControl is now part of JungleControls library. All you need to make it work is to add this short piece of supporting code to your app.

You can see the original ExposeControl implementation below. Note the Dispatcher.BeginInvoke, which prevents the property from being modified while previous change notification is being processed.

public static class ExposeControl
{
    static readonly object NoObject = new object();
        
    static readonly FrameworkPropertyMetadata AsMetadata
        = new FrameworkPropertyMetadata(
            NoObject,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            AsChanged);
        
    public static readonly DependencyProperty AsProperty
        = DependencyProperty.RegisterAttached(
            "As",
            typeof(object),
            typeof(ExposeControl),
            AsMetadata);
        
    public static object GetAs(DependencyObject obj)
    {
        return obj.GetValue(AsProperty);
    }

    public static void SetAs(DependencyObject obj, object value)
    {
        obj.SetValue(AsProperty, value);
    }

    static void AsChanged(
        DependencyObject obj,
        DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue != obj)
        {
            obj.Dispatcher.BeginInvoke(() =>
            {
                obj.SetCurrentValue(AsProperty, obj);
            });
        }
    }
}