Lexa

WPF, C#, Objective C and a little Math

Modal UserControl in WPF

Common approach to show some modal view (dialog) in WPF application is to use some kind of “IModalDialogService” realization which creates a window on View side and places View corresponding to requested ViewModel inside it.

There is also the other way – just to put a control element above the current main window content. This article is about this approach.

MVVM is going to be used in the following manner:
– There are some ViewModel types that represent windows (or pages or similar objects) in the application.
– These types have some property (i.e.”ModalContent”) of type object.
– If ModalContent property is not null, its content shall be shown with some View over the parent ViewModels View.

As example, I create WPF application project and add to main window the described above ModalContent property and 2 handlers – to set and unset it (this is the very first test, so I will not use ViewModel – MainWindow will be View and ViewModel at the same time, oops):

public partial class MainWindow : Window
{
    public object ModalContent
    {
        get { return (object)GetValue(ModalContentProperty); }
        set { SetValue(ModalContentProperty, value); }
    }

    public static readonly
        DependencyProperty ModalContentProperty =
            DependencyProperty.Register(
            "ModalContent",
            typeof(object),
            typeof(MainWindow),
            new UIPropertyMetadata(null));

    private void ShowModalClick(object sender, RoutedEventArgs e)
    {
        this.ModalContent = new ModalContent();
    }

    private void HideModalClick(object sender, RoutedEventArgs e)
    {
        this.ModalContent = null;
    }
}

ModalContent class is contains only one property – Text:

public class ModalContent
{
    public ModalContent()
    {
        this.Text = "Current time is " + DateTime.Now.
    }
    public string Text { get; set; }
}

We need to configure xaml, initial layout rool will represent a window with a button. Button will show modal content when user presses it:

<Grid>
  <Button
    Click="ShowModalClick"
    Content="ShowModal"
    VerticalAlignment="Top"/>
</Grid>

Control, representing modal control shall be above the button. I will use ContentPresenter and connect it to ModalContent property:

<Grid>
  <Button
    Click="ShowModalClick"
    Content="ShowModal"
    VerticalAlignment="Top"/>

  <ContentPresenter Content="{Binding ModalContent}">
    <ContentPresenter.Resources>
    <DataTemplate DataType="{x:Type Source:ModalContent}">
      <Border
        Background="LightGreen"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Padding="30"
        CornerRadius="5">
        <StackPanel>
          <TextBlock Text="{Binding Text}"/>
          <Button Click="HideModalClick">
            Hide Modal
          </Button>
        </StackPanel>
      </Border>
    </DataTemplate>
    </ContentPresenter.Resources>
  </ContentPresenter>
</Grid>

 

Now, when button is “ShowModal” is pressed, we have a UserControl on top.

Problem is that it is not completely modal – we still can press “underlying” “ShowModal” button. Luckily, it is easy to fix using by setting IsEnabled property:

<Button
  Click="ShowModalClick"
  Content="ShowModal"
  VerticalAlignment="Top">
  <Button.Style>
    <Style TargetType="{x:Type Button}">
      <Setter Property="IsEnabled" Value="false" />
        <Style.Triggers>
          <DataTrigger
            Binding="{Binding ModalContent}"
            Value="{x:Null}">
            <Setter Property="IsEnabled" Value="True"/>
          </DataTrigger>
        </Style.Triggers>
      </Style>
  </Button.Style>
</Button>

Its working as expected now. For user it is also clearer that underlying view cannot be used – it is “gray”.

The project is called SimpleModalUserControl and can be found at the end of the article.

Next and final example is a bit more complex. I defined new ViewModel base class called ModalHostViewModel, which contains ModalContent property. ModalHostViewModel can also play a role of modal content, thats why it also has ModalParent property referencing it.

To present content of ModalHostViewModel in a View, I used ContentControl with custom style. The modal ViewModel object shall be binded to ContentControl Tag property and when it becomes valid (not null), it displayed over content of ContentControl as modal element using ContentPresenter (so DataTemplate for ModalContent shall be defined somewhere in resources). Sound a bit tricky, so look at example of the usage:

<ContentControl
  Tag="{Binding ModalChild}"
  Style="{StaticResource ModalHostControlStyle}">

  <!-- here goes content of the window, which will be
  disabled when ModalChild will become valid -->

</ContentControl>

 

The custom ContentControl Style applies the following template to ContentControl:

<ControlTemplate TargetType="{x:Type ContentControl}">
  <Grid Focusable="False">
  <ContentPresenter Content="{TemplateBinding Content}">
    <ContentPresenter.Style>
      <Style TargetType="{x:Type ContentPresenter}">
        <Setter Property="IsEnabled" Value="false"/>
        <Style.Triggers>
          <DataTrigger
            Binding="{Binding Tag, RelativeSource={RelativeSource
              AncestorType={x:Type ContentControl}}}"
            Value="{x:Null}">
          <Setter Property="IsEnabled" Value="True"/>
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </ContentPresenter.Style>
    </ContentPresenter>
    <ContentPresenter
        Content="{Binding Tag, RelativeSource={RelativeSource 
            AncestorType={x:Type ContentControl}}}"/>
  </Grid>
</ControlTemplate>

So, complex demo contains a list of .Net default colors.

If user performs double click on color record, the application shows color modal content (the same “dialog” will be called if user presses enter key).

If user presses “SayHello” button, the other modal content is placed on top of it.

The last issue which I came across during implementation – incorrect behavior of the keyboard focus. The focus was not restored to the correct element when modal view was “closed”. The solution which I found – is to memorize focused element of the view become disabled, and restore the focus when it is enabled again. It is implemented using attached properties, and used in described above ContentControl Style. See attached solution for details.

Complete vs 2008 solution can be found here.


Categorised as: UI, WPF


Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>