A Silverlight View Model Style Popup
Live example: http://silverlight.adefwebserver.com/mvvmpopup/default.aspx
How to Unit Test the PopUp: SMVVMUnitTest.aspx
Note: Also see this newer version that is easier to use: Silverlight : Simple PopUp Behavior for VM, MVVM
See this version that is fully MVVM: HisowaModPopUpBehavior
The View Model pattern allows a programmer to create an application that has absolutely no UI (user interface). The programmer only creates a ViewModel 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 it is suggested that you read Silverlight View Model: An (Overly) Simplified Explanation for an introduction.
This article demonstrates how you can easily implement a Modal popup. It also demonstrates the implementation of a Value Converter. We will use graphics from Alan Beasley’s 10 Cool Buttons for Download in Expression Blend & Silverlight.
The Basic Popup
You click the Show Window button and the modal popup appears. You select Yes or No and click OK or Cancel.
The results of the Combo Box selection, and the button clicked, will show up on the main page.
The Popup Meets the Designer
We use View Model because, we want to allow a Designer, to easily change the design of an application, without requiring any code changes.
When the Designer opens the project up in Microsoft Expression Blend, they will see the MainPage.xaml file and the PopUpWindow.xaml file. The Designer knows that they can alter any file with a .xaml extension to re-design the application.
When the Designer opens the PopUpViewModel.xaml file, and they also open the Data window, they see that there is a Data Context associated with this control. The arrows in the graphic above, indicate what is currently bound to what.
The Re-Design
The Designer deletes the existing items and replaces them with a new design (the graphics are from Alan Beasley’s 10 Cool Buttons for Download in Expression Blend & Silverlight. I was also able to get Alan Beasley to do the actual layout of the popup).
Hooking the new OK and Cancel buttons is easy, The Designer simply drops a InvokeCommandAction behavior on each button.
In the Properties for each button, Click is selected for EventName, and the DataBind button is clicked next to Command.
The button is then bound to the appropriate ICommand in the associated View Model.
Dude We Need A Value Converter!
One of the reasons we use View Model, is that it allows a "separation of concerns". A Designer can completely change a design without the Developer needing to change any code.
However, in this case, the original design had a Combo Box bound to a String value (SelectedPopUpValueProperty), in the View Model. In the new design, the Designer wants to use a Toggle Button control that uses a boolean (IsChecked). Expression Blend will not allow the Designer to bind IsChecked to SelectedPopUpValueProperty.
So the Designer calls the Developer up, who is vacationing in Aruba after turning in his View Model (after unit testing it to assure he met the requirements), and explains the problem. The Developer responds:
"Dude are you serious? If I had any idea a Toggle Button was going to be in the design I would have simply made a boolean property for you to bind to in the View Model, and that property would have set the SelectedPopUpValueProperty that the main page is using."
"The problem is, the View Model is now being worked on by the New York office and you don't want to deal with those guys. We have to fix this without changing the View Model at all. I will whip up a Value Converter and you can just use that."
"When you check-in your .xaml page into source control, you will also check-in the file I give you. The guys in New York will not have to change what they are doing at all, it will just work."
The Value Converter
The Designer drops the Value Converter file (BoolToStringConverter.cs), into the project and hits the F5 key to build the project (so the file will be built and it can be used). The Designer then uses the following steps to create the binding:
- Clicks the Use a custom path expression box
- Enters the name of the property to bind to (SelectedPopUpValueProperty) in the box
- Selects the BoolToStringConverter in the Value converter drop down.
- Clicks OK
The application is complete!
The Code - Making a Popup
When you create a new Silverlight Child Window control...
It creates a control with a code behind and OK and Cancel buttons with event handlers.
Remove the event handlers from the buttons and clear out all the methods in the code behind.
Create a class file (PopUpViewModel.cs) that will serve as the View Model for the Popup:
public class PopUpViewModel : INotifyPropertyChanged
{
private ChildWindow PopUP;
public PopUpViewModel()
{
SetPopUpCommand = new DelegateCommand(SetPopUp, CanSetPopUp);
OKButtonCommand = new DelegateCommand(OKButton, CanOKButton);
CancelButtonCommand = new DelegateCommand(CancelButton, CanCancelButton);
SelectedPopUpValueProperty = "Yes";
}
#region SetPopUpCommand
public ICommand SetPopUpCommand { get; set; }
public void SetPopUp(object param)
{
PopUP = (ChildWindow)param;
}
private bool CanSetPopUp(object param)
{
return true;
}
#endregion
#region OKButtonCommand
public ICommand OKButtonCommand { get; set; }
public void OKButton(object param)
{
PopUP.DialogResult = true;
}
private bool CanOKButton(object param)
{
return true;
}
#endregion
#region CancelButtonCommand
public ICommand CancelButtonCommand { get; set; }
public void CancelButton(object param)
{
PopUP.DialogResult = false;
}
private bool CanCancelButton(object param)
{
return true;
}
#endregion
#region SelectedPopUpValueProperty
private string _SelectedPopUpValueProperty;
public string SelectedPopUpValueProperty
{
get
{
return this._SelectedPopUpValueProperty;
}
set
{
this._SelectedPopUpValueProperty = value;
this.NotifyPropertyChanged("SelectedPopUpValueProperty");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
This class does the following:
- Implements INotifyPropertyChanged so that the UI is automatically updated when values in the View Model are updated
- Creates a property for the Selected PopUp Value (SelectedPopUpValueProperty)
- Creates ICommands for the OK and Cancel buttons
- Creates an ICommand that allows an instance of the PopUp UI to be set (SetPopUpCommand)
Databind The Popup UI to the View Model
The Popup is simple because we are basically passing an instance of the entire Popup UI to the View Model. The View Model then has access to all the functionality of the Popup. Programming the Popup is as easy as if it were done using normal code behind, however, it's still View Model, so the Designer has full control over the UI.
In Blend, in the Objects and Timeline window, you drop a InvokeCommandAction behavior on childWindow.
In the Properties for the InvokeCommandAction behavior, you set the EventName to Loaded, and Data bind the Command to SetPopUpCommand.
You then bind the CommandParameter to the PopUpWindow.
Calling The PopUp From The Main View
Calling the Popup from the View Model of the main View (MainViewModel.xaml) is easy:
Drop a InvokeCommandAction behavior on the button, and configure it to call the ShowPopUPCommand method in the View Model.
Here is the complete code for the View Model:
public class MainViewModel : INotifyPropertyChanged
{
private ChildWindow PopUP;
public MainViewModel()
{
PopUP = new PopUpWindow();
PopUP.Closed += new EventHandler(PopUP_Closed);
ShowPopUPCommand = new DelegateCommand(ShowPopUP, CanShowPopUP);
}
#region ShowPopUPCommand
public ICommand ShowPopUPCommand { get; set; }
public void ShowPopUP(object param)
{
PopUP.Show();
}
private bool CanShowPopUP(object param)
{
return true;
}
#endregion
#region SelectedPopUpValueProperty
private string _SelectedPopUpValueProperty;
public string SelectedPopUpValueProperty
{
get
{
return this._SelectedPopUpValueProperty;
}
set
{
this._SelectedPopUpValueProperty = value;
this.NotifyPropertyChanged("SelectedPopUpValueProperty");
}
}
#endregion
#region PopUP_Closed
void PopUP_Closed(object sender, EventArgs e)
{
if (PopUP.DialogResult != null)
{
bool boolDialogResult = (PopUP.DialogResult != null) ?
Convert.ToBoolean(PopUP.DialogResult) : false;
PopUpViewModel objPopUpViewModel = (PopUpViewModel)PopUP.DataContext;
SelectedPopUpValueProperty = String.Format("{0} - {1}",
objPopUpViewModel.SelectedPopUpValueProperty,
(boolDialogResult) ? "OK" : "Cancel");
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
The Type Converter
Oh yes, that file that the Developer whipped up in Aruba, here it is:
public class BoolToStringConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((string)value == "No")
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value == true)
{
return "No";
}
else
{
return "Yes";
}
}
#endregion
}
This Seems Too Easy...
Some would argue that creating Popups like this, is tying your View Model to the Popup, and that is wrong because the Popup is a UI element. However, the Popup is a function that the View Model is performing, not the View (the View only requested the PopUp to be shown). It is displayed in front of the View and it sends values back to the View thru bindings, but it belongs to the View Model. So, it is perfectly fine for the View Model to directly instantiate a Popup class just as it would another class such as a web service.
Hey We Added Code!
We care about View Model, because we want to allow the Designer the freedom to design the application without code changes, and this example achieves that. Ok perhaps a Value Converter may need to be added is some situations, but, even in this case, the View Model did not need to be changed.
If the Developer is wearing both hats, and is also the Designer, it may be easier to just alter the View Model if a property type needs to be added or changed, however, you have options. A Value Converter allows you to bind UI elements to the View Model when the View Model is faced with an unexpected change.
How Do I Get Started With Silverlight?
To learn how to use Expression Blend, all you have to do is go to:
http://www.microsoft.com/design/toolbox/