Teaching An Old Control New Tricks With Templates

MiniTutorial

This is the fourth in a short series on Templates and DataValidation.
[ First In Series    Previous In Series ]

You will remember that in the previous post in this small series, we created a new button template with a new appearance and new behavior for the “standard” view states. 

This button will be used with our form, and the form in turn will (eventually) be prepopulated from a database. It would be very convenient if the button reflected the need for the data to be saved when the user updates any of the fields.  The design calls for the button’s border to turn red when the user makes any changes, and to return to normal once the changes are saved. To jazz it up a bit, let’s have the button flash green on its way back to normal.

Because we want the OK button to flash green only if, in fact, something has changed (and is now being saved), we’ll add a flag to keep track of the state of the form:

private enum Status
{
    Dirty,
    Clean,
    Saved
}

If we think about what we want to see in the UI, however, there are two key elements:

  • A change in appearance to represent the new state
  • A transition from one state to the other

stoNinjaSmall  As Clark Kent might have said, “This is a job for STO Ninja!!

No, sorry, that’s not it.  “This is a job for Visual State Manager.”  

In fact, what we need is to add a new Visual State Group: DirtyStates with the three visual state to correspond to the enumerated states listed above.

When the form opens, the button will start off in the Clean state.

When the user changes anything on the form (e.g, clicking a check box) the button transitions to the Dirty state and its appearance is modified to reflect the need for the change(s) to be saved.

When the user clicks a Button that is in the dirty state, it transitions to the Saved state, flashing green and then returning to its initial color

Implementing In Blend

stoNinjaSmall To begin, we’ll want to create a new Visual State Group and three visual states.  This is, as has been noted, far easier to do in Blend. Make a copy of the code from the previous posting, and click to edit the RoundButton template.  Once opened, click on the States panel and then find the “Add state group” button shown in the illustration.  Add a new state group DirtyStates with three new states Dirty, Clean, and Saved

Set the default transition to 0.3 seconds, and then click on the Clean State.  This will cause the red box and the message Clean state recording is on to appearTurn the indicator next to the body revealing the stroke, and then in turn the color. With all three highlighted, click on the Keyframe button, much as you did with the Normal state in the Common States.  Click on the red dot in the upper left of the red box and then click on the Dirty state to record its storyboard, in which you’ll turn the stroke red momentarily.

To accomplish this, you need only change the stroke to red, and then, once again, with the timeline on zero, you can take a snapshot by pressing the keyframe button. You do not have to, and you do not want to animate from blue to red. By simply setting the state to red at zero seconds, and setting the transition time to 0.3 seconds, you’ve created code that will cause the stroke to transition from whatever color it is to start to red over 3/10 of a second.  Here’s the Xaml that is generated:

<VisualState
    x:Name = "Dirty" >
    <Storyboard>
        <ColorAnimation
            Duration = "0"
            To = "Red"
            Storyboard.TargetProperty = 
                "(Shape.Stroke).(SolidColorBrush.Color)"
            Storyboard.TargetName = "body" />
    </Storyboard>
</VisualState>

 

Flashing Green

Finally, when you transition to the Saved state you want to first flash green and then return to the original color. To do this you will need to do a bit more on the timeline. I took a fairly simple approach and created three keyframes:

  1. At zero I set the stroke to red
  2. At 1/2 second I set the stroke to green
  3. At 1 second I set the stroke to blue

With that done, we can exit the template editor and save the files. 

Adding The Logic

All this is great, but not much is going to happen unless we transition to these myriad visual states based on user actions. To do this we need to respond to a number of events: those generated by the elements in the form as the user clicks on buttons and checkboxes, and enters text; and the event of clicking on the button.

To ensure that all of the controls are fully instantiated before setting any of the event handlers, I typically register the Loaded event handler in the constructor, and register all the other event handlers therein.  I find it most comfortable to edit the logic (code) in Visual Studio…

public MainPage()
{
    InitializeComponent();
    Loaded += MainPage_Loaded;
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    OK.Click += OkClick;
    FullName.TextChanged += StateChanged;
    Male.Click += StateChanged;
    Female.Click += StateChanged;
    CSharp.Click += StateChanged;
    VB.Click += StateChanged;
    Skills.SelectionChanged += StateChanged;
    SetVisualState(Status.Clean);
}
 

Note that all the input controls share a single event handler (StateChanged), despite the fact that their events require different parameters to be passed to the TextChanged event handler of TextBox and the SelectionChanged event of the ListBox. We can do this because the variant parameters ( TextChangedEventArgs and SelectionChangedEventArgs respectively) both inherit from the RoutedEventArgs parameter used in the StateChanged event handler.

The StateChanged event handler checks to make sure that the current state is clean, and if so, sets it to dirty.  It then (indirectly)  instructs the Visual State Manager to transition to the Dirty visual state and updates the member variable.

private void StateChanged(object sender, RoutedEventArgs e)
{
    if (currentStatus == Status.Clean)
    {
        SetVisualState(Status.Dirty);
        currentStatus = Status.Dirty;
    }
}

The call to SetVisualState factors out the work of setting the Visual State Manager to each of the three possible DirtyState states.

private void SetVisualState(Status currentStatus)
{
    VisualStateManager.GoToState
        (OK,
          currentStatus.ToString(),
          true);
}

With all this in place, the event handler for the OK button is straightforward as it is the mirror of the StateChanged event handler. That is, it checks whether the currentStatus is Dirty and if so it (indirectly) asks the Visual State Manager to transition to the Saved state and sets the current state to Clean.

private void OkClick(object sender, RoutedEventArgs e)
{
    if (currentStatus == Status.Dirty)
    {
        SetVisualState(Status.Saved);
        currentStatus = Status.Clean;
    }
}

Here is the working demo:

 

Source Code

The complete source code for this example is posted here.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Blend, Mini-Tutorial, Styles and Templates and tagged , , . Bookmark the permalink.

3 Responses to Teaching An Old Control New Tricks With Templates

Comments are closed.