A common UI problem

This blog post is all about a common UI-related programming problem. Ever since my first UI-based application I’ve come across with this problem and never found a satisfying way to resolve it (until now 😉 ). This first blog post is about describing the problem and my former solution approaches. In the next blog post I will demonstrate a more proper solution.

The code in these two blog posts is using WinForms and C#, but it should be easy adoptable to other UI technologies as well.

Problem description

It’s a common problem: you want to react on an event like the TextChanged event on a TextBox, but ONLY if the user changed the text and it’s not changed programmatically. For example you want to update some data, when the user changes the content of a TextBox, but this update process should not be triggered when the progam itself sets some data and therefore updates the text. The starting point for that could be something like the following code:

public partial class SomeControl : UserControl
{
    // ...

    private ViewData _viewData;
    public ViewData ViewData
    {
        get { return _viewData; }
        set
        {
            _viewData = value;
            if (value != null)
            {
                _someTextBox.Text = value.SomeValue;
                // other operations
            }
        }
    }

    private void OnSomeTextBoxTextChanged(object sender, EventArgs e)
    {
        // perform data/view update operations
    }
}

Here the OnSomeTextBoxTextChanged() method is bound to the _someTextBox.TextChanged event. ViewData is a class with arbitrary data that should show up in the UI.
Notice the problem: when the ViewData property is set programmatically the OnSomeTextBoxTextChanged() method is being executed, which is not what was intended (firing the event only when the user changes the value of the TextBox).

A similar problem arises with infinite loops. Imagine a user changes something in the UI and an event X is fired. You hook this event and start a complex UI workflow, which at the end fires the event X again. The proces is executed again and very easy you come into an infinite loop situation. If you are an UI developer you would very probably agree that UI event chains are often opaque and can get messy.

Let’s look at some obvious, however not very elegant, solutions.

Approach #1: Temporary event detach

One of those simple solution is the temporary detachment of the problematic event. For example if you don’t want to react on the TextChanged event when the TextBox.Text property is set programmatically, you could detach your event handler from TextChanged before setting the Text and afterwards attach it again. This approach is shown in the following example code:

public partial class SomeControl : UserControl
{
    // ...

    private ViewData _viewData;
    public ViewData ViewData
    {
        get { return _viewData; }
        set
        {
            _viewData = value;
            if (value != null)
            {
                _someTextBox.TextChanged -= OnSomeTextBoxTextChanged;
                _someTextBox.Text = value.SomeValue;
                _someTextBox.TextChanged += OnSomeTextBoxTextChanged;

                // other operations
            }
        }
    }

    private void OnSomeTextBoxTextChanged(object sender, EventArgs e)
    {
        // perform data/view update operations
    }
}

While this is working, it’s not a recommendable solution, because there are several shortcomings. First the manual event handling is not very intuitive and doesn’t explicitly reveal the intent of the programmer with this manual process. Thus your code is more difficult to read for others (and after some months for you as well). Moreover, you get undefined states when exceptions are thrown and caught in an outer component (and you miss a finally which attaches the event again). Then the event handler could be detached further on and the UI isn’t working properly afterwards. The whole event detach/attach process is getting very messy if you have complex views with many such problematic events. Last but not least this manual event handling process binds your code tightly to the view and you get trouble if you want to refactor several parts out.

Approach #2: Boolean flags

A similarly simple approach comes with boolean flags which indicate that a value is currently set programmatically and thus that Changed events should not be handled. The following code shows an example how this could solve our initial problem:

public partial class SomeControl : UserControl
{
    // ...

    private bool _isTextSetByProgram = false;

    private ViewData _viewData;
    public ViewData ViewData
    {
        get { return _viewData; }
        set
        {
            _viewData = value;
            if (value != null)
            {
                _isTextSetByProgram = true;
                _someTextBox.Text = value.SomeValue;
                _isTextSetByProgram = false;

                // other operations
            }
        }
    }

    private void OnSomeTextBoxTextChanged(object sender, EventArgs e)
    {
        if (!_isTextSetByProgram)
        {
            // perform data/view update operations
        }
    }
}

I think this is the most common solution that I’ve seen for the problem. For myself I must admit that I’ve mostly used this approach. But it has the same disadvantages like the event detach/attach solution (except the tight view coupling). Boolean variables don’t explicitly show the intent behind them and get likewise messy if used in complex situations where you could have dozens of those variables scattered around a view.

So while those solutions are very widespread and work they just don’t feel right and clean. But what’s the alternative? An interesting little one I will show you in the next post which comes shortly.

kick it on DotNetKicks.com

2 Gedanken zu „A common UI problem“

  1. That’s right and applicable in many situations with DataBinding (while manual handling of the BindingComplete event doesn’t feel very nice as well). But if you can’t/don’t want to use DataBinding, there’s another great option 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.