WF4 ViewStateService

A comment posted by Notre asked for some more details about view state and attached property services, so I thought I would dive into those next.  I will follow-up in a subsequent post on the AttachedPropertyService, as there is a little bit more going on there.

Motivation

Why do I care about viewstate?  Well, usually it is because we want to write something down and store it for later that is not required for runtime.  A common example of viewstate is the position of nodes within a flowchart.  While not required to execute the flowchart, they are required to effectively view the flowchart. 

Where to write them down?

This was a question that caused a fair amount of debate on the team.  There are basically two places to write down things like view state in a file-based world. 

  1. In the source document itself
  2. In a document that stays close to the source (usually referred to as a sidecar file)

We had customers asking for both.  The motivation for the first is that for things like flowchart, where I may always care about the visualization representation, I want to keep that metadata around and only deal with one element.  For the second, it is motivated by the reason that we want a clean source document that only describes the minimal artifact to run.  Now, there are certainly many stops along the spectrum (for instance, we might always want to keep annotations or source comments in the source document, and put positioning elsewhere).  For VS2010, we landed with a unified API to use, and we write in the source document.  This is something that is likely to change in future releases, as it does make things like textual diffs rather painful.

So, that’s why we want to use it.

How do we use it?

We are going to create a simple activity designer that lets me write down a comment.

A few simple steps:

  1. Create a new VS Project, let’s create an Activity Library
  2. Add a Designer to that activity library
  3. Add an attribute to the activity pointing to the designer
  4. Add a new WorkflowConsoleApp Project
  5. Build

image

Now, let’s go and make our activity designer a little interesting.

Let’s add a text box and a button.  We’ll make the text of the button something obvious like “commit comment” The XAML for the activity designer looks like this:

 <sap:ActivityDesigner x:Class="simpleActivity.CommentingDesigner"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
  <Grid>
        <StackPanel Name="stackPanel1" VerticalAlignment="Top" >
            <TextBox  Name="commentBlock"   />
            <Button Content="Load View State" Name="loadViewState" Click="loadViewState_Click" />
            <Button Content="Commit View State"  Name="button1" Click="button1_Click" />
        </StackPanel>
    </Grid>
</sap:ActivityDesigner>

Now, let’s add some code to the button (and to the initialization of the form)

ViewStateService has a few useful methods on it.  I want to call out a subtle difference.  You will see StoreViewState and StoreViewStateWithUndo.  The primary distinction as the name implies is that one will simply write the view state down and will bypass the undo/redo stack.  This is for view state like an expanded/collapsed view.  You don’t really want ctl-z to simply flip expanded versus collapsed for you.  But for something like flowchart, where changing some of the viewstate, like position, might be such a thing that you want support for undoing the action.  That’s the primary difference.

So, our code for the button looks like this:

 private void button1_Click(object sender, RoutedEventArgs e)
{
    ViewStateService vss = this.Context.Services.GetService<ViewStateService>();
    vss.StoreViewStateWithUndo(this.ModelItem, "comment", commentBlock.Text);
}

Now, on load, we want to be able to populate the value, so we will use the RetrieveViewState method in order to extract this.

 private void loadViewState_Click(object sender, RoutedEventArgs e)
{
    ViewStateService vss = this.Context.Services.GetService<ViewStateService>();
    commentBlock.Text = vss.RetrieveViewState(this.ModelItem, "comment") as string;
}

Now, let’s go back to our workflow project and put an instance of this activity on the surface:

image

Let’s add some viewstate information and commit it.  Now let’s look at the XAML:

 <s4:NotRealInterestingActivity Text="{x:Null}" sap:VirtualizedContainerService.HintSize="200,99">
  <sap:WorkflowViewStateService.ViewState>
    <scg3:Dictionary x:TypeArguments="x:String, x:Object">
      <x:String x:Key="comment">basic comment</x:String>
    </scg3:Dictionary>
  </sap:WorkflowViewStateService.ViewState>
</s4:NotRealInterestingActivity>

Ok, now, to show that we can pull this in, let’s change the text in the xaml and then reload our designer.

image 

You can muck around with ctl-z to see that this does get handled correctly via the undo. 

The other important thing to note is that this takes an object, so your viewstate is not limited to strings, you can have more full featured objects if you’d like. Finally, the ViewStateService also has a ViewStateChanged you can subscribe to in order to handle, dispatch, and react to view state changes in the designer.