OhBeWise
OhBeWise

Reputation: 5454

How to get ControlTemplate not to override DataTemplate?

Similar to gmail's Select button, I wanted to create a ComboBox for the ListView which allows the user to quickly select entries of their choosing (ex. All, None, Read, Unread). However, the selected value would display a tri-state CheckBox equivalent to All, Some, or None of the entries being selected. I succeeded in doing so. Below is the xaml for an example Window utilizing this feature(*):

<Window x:Class="WPFTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfTest="clr-namespace:WPFTest.ViewModels"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Mvvm;assembly=Microsoft.Practices.Prism.Mvvm.Desktop"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:local="clr-namespace:WPFTest.Models"
        mc:Ignorable="d" Title="WPF Test" Height="221.256" Width="605"
        d:DataContext="{d:DesignInstance wpfTest:MainWindowViewModel, IsDesignTimeCreatable=True}"
        prism:ViewModelLocator.AutoWireViewModel="True" WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary
                    Source="/WPFTest;Component/Resources/Resources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid>
        <ListView 
            ItemsSource="{Binding Entries}"
            SelectedValue="{Binding SelectedEntry}"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            ScrollViewer.CanContentScroll="True"
            SelectionMode="Single"
            Margin="10">

            <ListView.View>
                <GridView>
                    <GridViewColumn Width="Auto">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <CheckBox 
                                    IsChecked="{Binding Selected}" 
                                    Command="{Binding DataContext.RowSelectedCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
                                    HorizontalAlignment="Center"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                        <GridViewColumnHeader>
                            <ComboBox 
                                ItemsSource="{Binding Options.Items}"
                                SelectedValue="{Binding Options.SelectedItem}"
                                ItemTemplateSelector="{DynamicResource itemTemplateSelector}"
                                HorizontalAlignment="Stretch"
                                Margin="0,0,0,0"
                                VerticalAlignment="Stretch"
                                Width="44" 
                                Height="34"
                                FontSize="20"
                                VerticalContentAlignment="Top"
                                HorizontalContentAlignment="Left">
                                <ComboBox.Resources>
                                    <DataTemplate x:Key="selectedTemplate">
                                        <TextBlock 
                                            x:Name="displayText"
                                            Text="{Binding DataContext.Options.SelectedDisplay, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
                                            FontSize="20"
                                            Height="22"/>
                                    </DataTemplate>
                                    <DataTemplate x:Key="dropDownTemplate">
                                        <TextBlock Text="{Binding}" FontSize="12"/>
                                    </DataTemplate>
                                    <local:ComboBoxItemTemplateSelector
                                        x:Key="itemTemplateSelector"
                                        SelectedTemplate="{StaticResource selectedTemplate}"
                                        DropDownTemplate="{StaticResource dropDownTemplate}"/>
                                </ComboBox.Resources>
                            </ComboBox>
                        </GridViewColumnHeader>
                    </GridViewColumn>
                    <GridViewColumn
                        Width="Auto"
                        DisplayMemberBinding="{Binding Type, Mode=OneWay}"
                        Header="Type"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

Demo choosing options in combo

Recently I was asked to make a style for every ComboBox on our screens - changing their backgrounds. Because I'm on Windows8, setting the Background alone isn't enough. Using this tutorial I was able to create the ControlTemplate to get the correct behavior, with one minor error fix:

<MultiTrigger.Conditions>
    <Condition Property="IsGrouping" Value="True"/>
    <!-- Comment out the following, it throws an error. -->
    <!--<Condition>
            <Condition.Value>
                <sys:Boolean>False</sys:Boolean>
            </Condition.Value>
        </Condition>-->
</MultiTrigger.Conditions>

And usage:

<Style
    TargetType="{x:Type ComboBox}">
    ...
    <Setter
        Property="Template"
        Value="{StaticResource ComboBoxStyle1}" />
</Style>

This successfully styles the ComboBox Background. However, revisiting the former screen, I noticed that this breaks my gmail-like display.

Background colored but display is wrong

How can I get this ControlTemplate and the dynamic DataTemplate to cooperate?

(*) ViewModels and Models can be provided if necessary for a solution. Or see full working example.

Upvotes: 1

Views: 702

Answers (2)

OhBeWise
OhBeWise

Reputation: 5454

Color the Background

As per the OP, following this tutorial, you generate the ControlTemplate.

To do this, you can right-click on the ComboBox element in design mode in Visual Studio 2012 or 2013 and select the “Edit template” option and then the “Edit a copy…” option.

Again note the bug fix from the OP. Changing the background can then be done by:

<Border 
    x:Name="templateRoot"
    BorderBrush="#FFACACAC"
    BorderThickness="{TemplateBinding BorderThickness}"
    SnapsToDevicePixels="True"
    Background="{StaticResource MyComboBackgroundBrush}"> <!-- option 1 -->

    <!-- <Border.Background> -->

         <!-- option 2 -->
         <!-- <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                  <GradientStop Color="#FFF0F0F0" Offset="0"/>
                  <GradientStop Color="#FFE5E5E5" Offset="1"/>
              </LinearGradientBrush>-->

         <!-- option 3 -->                     
         <!-- <SolidColorBrush Color="Yellow"/> -->

    <!-- </Border.Background>
         <Border x:Name="splitBorder" BorderBrush="Transparent" BorderThickness="1" HorizontalAlignment="Right" Margin="0" SnapsToDevicePixels="True" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}">
             <Path x:Name="Arrow" Data="F1M0,0L2.667,2.66665 5.3334,0 5.3334,-1.78168 2.6667,0.88501 0,-1.78168 0,0z" Fill="#FF606060" HorizontalAlignment="Center" Margin="0" VerticalAlignment="Center"/>
         </Border> -->
</Border>

Interact with the DataTemplate

  1. In the resource file, following the above instructions, you declared:

    <ControlTemplate x:Key="ComboBoxControlTemplateBasic" TargetType="{x:Type ComboBox}">
        ...
        <ContentPresenter 
            x:Name="contentPresenter"
            ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" 
            ... />
        ...
    </ControlTemplate>
    

    Create a copy of it with the following change, where selectedTemplate is the key of the DataTemplate you want to cooperate with the ControlTemplate:

    <ControlTemplate x:Key="ComboBoxControlTemplateHeader" TargetType="{x:Type ComboBox}">
        ...
        <ContentPresenter 
            x:Name="contentPresenter"
            ContentTemplate="{DynamicResource selectedTemplate}" 
            ... />
        ...
    </ControlTemplate>
    
  2. Declare the appropriate styles:

    <Style
        TargetType="{x:Type ComboBox}">
        <Setter Property="Template"
                Value="{StaticResource ComboBoxControlTemplateBasic}" />
        <!-- other generic style setters -->
    </Style>
    
    <Style
        x:Key="ComboBoxHeader"
        x:Name="ComboBoxHeader"
        TargetType="{x:Type ComboBox}"
        BasedOn="{StaticResource {x:Type ComboBox}}">
        <Setter Property="Template"
                Value="{StaticResource ComboBoxControlTemplateHeader}" />
        <Setter Property="ItemTemplateSelector"
                Value="{DynamicResource itemTemplateSelector}"/>
        <!-- other specific style setters -->
    </Style>
    

Results

  • Every ComboBox shares the same look.
  • The dynamic use of DataTemplate selectedTemplate allows each ListView header gmail-like ComboBox to have the same look with unique binding values.

Take for example the OP ComboBox header plus the following ComboBox with the same item source:

<ComboBox 
    HorizontalAlignment="Left" 
    Margin="10,152,0,0" 
    VerticalAlignment="Top" 
    Width="120"
    ItemsSource="{Binding Options.Items}"
    SelectedValue="{Binding Options.SelectedItem}"/>

<GridViewColumnHeader>
    <ComboBox 
        ItemsSource="{Binding Options.Items}"
        SelectedValue="{Binding Options.SelectedItem}"
        Style="{StaticResource ComboBoxHeader}">
        <ComboBox.Resources>
            <DataTemplate x:Key="selectedTemplate">
                <TextBlock 
                    x:Name="displayText"
                    Text="{Binding DataContext.Options.SelectedDisplay, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}"
                    FontSize="20"
                    Height="22"/>
            </DataTemplate>

            <DataTemplate x:Key="dropDownTemplate">
                <TextBlock Text="{Binding}" FontSize="12"/>
            </DataTemplate>

            <local:ComboBoxItemTemplateSelector
                x:Key="itemTemplateSelector"
                SelectedTemplate="{StaticResource selectedTemplate}"
                DropDownTemplate="{StaticResource dropDownTemplate}"/>
        </ComboBox.Resources>
    </ComboBox>
</GridViewColumnHeader>

working example

Upvotes: 0

Alex F
Alex F

Reputation: 3539

I'm not sure why you doing so complicated ComboBox - you defined ItemTemplateSelector twice...

Ok - here is my 2 cents: ComboBox is lookless control. It based on ControlTemplate target type = ComboBox. Inside ComboBox ControlTemplate you will find ContentPresenter. Whatever coming into Content of ContentPresenter could be styled with DataTemplate. Generally - when you define DataTemplate it wrap only ContentPresenter or ItemsPresenter for range based controls - not the whole ControlTemplate obviously.

So if you want to change 'Selected' template for ComboBox is ok but all other data should be defined via DataTemplate for this type {x:Type local:SomeType} that will be used by ComboBox.

Also - consider using @galakt suggestion: use Style with TargetType - it easy to read, refactor, find, understand...

Upvotes: 1

Related Questions