Let’s look at the first of the screens we need to build for our application - the Add Word screen, used (surprisingly enough) to add a new word to the list of words we’re currently viewing.

Before we can start writing code, we need to do a little bit of design: what do we want this screen to look like?

Here's a mockup of one possible design. It's deliberately a low fidelity image, spelling mistake and all, intended to start a discussion about the user experience.

I've found the putting together a polished mockup invites debate about font choices, colours, and spacing of elements. It seems that people get distracted by "shiny" and stop thinking about the important issues - such as whether the UX will deliver what the users actually need. This even applies to experienced subject matter experts.

At a previous job, we used Balsamiq Mockups to good effect. Unfortunately, I no longer have access to that tool - so I used quickMockup instead.

We can start by declaring the properties that our screen needs to expose:

public class AddVocabularyWordScreen : Screen
{
    public string Spelling { get; } = string.Empty;
    public string Pronunciation { get; } = string.Empty;
    public string Phrase { get; } = string.Empty;

We start each property with an empty string so that we never need to deal with nulls.

Our constructor is straightforward - in fact, we could have omitted it completely.

For each of the properties, we follow the convention we’ve already established, providing a WithXXX() method that returns a near-clone of the original.

    public AddVocabularyWordScreen WithSpelling(string spelling)
    {
        return new AddVocabularyWordScreen(
            this,
            spelling: spelling ?? string.Empty);
    }

I’ll skip over the implementations of WithPhrase() and WithPronunciation(), jumping straight to the private constructor that makes them possible:

    protected AddVocabularyWordScreen(
        AddVocabularyWordScreen original,
        string spelling = null,
        string pronunciation = null,
        string phrase = null)
    {
        if (original is null)
        {
            throw new ArgumentNullException(
                nameof(original));
        }

        Spelling = spelling ?? original.Spelling;
        Pronunciation = pronunciation ?? original.Pronunciation;
        Phrase = phrase ?? original.Phrase;
    }
}

You’ll note that we don’t have any provision here for undo or redo, even though those were shown in the mockup. We’ll come back and put those in later on, once we’ve got a few more moving parts hooked up.

Recall that the Redux model revolves around messages that are sent to our central store, with reducers that apply those message to our current state.

Let’s create a trio of messages that can be used to update our AddVocabularyWordScreen as the user makes changes. Here’s the declaration of ModifySpellingMessage:

public class ModifySpellingMessage : IReduxMessage
{
    public string Spelling { get; }

    public ModifySpellingMessage(string spelling)
    {
        Spelling = spelling;
    }
}

The message contains just what we need, nothing more. The implementations of ModifyPronunciationMessage and ModifyPhraseMessage are similarly simple.

We can now start on our ScreenReducer class, used to combine screens and messages. Our entry point is based on implementing the IReduxReducer interface:

public class ScreenReducer : IReduxReducer<Screen>
{
    public Screen Reduce(IReduxMessage message, Screen screen)
    {
        switch (screen)
        {
            case AddVocabularyWordScreen add:
                return ReduceAddVocabularyWord(message, add);

            default: return screen;
        }
    }

For each kind of screen, we’ll delegate out to a specified private reducer method:

    private Screen ReduceAddVocabularyWord(
        IReduxMessage message, 
        AddVocabularyWordScreen screen)
    {
        switch(message)
        {
            case ModifySpellingMessage msg:
                return screen.WithSpelling(msg.Spelling);

            case ModifyPhraseMessage msg:
                return screen.WithPhrase(msg.Phrase);

            case ModifyPronunciationMessage msg:
                return screen.WithPronunciation(msg.Pronunciation);

            default:
                return screen;
        }
    }
}

In this case, the handling of each message is simple, so we do it inline. For more complicated situations, we would break the logic out into a dedicated method for that one case.

Prior post in this series:
WPF Projects & ViewModelBase
Next post in this series:
Add Word View Model

Comments

blog comments powered by Disqus