Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / UWP

Edit Template or Edit Additional Templates in Visual Studio XAML without all the Bloat

5.00/5 (3 votes)
19 Jun 2020CPOL5 min read 7.1K  
How to edit templates in VS XAML without the bloat
If you've worked with XAML for a while and customized controls like Buttons, ListView, etc., you are familiar with what happens when you use Visual Studio's Edit Template functionality. You get a copy of the entire underlying Template or Style of the control. This can be many lines of code added to your XAML which drowns out the important stuff. Then you want to change one or two properties in that pile of XAML. Hardly worth it. In this post, I offer you a way to help with that and make your code more readable and maintainable.

Background

Visual Studio (I'm using 2019) gives users help in customizing built in controls and it's great functionality giving you everything you need to customize any of their build in controls. In this tip, we will be creating a <ListView/> with some styling. It will be a list of items, each with a FontIcon in front of a gray circle.

  1. Here, we have the ListView in its simplest form:
    XAML
    <ListView/>

    A wonder to behold, but by itself resides renders nothing. We style the items in the list by modifying the ItemTemplate...

  2. Right click the ListView in the Document Outline
  3. Select Edit Additional Templates->Edit Generated Items (ItemTemplate)->Create Empty...
    XAML
    <DataTemplate x:Key="ListViewItemTemplate1">
        <Grid/>
    </DataTemplate>
    ...
    <ListView ItemTemplate="{StaticResource ListViewItemTemplate1}"/>

    Still, pretty nice. The ListView's ItemTemplate is pretty concise and well suited for modification. Let's add some styling...

  4. Add the Ellipse and FontIcon. Also, add some data so each FontIcon has a character to bind to.
    XAML
    <DataTemplate x:Key="ListViewItemTemplate1">
        <Grid>
            <Ellipse Width="80" Height="80" Fill="#16808080" />
            <FontIcon Glyph="{Binding}" FontSize="36" 
             VerticalAlignment="Center" HorizontalAlignment="Center" />
        </Grid>
    </DataTemplate>
    ...
    <ListView ItemTemplate="{StaticResource ListViewItemTemplate1}>
        <x:String>&#xE962;</x:String>
        <x:String>&#xE946;</x:String>
        <x:String>&#xE909;</x:String>
        <x:String>&#xEB50;</x:String> 
    </ListView>

    Now it's a vertical list of circles with Glyphs in front of them. Nice, but we want them horizontal and wrapping in case the user has the gall to resize the window.

  5. Right click the ListView in the Document Outline.
  6. Select Edit Additional Templates->Edit Layout Items (ItemsPanel)->Edit a Copy...
    XAML
    ...
    <ItemsPanelTemplate x:Key="ListViewItemsPanel1">
        <ItemsStackPanel Orientation="Vertical"/>
    </ItemsPanelTemplate>
    ...
    <ListView ItemTemplate="{StaticResource ListViewItemTemplate1}
        ItemsPanel="{StaticResource ListViewItemsPanel1}">
        <x:String>&#xE962;</x:String>
        <x:String>&#xE946;</x:String>
        <x:String>&#xE909;</x:String>
        <x:String>&#xEB50;</x:String> 
    </ListView>

    Again, pretty concise and well suited for modification. Now let's make it wrap.

  7. Replace the ItemsStackPanel with ItemsWrapGrid.
    XAML
    ...
    <ItemsPanelTemplate x:Key="ListViewItemsPanel1">
        <ItemsWrapGrid Orientation="Horizontal" />
    </ItemsPanelTemplate>
    ...

    If you made it this far, you're probably wondering what's your point? Hang in there. I'm almost there. Here's a little nugget of value add to tide you over. I like things compartmentalized. The ListView does its thing and a Style does the styling of it. You know, separation of responsibility.

    XAML
    <Style x:Key="CircleGlyphItem" TargetType="ListView">
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid>
                        <Ellipse Width="80" Height="80" Fill="#16808080" />
                        <FontIcon Glyph="{Binding}" FontSize="36" 
                         VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    ...
    <ListView Style="{StaticResource CircleGlyphItem}">
        <x:String>&#xE962;</x:String>
        <x:String>&#xE946;</x:String>
        <x:String>&#xE909;</x:String>
        <x:String>&#xEB50;</x:String>
    </ListView>

    Looks great if I do say so myself, but it could be better. I'd like a bit more space between the items. In order to do this, you need to modify the ItemContainerStyle. This step produces something that is not at all concise and well suited for modification. And, all I want to do is add a little margin.

Interesting Part

  1. Right click the ListView in the Document Outline.
  2. Select Edit Additional Templates->Edit Generated Items Container (ItemContainerStyle)->Edit a Copy...
    XAML
    <Style x:Key="ListViewItemContainerStyle1" TargetType="ListViewItem">
        <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
        <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
        <Setter Property="Background" Value="{ThemeResource ListViewItemBackground}"/>
        <Setter Property="Foreground" Value="{ThemeResource ListViewItemForeground}"/>
        <Setter Property="TabNavigation" Value="Local"/>
        <Setter Property="IsHoldingEnabled" Value="True"/>
        <Setter Property="Padding" Value="12,0,12,0"/>
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="MinWidth" Value="{ThemeResource ListViewItemMinWidth}"/>
        <Setter Property="MinHeight" Value="{ThemeResource ListViewItemMinHeight}"/>
        <Setter Property="AllowDrop" Value="False"/>
        <Setter Property="UseSystemFocusVisuals" 
         Value="{StaticResource UseSystemFocusVisuals}"/>
        <Setter Property="FocusVisualMargin" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListViewItem">
                    <ListViewItemPresenter x:Name="Root" 
                     CheckBrush="{ThemeResource ListViewItemCheckBrush}" 
                     ContentMargin="{TemplateBinding Padding}" 
                     CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}" 
                     ContentTransitions="{TemplateBinding ContentTransitions}" 
                     CheckMode="{ThemeResource ListViewItemCheckMode}" 
                     DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}" 
                     DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}" 
                     DragBackground="{ThemeResource ListViewItemDragBackground}" 
                     DragForeground="{ThemeResource ListViewItemDragForeground}" 
                     FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}" 
                     FocusVisualMargin="{TemplateBinding FocusVisualMargin}" 
                     FocusSecondaryBorderBrush="{ThemeResource 
                     ListViewItemFocusSecondaryBorderBrush}" 
                     HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" 
                     Control.IsTemplateFocusTarget="True" 
                     PressedBackground="{ThemeResource ListViewItemBackgroundPressed}" 
                     PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}" 
                     PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}" 
                     PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}" 
                     RevealBorderThickness="{ThemeResource 
                                             ListViewItemRevealBorderThemeThickness}"
                     ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}" 
                     RevealBorderBrush="{ThemeResource ListViewItemRevealBorderBrush}" 
                     RevealBackground="{ThemeResource ListViewItemRevealBackground}" 
                     SelectedForeground="{ThemeResource ListViewItemForegroundSelected}" 
                     SelectionCheckMarkVisualEnabled="{ThemeResource 
                     ListViewItemSelectionCheckMarkVisualEnabled}" 
                     SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}" 
                     SelectedPressedBackground="{ThemeResource 
                     ListViewItemBackgroundSelectedPressed}" 
                     SelectedPointerOverBackground="{ThemeResource 
                     ListViewItemBackgroundSelectedPointerOver}" 
                     VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="Selected"/>
                                <VisualState x:Name="PointerOver">
                                    <VisualState.Setters>
                                        <Setter Target="Root.(RevealBrush.State)" 
                                         Value="PointerOver"/>
                                        <Setter Target="Root.RevealBorderBrush" 
                                         Value="{ThemeResource 
                                         ListViewItemRevealBorderBrushPointerOver}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="PointerOverSelected">
                                    <VisualState.Setters>
                                        <Setter Target="Root.(RevealBrush.State)" 
                                         Value="PointerOver"/>
                                        <Setter Target="Root.RevealBorderBrush" 
                                         Value="{ThemeResource 
                                         ListViewItemRevealBorderBrushPointerOver}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="PointerOverPressed">
                                    <VisualState.Setters>
                                        <Setter Target="Root.(RevealBrush.State)" 
                                         Value="Pressed"/>
                                        <Setter Target="Root.RevealBorderBrush" 
                                         Value="{ThemeResource 
                                         ListViewItemRevealBorderBrushPressed}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <VisualState.Setters>
                                        <Setter Target="Root.(RevealBrush.State)" 
                                         Value="Pressed"/>
                                        <Setter Target="Root.RevealBorderBrush" 
                                         Value="{ThemeResource 
                                         ListViewItemRevealBorderBrushPressed}"/>
                                    </VisualState.Setters>
                                </VisualState>
                                <VisualState x:Name="PressedSelected">
                                    <VisualState.Setters>
                                        <Setter Target="Root.(RevealBrush.State)" 
                                         Value="Pressed"/>
                                        <Setter Target="Root.RevealBorderBrush" 
                                         Value="{ThemeResource 
                                         ListViewItemRevealBorderBrushPressed}"/>
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="DisabledStates">
                                <VisualState x:Name="Enabled"/>
                                <VisualState x:Name="Disabled">
                                    <VisualState.Setters>
                                        <Setter Target="Root.RevealBorderThickness" 
                                         Value="0"/>
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </ListViewItemPresenter>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    ...
    <ListView Style="{StaticResource CircleGlyphItem}"
        ItemContainerStyle="{StaticResource ListViewItemContainerStyle1}" >
        <x:String>&#xE962;</x:String>
        <x:String>&#xE946;</x:String>
        <x:String>&#xE909;</x:String>
        <x:String>&#xEB50;</x:String>
    </ListView>

    Holy code bloat. That's a big bunch of XAML that I have to manage now. I'm going to add a Margin setter, and that's it. All that code for one simple change. But wait, there's something we can do about that. Before modifying it,

  3. Copy the Style as is to Global.xaml. If you don't have one, see the Global.xaml section below.
  4. Back in our original XAML file in the original copy of the style, remove all the Setters in the style.
  5. Rename it to something more appropriate. It doesn't matter what as that name will be gone in the final rendition.
  6. Add a BasedOn reference:
    XAML
    <Style x:Key="ListViewItemContainerWithMargin" 
     BasedOn="{StaticResource ListViewItemContainerStyle1}" TargetType="ListViewItem">
    </Style>
    ...
  7. Add a Setter to set the margin.
    XAML
    <Style x:Key="ListViewItemContainerWithMargin" TargetType="ListViewItem" 
     BasedOn="{StaticResource ListViewItemContainerStyle1}">
        <Setter Property="Margin" Value="0 0 20 0" />
    </Style>
    ...

    UI looks just how I want it to look. All that XAML is out of site. The next developer that comes along can see exactly what is changing in the ListViewItemContainerStyle. I just wish the SDK made global resources out of the styles and templates. That way, we can reference them directly with the BasedOn attribute and not have to make our own copy. Lastly, we want to do that separation of responsibility thing like we did at the end of the previous section...

  8. Put the ItemContainerStyle inside our CircleGlyphStyle:
    XAML
    <Style x:Key="CircleGlyphItem" TargetType="ListView">
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid>
                        <Ellipse Width="80" Height="80" Fill="#16808080" />
                        <FontIcon Glyph="{Binding}" FontSize="36" 
                         VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemContainerStyle">
            <Setter.Value>
                <Style TargetType="ListViewItem" 
                 BasedOn="{StaticResource ListViewItemContainerStyle}">
                    <Setter Property="Margin" Value="0 0 20 0" />
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
    ...
    <ListView Style="{StaticResource CircleGlyphItem}">
        <x:String>&#xE962;</x:String>
        <x:String>&#xE946;</x:String>
        <x:String>&#xE909;</x:String>
        <x:String>&#xEB50;</x:String>
    </ListView>

IMHO, elegant code. Now you have a clean ListView element with its data and a clean styling of that ListView. Future developers that come across this will clearly understand that this is a ListView with an Ellipse under a Glyph that it's layed out it a WrapGrid and that it has extra margin between items. The list contains four items. Of course, the functionality of what happens when the list items are selected is a whole other topic...

Change Happens

As new WinRt versions are released, the expanded templates for a given control can change. It's a good idea to go through a process after upgrading your SDK to expand the templates again and replace the contents you put in your Global.xaml with the freshly expanded contents.

Conclusion

In this, I took you through expanding a large Style (the ListView's ListContainerStyle) which is huge and modify it a bit in an elegant and maintainable way. We also saw how to separate responsibility in your XAML separating your UI component from its styling. In other parts of your app, you can have new Styles that are BasedOn ListViewContainerStyle that modify other properties. Maybe you don't like that the items are vertically centered. Make a new Style where you change the VerticalContentAlignment to something else. You can further increase code reuse by moving the CircleGlyphItem Style definition to the Global.xaml file. It can then be reused in other parts of your app where you may have similar lists.

Global.xaml

Often, you want to style controls and then share that styling with other XAML files in your app. One way to do it is to add it to your App resources in your App.xaml file. Another, more compartmentalized way is to add it to a new file (Global.xaml) resource dictionary.

  • Add a Resource Dictionary file to your project and call it Global.xaml.
  • In your App.xaml file, add this:
    XAML
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Global.xaml" />
                <!-- You can have other resource dictionaries here for other purposes -->
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>

Note: I actually do create another resource dictionary called WinRt.xaml and put all these expanded templates in there. I use Global.xaml for Styles, converters and the like. I put a big note in the WinRt.xaml that the items in the dictionary are the expansions from a specific WinRt SDK version and that they are not to be modified.

History

  • 19th June, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)