Click here to Skip to main content
12,898,663 members (62,479 online)
Click here to Skip to main content

Stats

644.3K views
5.8K downloads
110 bookmarked
Posted 28 Oct 2007

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 x:Class="Demo2"
    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="Selected Item Large Indicator" 
    Background="#FFFCC5C5"
    >

    <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="#FFFFFFFF"/>
        <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" EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FFF8FF72" Offset="0.1"/>
            <GradientStop Color="#FFFF0000" Offset="1"/>
        </LinearGradientBrush>


        <Style x:Key="largeIndicatorArrowListBoxItemStyle" 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="32"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <!--The grid left margin of -36, 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="32" Height="32" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="-36,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="-25,0,10,0" Grid.Column="1" SnapsToDevicePixels="true" x:Name="Bd" VerticalAlignment="Center" MinHeight="32" 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="largeIndicatorArrowListBoxStyle" TargetType="{x:Type ListBox}">
            <Setter Property="ItemContainerStyle" Value="{StaticResource largeIndicatorArrowListBoxItemStyle}"/>
            <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="36,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 largeIndicatorArrowListBoxStyle}"
                 IsSynchronizedWithCurrentItem="True" FontSize="28" 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 largeIndicatorArrowListBoxStyle}"
                 IsSynchronizedWithCurrentItem="True" FontSize="12" Height="Auto"
                 BorderBrush="{StaticResource scrollViewerBorderBrush}" BorderThickness="1"
                 SelectedIndex="0"
                 Margin="10,10,10,10" Grid.Row="2" VerticalAlignment="Stretch">

            <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 Infragistics
United States United States

I’m a passionate Platform Architect at Infragistics.


I’m a long-time WPF-Prism fanatic who enjoys writing developer tools and line of business applications.


My current front end passions are: XAML platforms (Xamarin.Forms, Xamarin, UWP, and WPF), Electron, ES2015 (ES6), Node.js, Aurelia, and AngularJS (Angular 1.5.x).


For the back end I use what is appropriate for the project: SQL Server and ASP.NET WebAPI, MongoDB, Express, Azure, Firebase, etc.


I am very pragmatic software engineer and strive to write simple, maintainable, and testable code. Simple code allows for solving complex problems in a maintainable way.


My Blog


My Github Repros


My YouTube Videos


Just a grain of sand on the worlds beaches.


You may also be interested in...

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170424.1 | Last Updated 28 Oct 2007
Article Copyright 2007 by Karl Shifflett
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid