Click here to Skip to main content
15,881,725 members
Articles / Desktop Programming / WPF

A Recursively Splittable WPF Content Control Style for User Customizable Interfaces

Rate me:
Please Sign up or sign in to vote.
4.93/5 (4 votes)
5 Mar 2013CPOL15 min read 40.3K   1.1K   31  
A recursively splittable user control for designing a custom interface, which can be serialized for sharing
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
					xmlns:sys="clr-namespace:System;assembly=mscorlib"
					xmlns:winInter="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
					xmlns:hwpf="clr-namespace:HurWpfLib">
	
	<BooleanToVisibilityConverter x:Key="boolVisConverter"/>
	<hwpf:DoubleGridLengthConverter x:Key="splitPositionConverter"/>
	
    <!--The style which is applied to an unsplit view's inner border-->
    <Style x:Key="DefaultUnsplitBorderStyle" TargetType="Border">
        <Style.Setters>
            <Setter Property="Background" Value="LightBlue"/>
            <Setter Property="CornerRadius" Value="0"/>
        </Style.Setters>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
                <Setter Property="BorderBrush" Value="RoyalBlue"/>
                <Setter Property="BorderThickness" Value="1"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
                <Setter Property="BorderBrush" Value="Red"/>
                <Setter Property="BorderThickness" Value="2"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
    
	<sys:Boolean x:Key="trueValue">True</sys:Boolean>
	
    <DataTemplate x:Key="UnsplitTemplate">
            <!--The elements in this template will appear in the content template of the styled ContentControl.
                Therefore, they make use of the content as their DataContext.-->
        <Border x:Name="viewBorder" Style="{StaticResource DefaultUnsplitBorderStyle}">
			<ContentPresenter x:Name="innerContentPresenter" Content="{Binding Path=InnerContent}"
					ContentTemplateSelector="{x:Static hwpf:RecursiveSplitViewModel.InnerContentTemplateSelector}">
			</ContentPresenter>
			<!--This should help an unsplit view to get its viewmodel selected if clicked or became the target of a drop action.-->
			<winInter:Interaction.Triggers>
				<winInter:EventTrigger EventName="PreviewMouseDown">
					<winInter:InvokeCommandAction Command="{Binding Path=SelectCommand, Mode=OneWay}" CommandParameter="{StaticResource trueValue}"/>
				</winInter:EventTrigger>
				<winInter:EventTrigger EventName="PreviewDrop">
					<winInter:InvokeCommandAction Command="{Binding Path=SelectCommand, Mode=OneWay}" CommandParameter="{StaticResource trueValue}"/>
				</winInter:EventTrigger>
			</winInter:Interaction.Triggers>
		</Border>
    </DataTemplate>

    <DataTemplate x:Key="HorizontalSplitTemplate">
        <!--The elements in this template will appear in the content template of the styled ContentControl.
                Therefore, they make use of the content as their DataContext.-->
        <Grid x:Name="splitViewGrid">
            <Grid.RowDefinitions>
					<!--The top panel height is bound to the SplitPosition property.
						I would love to be able to express SplitPosition as a fractional value (ranging from 0 to 1),
						but I just wasn't able to use the control size as a ConverterParameter.-->
				<RowDefinition Height="{Binding Path=SplitPosition, Mode=TwoWay, Converter={StaticResource splitPositionConverter}}"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

                <!--The splittable view associated with Child1 viewmodel object-->
            <ContentControl  Grid.Row="0" Style="{DynamicResource RecursiveSplitViewStyle}"
                    DataContext="{Binding Path=Child1}" x:Name="child1View"/>
                <!--The grid splitter object separating the child views.-->
            <GridSplitter  Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeDirection="Rows"
                           Height="{Binding Source={x:Static hwpf:RecursiveSplitViewModel.DefaultSplitterWidth}}"/>

                <!--The splittable view associated with Child2 viewmodel object-->
            <ContentControl  Grid.Row="2" Style="{DynamicResource RecursiveSplitViewStyle}"
                    DataContext="{Binding Path=Child2}" x:Name="child2View"/>
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="VerticalSplitTemplate">
        <!--The elements in this template will appear in the content template of the styled ContentControl.
                Therefore, they make use of the content as their DataContext.-->
        <Grid x:Name="splitViewGrid">
            <Grid.ColumnDefinitions>
				<!--The left panel width is bound to the SplitPosition property.
						I would love to be able to express SplitPosition as a fractional value (ranging from 0 to 1),
						but I just wasn't able to use the control size as a ConverterParameter.-->
				<ColumnDefinition Width="{Binding Path=SplitPosition, Mode=TwoWay, Converter={StaticResource splitPositionConverter}}"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <!--The splittable view associated with Child1 viewmodel object-->
            <ContentControl  Grid.Column="0" Style="{DynamicResource RecursiveSplitViewStyle}"
                    DataContext="{Binding Path=Child1}" x:Name="child1View"/>
            <!--The grid splitter object separating the child views.-->
            <GridSplitter  Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeDirection="Columns"
                           Width="{Binding Source={x:Static hwpf:RecursiveSplitViewModel.DefaultSplitterWidth}}"/>

            <!--The splittable view associated with Child2 viewmodel object-->
            <ContentControl  Grid.Column="2" Style="{DynamicResource RecursiveSplitViewStyle}"
                    DataContext="{Binding Path=Child2}" x:Name="child2View"/>
        </Grid>
    </DataTemplate>

	<!--I learned about pass enum values as parameters from http://social.msdn.microsoft.com/forums/en-US/wpf/thread/03e543f8-c8f5-4a1c-9348-91200c9c1091/-->
	<ContextMenu x:Key="splitViewContextMenu" x:Shared="True" x:Name="splitViewContextMenu">
		<MenuItem Header="Horizontal Split" Command="{Binding Path=SplitCommand}" CommandParameter="{x:Static hwpf:RecursiveSplitState.HorizontalSplit}"/>
		<MenuItem Header="Vertical Split" Command="{Binding Path=SplitCommand}" CommandParameter="{x:Static hwpf:RecursiveSplitState.VerticalSplit}"/>
	</ContextMenu>
	
    <!--A ContentControl with this style will have a RecursiveSplitViewModel object as its DataContext.
            It will also have this object as its Content, so that the elements in its current ContentTemplate
            will use it as their DataContext, as well.-->
    <Style x:Key="RecursiveSplitViewStyle" TargetType="{x:Type ContentControl}">
        
        <Style.Setters>
			<Setter Property="AllowDrop" Value="{Binding Path=IsEnabled}"/>
			<Setter Property="Content" Value="{Binding}"/>
        </Style.Setters>
        
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=SplitState}" Value="Unsplit">
                <Setter Property="ContentTemplate" Value="{StaticResource UnsplitTemplate}"/>
				<Setter Property="ContextMenu" Value="{StaticResource splitViewContextMenu}"/>
			</DataTrigger>
            <DataTrigger Binding="{Binding Path=SplitState}" Value="HorizontalSplit">
                <Setter Property="ContentTemplate" Value="{StaticResource HorizontalSplitTemplate}"/>
				<Setter Property="ContextMenu" Value="{x:Null}"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=SplitState}" Value="VerticalSplit">
                <Setter Property="ContentTemplate" Value="{StaticResource VerticalSplitTemplate}"/>
				<Setter Property="ContextMenu" Value="{x:Null}"/>
            </DataTrigger>
			
		</Style.Triggers>
    </Style>
    
</ResourceDictionary>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Employed (other) Mersin University
Turkey Turkey
A Physics PhD with an engineering background who also earned an MBA. I am a long-time programmer who was confined to coding for academic purposes. Since 2004, I have been extending my coding skills into wider areas, like desktop interface development with data binding, automation of office applications, and development of physics simulations targeting social phenomena.

Comments and Discussions