Archive for the 'XAML' Category



Alternating row background color in WPF ListView

In yesterday’s post you’ll notice that the ListView control displays using different colors for alternating rows. I implemented this effect by using a style selector. A style selector is basically a class that inherits from StyleSelector and overrides the SelectStyle method. Here’s my implementation.

public class ListViewItemStyleSelector : StyleSelector
{
    public override Style SelectStyle(object item, DependencyObject container)
    {
        ListView listView = ItemsControl.ItemsControlFromItemContainer(container) as ListView;

        Style st;
        int index = listView.ItemContainerGenerator.IndexFromContainer(container);
        if (index % 2 == 0)
        {
            st = (Style)listView.FindResource("ListViewItemRow2");
        }
        else
        {
            st = (Style)listView.FindResource("ListViewItemRow1");
        }
        return st;
    }
}

Notice that the SelectStyle method first determines the index of the item being rendered. It uses this index to select a Style instance from the application’s static resources. I’ll get to these resources shortly.

Next I create an instance of this class in the application resource dictionary.

<controls:ListViewItemStyleSelector x:Key="ListViewItemStyleSelector"/>

The ListView property ItemContainerStyleSelector must be set to this or any other instance of my ListViewItemStyleSelector class. Actually ItemContainerStyleSelector is a property of ItemsControl, so this technique can be used on any ItemsControl, such as ListBox, not just the ListView used here.

<Style x:Key="{x:Type ListView}" TargetType="{x:Type ListView}">
  <Setter Property="ItemContainerStyleSelector" Value="{StaticResource ListViewItemStyleSelector}"/>
…
</Style>

Finally the two styles referenced by SelectStyle must be inserted into the application resource dictionary.

<Style x:Key="ListViewItemBase" TargetType="{x:Type ListViewItem}">
  <Setter Property="SnapsToDevicePixels" Value="true"/>
  <Setter Property="OverridesDefaultStyle" Value="true"/>
  <Setter Property="Foreground" Value="{StaticResource DarkForegroundBrush}"/>
  <Setter Property="Template" Value="{StaticResource ListViewItemTemplate}"/>
  <Setter Property="Margin" Value="2,0,2,1"/>
</Style>
  
<Style x:Key="ListViewItemRow1" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemBase}">
    <Setter Property="Background" Value="{StaticResource ListViewLine1Brush}"/>
</Style>

<Style x:Key="ListViewItemRow2" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource ListViewItemBase}">
  <Setter Property="Background" Value="{StaticResource ListViewLine2Brush}"/>
</Style>

Here I factored all of the common property settings into a base instance of Style named ListViewItemBase. The two Style instances used to render the ListView rows, ListViewItemRow1 and ListViewItemRow2, inherit these base settings using the BasedOn property: BasedOn=”{StaticResource ListViewItemBase}”.

This approach works great when the collection bound to ListView.ItemsSource is static, as it is in my test window. Everything breaks down, however, when items are added or deleted to the underlying collection.

I created a new Visual Studio project to isolate the ListView. In this project I bind ListView.ItemsSource to an ObservableCollection instead of specifying a collection of ListViewItems in my main window XAML as I did in the last example. Here’s the ListView in the new test window.

<ListView x:Name="_list" Margin="8" Height="120" Width="240" ItemsSource="{Binding}">
    <ListView.View>
        <GridView AllowsColumnReorder="true">
            <GridViewColumn DisplayMemberBinding="{Binding}" Header="Content" Width="100"/>
            <GridViewColumn DisplayMemberBinding="{Binding Path=Length}" Header="Length" Width="100"/>
        </GridView>
    </ListView.View>
</ListView>

And the relevant C# code in the code behind file.

private ObservableCollection<string> _items = new ObservableCollection<string>();
public Window1()
{
    _items.Add("First Item");
    _items.Add("Second Item");
    _items.Add("Third Item");
    _items.Add("Fourth Item");
    _items.Add("Fifth Item");
    _items.Add("Sixth Item");
    _items.Add("Seventh Item");
    _items.Add("Eighth Item");
    InitializeComponent();
    DataContext = _items;
}

I also added some buttons and text boxes to the test window to facilitate adding and deleting of collection items. Here are the relevant Button.Click event handlers.

private void DelButton_Click(object sender, RoutedEventArgs e)
{
    int n = 0;
    if (Int32.TryParse(_delItemNum.Text, out n))
    {
        if (n >= 1 && n <= _items.Count)
        {
            _items.RemoveAt(n-1);
        }
    }
}

private void AddButton_Click(object sender, RoutedEventArgs e)
{
    int n = 0;
    if (Int32.TryParse(_addItemNum.Text, out n) && String.IsNullOrEmpty(_addItemText.Text))
    {
        if (n >= 1 && n <= _items.Count)
        {
            _items.Insert(n - 1, _addItemText.Text);
        }
        else
        {
            _items.Add(_addItemText.Text);
        }
    }
}&#91;/sourcecode&#93;

The screenshot below shows what happens after deleting the third collection item.  Notice that the individual row background colors were not reset after deleting the item.  “Second Item” and “Fourth Item” are both the darker shade of gray.

<img src="https://patconroy.files.wordpress.com/2009/01/badlistview.jpg" alt="badlistview" title="badlistview" width="375" height="375" class="alignnone size-full wp-image-51" />

It turns out that once the item style is selected it will never be modified regardless of how the underlying data collection changes.  A workaround for this is to refresh the ListView’s collection view.  This can be done just after adding or deleting an item.

_items.RemoveAt(n-1);
System.ComponentModel.ICollectionView dataView =
    CollectionViewSource.GetDefaultView(_list.ItemsSource);
dataView.Refresh();

A better place to put this is in an event handler for ObservableCollection.CollectionChanged.

private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    System.ComponentModel.ICollectionView dataView =
        CollectionViewSource.GetDefaultView(_list.ItemsSource);
    dataView.Refresh();
}

Now the individual row backgrounds get reset correctly after items are added or removed from the data collection, and the alternating background color pattern is maintained.

The technique described here works for all ItemsControls in all versions of .NET that include WPF, 3.0, 3.1 and 3.1 SP1. A new feature was introduced in 3.5 SP1 that simplifies this a great deal. I’ll describe that in my next post. Stay tuned.

Restyling all WPF Controls

In my last post I detailed how I restyled a WPF expander control. I started with the restyled expander control from the Simple Styles SDK sample and altered it to display using the Vista black or deep gray color scheme. Well I wasn’t satidfied having just an expander in this color scheme, so I went ahead and restyled all of the controls in the Simple Styles sample. I added IsMouseOver property triggers to a number of the visual elements to give the user more feedback when something is clickable. Now I have a nice toolbox full of controls in this color scheme that I can use to build applications. Here are screen shots of my results. Click on an image to see it in full size.

mystyles1
mystyles2

All source code for this including the Visual Studio 2008 project is located Here. You may use any of this code in your own applications as you wish, as long as you don’t violate whatever code use argrements accompany the Microsoft SDK. This code did after all originate in the SDK samples.

Restyling the WPF Expander control

With minimal effort any of the supplied Windows Presentation Foundation (WPF) controls can be visually restyled to suit an application’s needs.  With only a little more effort the control’s entire visual tree can be replaced using templates without affecting the underlying behavior of the control itself.

I recently found myself wishing the the toggle button on the Expander control was on the right hand side rather than the left.  The following XAML can be pasted into XAMLPad to show the default look of the Expander control.  I personally use Kaxaml for such purposes.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>
    <Expander Width="200" Header="This is the Header">
      <Border Height="100">
        <StackPanel>
          <TextBlock>TextBlock text</TextBlock>
          <Label>Label text</Label>
          <Label IsEnabled="False">Disabled label</Label>
        </StackPanel>
      </Border>
    </Expander>
  </StackPanel>
</Page>

WPF applies the default control template for the current theme. On Windows Vista the above XAML will produce this.

Unstyled Expander

A Style can set any of the public properties of a control. Of course the same public properties can be set in the control declaration itself, but the use of styles facilitates reuse and helps achieve a common look and feel across the application. Styles are typically declared as static resources in a window or application resource dictionary. Styles may be applied to a control either explicitly or implicitly. Each style declaration must specify to which control type it applies, and it may only apply to one type.

  • A style must be explicitly applied to a control if the style if given a key in the resource dictionary. A style declared thusly, <Style k:Key="ExpanderStyle" TargetType="{x:Type Expander}">...</Style>, must be referenced in the control declaration like this: <Expander Style="{StaticResource ExpanderStyle}">...</Expander>.
  • By omitting the Key from the Style declaration the style is implicitly applied to all controls of the appropriate type under the scope of the dictionary. For example if this is placed in the application resource dictionary, <Style TargetType="{x:Type Expander}">...</Style>, then this style will apply to all instances of Expander in the application.

The following XAML shows how to implicitly style the Expander.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>
    <Style TargetType="{x:Type Expander}">
      <Setter Property="BorderThickness" Value="1"/>
      <Setter Property="BorderBrush" Value="DarkGray"/>
      <Setter Property="Foreground" Value="#202020"/>
      <Setter Property="Background" Value="#D0D0D0"/>
    </Style>
  </Page.Resources>
  <StackPanel>  
    <Expander Width="200"
              Margin="0,8,0,0"
              Header="This is the Header">
      <Border Height="100">
        <StackPanel>
          <TextBlock>TextBlock text</TextBlock>
          <Label>Label text</Label>
          <Label IsEnabled="False">Disabled label</Label>
        </StackPanel>
      </Border>
    </Expander>
  </StackPanel>
</Page>

Styled Expander

This Style changes the Expander to be visually more to my liking, however the toggle button is still on the left hand side. I want an Expander with the button on the right. There is no Expander public property to move the toggle button. Luckily the SimpleStyles sample in the Windows SDK does have such an Expander. Examination of the SDK sample shows that a control template must be used. I modified the SDK sample to my own liking. In addition to moving the button I wanted black/dark gray color scheme that is so trendy these days since the launch of Vista. The XAML below shows what I have done so far.

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Background>
    <!-- Window background brush removed for brevity-->
  </Page.Background>
  <Page.Resources>
    <!-- Color brushes removed for brevity.  Download code to see. -->

    <!-- Expander toogle button template.
           Removed for brevity.  Please download code to see. -->

    <!-- Expander style -->  
    <Style TargetType="Expander">
      <Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
      <Setter Property="Template">
        <Setter.Value>
          <!-- Control template for expander -->
          <ControlTemplate TargetType="Expander">
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Name="ContentRow" Height="0"/>
              </Grid.RowDefinitions>
              <Border 
                Name="Border" 
                Grid.Row="0" 
                Background="{StaticResource HeaderBrush}"
                BorderBrush="{StaticResource NormalBorderBrush}"
                BorderThickness="1" 
                CornerRadius="4,4,0,0" >
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="20" />
                  </Grid.ColumnDefinitions>
                  <!-- The following puts the toggle button in the right hand column, just like I want! -->
                  <ToggleButton
                    Grid.Column="1"
                    IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
                                        RelativeSource={RelativeSource TemplatedParent}}"
                    OverridesDefaultStyle="True" 
                    Template="{StaticResource ExpanderToggleButton}" 
                    Background="{StaticResource NormalBrush}" />
                  <ContentPresenter 
                    Grid.Column="0"
                    Margin="4" 
                    ContentSource="Header" 
                    RecognizesAccessKey="True" />
                </Grid>
              </Border>
              <Border 
                Name="Content" 
                Grid.Row="1" 
                Background="{StaticResource GroupBackgroundBrush}"
                BorderBrush="{StaticResource OpenGroupBorderBrush}" 
                BorderThickness="1,0,1,1" 
                CornerRadius="0,0,4,4" >
                <ContentPresenter Margin="4" />
              </Border>
            </Grid>
            <ControlTemplate.Triggers>
              <Trigger Property="IsExpanded" Value="True">
                <Setter TargetName="ContentRow" Property="Height"
                        Value="{Binding ElementName=Content,Path=DesiredHeight}" />
                <Setter TargetName="Border" Property="BorderBrush"
                        Value="{StaticResource OpenHeaderBorderBrush}"/>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Page.Resources>
  
  <!-- Test : Removed for brevity-->
</Page>

Templated Expander

I had to cut much of the code out because it is too much to list here. Download this XAML code here to see it all. Load it into XAMLPad to see how it works.

Notice how the expander control template has an outer grid and an inner grid. The outer grid has two rows, one for the header and one for the content. The content row has a height of zero and is thus not visible by default. A property trigger <Trigger Property="IsExpanded" Value="True"> sets this row’s height when the control is expanded. The header row contains another grid with two columns. The header content is placed into the left-hand column and the toggle button into the right. The button is now where I desire it to be.


August 2017
M T W T F S S
« Apr    
 123456
78910111213
14151617181920
21222324252627
28293031  
I am a part of all that I have met;
Yet all exprience is an arch whitherthro'
Gleams that untravell'd world, whose margin fades
For ever and for ever when I move.
How dull it is to pause, to make an end,
To rust unburnish'd, not to shine in use!
Alfred, Lord Tennyson