Hi,
in these days I needed to override the default highlight color used for marking selected items in GridView of WPF Application running on Windows 7.
First of all I noticed that the well known strategy that consist in overriding the SystemColors.HighlightBrushKey didn’t work for ListView that contains GridView on Windows 7 even if the same strategy works fine in ListBox and ListView (that does not have GridView inside) on both Windows XP and 7. This strange behavior is also described in the following msdn post: http://social.msdn.microsoft.com/Forums/en/wpf/thread/1f62b66f-bdcb-4092-8568-a8fa4d39ea9b.

In order to solve that problem, my idea was to define a global style for ListViewItem, but ListViewItem Style  must be different for ListView that contains GridView and ListView without GridView.
In fact when a ListView contains a GridView, the ListViewItem Style must display items via GridViewRowPresenter.
Instead when ListView does not contains the GridView, the ListViewItem Style must display item via ContentPresenter.
This implies to implement a Trigger into ListViewItem Style in order to hide\show the GridViewRowPresenter or the ContentPresenter.

Below a screenshot of ListView and ListView + GridView using a common ListViewItem Style! The solution is downloadable here.

Highlighted item in ListView via ListViewItem Style

And here you are the final working solution for defining a common ListViewItem Style. Notice the Triggers in lines 36-38 that shows/hides the ContentPresenter.

        <Style TargetType="{x:Type ListViewItem}" x:Key="{x:Type ListViewItem}" >
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="UIElement.SnapsToDevicePixels" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListViewItem}">

                        <Border SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" 
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            BorderBrush="{TemplateBinding BorderBrush}"
                            Background="{TemplateBinding Background}">

                            <Grid>

                                <!-- This is used when GridView is put inside the ListView -->
                                <GridViewRowPresenter Content="{TemplateBinding ContentControl.Content}"
                                                  HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                                                  VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                                                  SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>

                                <!-- This is used for ListView that does not use GridView -->
                                <ContentPresenter x:Name="contentPresenter"
                                              Content="{TemplateBinding ContentControl.Content}" 
                                              Visibility="Collapsed"
                                              ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" 
                                              ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" 
                                              HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                                              VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                                              SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
                            </Grid>

                        </Border>

                        <ControlTemplate.Triggers>
                            <Trigger Property="GridView.ColumnCollection" Value="{x:Null}">
                                <Setter TargetName="contentPresenter" Property="Visibility" Value="Visible"/>
                            </Trigger>

                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Background" Value="#FFB3D0E5" />
                            </Trigger>

                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                            </Trigger>
                        </ControlTemplate.Triggers>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Stay tuned!
Marzio.

Advertisements

Hi guys!
Today I developed a nice application showing how is possibile displaying items in a radial panel.The application we are going to develop looks like the following figure and is downloadable here:

ListView Radial Items

Clicking the ‘play’ centered toggle button some numbers appear moving away the toggle button forming a radial figure as the following pictures show:

Radial Menu WPF

Radial Menu WPF

The result of clicking a second time on the toggle button is to move the numbers form their radial position towards the toggle button self. After that the numbers disappear. Morover, moving the mouse over a number the nice effect shown in this figure is applied:

Mouse Over Trigger WPF

A sample application scenario could be touch screen applications, but here it is useful to understand the following WPF concepts:  XmlDataProvider, DataTemplate, Storyboard and Triggers.

Step 1 – Binding ItemsSource to XmlDataProvider

The first step is to provide data and binding them into our ItemsSource control. For doint it in a fastest but customizable way I created an xml file (MenuItems.xml)  that looks like this:

<MenuItems>

  <MenuItem>
    <Text>1</Text>
  </MenuItem>

  <MenuItem>
    <Text>2</Text>
  </MenuItem>
...
</MenuItems>

For reading the xml file I created an XmlDataProvider into Windows.Resources tag.  The XmlDataProvider is a WPF class that allow you to read any xml data. The advantage of using XmlDataProvider is that you can bind it to any UI Source Property in order to display the data in UI controls. I created my DataDS XmlDataProvider for reading my xml file in the following way:

<XmlDataProvider x:Key="DataDS" Source="MenuItems.xml" />

I binded the DataDS’s MenuItems into my ItemsControl ItemsSource with these lines of xaml code:

<ItemsControl x:Name="itemsControl"
              Width="300"
              Height="300"
              ItemsSource="{Binding Mode=Default, Source={StaticResource DataDS},
				 XPath=/MenuItems/MenuItem}">
</ItemsControl>

Step 2 – Making a Radial Panel Template

You can set the Panel Template using the PanelTemplate Property after creating your own ItemsPanelTemplate resource. A Panel is a class for defining how objects should be displayed into a certain space. The mains Panels provided by WPF are: WrapPanel, StackPanel, Grid, DockPanel and Canvas. However a radial Panel is not provided, so inspired by the article i read here , I decided to write my own RadialPanel class that basically consinst in the following lines of code:

public class RadialPanel : Panel
    {

	...

        // Measure each children and give as much room as they want
        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement elem in Children)
            {
                //Give Infinite size as the avaiable size for all the children
                elem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }

	    ...

            return base.MeasureOverride(availableSize);
        }

        //Arrange all children based on the geometric equations for the circle.
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Children.Count == 0)
                return finalSize;

            double _angle = 0;

            //Degrees converted to Radian by multiplying with PI/180
            double _incrementalAngularSpace = (360.0 / Children.Count) * (Math.PI / 180);

            //An approximate radii based on the avialable size , obviusly a better approach is needed here.
            double radiusX = finalSize.Width / 2.4;
            double radiusY = finalSize.Height / 2.4;

            foreach (UIElement elem in Children)
            {

                //Calculate the point on the circle for the element
                Point childPoint = new Point(Math.Cos(_angle) * radiusX, - Math.Sin(_angle) * radiusY);

                // Center Element
                Point centerPoint = this.TranslatePoint(new Point(finalSize.Width / 2, finalSize.Height / 2), this);
                elem.Arrange(new Rect(centerPoint.X - elem.DesiredSize.Width / 2,
                                      centerPoint.Y - elem.DesiredSize.Height / 2,
                                      elem.DesiredSize.Width,
                                      elem.DesiredSize.Height));

                //Calculate the new _angle for the next element
                _angle += _incrementalAngularSpace;

                ...
            }

            return finalSize;
        }
    }

In order to display the elements in a radial way I override the two Panel method calculating for every elements the size and the position:

  • MeasureOverride: calculates the size in layout required for child elements and determines a size for the FrameworkElement derived class

  • ArrangeOverride: calculate the child elements positions and determines a size for a FrameworkElement derived class

Step 4 – Creating ItemTemplate

The way in wich numbers are displayed is described by the following DataTemplate that consists into basics two parts: a Border describing the circle around the number and a TextBox showing the number. Here you are the Xaml code and the relevant graphic result:

       <DataTemplate x:Key="itemTemplate">
            <Button Tag="{Binding}">
                <Button.Template>
                        <ControlTemplate>

			   <Grid Width="70" Height="70" x:Name="grid" RenderTransformOrigin="0.5,0.5">

                                <Border BorderThickness="8,8,8,8" CornerRadius="50,50,50,50" x:Name="border">

				    <Border.Background>
                                        <RadialGradientBrush>
                                            <GradientStop Color="#FFFFFFFF" Offset="0"/>
                                            <GradientStop Color="#FFFFFFFF" Offset="1"/>
                                        </RadialGradientBrush>
                                    </Border.Background>

				    <Border.BorderBrush>
                                        <RadialGradientBrush>
                                            <GradientStop Color="#FF0835B3" Offset="0.737"/>
                                            <GradientStop Color="#FF8DACFF" Offset="0.888"/>
                                            <GradientStop Color="#FF0835B3" Offset="1"/>
                                        </RadialGradientBrush>
                                    </Border.BorderBrush>

                                    <TextBlock Text="{Binding XPath=Text}"
                                       TextWrapping="Wrap"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center"
                                       FontSize="48"
                                       FontFamily="Rosewood Std"
                                       FontWeight="Normal"/>

                                </Border>
                            </Grid>
                </Button.Template>
            </Button>
        </DataTemplate>

Datatemplate radial list

Step 3 – Expaning\Collapsing Items with Animation

These kind of animations are built and enterly managed into the Radial Panel object.
Each displayed item is associated to two storyboards:

  • a storyboard describing the expansion of the item, fom the the button to the it’s final position,
  • a storyboard describing the collapse of the item from the external to the button

These animations are built and attached to the item by the Radial Panel when the item is added. This is done into ArrangeOverride method and you can easily see the relevant region code downloading the source code.
The first animation starts when the Radial Panel became visible, the second one starts when the panel became hidden. For doing it I handled the Radial Panel IsVisibleChanged event in the following way:

        private void RadialPanel_IsVisibleChanged(object sender,
                                        DependencyPropertyChangedEventArgs e)
        {
            if (Boolean.Parse(e.NewValue.ToString()) == true)
            {
                this.StartAnimation();
            }
            else
            {
                this.PlayBackAnimation();
            }
        }

        private void StartAnimation()
        {
            foreach (Storyboard s in _expandStoryBoard)
                s.Begin();
        }

        private void PlayBackAnimation()
        {
            _parentItemsControl.IsVisibleChanged -= RadialPanel_IsVisibleChanged;

            _parentItemsControl.Visibility = Visibility.Visible;

            foreach (Storyboard s in _collapseStoryBoard)
            {
                s.Completed += new EventHandler(s_Completed);
                s.Begin();
            }
        }

        void s_Completed(object sender, EventArgs e)
        {
            if (_parentItemsControl.Visibility == Visibility.Hidden)
                return;

            _parentItemsControl.Visibility = Visibility.Hidden;
            _parentItemsControl.IsVisibleChanged += new DependencyPropertyChangedEventHandler(RadialPanel_IsVisibleChanged);

        }

As you can see there is a little tricky for displaying the collapsing animation. During the execution of this storyboard the the Radial Panel Visibility is hidden, so before starting it I unhandled the IsVisibleChanged event, then I set the visibility to Visible and when the storyboard is ended I finally hide the Radial Panel and then I handled the IsVisibleChanged event again.

Step 4 – Applying OnMouseOver Trigger

When the mouse is over an item, the number and the circle became a little bit bigger and the background became blue in a slowly manner. For doing these effects I build the following storyboard using Microsoft Expression Blend:

  <ControlTemplate.Resources>
      <Storyboard x:Key="OnMouseEnter1">
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.1"/>
          </DoubleAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.1"/>
          </DoubleAnimationUsingKeyFrames>
          <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
              <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
              <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FF5F95E4"/>
          </ColorAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Offset)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
          </DoubleAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
          </DoubleAnimationUsingKeyFrames>
          <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
              <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
              <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FFCBE2F9"/>
          </ColorAnimationUsingKeyFrames>
      </Storyboard>
  </ControlTemplate.Resources>

Basically the first two DoubleAnimationUsingKeyFrames applying a rendertransform to the Grid containing the entire item. This allow the item to increase its size and off course you have to add the Grid.Transform to the Grid itself as you can notice downloading the sample.
The rests of lines allow the background to became blue in a slow way. I don’t know the precise meaning of these lines, but Expression Blend build them for me
Finally here you are the Triggers that start and end the storyboard when the mouse is over or leave the item:

<ControlTemplate.Triggers>
   <EventTrigger RoutedEvent="Mouse.MouseLeave">
     <RemoveStoryboard BeginStoryboardName="OnMouseEnter1_BeginStoryboard"/>
   </EventTrigger>

   <EventTrigger RoutedEvent="Mouse.MouseEnter">
      <BeginStoryboard Storyboard="{StaticResource OnMouseEnter1}" x:Name="OnMouseEnter1_BeginStoryboard"/>
   </EventTrigger>
</ControlTemplate.Triggers>