

Live example: Click here
Also see
Dynamically Creating Views
When using "View Model Style" programming, you may find a need to dynamically create Views. The challenge is to create them dynamically, while also allowing a Designer to easily design the UI.
View Model Style

View Model Style allows a programmer to create an application that has absolutely no UI (user interface). The programmer only creates a View Model and a Model. A designer with no programming ability at all is then able to start with a blank page and completely create the View (UI) in Microsoft Expression Blend 4 (or higher).

If you are new to View Model Style, it is suggested that you read Silverlight View Model Style: An (Overly) Simplified Explanation for an introduction.
RIA Tasks 2

This article uses much of the same code used in RIATasks: A Simple Silverlight CRUD Example.
While this article uses the same database and website code, it covers these additional things:
- Dynamically creating Views (with View Models)
- Using the Silverlight Tab Control
- Creating a Design-time View for a dynamically created View Model
- Programmatically setting the selected Tab
- Programmatically creating TabItems and binding the Tab Control to them
- Programmatically setting the Style of dynamically created Tabs to a Static Resource
The Application

The previous application (RIATasks: A Simple Silverlight CRUD Example) only allows you to edit one Task at a time.

This application allows you to create unlimited Tabs, that each contain an editable Task.
The Web Service

While we normally use a View Model so that we don't need to change the UI when the design changes, if the basic requirements change, we usually need to actually change the code. In this case, the requirements have changed.
For the Web Site, the only code that needed to be changed was the Web Service code. The GetsTask method was removed, and the GetTasks method was altered to return the Task Description.
[WebService(Namespace = "http://OpenLightGroup.net/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class WebService : System.Web.Services.WebService
{
#region GetCurrentUserID
private int GetCurrentUserID()
{
int intUserID = -1;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
intUserID = Convert.ToInt32(HttpContext.Current.User.Identity.Name);
}
return intUserID;
}
#endregion
#region GetTasks
[WebMethod]
public List<Task> GetTasks()
{
List<Task> colResult = new List<Task>();
RIATasksDBDataContext DB = new RIATasksDBDataContext();
var colTasks = from Tasks in DB.Tasks
where Tasks.UserID == GetCurrentUserID()
select Tasks;
return colTasks.ToList();
}
#endregion
#region DeleteTask
[WebMethod]
public string DeleteTask(int TaskID)
{
string strError = "";
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
var result = (from Tasks in DB.Tasks
where Tasks.TaskID == TaskID
where Tasks.UserID == GetCurrentUserID()
select Tasks).FirstOrDefault();
if (result != null)
{
DB.Tasks.DeleteOnSubmit(result);
DB.SubmitChanges();
}
}
catch (Exception ex)
{
strError = ex.Message;
}
return strError;
}
#endregion
#region UpdateTask
[WebMethod]
public string UpdateTask(Task objTask)
{
string strError = "";
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
var result = (from Tasks in DB.Tasks
where Tasks.TaskID == objTask.TaskID
where Tasks.UserID == GetCurrentUserID()
select Tasks).FirstOrDefault();
if (result != null)
{
result.TaskDescription = objTask.TaskDescription;
result.TaskName = objTask.TaskName;
DB.SubmitChanges();
}
}
catch (Exception ex)
{
strError = ex.Message;
}
return strError;
}
#endregion
#region InsertTask
[WebMethod]
public Task InsertTask(Task objTask)
{
RIATasksDBDataContext DB = new RIATasksDBDataContext();
try
{
Task InsertTask = new Task();
InsertTask.TaskDescription = objTask.TaskDescription;
InsertTask.TaskName = objTask.TaskName;
InsertTask.UserID = GetCurrentUserID();
DB.Tasks.InsertOnSubmit(InsertTask);
DB.SubmitChanges();
objTask.TaskID = InsertTask.TaskID;
}
catch (Exception ex)
{
objTask.TaskID = -1;
objTask.TaskDescription = ex.Message;
}
return objTask;
}
#endregion
}
The Model

The Model was altered to not use Rx Extensions. While the code works either way, I was shown code by Richard Waddell and Shawn Wildermuth that requires about the same amount of code as Rx Extensions but does not require you at add any additional assemblies:
public class TasksModel
{
#region GetTasks
public static void GetTasks(EventHandler<GetTasksCompletedEventArgs> eh)
{
WebServiceSoapClient WS = new WebServiceSoapClient();
WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());
WS.GetTasksCompleted += eh;
WS.GetTasksAsync();
}
#endregion
#region DeleteTask
public static void DeleteTask(int TaskID,
EventHandler<DeleteTaskCompletedEventArgs> eh)
{
WebServiceSoapClient WS = new WebServiceSoapClient();
WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());
WS.DeleteTaskCompleted += eh;
WS.DeleteTaskAsync(TaskID);
}
#endregion
#region UpdateTask
public static void UpdateTask(Task objTask,
EventHandler<UpdateTaskCompletedEventArgs> eh)
{
WebServiceSoapClient WS = new WebServiceSoapClient();
WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());
WS.UpdateTaskCompleted += eh;
WS.UpdateTaskAsync(objTask);
}
#endregion
#region InsertTask
public static void InsertTask(Task objTask,
EventHandler<InsertTaskCompletedEventArgs> eh)
{
WebServiceSoapClient WS = new WebServiceSoapClient();
WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());
WS.InsertTaskCompleted += eh;
WS.InsertTaskAsync(objTask);
}
#endregion
#region GetBaseAddress
private static Uri GetBaseAddress()
{
string strBaseWebAddress = App.Current.Host.Source.AbsoluteUri;
int PositionOfClientBin =
App.Current.Host.Source.AbsoluteUri.ToLower().IndexOf(@"/clientbin");
strBaseWebAddress = Strings.Left(strBaseWebAddress, PositionOfClientBin);
Uri UriWebService =
new Uri(String.Format(@"{0}/WebService.asmx", strBaseWebAddress));
return UriWebService;
}
#endregion
}
The View Model

This is where a lot of changes were made. Instead of one View Model, we now have three (we also have three Views).

Each View Model handles a different part of the application. Here is the overview:
- MainPageModel.cs - This is the View Model for the main View that loads all the other embedded Views. The Add New Task button is on the View of this View Model, but it calls an
ICommand that is in the TabControlModel's View Model (using View Model to View Model Communication described in this article: Silverlight View Model Communication).
- TabControlModel.cs - This View Model dynamically creates
TabItems and places an instance of the TaskDetails View, and its View Model (TaskDetailsModel.cs) on the TabItem.
- TaskDetailsModel.cs - This View Model holds the details of a single Task.
MainPageModel

This class does not contain a lot of code. It mostly contains a property (TabControlVM) that will hold an instance of the TabControlModel.
This allows the MainPage View to call methods in the TabControlModel through its View Model (MainPageModel). This View Model to View Model Communication technique is covered in this article: Silverlight View Model Communication.
Here is the full code:
using System;
using System.ComponentModel;
namespace RIATasks
{
public class MainPageModel : INotifyPropertyChanged
{
public MainPageModel()
{
}
#region TabControlVM
private TabControlModel _TabControlVM = new TabControlModel();
public TabControlModel TabControlVM
{
get { return _TabControlVM; }
private set
{
if (TabControlVM == value)
{
return;
}
_TabControlVM = value;
this.NotifyPropertyChanged("TabControlVM");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
TabControlModel

This class performs 90% of the work of the application.
The main purpose of this View Model is to expose the collection of Tasks to the View. In the original RIA Tasks application, this collection was ObservableCollection<Task>, but, for RIA Tasks 2 we decided to use the Tab Control and expose a collection of TabItems (ObservableCollection<TabItem>):
#region colTabItems
private ObservableCollection<TabItem> _colTabItems
= new ObservableCollection<TabItem>();
public ObservableCollection<TabItem> colTabItems
{
get { return _colTabItems; }
private set
{
if (colTabItems == value)
{
return;
}
_colTabItems = value;
this.NotifyPropertyChanged("colTabItems");
}
}
#endregion
To fill the collection of TabItems, we use the GetTasks() method that calls the Model and retrieves the Tasks for the currently logged in user:
#region GetTasks
private void GetTasks()
{
colTabItems.Clear();
TasksModel.GetTasks((Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
foreach (var objTask in EventArgs.Result)
{
TabItem objTabItem = CreateTaskItem(objTask);
colTabItems.Add(objTabItem);
}
if (colTabItems.Count == 0)
{
Message = "No Records Found";
}
else
{
Message = "";
}
if (CurrentTaskID != -1)
{
var objTabItem = (from TaskItem in colTabItems
let VM = (TaskItem.Content as TaskDetails).DataContext
where (VM as TaskDetailsModel).CurrentTask.TaskID == CurrentTaskID
select TaskItem).FirstOrDefault();
if (objTabItem != null)
{
objTabItem.IsSelected = true;
}
}
}
});
}
#endregion
The constructor for the View Model calls the GetTasks() method when the View is loaded:
public TabControlModel()
{
AddNewTaskCommand = new DelegateCommand(AddNewTask, CanAddNewTask);
DeleteTaskCommand = new DelegateCommand(DeleteTask, CanDeleteTask);
UpdateTaskCommand = new DelegateCommand(UpdateTask, CanUpdateTask);
if (!DesignerProperties.IsInDesignTool)
{
GetTasks();
}
}
The constructor also sets up the ICommands that are used to Add, Update and Delete Tasks.
Here is the code for the ICommands:
#region AddNewTaskCommand
public ICommand AddNewTaskCommand { get; set; }
public void AddNewTask(object param)
{
SetToNewTask();
}
private bool CanAddNewTask(object param)
{
var colNewTasks = from Tasks in colTabItems
where (Tasks.Header as string).Contains("[New]")
select Tasks;
return (colNewTasks.Count() == 0);
}
#endregion
#region DeleteTaskCommand
public ICommand DeleteTaskCommand { get; set; }
public void DeleteTask(object param)
{
Task objTask = GetTaskFromTaskDetails((param as TaskDetails));
if (objTask.TaskID != -1)
{
DeleteTask(objTask);
}
else
{
RemoveTask(objTask.TaskID);
}
}
private bool CanDeleteTask(object param)
{
return ((param as TaskDetails) != null);
}
#endregion
#region UpdateTaskCommand
public ICommand UpdateTaskCommand { get; set; }
public void UpdateTask(object param)
{
Task objTask = GetTaskFromTaskDetails((param as TaskDetails));
if (objTask.TaskID == -1)
{
InsertTask(objTask);
}
else
{
UpdateTask(objTask);
}
}
private bool CanUpdateTask(object param)
{
return ((param as TaskDetails) != null);
}
#endregion
These Commands use the following methods to call the Model and perform their operations:
#region DeleteTask
private void DeleteTask(Task objTask)
{
TasksModel.DeleteTask(objTask.TaskID, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
Message = EventArgs.Result;
RemoveTask(objTask.TaskID);
}
});
}
#endregion
#region UpdateTask
private void UpdateTask(Task objTask)
{
TasksModel.UpdateTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
Message = EventArgs.Result;
}
});
}
#endregion
#region InsertTask
private void InsertTask(Task objTask)
{
TasksModel.InsertTask(objTask, (Param, EventArgs) =>
{
if (EventArgs.Error == null)
{
CurrentTaskID = EventArgs.Result.TaskID;
GetTasks();
Message = "";
}
});
}
#endregion
You will notice that the AddNewTask method calls the SetToNewTask() method:
#region SetToNewTask
private void SetToNewTask()
{
foreach (var item in colTabItems)
{
item.IsSelected = false;
}
Task objTask = new Task();
objTask.TaskID = -1;
TabItem objNewTabItem = CreateTaskItem(objTask);
objNewTabItem.IsSelected = true;
this.colTabItems.Add(objNewTabItem);
}
#endregion
Note: This method first sets all TabItems' IsSelected to false, then sets the new TabItem's IsSelected to true. This is how you make a Tab Control programmatically select a tab.
The SetToNewTask() method calls the CreateTaskItem(objTask) method (shown below) that converts a Task to TabItem. The SetToNewTask() method then adds the TabItem to the colTabItems collection (the Tab Control on the View is bound to this collection so that it can show the tabs):
#region CreateTaskItem
private TabItem CreateTaskItem(Task Task)
{
TaskDetails objTaskDetails = new TaskDetails();
TaskDetailsModel objTaskDetailsModel =
(TaskDetailsModel)objTaskDetails.DataContext;
objTaskDetailsModel.SetCurrentTask(Task);
TabItem objTabItem = new TabItem();
objTabItem.Name = string.Format("DynamicTab_{0}", Task.TaskID.ToString());
objTabItem.Style = (Style)App.Current.Resources["TabItemStyle1"];
string strTaskID = (Task.TaskID == -1) ? "[New]" : Task.TaskID.ToString();
objTabItem.Header = String.Format("Task {0}", strTaskID);
objTabItem.Content = objTaskDetails;
return objTabItem;
}
#endregion
- The
CreateTaskItem(objTask) method creates an instance of the View, TaskDetails, and an instance of its View Model, TaskDetailsModel, and sets the current Task for it to the selected Task (using the SetCurrentTask(Task) method).
- It then places this View on a dynamically created
TabItem (it sets the View as the Content of the TabItem).
- It then returns the
TabItem so that the SetToNewTask() method can add it to the colTabItems collection.
Note: the line objTabItem.Style = (Style)App.Current.Resources["TabItemStyle1"]; is used to allow a Designer to alter the style of this dynamically created TabItem by changing the style for the key TabItemStyle1. In the RiaTasks2.zip project, that style is in the "RIATasks\Assets\TabControl.xaml" file.
TaskDetailsModel

The TaskDetailsModel View Model is very simple. It contains a property to hold the current Task and a method that allows it to be set:
public class TaskDetailsModel : INotifyPropertyChanged
{
public TaskDetailsModel()
{
}
public void SetCurrentTask(Task param)
{
CurrentTask = param;
}
#region CurrentTask
private Task _CurrentTask = new Task();
public Task CurrentTask
{
get { return _CurrentTask; }
private set
{
if (CurrentTask == value)
{
return;
}
_CurrentTask = value;
this.NotifyPropertyChanged("CurrentTask");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
Note: The reason this class contains a method to set the Task property is that the View that is bound to the Task property will not update if you dynamically create the View and attempt to set the Task property directly.
The View

We will now create the Views.

Here is the overview:
- MainPage.xaml - The View that loads all the other Views.
- TabControl.xaml - The View that contains the Tab Control and displays the Tabs.
- TaskDetails.xaml - The View that displays a single Task in a Tab.
MainPage View

MainPage contains an Add New Tasks button.

We drop an InvokeCommandAction behavior on the button.

When we Data bind the Command parameter of the behavior...

...we see that we are able to bind the ICommand that is in the TabControlModel View Model (that is stored in the TabControlVM property).

We can then go to Assets and drag and drop the TabControl View Model onto the page...

...and bind its DataContext to the TabControlVM property (this enables View Model to View Model Communication described in this article: Silverlight View Model Communication).
TabControl View

While the View Model for this View does most of the work for the application, the View is actually very straightforward.
The buttons are bound to the appropriate ICommands (using InvokeCommandAction behaviors), and the Tab Control is bound to the colTabItems collection.

It is important to note that the Update and Delete buttons, pass as a parameter, the currently selected TabItem (actually, they pass the Content of the TabItem which is the TaskDetails View and View Model).
This is how the methods know what Task to Update or Delete.
TaskDetails View

The binding for this View is also really simple.
However, this demonstrates the power of View Models:
- The
TabControlModel creates a Task and binds it to a dynamically created instance of the TaskDetailsModel
- It then places it in a collection of
TabItems and the Tab Control is bound to it
- To Update or Delete the
Task, the View is simply passed as a parameter to the appropriate method
Alternate Styles
The primary reason for using a View Model, besides that it is usually less code than using the code-behind style, is that you decouple the View from the code and allow a designer to create and re-create the View without altering any code.

Alan Beasley provided a style for the tabs and the buttons. The Tabs style styles the Tabs in all four positions (RIATasks2ABVersion.zip). To use it, we only needed to alter the resource files in the Assets directory. We have also specified the Tab Control to display the Tabs on the left side. This property is a standard property of the Tab Control.

Haruhiro Isowa created a Tab Control (RiaTasks2Hiro.zip) that displays all the Tabs on one scrollable row. He did have to create code to make his Tab Control scrollable, but the RIA Tasks 2 code was not altered at all (other than the .xaml files that the Designer would normally modify).
View Model - Not Hard At All
Hopefully you can see that View Model is not hard at all. It really is not complicated once you see how it is done. Expression Blend was designed to work in "View Model Style", so you should have an easier time using Expression Blend when you use this simple pattern.
We also demonstrated View Model Communication that you will hopefully find easy to understand. In addition, we covered dynamically creating Views while allowing a Designer full easy access to completely change the look of the application.
Michael Washington is a Microsoft Silverlight MVP. He is a Silverlight developer and an ASP.NET, C#, and Visual Basic programmer.
He is a DotNetNuke Core member and has been involved with DotNetNuke for over 4 years. He is the Co-Author of Building Websites with DotNetNuke (4 and 5).
He is one of the founding members of The Open Light Group (http://openlightgroup.net).
He is the founder of http://LightSwitchHelpWebsite.com
He has a son, Zachary and resides in Los Angeles with his wife Valerie.
I've been playing with computers since my first Acorn Electron, & after blowing up a few ZX Spectrums. I moved on to the C64 & Amiga, & eventually reluctantly on to the PC.
I have learnt a wide set of skills during my 38 years of existence, living in the UK, on the sunny south coast.
My main area of expertise is Graphic/Visual Design, Usability & UI Design. I am not a programmer, but am fairly technically minded due to studying Mechanical Engineering at Uni.
I have work both Freelance & for IBM as a Graphic Designer, & am skilled in the usual graphics packages like, PhotoShop, CorelDraw or Illustrator, Premier, Dreamweaver, Flash etc.
But I originally started with Lightwave & 3D animation.