Showing the XamDialogWindow from a ViewModel in Silverlight and WPF

Brian Lagunas / Sunday, May 19, 2013

The Problem

If you have used the Infragistics xamDialogWindow control in your WPF or Silverlight application, I can probably guess your biggest complaint with the control.  I know what mine is!  Currently, the XamDialogWindow has to be part of the visual tree in order for you to use it in your View.  What do I mean by that?  It means that you have to predefine your XamDialogWindow above the top most element in your View, being sure to add any Grid.RowSpan and Grid.ColumnSpan properties, so that it will be properly displayed when you show the dialog to the user by using the WindowState property or calling the Show method.  If that still isn’t clear, let the code do the talking.

This is what I have to do:

<Grid x:Name="LayoutRoot" Background="White">
    <ig:XamDialogWindow x:Name="_xamDialogWindow" StartupPosition="Center" WindowState="Hidden">
        <StackPanel Margin="25" Orientation="Horizontal">
            <Button Width="75" Margin="4" Content="OK" />
            <Button Width="75" Margin="4" Content="Cancel" />
        StackPanel>
    ig:XamDialogWindow>
    <Button Content="Show xamDialogWindow" VerticalAlignment="Center" HorizontalAlignment="Center"
            Click="Button_Click"/>
Grid>

private void Button_Click(object sender, RoutedEventArgs e)
{
    _xamDialogWindow.Show();
}

Remember, the xamDialogWindow has to be part of the visual tree, so first I have to declare all instances of the xamDialogWindow that your View will need in XAML.  Once you have declared your xamDialogWindow instances, you must then provide some type of mechanism to display the dialog to your user.  In this example we add a simple button with an event handler.  Now, you could create some reusable derived dialogs that will allow you to encapsulate any dialog logic and help you reuse the dialog throughout your application, but you will still need to add it the visual tree of your View somehow.  This is not what I want to do.  I want it simple.  Very simple.

This is what I want to do:

XamDialogWindow dialog = new XamDialogWindow();
dialog.StartupPosition = StartupPosition.Center;
dialog.Show();

I want to be able to simply create a xamDialogWindow instance and have it displayed to the user whenever I call the XamDialogWindow.Show method.  It shouldn’t matter if I am in the code-behind (which I hope you aren’t), or from within my ViewModel (which you still shouldn’t be).  I’ll explain all that later.  It should be simple.

If you use the ChildWindow control in Silverlight, then you know exactly what I am talking about.  A common scenario is that you create a derived ChildWindow and then you can show it anywhere you feel like it form within your code.  Well, that is what we are going to do with the xamDialogWindow.

The Solution – Silverlight

In order to achieve our desired result, we need to create a derived version of the xamDialogWindow.  Let’s start with a new Silverlight application, and then add a new user control to the project.  Yes you heard me correctly, I said SILVERLIGHT!  This is what my derived dialog looks like.

<ig:XamDialogWindow x:Class="XamDialogWindowWithCode.Dialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ig="http://schemas.infragistics.com/xaml">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Margin="25" Orientation="Horizontal">
            <Button Width="75" Margin="4" Content="OK" />
            <Button Width="75" Margin="4" Content="Cancel" />
        StackPanel>
    Grid>
    
ig:XamDialogWindow>

It’s nothing complicated.  Just a couple of buttons.  Now, you may be asking yourself, how are we going to get around this limitation of the control.  Simple!  We are going to place it inside a popup.  Let’s add some code to the code-behind to see what I mean.

public partial class Dialog : XamDialogWindow
{
    Popup _hostPopup;

    public Dialog()
    {
        InitializeComponent();
        WindowStateChanged += Dialog_WindowStateChanged;

        _hostPopup = new Popup();
        _hostPopup.Child = this;
    }

    void Dialog_WindowStateChanged(object sender, WindowStateChangedEventArgs e)
    {
        if (e.NewWindowState == Infragistics.Controls.Interactions.WindowState.Hidden)
            _hostPopup.IsOpen = false;
    }

    public new void Show()
    {
        base.Show();

        _hostPopup.IsOpen = true;
    }
}

All we did was programmatically create a Popup, set it’s content to the derived Dialog control, the control displaying the popup by creating a new Show method and making sure we close the Popup when the WindowState changes to Hidden.  Not so complicated now is it?  Now let’s look at how we can create an instance of the control in code and display it to our user by calling the Show method.


private void Button_Click(object sender, RoutedEventArgs e)
{
    Dialog dialog = new Dialog();
    dialog.Show();
}

xamDialogWindow in code behind for WPF and Silverlight

That’s it.  So what about a ViewModel?  Alright, in case you still don’t believe me that it will work in code no matter where you are at, let’s check out a ViewModel approach.

public class ViewModel
{
    public ICommand ShowDialogCommand { get; private set; }

    public ViewModel()
    {
        ShowDialogCommand = new ShowDialogCommand();
    }
}

public class ShowDialogCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        Dialog dialog = new Dialog();
        dialog.Show();
    }
}
<Grid x:Name="LayoutRoot" Background="White">
    <Button Content="Show xamDialogWindow" VerticalAlignment="Center" HorizontalAlignment="Center"
            Command="{Binding ShowDialogCommand}"/>
Grid>

We just created a simple ViewModel, a simple Command (since we aren’t using a DelagateCommand or a Relay Command), and then updated the View’s DataContext and set our Button’s binding.  Nothing complicated, yet we still get the same result when we click the button. 

image

Oh, and don’t worry, it will still work as Modal as well.  Just so you know, the gray is the Modal background.

A Modal XamDialogWindow in code behind for WPF and Silverlight

Now this particular sample was done in Silverlight, but the same approach can be taken in WPF. 

Solution – WPF

This was really easy to implement for Silverlight, but to get the same type of functionality in WPF, it will take a little extra work to get the Popup to behave like you want it to.  First let’s add a new WPF Application project to our Visual Studio Solution and immediately open up the Project Properties.  Now change the default namespace to match that of the Silverlight namespace.  Next, go to the Build tab and add a “Conditional compilation symbol” named WPF.  This will allow us to share all of our code across both WPF and Silverlight.

image

Now, let’s add both the Dialog.xaml/cs and the ViewModel.cs files from our Silverlight project to our WPF project as a file link.  Right click the WPF project and selected “Add Existing Item”.  Navigate to the Silverlight files and add the files as a link by selecting the drop down arrow in the add button and selecting the “Add as Link” option.

image

Now we will need to modify our Dialog.cs file to accommodate our WPF specifics.  First off, in Silverlight we don’t have to worry about multiple Windows like we do in WPF.  We also don’t have to worry about the moving of a Window or the resizing of a Window in Silverlight like we do in WPF.  The goal of the WPF version is to keep the Dialog within the bounds of the Window, but still be able to show the Dialog from code.  Also, if it’s Modal, we want to cover up the entire Window including the minimize, maximize, and close buttons.  We want it to be truly modal.  We will also need to handle the resizing of a parent Window and the movement of the Window.  Seems WPF introduces a lot of hurdles we must jump over, but it’s nothing we can’t handle.  I made some modifications to the Dialog.cs file.  Let’s look at the file, then talk about it.

    public partial class Dialog : XamDialogWindow
    {
        Popup _hostPopup;

#if WPF
        Window _parentWindow;
#endif

        public Dialog()
        {
            InitializeComponent();
            WindowStateChanged += Dialog_WindowStateChanged;

            _hostPopup = new Popup();
            _hostPopup.Child = this;
        }

        void Dialog_WindowStateChanged(object sender, WindowStateChangedEventArgs e)
        {
            if (e.NewWindowState == Infragistics.Controls.Interactions.WindowState.Hidden)
            {
#if WPF
                if (_parentWindow != null)
                {
                    //unhook our event handlers to prevent memory leaks
                    _parentWindow.LocationChanged -= ParentWindow_LocationChanged;
                    _parentWindow.SizeChanged -= ParentWindow_SizeChanged;
                }
#endif
                _hostPopup.IsOpen = false;

            }
        }

        public new void Show()
        {
            base.Show();

#if WPF
            _hostPopup.AllowsTransparency = true;

            foreach (Window window in App.Current.Windows)
            {
                if (window.IsActive) //I want to show the popup over the current active window
                {
                    _parentWindow = window;

                    _hostPopup.PlacementTarget = _parentWindow;
                    _hostPopup.Placement = PlacementMode.Relative;

                    _hostPopup.HorizontalOffset = -7.5; //account for window padding
                    _hostPopup.VerticalOffset = -30.5; //account for window title bar

                    CalculatePopupSize();

                    _parentWindow.LocationChanged += ParentWindow_LocationChanged;
                    _parentWindow.SizeChanged += ParentWindow_SizeChanged;

                    break;
                }
            }
#endif
            _hostPopup.IsOpen = true;

        }

#if WPF
        void CalculatePopupSize()
        {
            _hostPopup.Width = _parentWindow.Width;
            _hostPopup.Height = _parentWindow.Height;
        }

        void ParentWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            CalculatePopupSize();
        }

        void ParentWindow_LocationChanged(object sender, System.EventArgs e)
        {
            //this forces the popup to re-evaluate it's positioning
            _hostPopup.Placement = PlacementMode.Custom;
            _hostPopup.Placement = PlacementMode.Relative;
        }
#endif

    }

The first thing you are going to notice it the addition of an #if WPF … #endif regions.  What this does is allows us to add WPF specific code to the WPF project without effecting the Silverlight application code.  Basically we say that anything inside the #if WPF region is to be compiled into the WPF project only and Silverlight will ignore it completely when you compile the Silverlight project.  Pretty col huh?

Now you will see the declaration of a private variable called _parentWindow because this is WPF and a WPF application can have many windows.  We have to know which Window is the parent of the Dialog.  Next we add code to the Dialog.Show method.  This code looks for the active Window, then sets the _parentWindow variable because we will need it later.  Next we set the host Popup.PlacementTarget to the active window, and set the Popup.Placement to Relative which will place the popup in the 0,0 location of the parent window content area.  Problem is that we want it to cover the ENTIRE window in the case of a Modal popup.  So we need to set the horizontal and vertical offsets to account for the Window title bar and border space.  Next, we need to calculate the size of the popup to be the same as the parent active window.

Next, there are two scenarios we need to handle.  One is when the parent window is dragged to a new location.  By default, Popups do not reposition themselves automatically when the parent is moved.  So, we hook into the LocationChanges event and toggles the _hostPopup.Placement property.  This will force the Popup to re-evaluate it’s position.  We also add a handler to the Window.SizeChanged event so when the user resizes the Window, our popup will be resized accordingly.  Lastly, we need to make sure that we unhook our event handlers from the parent window when we close the Dialog.

Now, we can show our Dialog in a non-modal mode with the ability to move the dialog around within the bounds of the window. 

XamDialogWindow in code WPF

Or, we can show the Dialog in a modal mode blocking all interaction with the contents of the windows including the minimize, maximize, an the close buttons.

XamDialogWindow in code Modal WPF

Taking it a Step Further

Now that you know how to show your custom dialog from just using code, let’s talk about how you SHOULD be using this new found power.  First off, don’t create and show your Dialog from your View’s code-behind file.  Why, because you should be using the MVVM pattern  when developing your XAML application, and we all know that anything in the code behind is strongly discouraged.

Now that we are all on the same page, and you are using the MVVM pattern to develop your Views, let’s discuss how you should call your Dialog from your ViewModel.  Unlike in this very simple demo where I am showing the Dialog from within my ViewModel, TRY NOT to do this.  A dialog is a View concern.  In MVVM, your ViewModel should have no knowledge of View specific implementations, including showing a dialog window.  How SHOULD you do it?  Well, you should be using a Dialog Service.  What is a Dialog Service?  A Dialog Service is a class that your ViewModel will know about, preferably only through an interface, that will abstract or hide the implementation of showing the Dialog you just created.

Unfortunately, I will not be covering a Dialog Service in this post.  Fortunately I will be writing a post on a Dialog Service soon.  It will discuss different approaches as well as handling callbacks in your ViewModel when the dialog closes so that you can handle the dialog result or other required tasks of the completed dialog.

Until then, feel free to download the source code and play around.  Contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions you may have.