Agile UI Development in .NET: Nested Views

Updated: Something went wrong with the code snippets. Now it’s okay.

Today, we”l have a look at nested views in my series on agile UI development in .NET using an extended MVVM pattern (table of contents).

There are two kinds of nested views:

  • contextually nested views and
  • hierarchically nested views (master-detail scenarios)

ProCollEE

As always in this series, I use ProCollEE as sample application. Note that ProCollEE is currently only an architectural prototype.

The UI looks like this:

Contextually Nested Views

The first form of nesting is a view containing another view as its child:

In the above example, you can see that the main view (light green) contains the message entry view (blue) and the dashboard (gold). And the message entry view contains the channel selection view beside its own controls to enter the message to send.

ViewModel

public class MessageEntryViewModel : ViewModelBase, IMessageEntryViewModel
{
    private readonly IChannelSelectionViewModel channelSelectionViewModel;

    public MessageEntryViewModel()
    {}

    public MessageEntryViewModel(IChannelSelectionViewModel channelSelectionViewModel)
    {
        this.channelSelectionViewModel = channelSelectionViewModel;
    }

    public IEnumerable Channels
    {
        get
        {
            return this.channelSelectionViewModel.SelectedChannels;
        }
    }
}

The ViewModel takes its child ViewModel as a paramter in the constructor. The call to the selected channels is delegated to the child ViewModel.

View

public partial class MessageEntryView : IMessageEntryView
{
    // other stuff ...

    public void Initialize(IMessageEntryViewModel viewModel, IChannelSelectionView channelSelectionView)
    {
        this.DataContext = viewModel;

        this.channelSelection.Content = channelSelectionView.AsUserControl;
    }
}
<UserControl x:Class="ProCollEE.WpfClient.MessageEntry.MessageEntryView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ProCollEE.WpfClient.MessageEntry">
    <UserControl.DataContext>
        <local:MessageEntryViewModel Text="Hello ProCollEE World"/>
    </UserControl.DataContext>

    <StackPanel>
        <ContentControl Name="channelSelection"></ContentControl>
         <Grid Margin="4,4,4,8">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto" MinWidth="47" />
            </Grid.ColumnDefinitions>
            <TextBox AutomationProperties.AutomationId="message" Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="0.001,0,8,0" AutoWordSelection="True" TextWrapping="WrapWithOverflow" />
            <Button AutomationProperties.AutomationId="sendButton" Grid.Column="1"  HorizontalAlignment="Right" Padding="8,0,8,0" Width="Auto" Command="{Binding SendMessageCommand}" IsDefault="True">Send</Button>
        </Grid>
    </StackPanel>

</UserControl>

The view gets access to its child view by the parameter passed in the Initialize method (injected by Ninject – for those you have read the post about Ninject). The child view is set as the content of the ContentControl that is used as a placeholder.

Presenter

public class MessageEntryPresenter : IMessageEntryPresenter
{
    // ...

    private readonly IChannelSelectionPresenter channelSelectionPresenter;

    public MessageEntryPresenter(
        IMessageEntryViewModel messageEntryViewModel,
        IChannelSelectionPresenter channelSelectionPresenter)
    {
        // ...

        this.channelSelectionPresenter = channelSelectionPresenter;
    }

    public void Show()
    {
        this.channelSelectionPresenter.Show();
    }
}

The presenter gets the presenter of the child view injected and relays the Show method to it.

Hierarchically Nested Views (Master-Detail)

Above, you can see that the dashboard (green) is the master of the individual messages (red). And a message is the master of a list of channels the message was sent on (blue).

Dashboar Message ViewModel

public class DashboardMessageViewModel : ViewModelBase, IDashboardMessageViewModel
{
    private readonly ObservableCollection channels;

    public DashboardMessageViewModel()
    {
        this.channels = new ObservableCollection();
    }

    // ... other properties ...

    public ObservableCollection Channels
    {
         get { return this.channels; } }
    }

The ViewModel provides an ObservableCollection of its ViewModel children.

Dashboard View

<UserControl x:Class="ProCollEE.WpfClient.Dashboard.Dashboard"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ProCollEE.WpfClient.Dashboard"
    HorizontalAlignment="Stretch" VerticalAlignment="Top" AutomationProperties.AutomationId="DashboardUserControl">

    <Grid HorizontalAlignment="Stretch">

        <StackPanel HorizontalAlignment="Stretch">
            <ListView ItemsSource="{Binding Path=Messages}" VerticalContentAlignment="Top" AutomationProperties.AutomationId="DashBoardListView">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="VerticalContentAlignment" Value="Top" />
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Poster" DisplayMemberBinding="{Binding Path=Poster}" />
                        <GridViewColumn Header="Text">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Path=Text}" TextWrapping="WrapWithOverflow" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Time" DisplayMemberBinding="{Binding Path=SentTime}"/>
                        <GridViewColumn Header="Channels">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <ListBox ItemsSource="{Binding Path=Channels}" BorderThickness="0" Background="Transparent">
                                        <ListBox.ItemTemplate>
                                            <DataTemplate>
                                                <Label Content="{Binding Path=ChannelName}" />
                                            </DataTemplate>
                                        </ListBox.ItemTemplate>
                                    </ListBox>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
    </Grid>

</UserControl>

The XAML uses the DashboardViewModel as its DataContext and can now use data binding and data templates to visualize the hierarchical data.

Conclusions

This post contains a lot of code and little comment. We have seen the differences between contextual and hierarchical nesting:

  • contextual nesting: presenter-view-viewModel tripples are nested together
  • hierarchical nesting: a single presneter and view use several nested viewModels

And how to achieve nesting in code.

About the author

Urs Enzler

Add comment

By Urs Enzler

Recent Posts