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

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

, 28 Oct 2007
Article on solving a problem using a XAML only approach and then solving that same problem using WPF custom controls.
<UserControl 
    x:Class="Demo5"
    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"
    Tag="Custom CheckListBox Two" 
    >
    <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>

    	<SolidColorBrush x:Key="outerBorderBackgroundBrush"  Color="#FFD7FBD7"/>
    	<LinearGradientBrush x:Key="outerBorderBorderBrush" EndPoint="1,0.5" StartPoint="0,0.5">
    		<GradientStop Color="#FF34B100" Offset="0"/>
    		<GradientStop Color="#FF19C7D0" Offset="1"/>
    	</LinearGradientBrush>
    	<SolidColorBrush x:Key="scrollViewerBorderBrush" Color="#FF000000"/>
		<SolidColorBrush x:Key="scrollViewerBackgroundBrush" Color="#FFFFFFFF"/>
        <SolidColorBrush x:Key="customCheckBoxBorderBrush" Color="Gray"/>

        <LinearGradientBrush x:Key="customCheckBoxBrush" EndPoint="1,0.5" StartPoint="0,0.5">
            <GradientStop Color="#FF0034AD" Offset="0"/>
            <GradientStop Color="#FF09E6AC" Offset="1"/>
        </LinearGradientBrush>


        <Style x:Key="customCheckListBoxItemStyle" 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.ColumnDefinitions>
                                <ColumnDefinition Width="20"/>
                                <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 CheckBoxes are 
                                outside our ListBox control-->
                            <Grid Background="Transparent" Width="20" Height="20" HorizontalAlignment="Left" Margin="-24,0,0,0">

                                <BulletDecorator Margin="2,0,0,0" SnapsToDevicePixels="true" Background="Transparent" VerticalAlignment="Center">
                                    
                                    <BulletDecorator.Bullet>
                                        
                                        <!-- I swapped out the default Bullet with the one from the simple controls CheckBox.
                                             You can do anything you want because it's WPF and your the programmer!-->
                                        <Grid Width="16" Height="16" >
                                            <Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{StaticResource customCheckBoxBorderBrush}" BorderThickness="1"/>
                                            <Path Stretch="Fill" x:Name="checkMark" Stroke="{StaticResource customCheckBoxBrush}" StrokeThickness="4" SnapsToDevicePixels="False" Data="M 0 0 L 13 13 M 0 13 L 13 0"/>
                                        </Grid>
                                    </BulletDecorator.Bullet>

                                </BulletDecorator>
                            </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="20" 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="false">
                                <Setter Property="Visibility" Value="Collapsed" TargetName="checkMark"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>

                            <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="customCheckListBoxStyle" TargetType="{x:Type ListBox}">
            <Setter Property="ItemContainerStyle" Value="{StaticResource customCheckListBoxItemStyle}"/>
            <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.-->
                        <Border CornerRadius="0" Background="{TemplateBinding Background}">
                            
                            <!--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="24,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>

        <Border CornerRadius="0,0,0,0" Background="{StaticResource outerBorderBackgroundBrush}" Padding="0,0,0,0" Grid.Row="1" BorderBrush="{StaticResource outerBorderBorderBrush}" BorderThickness="6,6,6,6" Margin="10,10,10,10" >
            
            <Grid>
                <Rectangle Margin="24,-1,-1,-1" Fill="{StaticResource scrollViewerBorderBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                
                <ListBox Background="{StaticResource outerBorderBackgroundBrush}"
                         ItemsSource="{Binding Path=Items, Mode=OneWay, Source={StaticResource someTestData}}"
                         SelectionMode="{Binding Path=Text, ElementName=selectionModeCombo, Mode=Default}"
                         Style="{StaticResource customCheckListBoxStyle}"
                         IsSynchronizedWithCurrentItem="True" FontSize="18" Height="Auto"
                         VerticalAlignment="Stretch" SelectedIndex="0"/>
            </Grid>
        </Border>

        <Border CornerRadius="0,0,0,0" 
                Background="{StaticResource outerBorderBackgroundBrush}" 
                Padding="0,0,0,0" 
                Grid.Row="2" 
                BorderBrush="{StaticResource outerBorderBorderBrush}" 
                BorderThickness="6,6,6,6" 
                Margin="10,10,10,10">
            
            <Grid>
                <Rectangle Margin="24,-1,-1,-1" 
                           Fill="{StaticResource scrollViewerBorderBrush}" 
                           HorizontalAlignment="Stretch" 
                           VerticalAlignment="Stretch" />
                
                <ListBox Background="{StaticResource outerBorderBackgroundBrush}" 
                         SelectionMode="{Binding Path=Text, ElementName=selectionModeCombo, Mode=Default}" 
                         Style="{StaticResource customCheckListBoxStyle}" 
                         IsSynchronizedWithCurrentItem="True" FontSize="18" Height="Auto" 
                         VerticalAlignment="Stretch" SelectedIndex="0">
                    
                    <ListBoxItem>CTRL-Click</ListBoxItem>
                    <ListBoxItem>To deselect item.</ListBoxItem>
                    <ListBoxItem>Don't be laughing</ListBoxItem>
                    <ListBoxItem>at Jack-O's.</ListBoxItem>
                    <Grid>
                        <Ellipse HorizontalAlignment="Left" Height="50" Width="50"
                             Margin="0,5,0,5" Stroke="BurlyWood" Fill="AntiqueWhite"
                             StrokeThickness="1"/>
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="8,15.113,0,0" 
                                 VerticalAlignment="Top" Width="10.88" Height="7.273"/>
                        
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="27.934,15.113,0,0" 
                                 VerticalAlignment="Top" Width="10.963" Height="7.273"/>
                        
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="20.672,24.705,0,26.329" 
                                 Width="7.957"/>
                        
                        <Path Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" 
                              HorizontalAlignment="Left" Margin="8.086,37.103,0,0" 
                              VerticalAlignment="Top" Width="33.635" Height="10.562">
                            <Path.Data>
                                <PathGeometry>
                                    <PathFigure IsClosed="True" StartPoint="32.107,4.0055">
                                        <PolyBezierSegment IsSmoothJoin="True" 
                                                           Points="30.999835,5.5937108 25.031532,7.511 
                                                           16.3035,7.511 7.575468,7.511 1.781416,5.4567758 
                                                           0.5,4.0055 -12.09973,-10.264404 7.5781247,3.5549446 
                                                           16.306157,3.5549446 25.034189,3.5549446 
                                                           42.945024,-11.541472 32.107,4.0055"/>
                                    </PathFigure>
                                </PathGeometry>
                            </Path.Data>
                        </Path>
                    </Grid>

                    <Grid>
                        <Ellipse HorizontalAlignment="Left" Height="50" Width="50"
                             Margin="0,5,0,5" Stroke="#FF5DD66C" Fill="#FF9EFF32"
                             StrokeThickness="1"/>
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="8,15.113,0,0" 
                                 VerticalAlignment="Top" Width="10.88" Height="7.273"/>
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="27.934,15.113,0,0" 
                                 VerticalAlignment="Top" Width="10.963" Height="7.273"/>
                        <Ellipse Fill="#FFFFFFFF" Stroke="#FF000000" 
                                 HorizontalAlignment="Left" Margin="20.672,24.705,0,0" 
                                 VerticalAlignment="Top" Width="7.957" Height="8"/>
                        <Path Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" 
                              HorizontalAlignment="Left" Margin="8.086,37.103,0,0" 
                              VerticalAlignment="Top" Width="33.635" Height="10.562">
                            <Path.Data>
                                <PathGeometry>
                                    <PathFigure IsClosed="True" StartPoint="32.107,4.0055">
                                        <PolyBezierSegment 
                                            IsSmoothJoin="True" 
                                            Points="30.999835,5.5937108 25.031532,7.511 16.3035,7.511 
                                                7.575468,7.511 1.781416,5.4567758 0.5,4.0055 -12.09973,-10.264404 
                                                7.5781247,3.5549446 16.306157,3.5549446 25.034189,3.5549446 
                                                42.945024,-11.541472 32.107,4.0055"/>
                                    </PathFigure>
                                </PathGeometry>
                            </Path.Data>
                        </Path>
                        <Ellipse RenderTransformOrigin="0.121,-0.15" Fill="#FF000000" 
                                 Stroke="#FF000000" StrokeThickness="1" HorizontalAlignment="Left" 
                                 Margin="11.548,17.571,0,0" VerticalAlignment="Top" Width="3.479" Height="2.109">
                            <Ellipse.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="25.39"/>
                                </TransformGroup>
                            </Ellipse.RenderTransform>
                        </Ellipse>
                        <Ellipse RenderTransformOrigin="0.121,-0.15" Fill="#FF000000" 
                                 Stroke="#FF000000" StrokeThickness="1" Height="2.109" 
                                 HorizontalAlignment="Left" Margin="32.7,17.2,0,0" VerticalAlignment="Top" 
                                 Width="3.479">
                            <Ellipse.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="34.039"/>
                                </TransformGroup>
                            </Ellipse.RenderTransform>
                        </Ellipse>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" HorizontalAlignment="Left" 
                                   Margin="14.769,43,0,0" VerticalAlignment="Top" Width="2.74" Height="2.636">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="19.184"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" Width="1.441" 
                                   Height="2.636" HorizontalAlignment="Left" Margin="12.063,39,0,0" 
                                   VerticalAlignment="Top">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="19.184"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" Height="1.998" 
                                   HorizontalAlignment="Left" Margin="32,41,0,0" VerticalAlignment="Top" 
                                   Width="2.066">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="62.741"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" Height="1.821" 
                                   HorizontalAlignment="Left" Margin="27,45,0,0" VerticalAlignment="Top" 
                                   Width="1.586">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="-7.418"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" Width="2.74" 
                                   Height="1.837" HorizontalAlignment="Left" Margin="19.904,43,0,0" 
                                   VerticalAlignment="Top">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="19.184"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle RenderTransformOrigin="0.5,0.5" Fill="#FF000000" 
                                   Stroke="#FF000000" StrokeThickness="1" Height="2.277" 
                                   HorizontalAlignment="Left" Margin="37,42,0,0" VerticalAlignment="Top" 
                                   Width="1.56">
                            <Rectangle.RenderTransform>
                                <TransformGroup>
                                    <RotateTransform Angle="33.41"/>
                                </TransformGroup>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                    </Grid>

                    <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>
        </Border>
    </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.140905.1 | Last Updated 28 Oct 2007
Article Copyright 2007 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid