The life of a code monkey

Text

MVVM Popups

Recently I was faced with an interesting problem of how to properly display a popup and still follow the MVVM pattern. After a lot of thought, the following is the solution that I have finally landed on.

First of all the main window for the application:

<Window x:Class="MVVMPopup.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mvp="clr-namespace:MVVMPopup"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <mvp:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Button Content="Toggle Popup" Command="{Binding DisplayPopupCommand}" 
                HorizontalAlignment="Center" VerticalAlignment="Top" Name="button"/>
        <Popup IsOpen="{Binding PopupViewModel.IsOpen}" PlacementTarget="{Binding ElementName=button}">
            <Popup.Resources>
                <DataTemplate DataType="{x:Type mvp:ChildViewModel}">
                    <StackPanel Width="100" Margin="5">
                        <TextBlock Text="First Name" />
                        <TextBox Text="{Binding FirstName}" />
                        <TextBlock Text="Last Name" />
                        <TextBox Text="{Binding LastName}" />
                    </StackPanel>
                </DataTemplate>
            </Popup.Resources>
            <Border BorderThickness="1" BorderBrush="Black" Background="White">
                <ContentPresenter Content="{Binding PopupViewModel.Data}" />
            </Border>
        </Popup>
    </Grid>
</Window>

Though pretty straight forward there are a few things to note. 

  • The button has its command property set. I did not want to merely set a click handler for the button since I wanted to be able to display the popup from multiple sources, and to keep the code cleaner.
  • The popup’s content is being set using a data template for a view model that is bound to a content presenter. This means that for every possible view model that may be bound, an additional data template must be created.

Enough XAML now for some view models.

public class MainWindowViewModel : BaseViewModel
{
    private readonly PopupViewModel<ChildViewModel> _popupViewModel;
    private readonly DelegateCommand _displayPopupCommand;

    public MainWindowViewModel()
    {
        _popupViewModel = new PopupViewModel<ChildViewModel>(new ChildViewModel { FirstName ="John", LastName = "Doe"});
        _displayPopupCommand = new DelegateCommand(() => PopupViewModel.IsOpen = PopupViewModel.IsOpen == false);
    }

    public ICommand DisplayPopupCommand
    {
        get { return _displayPopupCommand; }
    }

    public PopupViewModel<ChildViewModel> PopupViewModel
    {
        get { return _popupViewModel; }
    }
}

Please note that I am taking advantage of the Prism library for the DelegateCommand class.

public class PopupViewModel<T> : BaseViewModel
{
    private readonly T _data;
        
    public PopupViewModel(T data)
    {
        _data = data;
    }

    public T Data
    {
        get { return _data; }
    }

    private bool _isOpen;
    public bool IsOpen
    {
        get { return _isOpen; }
        set
        {
            if (_isOpen != value)
            {
                _isOpen = value;
                OnPropertyChanged("IsOpen");
            }
        }
    }
}

The purpose of this view model is to provide the popup with a place to bind its properties (in this case just the IsOpen and Content properties). These properties could have been added to the MainWindowViewModel, however I wanted to wrap up the popup’s functionality in one place and not clutter my actual view model with a bunch of UI properties. Since this is where the work is done, let me point out a few things.

  • The Data property provides the Content for ContentPresenter that is in the popup. If you wish to change out the view model (and change the content of the popup) this property will need to raise the PropertyChanged event (in my example this is accomplished by calling OnPropertyChanged(“Data”)).
  • Currently only the IsOpen property on the popup is being bound, if other properties (such as Placement, PlacementTarget, PopupAnimation, etc) could be added to this view model and then have their properties bound to give the view model greater control over the popup.

Finally for the sake of brevity, here are the final two class, neither of which need any comment.

public class ChildViewModel : BaseViewModel
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (_firstName != value)
            {
                _firstName = value;
                OnPropertyChanged("FirstName");
            }
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            if (_lastName != value)
            {
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }

}
public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
Posted on Tuesday, November 1 2011. Tagged with: CMVVMPopupPopupsWPFICommandCommand
8
Notes
  1. dotnetgeek posted this
Got a question or comment? Ask me.
Previous Next