Home > Uncategorized > Eventless Programming – Part 4: More Views Than You Can Shake A Stick At

Eventless Programming – Part 4: More Views Than You Can Shake A Stick At

Posts in this series:

Last time we got a basic UI together. Next we’ll throw lots of typical ingredients into it, but common theme will be: everything stays consistent. And there ain’t much code.

First of all, just to follow a tidy pattern, we’ll define a proper “view model” class for our main Form:

public class Notes
{
    public readonly ISetableList<Note> AllNotes = new SetableList<Note>();
    public readonly IGetable<IEnumerable<Note>> SelectedNotes;
    public readonly ISetable<Note> ActiveNote = new Setable<Note>();

    public Notes(IEnumerable<string> initialNotes)
    {
        AllNotes.AddRange(initialNotes.Select(
            text => new Note(this) { Text = { Value = text } }));

        SelectedNotes = Computed.From(
            () => AllNotes.Where(n => n.IsSelected.Value).ToList());
    }

    public void Add(string note)
    {
        AllNotes.Add(new Note(this) { Text = { Value = note } });
    }
}

This is just the same code as before, minus any bindings to UI controls. The idea is that you could then make a bunch of different views that bind to the same data (and of course, you can write unit tests). We won’t bother to do that with the whole Notes system, but we will do it for individual notes. I just wanted to neaten this up before continuing.

There is one new thing: the ActiveNote property. Nothing fancy, it just holds a single Note (or null, of course).

I’ve changed Note so it takes a reference to Notes in its constructor (oh yes, it has a constructor now):

public class Note
{
    public readonly ISetable<string> Text = new Setable<string>();
    public readonly ISetable<bool> IsSelected = new Setable<bool>();
    public readonly ISetable<NotePriority> Priority = new Setable<NotePriority>();
    public readonly ISetable<bool> IsActive;
        
    public Note(Notes notes)
    {
        IsActive = Computed.From(
            get: () => notes.ActiveNote.Value == this,
            set: value =>
                {
                    if (value)
                        notes.ActiveNote.Value = this;
                    else if (notes.ActiveNote.Value == this)
                        notes.ActiveNote.Value = null;
                }
        );
    }
}

This allows it to set up its own new IsActive property that is true only if this property is currently the active one. It’s Computed with a getter and a setter, so it provides a different way to interact with Notes.ActiveNote:

noteA.IsActive = true; // activeNote is now noteA
noteB.IsActive = true; // activeNote is now noteB
noteB.IsActive = false; // activeNote is now null

So although it looks like each Note has an independent bool property, they are utterly co-dependent. Only one can be true at a time. Compare this to the typical hacky approach where you actually give each Note a separate bool and then try to keep them in line by manually setting them. How much more elegant to just declare how to simulate a bool property by using a single shared ActiveNote property. It’s pure poetry!

The main NotesForm just sets up the binding to UI controls. In fact I’ve made it implement IBindsTo<Notes> just to follow the pattern. But really this is the same code as before, just rearranged. Oh, I added the concept of NotePriority:

public enum NotePriority
{
    Normal, Low, High
}

So Note has an setable Priority as well. So we have a couple of new things we could bind to in NoteListItemForm:

public void Bind(Note note)
{
    textBoxContent.BindText(note.Text);
    checkBoxIsSelected.BindChecked(note.IsSelected);

    Computed.Do(() => BackColor = note.IsActive.Value
                            ? SystemColors.Highlight
                            : SystemColors.Window);

    Computed.Do(() => pictureBox.Image = 
        note.Priority.Value == NotePriority.High ? Properties.Resources.high :
        note.Priority.Value == NotePriority.Low ? Properties.Resources.low :
        null);

    textBoxContent.Click += (s, ev) => note.IsActive.Value = true;
}

This is a chance to show how you can bind anything in your UI, even if there isn’t a special extension method for whatever it may be. The trick is to use Computed.Do, which we haven’t used so far. It’s just like Computed.From but it returns void and takes a plain Action. Now what the heck is the point of that? A computed observable that computes nothing and gives you back nothing to observe? Oh but it does! As with any void function, it returns a new version of the universe. Or more mundanely, it has side effects on this universe. Or even more mundanely, it has side-effects on some control in our UI.

The beauty of it is that it runs the action once when you set it up, and then again whenever anything it depends on changes. So although you have to write imperative side-effecting statements, they are typically just assignments, and the right-hand side is an expression in terms of observables, just like you’d use in a binding. So it’s a way to roll your own kind of binding. Here I have two examples: the background color is different for the active note, and an icon is used for the two non-normal priorities:

part4-active-icons

There’s also a plain old Click event handler so the user has a way to set this Note to be the active one.

We can also display a more expanded view of the active note, using another small form called NoteEditingForm, which implements IBindsTo<Note> in the usual way.

part4-active

Edit the text in the active view and it immediately changes in the list, and vice versa. Cool!

This is ridiculously easy. On the main form we add a new empty Panel, and then we bind it like this:

panelActiveNote.BindContent(notes.ActiveNote).As<NoteEditingForm>();

That’s it. BindContent is actually a close relative of BindForEach, the difference being that it uses a single value that may be null, which we could think of as a list that has either zero items or one item. NoteEditingForm has one minor thing we haven’t seen previously: binding to radio buttons:

public void Bind(Note note)
{
    textBoxContent.BindText(note.Text);
    checkBoxIsSelected.BindChecked(note.IsSelected);

    radioButtonHigh.BindChecked(note.Priority, NotePriority.High);
    radioButtonNormal.BindChecked(note.Priority, NotePriority.Normal);
    radioButtonLow.BindChecked(note.Priority, NotePriority.Low);
}

Easy-peasy. Okay, so what else could we do? What if there was an alternative view where we just show you a big scrollable list of these NoteEditingForm tiles? Sounds like a lot more work… No, you dummy! It’s just this:

panelEditors.BindForEach(notes.AllNotes).As<NoteEditingForm>();

And immediately we have this:

part4-editors

Of course, you can edit in one view and it changes in all the others. They’re all bound to the same simple underlying view model, and the bindings are declarative, and therefore hard to get wrong. It makes the UI very malleable – you can very quickly try out ideas, move pieces around, and know that the pieces will keep working. And that in turn gives you the power to make a better UI, because experimentation (with feedback from real users, even if that’s just yourself) is the only way to make a better UI.

Code in the usual place. Next time, how we can integrate with async/await, and use throttling to reduce the rate of recomputation.

Advertisements
Categories: Uncategorized Tags: , ,
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: