Dialogs and ViewModel - Using Tasks as a Pattern

This post describes the additions to Silverlight.FX to facilitate dialog UI scenarios in the context of a ViewModel/MVVM-based application... using a Task-pattern, where a view model surfaces a child task view model, and the view launches a dialog to present the task to the user.

The ViewModel/MVVM pattern continues to gain popularity, with a blog post showing up every so often, and with tweets and retweets popping up even more often :-). At the same time, there are some interesting topics beyond the core pattern that continue to fuel experimentation. A big one amongst those is how should applications use dialogs when using the view model pattern.

The crux of the problem is the desire to keep the view model independent of UI concerns, and ensure it can be tested in a standalone manner, but that often comes to odds when you want the view model to launch a dialog, and/or do some work after the dialog is closed.

TaskList AppThe most recent version of Silverlight.FX (v3.2) that I published earlier this week, addresses this scenario using a Task pattern. This blog post discusses that pattern, and opens it up for your thoughts.

The screenshot on the right represents my updated TaskList sample application (Click to run and create a task item, and double click on it to launch an edit dialog). All of the sample code is available along with Silverlight.FX for running on your end, and using the same pattern in your own applications of course.

Silverlight.FX provides a concrete notion of a view model representing a task with commit and cancel semantics in the form of a TaskViewModel base class. This serves as the base class for view models associated with dialogs. The view model associated with the main/parent window creates and initializes an instance of a TaskViewModel-derived class to represent the task as hand (when the view decides it needs to launch the dialog). The TaskViewModel raises completion notifications that can be used to do additional work in the parent view model. The main/parent window then creates a Form and assigns the resulting TaskViewModel as Form's view model, and finally shows the dialog. The user interacts with the dialog. The Form implements OK and Cancel commands that the OK/Cancel buttons invoke, and these commands are wired up to call into the TaskViewModel to commit it or cancel it.

I'll use some actual sample code to hopefully make this more concrete.

First lets look at the Form and its associated view model.

<fxui:Form ...>
  ...
  <TextBox Text="{Binding Task.Name, Mode=TwoWay}" />
  ...
  <Button Content="OK" fxui:Interaction.Command="OK" />
  <Button Content="Cancel" fxui:Interaction.Command="Cancel" />
  ...
</fxui:Form>

public class EditTaskModel : TaskViewModel {

    private Task _originalTask;
    private Task _task;

    public Task Task {
        get { return _task; }
    }

    public void Initialize(Task t) {
       _originalTask = t;
       _task = t.Clone();
    }

    protected override void Commit() {
        _originalTask.Copy(_task);
        Complete(/* commit */ true);
    }
}

The EditTaskModel is like any other view model, other than deriving from TaskViewModel which introduces the Commit/Cancel methods, that the Form calls into from its implementation of its OK and Cancel commands.

The EditTaskModel implements and encapsulates the logic of what it means to commit the form. When it is done committing the task it represents, the view model calls Complete on its base class. This raises a notification that signals the Form that it should close itself. This call to Complete can be made within the override of Commit as shown. Alternatively, it can also be called after the completion of some asynchronous work if the act of committing is itself asynchronous. This is simple, yet flexible.

Now that we have our Form and its view model ready, lets look at how it is used from the main/parent window and its associated view model. The main window contains the UI that launches the dialog (in this case upon double clicking) and the associated view model creates the TaskViewModel (in this case to represent editing of a task item).

<ItemsControl ItemsSource="{Binding Tasks}">
  ...
  <!-- TextBlock bound to a particular Task item within the ItemsControl -->
  <TextBlock Text="{Binding Name}">
    <fxui:Interaction.Triggers>
      <fxui:DoubleClickTrigger>
        <fxaction:ShowForm FormType="TaskList.EditTaskForm"
          FormModelExpression="$model.EditTask($dataContext)" />
      </fxui:DoubleClickTrigger>
    </fxui:Interaction.Triggers>
  </TextBlock>
  ...
</ItemsControl>

public class TaskListWidgetModel : ViewModel {

    public IEnumerable<Task> Tasks {
        get { ... }
    }

    public EditTaskModel EditTask(Task task) {
        EditTaskModel model = new EditTaskModel();
        model.Initialize(task);
        model.Completed += delegate(object sender, EventArgs e) {
            if (model.IsCommitted) {
                SaveTasks();
            }
        };

        return model;
    }
}

Basically my view model encapsulates what needs to be done to create the child view model (the EditTaskModel), initialize it, and what to do once it is committed. The view on the is responsible for implementing the user gesture, and creating and showing the Form.

Note that I used declarative triggers and actions to invoke the view model and create/show the dialog. If you don't like triggers, you could write the following imperative code instead in the code-behind:

private void HandleDoubleClick() {
    // Get the view model associated with the main window
    // and the task to be edited
    TaskListWidgetModel mainModel = (TaskListWidgetModel)Model;
    Task task = (Task)((Button)sender).DataContext;

    // Invoke the view model to create the child view model to
    // use as the Form's view model
    EditTaskModel editModel = mainModel.EditTask(task);
    EditForm editForm = new EditForm(editModel);

    editForm.Show();
}

The declarative DoubleClickTrigger and ShowForm action are doing exactly the same thing, but from within XAML. Whether you use the declarative approach or not, is a your choice… this Task-based pattern I am describing works with either style. I personally prefer the declarative approach for specifying view logic.

To recap, let's look at the roles and responsibility's of each piece in this task pattern:

  1. The main window
    • Implements gesture for launching dialog
    • Invokes its view model to create an instance of a task
    • Creates a Form using the task as its model, and shows it
  2. The main window's view model
    • Creates and initializes the task as needed
    • Subscribes to completion of the task to do any work it needs to do once the task has been committed by the user
  3. The Form
    • Allows user to perform the task at hand
    • Implements UI of the dialog including the OK/Cancel buttons
    • Exposes OK/Cancel commands that invoke the Commit/Cancel methods on the associated view model
  4. The Form's view model
    • Represents and encapsulates the logic behind the task
    • Derives from TaskViewModel to support commit/cancel semantics
    • Marks the task as completed by calling Complete(bool commit) when the task has been completed

Based on this you can see the view concerns stay confined to the view. The view has flexibility of how to launch the dialog, the kind of dialog UI it wants, etc. These are things that can be "designed" by a "designer". On the other hand, the core logic behind the dialog have been moved into the view model in the form of a task. The task itself cares little about it is presented to the user, as long as the view creates it, and calls commit or cancel at some point. As such, the pattern helps maintain the separation of concerns.

Its interesting to mention some of the motivators behind this approach:

  • First, the I wanted the solution to be as similar to what people are used to doing today in code-behind. In other words, moving logic to the view model should be as close to refactoring out the non-UI parts from the view. This has been my general stance on the view model pattern.
  • Second, just because I want to use a dialog in my UI, I shouldn't have to pick up relatively more complicated solutions like an event aggregator, and messaging across view models, which in turn introduce conceptual dependencies like IoC. This messaging based solution is often presented as the way to implement dialogs, but these concepts seem complex enough that they shouldn't be prerequisites to something as commonplace and simple as showing a dialog.
  • Third and finally, I should be able to implement the bits that belong to the view both imperatively in code-behind, and declaratively through behaviors, triggers, and actions (as shown above).


What do you think? I would love to hear any and all feedback on thoughts, further ideas, and even alternative approaches, so as to further shape the implementation in Silverlight.FX.

Posted on Friday, 9/11/2009 @ 9:37 AM | #Silverlight


Comments

15 comments have been posted.

Jeremiah Morrill

Posted on 9/11/2009 @ 8:28 PM
Very cool! And mad props on the task pattern...that's a new one for me :).

One thing I have been thinking about with RIA is with the implementation/design of dialogs. In the context of MVVM, how are they any different than any other View/VM that exists within another View? Are "Ok, Cancel, Yes, No, Abort, Retry, Fail" just obselete remnants of Win32/Winforms way of doing things?

Should dialogs really just be a view construct? Only existing in concept to help the user visually focus on a specific task? Does it really need a concrete implementation w/ full support for a "DialogResult"? Or should we just treat a dialog as another View/VM, using whatever pattern we use throughout our application to aggregate UI interaction to the correct places?

I'm not trying say anything is wrong or right, but just wanted to ask that out loud :)

-Jer

Nikhil Kothari

Posted on 9/11/2009 @ 10:22 PM
@Jeremiah
I totally agree the general pattern here is one view/vm composing another view/vm and it just so happens the presentation is that of a traditional dialog for the purposes of this blog post. Earlier today, I had a discussion about this approach of parent/child view/vm, but applied to a region within a larger view, rather than a dialog.

The end-user notion of a modal task, that must be focused on as you said, and finished (or canceled if possible) is still very much valid even in RIAs, and dialogs are something end-users understand. And that is what the TaskViewModel is geared toward with its Commit/Cancel methods. As far as the view models go in this post, the dialog is indeed entirely a view construct.

Ido Ran

Posted on 9/12/2009 @ 12:37 AM
Hi,
Thank you for the post, very nice.
I think the problem of showing dialog (in WPF or SL) is more generally navigation - because as you said it's not always clear cut the responsibility of the View and ViewModel, also it's not always right to design a ViewModel specifically for dialog because it may depend on the context whether the ViewModel should be display in a dialog or regular window.

WPF does take navigation seriously (http://msdn.microsoft.com/en-us/library/ms750478.aspx) but it looks like they took the web approach little to far (maybe I'm wrong).
In my application I've created my own navigation service and the service can navigation (show) new content as well as display it in a dialog.

Again, thank you for your post.
Ido.

Jim Rogers

Posted on 9/12/2009 @ 6:08 AM
@Ido - I too invested a lot of time in navigation issues in general; I wish there were more information out there. With things like complex user controls on tabs, or switching between views in the main window, it's not immediately obvious how to best use MVVM.

I spent a lot of time thinking about dialogs, winding up with a similar solution, with imperative creation of the view. You don't mention it explicitly here, but one of my primary motivators was ensuring that I could unit test the dialogs' functions. With proper separate of view and view model, as you have here, that's a result...

Thanks for the cool post, I'll be looking at your declarative stuff for my next project.

Nikhil Kothari

Posted on 9/12/2009 @ 8:31 AM
@Ido, @Jim
I'd love to hear your thoughts on this post, which speaks about how MVVM and MVC complement each other in navigation scenarios: http://www.nikhilk.net/Silverlight-ViewModel-MVC.aspx

Specifically the view model encapsulates the logic of creating URIs to navigate to, the Frame does the navigation and converts URIs into controller action invocation, and the actions do the work of processing the request to produce some view/viewdata and the Frame uses those to create the view and initialize its associated view model.

Ambrose

Posted on 9/14/2009 @ 5:30 AM
Hi Nikhil,

All your artilces are good. To understand, first we have to learn your framework. Is there any tutorial OR material which will help me to learn your Silverlight.FX from the scratch.

Thanks
Ambrose

Nikhil Kothari

Posted on 9/14/2009 @ 7:17 AM
@Ambrose,
I hear you - the framework has organically grown... and I think its time to put up a post that brings it together, besides the more end-to-end sample I am trying to put together. Will try to get that done as soon as possible.

SimpleNick

Posted on 9/16/2009 @ 10:19 AM
I found a bug dude.
In short, It is difficult to open un-named tasks when I double click on them.
I suspect this is because the text block's area is determined by its contents (String.Empty)
would it be possible to move the dbl-click trigger to the item instead of the text block?

Nikhil Kothari

Posted on 9/16/2009 @ 10:59 AM
@SimpleNick
That sounds like a bug in the sample, which is simply there to show the view model + dialogs pattern. As such the focus is on the pattern and the implementation, and not on the sample.

Volodymyr Kovalenko

Posted on 9/22/2009 @ 3:17 AM
Hi Nikhil,
really cool )

By the way it would be great if commands functionality can support viewmodel. I mean something like
fxui:Interaction.Command="$model.Cancel" or CommandTarget="$model", something like this.

And maybe I don't understand your idea, but as for me it would be better to make Form more... TaskViewModel driven. For example when I on VM call Complete() View could automatically close.

And the last thing. Now Commands in Form are added to Resources in constructor, but when Form has resources declared in xaml this commands are erased.

Nikhil Kothari

Posted on 9/22/2009 @ 11:21 AM
@Volodymyr
Thanks for the comments.

Interesting suggestion on CommandTarget="$model" ... will look into that. I've sort of not been a huge fan of implementing ICommand properties on the view model. I think ICommand is UI glue... and that view models should simply have methods... and it is up to the infrastructure to surface them as ICommands or invoke them directly. Will consider support for it, for those folks who do want to implement ICommand properties on their view models.

Will look into enabling arbitrary calling of Complete on the VM to close the form. The reason I didn't do it is that requires Form to look for when its Model property is set, as the view model doesn't have to be passed in into the ctor. Like DataContext, there isn't any notification for when a Model is set [today].

On the resources in the constructor issue, I think I've heard about that, but forgot to address that. I think its odd that the resources dictionary gets replaced. I might have to move adding commands to resources in the Loaded event... but wonder if that will be too late. Need to look into it.

Volodymyr Kovalenko

Posted on 9/23/2009 @ 8:18 AM
@Nikhil
Thanks for reply.

I agree that placing ICommand in VM is not ideal from layering perspective. My main idea was to make creation of new commands and binding them easier in simple cases. Without copy-paste and simple wrapping VM methods. As more clean alternative may be smth like this
<fxui:Interaction.Behaviors>
<fxui:DelegateCommandBehavior ExecuteMethod="$model.Fire()" CanExecute="$model.CanFire" />
</fxui:Interaction.Behaviors>

As I remember in prism there was something like this, or elsewhere :)

Nikhil Kothari

Posted on 9/23/2009 @ 7:38 PM
@Volodymyr

How does this compare to what you have above?

<Button IsEnabled="{Binding CanFire}" fxui:Interaction.Action="$model.Fire()" />

Personally I'd rather have that rather then in the UI effectively/implicitly create an implementation of ICommand.

Volodymyr Kovalenko

Posted on 9/29/2009 @ 12:38 PM
@Nikhil
Yes, that's nice workaround. Of course if button have right dataContext (for example not in DataGrid row details). But you right, in most cases that's nicer :)

Besides, with all my respect, when you open second form (form and then one more over first one) tabStop is not turned off for first form. More correctly, IsEnabled of current form haven't been changed. As I see you don't change Screen._currentForm on new form opening.

PS Maybe I have to send patch, but I'm new in git and I don't know is this ethically 'cause we are not familiar :)

Roboblob

Posted on 1/21/2010 @ 1:09 PM
Nice solution.

I came to the similar one. Take a look here:
http://blog.roboblob.com/2010/01/19/modal-dialogs-with-mvvm-and-silverlight-4/

i have chosen to simplify it and reuse Silverlight ChildWindow control but completely abstracted so its decoupled and reusable.
Post your comment and continue the discussion.