Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

2008-05-08

Using Vista Aero theme in XP WPF apps

I found this article on the web recently which shows how to use the Vista Aero theme on XP in your WPF apps.

I found two things:
01: It is less complicated that the article states.
02: It is a bit different if you already have stuff in your app resources, such as styles or control templates etc.

So here are my steps
01: Add PresentationFramework.Aero to your applications References list. It is listed in the [.NET] tab.
02: Edit your App.xaml and change it from this

<Application.Resources>
  <!-- Your stuff here -->
</Application.Resources>

to this

<Application.Resources>
  <ResourceDictionary>
    <!-- Put your stuff here instead -->

    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="/PresentationFramework.Aero;component/themes/aero.normalcolor.xaml"/>
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

2008-05-04

Selecting WPF ListView row in code

An app I am considering migrating to WPF uses a DevExpress grid to show a list of events, as I animate my composition I highlight the current event in the grid. Things I have learned about WPF:

01: You can create a data grid type view like so:
<ListView Name="ListView">
  <ListView.View>
    <GridView AllowsColumnReorder="False">
      <GridViewColumn DisplayMemberBinding="{Binding Path=Title}" Header="Title"/>
      <GridViewColumn DisplayMemberBinding="{Binding Path=FirstName}" Header="FirstName"/>
      <GridViewColumn DisplayMemberBinding="{Binding Path=LastName}" Header="LastName"/>
    </GridView>
  </ListView.View>
</ListView>


02: Don’t put a ListView in a <StackPanel>! It renders all of the data at once. When you have 6K rows of data for example it takes about 1 second to select the next row. Instead you should put it in a grid

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="50"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>

  <Button Grid.Row="0"...../>
  <ListView Grid.Row="1"...../>
</Grid>

03: When selecting a row in code the row does not become visible. To automatically make the row visible you need to use ScrollIntoView()

  ListView.SelectedIndex++;
  ListView.ScrollIntoView(ListView.SelectedItem);

2008-04-25

WPF custom button templates


WPF Button

I’ve had a bit of a play with WPF for the first time today. I decided to create my own button template. The idea of a template is that you can redefine the visual elements that make up the control.

Create a new WPF application

Now add a button within the grid like so

 <Grid>
  <Button Content="Click me" Width="150" Height="50"/>
 </Grid>

So that we can see what we are designing add a gradient background to the window. Within the <Window> node add

 <Window.Background>
  <LinearGradientBrush>
   <GradientStop Color="Black" Offset="0"/>
   <GradientStop Color="White" Offset="1"/>
  </LinearGradientBrush>
 </Window.Background>


Now to start designing the button template. Within the <Window> node add
 <Window.Resources>
 </Window.Resources>

this is where we will add the template, within that new node add the following

 <ControlTemplate x:Key="PetesButton" TargetType="{x:Type Button}">
 </ControlTemplate>

and then change the button to use this template

 <Button Content="Click me" Width="150" Height="50" Template="{DynamicResource PetesButton}"/>


This creates a control template that targets the "Button" control. As a result the button will disappear, this is because our button template is currently empty. To add a rounded rectangle as the outline for the button by adding a <Border> element.

 <Border BorderThickness="1,1,1,1" CornerRadius="4,4,4,4" BorderBrush="Black" Background="Black">
 </Border>

Next a <Grid> will be added within that new <Border> element, this is because by default a grid will stack controls on top of each other in a Z order rather than laying them out vertically or horizontally. Within the <Grid> add another border which will act as the client area of the control

 <Grid>
  <Border x:Name="BorderUp" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4" Background="White">
  </Border>
 </Grid>

Next add a nice gradient to this border like so
 
 <Border.BorderBrush>
  <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" >
   <GradientStop Color="White" Offset="0"/>
   <GradientStop Color="#222222" Offset="1"/>
  </LinearGradientBrush>
 </Border.BorderBrush>

This sets the BorderBrush property for the Border to a LinearGradient which starts at 0.5,0 (X=50%, Y=0%) and ends at 0.5,1 (X=50%, Y=100%). The gradient is made up of two parts, White at offset 0 (the very beginning of the gradient) and #222222 at offset 1 (the very end of the gradient).

As this gradient looks so nice lets remove the Background="White" from the <Border> and inside it add a LinearGradient to the background

 <Grid>
  <Border x:Name="BorderUp" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4">
   <Border.Background>
    <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
     <GradientStop Color="#aaaaff" Offset="0"/>
     <GradientStop Color="#444466" Offset="0.6"/>
     <GradientStop Color="#444444" Offset="1"/>
    </LinearGradientBrush>
   </Border.Background>
  </Border>
 </Grid>


This does exactly the same as the BorderBrush, except there is an addition colour at offset 0.6 (60% of the way along the gradient).

Now we have a pretty looking button, but where is the text? To show the text we need a <ContentPresenter>. Beneath the "BorderUp" element (and within <Grid>) add the following

 <ContentPresenter x:Name="Contents" HorizontalAlignment="Center" VerticalAlignment="Center" Width="Auto" Margin="3,3,3,3"/>

Now you will see the text appear! Run the app, the button looks nice! Click the button, now it looks rubbish! :-)

Below the "BorderUp" element and above the "Contents" element add another border that is the reverse of BorderUp, by this I mean that the colours are switched in the gradient, name it "BorderDown"

 <Border x:Name="BorderDown" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4">
  <Border.BorderBrush>
   <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" >
    <GradientStop Color="White" Offset="1"/>
    <GradientStop Color="#222222" Offset="0"/>
   </LinearGradientBrush>
  </Border.BorderBrush>
  <Border.Background>
   <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
    <GradientStop Color="#aaaaff" Offset="0"/>
    <GradientStop Color="#444466" Offset="0.6"/>
    <GradientStop Color="#444444" Offset="1"/>
   </LinearGradientBrush>
  </Border.Background>
 </Border>

The button now looks like it is sunk into the form. Change the BorderDown element so that it has no opacity

 <Border x:Name="BorderDown" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4" Opacity="0">

Now to make this sunken border visible when the mouse is pressed down. Just before the closing </ControlTemplate> node add


Within that node add a trigger that reacts to the IsPressed property changing

 <ControlTemplate.Triggers>
  <Trigger Property="IsPressed" Value="True">
   <Setter TargetName="BorderDown" Property="Opacity" Value="1"/>
   <Setter TargetName="Contents" Property="Margin" Value="4,4,2,2"/>
  </Trigger>
 </ControlTemplate.Triggers>


Now try running the app and clicking the button. Much nicer, but I think we can improve on it some more!


Above the </ControlTemplate.Triggers> closing node add

 <ControlTemplate.Resources>
 </ControlTemplate.Resources>

Within this section add a <StoryBoard> node with the name "MouseDownTimeLine"

 <Storyboard x:Name="ButtonDownTimeLine">
 </StoryBoard>

Now we are going to animate a property of type Double, to do this we need a DoubleAnimationUsingKeyFrames node

 <Storyboard x:Key="ButtonDownTimeLine">
  <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
   <SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1"/>
  </DoubleAnimationUsingKeyFrames>
 </Storyboard>

and also the opposite, an animation setting the opacity of BorderDown back to zero

 <Storyboard x:Key="ButtonUpTimeLine">
  <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
   <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0"/>
  </DoubleAnimationUsingKeyFrames>
 </Storyboard>

Run the app and click the button. Notice how the same old behaviour is there, this is because we haven’t hooked up the StoryBoard animations to anything yet! Change the <ControlTemplate.Triggers> so that it reads as follows

 <ControlTemplate.Triggers>
  <Trigger Property="IsPressed" Value="True">
   <Trigger.EnterActions>
    <BeginStoryboard Storyboard="{StaticResource ButtonDownTimeLine}"/>
   </Trigger.EnterActions>
   <Trigger.ExitActions>
    <BeginStoryboard Storyboard="{StaticResource ButtonUpTimeLine}"/>
   </Trigger.ExitActions>
  </Trigger>
 </ControlTemplate.Triggers>

This will now start the relevant animation whenever the trigger occurs. However, we have lost some behaviour. Previously the contents would offset when the button was down. This was achieved by changing the margin of the "Contents" from 3,3,3,3 to 4,4,2,2 - to reintroduce this behaviour we need to add another animation within the two StoryBoards. As this property is of type Thickness we will need a <ThicknessAnimationUsingKeyFrames> node.

 <ControlTemplate.Resources>
  <Storyboard x:Key="ButtonDownTimeLine">
   <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
    <SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1"/>
   </DoubleAnimationUsingKeyFrames>
   <ThicknessAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Contents" Storyboard.TargetProperty="Margin">
    <SplineThicknessKeyFrame KeyTime="00:00:00.025" Value="4,4,2,2"/>
   </ThicknessAnimationUsingKeyFrames>
  </Storyboard>
  <Storyboard x:Key="ButtonUpTimeLine">
   <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
    <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0"/>
   </DoubleAnimationUsingKeyFrames>
   <ThicknessAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Contents" Storyboard.TargetProperty="Margin">
    <SplineThicknessKeyFrame KeyTime="00:00:00.25" Value="3,3,3,3"/>
   </ThicknessAnimationUsingKeyFrames>
  </Storyboard>
 </ControlTemplate.Resources>

Now the margin will also animate.


Here is the XAML in its entirety, just so I can copy/paste it myself at a later date :-)

    <Style TargetType="{x:Type Button}">
      <Setter Property="Foreground" Value="White"/>
    </Style>
    <ControlTemplate x:Key="GlassButton" TargetType="{x:Type Button}">
      <Border BorderThickness="1,1,1,1" CornerRadius="4,4,4,4" BorderBrush="Black" Background="Black">
        <Grid>
          <Border x:Name="BorderUp" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4">
            <Border.BorderBrush>
              <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" >
                <GradientStop Color="White" Offset="0"/>
                <GradientStop Color="#222222" Offset="1"/>
              </LinearGradientBrush>
            </Border.BorderBrush>
            <Border.Background>
              <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
                <GradientStop Color="#aaaaff" Offset="0"/>
                <GradientStop Color="#444466" Offset="0.6"/>
                <GradientStop Color="#444444" Offset="1"/>
              </LinearGradientBrush>
            </Border.Background>
          </Border>
          <Border x:Name="BorderDown" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4" Opacity="0">
            <Border.BorderBrush>
              <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" >
                <GradientStop Color="White" Offset="1"/>
                <GradientStop Color="#222222" Offset="0"/>
              </LinearGradientBrush>
            </Border.BorderBrush>
            <Border.Background>
              <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1">
                <GradientStop Color="#aaaaff" Offset="0"/>
                <GradientStop Color="#444466" Offset="0.6"/>
                <GradientStop Color="#444444" Offset="1"/>
              </LinearGradientBrush>
            </Border.Background>
          </Border>
          <ContentPresenter x:Name="Contents" HorizontalAlignment="Center" VerticalAlignment="Center" Width="Auto" Margin="3,3,3,3"/>
        </Grid>
      </Border>
      <ControlTemplate.Resources>
        <Storyboard x:Key="MouseDownTimeLine">
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
            <SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1"/>
          </DoubleAnimationUsingKeyFrames>
          <ThicknessAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Contents" Storyboard.TargetProperty="Margin">
            <SplineThicknessKeyFrame KeyTime="00:00:00.025" Value="4,4,2,2"/>
          </ThicknessAnimationUsingKeyFrames>
        </Storyboard>
        <Storyboard x:Key="MouseUpTimeLine">
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="BorderDown" Storyboard.TargetProperty="Opacity">
            <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0"/>
          </DoubleAnimationUsingKeyFrames>
          <ThicknessAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Contents" Storyboard.TargetProperty="Margin">
            <SplineThicknessKeyFrame KeyTime="00:00:00.25" Value="3,3,3,3"/>
          </ThicknessAnimationUsingKeyFrames>
        </Storyboard>
      </ControlTemplate.Resources>
      <ControlTemplate.Triggers>
        <Trigger Property="IsPressed" Value="True">
          <Trigger.EnterActions>
            <BeginStoryboard Storyboard="{StaticResource MouseDownTimeLine}"/>
          </Trigger.EnterActions>
          <Trigger.ExitActions>
            <BeginStoryboard Storyboard="{StaticResource MouseUpTimeLine}"/>
          </Trigger.ExitActions>
        </Trigger>
      </ControlTemplate.Triggers>
    </ControlTemplate>