Flowing Custom Control Values to ViewModel/Bindings

Friday, January 15, 2010 / Posted by Luke Puplett /

This article discusses an issue I had when making a custom login box control, particularly the issue of getting the values in the username and password boxes to flow back down to the view-model. The issue might be my design, which could be fundamentally wrong, if only I knew of another way to do it.

The problem with Windows Presentation Foundation and its more promiscuous sister, Silverlight, is that its as good as it is bad. That is, its hard to deny its brilliance when you're carving out complex drawings and alpha gradients, adding subtleties to button animations and then there's the whole hardware accelerated 3D bit. But its bloody complicated because its mandate is to be everything to everyone and when so much is done for you all that stuff has to be massively multifaceted.

My LoginControl uses the parts and states model and the WPFToolkit to get at the VisualStateManager. I won't go into these things as they're documented enough and Karen Corby has a great MIX session video on it, too.

So I have a few parts, two TextBox controls and a Button. You can guess that these are username, password and the logon button.

Firstly, and I'll get this one over and done with quickly: I don't use the PasswordBox control. It does not support data binding because it takes a cautious approach and does things securely. Instead I apply a strikethrough formatting to a regular TextBox within its default style in generic.xaml.

You can see how this is done by reading my comments at the bottom of the MSDN article on the PasswordBox.

Welcome to my LoginControl

Cutting straight to the meat of the implementation, my control has the following properties:

  • private TextBox UsernameTextBox // the property which stores the TextBox UI control part/element for the username
  • private TextBox PasswordTextBox // the property which stores the TextBox UI control part/element for the password
  • public string Username // the dependency property which stores the username that can be bound to from a view model
  • public string Password // ditto but for the password

Because I can't bind my view model to a property of a the LoginControl's username TextBox and I don't want to expose the TextBox controls publicly, I use the following trick to hook up a binding between the TextBox.Text dependency property and LoginControl.Username (ditto for the password):

private TextBox UsernameTextBox
{
    get
    {
        return (TextBox)GetValue(UsernameTextBoxProperty);
    }
    set
    {
        // Detach observer from outgoing value.
        //
        var oldValue = this.UsernameTextBox;
        if (oldValue != null)
        {
            BindingOperations.ClearAllBindings(oldValue);
            oldValue.GotKeyboardFocus -= new KeyboardFocusChangedEventHandler(TextBoxes_GotKeyboardFocus);
            oldValue.KeyUp -= new KeyEventHandler(Username_KeyUp);
        }

        SetValue(UsernameTextBoxProperty, value);

        if (value != null)
        {
            value.SetBinding(TextBox.TextProperty, new Binding("Username")
            {
                Source = this,
                Mode = BindingMode.TwoWay
            });
            value.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(TextBoxes_GotKeyboardFocus);
            value.KeyUp += new KeyEventHandler(Username_KeyUp);
        }
    }
}

And once that's done there's nothing else to consider except that when adding my LoginControl to my XAML page, I need to hook up the binding to the appropriate properties in the view model. That's easy:

Username="{Binding Path=Username, Mode=TwoWay}" Password="{Binding Path=Password, Mode=TwoWay}"

No worries there.

In Practice it Fails

The problem occurs when the login button is clicked and the ICommand is executed. The RelayCommand on the view model kicks in and my Action runs but if I stop the debugger right there I see that my view model does not have up-to-date Username and Password values!

My theory is that the binding system has not done its magic in between my typing of text and clicking of button. And although in theory the LostFocus events upon which data binding updates rely should have been triggered, my daisy-chained setup is somehow ballsing things up. Actually, there is a LostFocus on the username TextBox which should update the LoginControl.Username property, it doesn't, but even if it had, what's the activator to get the value to then jump from the Username property to its source down in my view model? By the way, flows up from source seem to work fine.

Whatever, I don't know. My solution was to grab each of my bindings and manually hit the UpdateSource method from within the login button's click method:

private void LoginButtonElement_Click(object sender, RoutedEventArgs e)
{
    var utbx = this.UsernameTextBox.GetBindingExpression(TextBox.TextProperty);
    utbx.UpdateSource();

    var ptbx = this.PasswordTextBox.GetBindingExpression(TextBox.TextProperty);
    ptbx.UpdateSource();

    var ubx = BindingOperations.GetBindingExpression(this, UsernameProperty);
    var pbx = BindingOperations.GetBindingExpression(this, PasswordProperty);

    ubx.UpdateSource();
    pbx.UpdateSource();

    if (this.PasswordTextBox != null)
    {
        if (!this.PasswordTextBox.IsFocused)
        {
            this.PasswordTextBox.Focus();
            if (!this.TryLoginButton.IsMouseOver)
                return;
        }
    }

    if (this.Command != null)
        this.Command.Execute(new KeyValuePair<stringstring>(this.Username, this.Password));
}

Which brings me right back to the point I made earlier about not doing this the right way. I'm not sure any other way would flow the values in time anyway. But then there's always the question of how everyone else doesn't seem to have this problem...

Labels: , , ,

0 comments:

Post a Comment