Click here to Skip to main content
12,349,911 members (23,208 online)
Click here to Skip to main content

Stats

222.3K views
5.4K downloads
109 bookmarked
Posted

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

Current passions are: Electron, ES6, Node.js, Angular, Aurelia, hybrid, Socket.IO, MQTT, and IoT.

Still keen on WPF and Prism, but currently Electron and ES6 deliver the same apps with much better performance, and they are cross-platform.

My Github Repros

Just a grain of sand on the worlds beaches.


You may also be interested in...

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