Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

A WPF Problem Solved Two Very Different Ways - Using XAML Only - Using a Custom Control

, 28 Oct 2007 CPOL
Article on solving a problem using a XAML only approach and then solving that same problem using WPF custom controls.
<UserControl
    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"
    x:Class="Demo1"
    Background="#FFFCC5C5"
    Tag="Selected Item Indicator" 
    Width="Auto" 
    >

    <UserControl.Resources>

        <ObjectDataProvider x:Key="SelectionModeValues" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
            
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="SelectionMode" />
            </ObjectDataProvider.MethodParameters>
            
        </ObjectDataProvider>

        <x:Array x:Key="someTestData" Type="{x:Type sys:String}">
            <sys:String>Love WPF!</sys:String>
            <sys:String>No Code</sys:String>
            <sys:String>Just</sys:String>
            <sys:String>XAML Baby!</sys:String>
            <sys:String>ListBox Works Using</sys:String>
            <sys:String>Single</sys:String>
            <sys:String>Muliple</sys:String>
            <sys:String>Extended</sys:String>
            <sys:String>Selection Modes.</sys:String>
            <sys:String>You can</sys:String>
            <sys:String>SHIFT-Click in</sys:String>
            <sys:String>Extened Mode</sys:String>
            <sys:String>over the text or</sys:String>
            <sys:String>over the CheckBox.</sys:String>
            <sys:String>DataBind Items!</sys:String>
            <sys:String>XAML Declared Items!</sys:String>
            <sys:String>Check Out</sys:String>
            <sys:String>All</sys:String>
            <sys:String>The</sys:String>
            <sys:String>Provided</sys:String>
            <sys:String>Samples.</sys:String>
            <sys:String>Does it look</sys:String>
            <sys:String>like several</sys:String>
            <sys:String>controls are</sys:String>
            <sys:String>doing the work</sys:String>
            <sys:String>or just one?</sys:String>
            <sys:String>Nice separation</sys:String>
            <sys:String>between the CheckBox</sys:String>
            <sys:String>and ListBoxItem!</sys:String>
        </x:Array>

        <!--Use the resources here to adjust the colors of your CheckListBox-->
        <SolidColorBrush x:Key="outerBorderBackgroundBrush" Color="#FFFFE6E6"/>
        <SolidColorBrush x:Key="outerBorderBorderBrush" Color="#FF0000FF"/>
        <SolidColorBrush x:Key="scrollViewerBorderBrush" Color="#FF000000"/>
        <SolidColorBrush x:Key="scrollViewerBackgroundBrush" Color="#FFFFFFFF"/>
        <SolidColorBrush x:Key="checkBoxBorderBrush" Color="#FF000000"/>

        <!-- The Brush used to paint the selection indicator. -->
        <LinearGradientBrush x:Key="indicatorArrowBrush" StartPoint="0.5,0" EndPoint="0.5,1">
            <GradientStop Color="LightBlue" Offset="0.1" />
            <GradientStop Color="Blue" Offset="1" />
        </LinearGradientBrush>

        <Style x:Key="indicatorArrowListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
            <Style.Resources>

                <!--To use the normal ListBox selection hightlight and selection color after
                the ListBox looses focus, just remove the below three resources-->

                <!-- This Style is used to prevent the ListBoxItems from using different 
             colors when selected than when not selected. -->

                <!-- Prevents the selected items from having a different color background. -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />

                <!-- Prevents the selected items from having a different color 
                 background when the ListBox no longer has focus. -->
                <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />

                <!-- Ensures the selected item's text is visible. Insurance policy-->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="{x:Static SystemColors.ControlTextColor}" />
            </Style.Resources>

            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
            <Setter Property="Padding" Value="2,0,0,0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Grid>
                            <Grid.Resources>
                                <BooleanToVisibilityConverter x:Key="bolToVis"/>
                            </Grid.Resources>

                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="16"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <!--The grid left margin of -20, gets the first column over to the left to give us that 
                            look that the indicator arrows are outside our ListBox control-->
                            <Grid Background="Transparent"
                                  Width="16"
                                  Height="16"
                                  HorizontalAlignment="Left"
                                  VerticalAlignment="Center"
                                  Margin="-20,0,0,0"
                                  Visibility="{Binding Path=IsSelected, Converter={StaticResource bolToVis}, RelativeSource={RelativeSource AncestorLevel=1, AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}">

                                <!-- A lightweight drop shadow under the selection indicator. -->
                                <Path Fill="LightGray" Stretch="Uniform" Data="M4,4 L16,10 L4,16 z" RenderTransformOrigin="0.5,0.5" SnapsToDevicePixels="True">
                                    <Path.RenderTransform>
                                        <TransformGroup>
                                            <TranslateTransform X="2" Y="2"/>
                                        </TransformGroup>
                                    </Path.RenderTransform>
                                </Path>

                                <!-- The selection indicator itself. -->
                                <Path Fill="{StaticResource indicatorArrowBrush}" Stretch="Uniform" Data="M2,2 L14,8 L2,14 z"/>

                            </Grid>

                            <!--This positions our content in the perfect position-->
                            <Border Margin="-10,0,10,0" Grid.Column="1" SnapsToDevicePixels="true" x:Name="Bd" VerticalAlignment="Center" MinHeight="16" Background="Transparent">

                                <ContentPresenter Content="{TemplateBinding Content}">
                                    <ContentPresenter.Resources>
                                        <Style TargetType="{x:Type TextBlock}">
                                            <Setter Property="TextWrapping" Value="Wrap"/>
                                        </Style>
                                    </ContentPresenter.Resources>

                                </ContentPresenter>
                            </Border>
                        </Grid>

                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="true">
                                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                            </Trigger>

                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsSelected" Value="true"/>
                                    <Condition Property="Selector.IsSelectionActive" Value="false"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>

                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                            </MultiTrigger>

                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>

                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="indicatorArrowListBoxStyle" TargetType="{x:Type ListBox}">
            <Setter Property="ItemContainerStyle" Value="{StaticResource indicatorArrowListBoxItemStyle}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBox}">
                        <!--This is the secret to having the area that the CheckBoxes are in 
                        blend with the color of the parent border control
                        making this area look like a separate control.-->

                        <!--Please note - the BorderBrush and BorderThickness are bound to the template.
                        In this example, we are not doing the trick with the border, grid, rectange and listbox.
                        This technique provides a border around the entire control.
                        If you want a nice visual separation, then use the techniques from the other example.-->
                        <Border CornerRadius="0" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">

                            <!--Disabling the HorizontalScrollBarVisibility allows the ContentPresenter 
                            TextBlocks to wrap if the ListBox is not wide enought for the text-->
                            <ScrollViewer HorizontalScrollBarVisibility="Disabled">

                                <!--This border is Mr. Cool.  It places a line down the middle of the 
                                control between the CheckBoxes and the ListBoxes items.-->
                                <Border Background="{StaticResource scrollViewerBackgroundBrush}" Margin="20,0,0,0" BorderBrush="{StaticResource scrollViewerBorderBrush}" BorderThickness="1,0,0,0" x:Name="border">

                                    <!--Our ListBoxItems are here-->
                                    <ItemsPresenter/>
                                    
                                </Border>
                            </ScrollViewer>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </UserControl.Resources>
    <Grid Width="Auto" Height="Auto">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <DockPanel HorizontalAlignment="Stretch">

            <StackPanel DockPanel.Dock="Left" Orientation="Horizontal" HorizontalAlignment="Left" Margin="10,10,10,10" VerticalAlignment="Top" Height="Auto">

                <TextBlock Margin="0,0,10,0" VerticalAlignment="Center">Selection Mode:</TextBlock>

                <ComboBox x:Name="selectionModeCombo" DataContext="{StaticResource SelectionModeValues}" IsReadOnly="True" ItemsSource="{Binding}" SelectedIndex="0"/>

            </StackPanel>
            
            <TextBlock DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,10,0" FontSize="18" FontStyle="Normal" FontWeight="Normal" Padding="5,5,5,5">
                <TextBlock.Background>
            		<LinearGradientBrush EndPoint="0.508,0.835" StartPoint="0.508,0.167">
            			<GradientStop Color="#FFFFE8D0" Offset="0"/>
            			<GradientStop Color="#FFF88206" Offset="1"/>
            		</LinearGradientBrush>
            	</TextBlock.Background>No Code, Just XAML!</TextBlock>
        </DockPanel>
        
        <ListBox Background="{StaticResource outerBorderBackgroundBrush}"
                 ItemsSource="{Binding Path=Items, Mode=OneWay, Source={StaticResource someTestData}}"
                 SelectionMode="{Binding Path=Text, ElementName=selectionModeCombo, Mode=Default}"
                 Style="{StaticResource indicatorArrowListBoxStyle}"
                 IsSynchronizedWithCurrentItem="True" FontSize="18" Height="Auto"
                 BorderBrush="{StaticResource scrollViewerBorderBrush}" BorderThickness="1"
                 Margin="10,10,10,10" VerticalAlignment="Stretch" Grid.Row="1"/>

        <ListBox Background="{StaticResource outerBorderBackgroundBrush}"
                 SelectionMode="{Binding Path=Text, ElementName=selectionModeCombo, Mode=Default}"
                 Style="{StaticResource indicatorArrowListBoxStyle}"
                 IsSynchronizedWithCurrentItem="True" FontSize="10" Height="Auto"
                 BorderBrush="{StaticResource scrollViewerBorderBrush}" BorderThickness="1"
                 SelectedIndex="0"
                 Margin="10,10,10,10" Grid.Row="2" VerticalAlignment="Stretch">
            
            <ListBoxItem>See small font works too.</ListBoxItem>
            <ListBoxItem>CTRL-Click</ListBoxItem>
            <ListBoxItem>To deselect item</ListBoxItem>
            <Rectangle Margin="0,5,0,5" HorizontalAlignment="Left" Fill="Beige" RadiusX="10" RadiusY="10" Stroke="Black" StrokeThickness="1" Width="50" Height="25"/>
            <Ellipse HorizontalAlignment="Left" Height="50" Width="50" Margin="0,5,0,5" Stroke="BurlyWood" Fill="AntiqueWhite" StrokeThickness="1"/>
            <Rectangle Margin="0,5,0,5" HorizontalAlignment="Left" Fill="Beige" RadiusX="10" RadiusY="10" Stroke="Black" StrokeThickness="1" Width="50" Height="25"/>
        </ListBox>

    </Grid>

</UserControl>

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)

Share

About the Author

Karl Shifflett
Architect Gayle Manufacturing Company
United States United States
Karl loves .NET, WPF, WCF, ASP.NET, VB.NET and C#.
 
Awards:
 
  • December 2008 VB.NET Code Project Article Award
  • 2009 Code Project MVP
  • 2008 Code Project MVP
  • 2008 Microsoft MVP - Client App Dev
  • December 2007 VB.NET Code Project Article Award
  • Gold Medal Winner at IBM's 1998 PROIV Programming Contest in Las Vegas
Click here to check out my Blog
 
Click here to learn about Mole 2010 debugging tool for Visual Studio 2010
 
Click here to read about XAML Power Toys
 

Just a grain of sand on the worlds beaches.

Follow on   Twitter

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 28 Oct 2007
Article Copyright 2007 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid