-
dotnetgeek posted this
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.
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.
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)); } }