Title: Make video control buttons in WPF and C#
I recently wanted some images for video control buttons: play, pause, stop, fast forward, and so forth. I googled around and found lots of nice buttons, but all of the sets I found were missing one or more of the buttons I wanted so I decided to make my own.
One of the great strengths (and weaknesses) of WPF is that it is very flexible so you can spend hours fiddling with it to produce exactly the look you want. The interactive designer also isn't great at this sort of thing so it can mean an awful lot of tweaking XAML code until you get just the right look. After a huge amount of messing about, I came up with the basic button shown on the right.
The following XAML produces this basic button.
<Viewbox Stretch="Uniform"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid Width="30" Height="30">
<!-- Outline -->
<Ellipse Height="30" Width="30"
Stroke="DarkBlue" StrokeThickness="1"
Fill="Blue"/>
<!-- Content -->
<!-- ... Insert the button's content here ... -->
<!-- Top highlight -->
<Ellipse Margin="5,2" Height="13" Width="20"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="#FFFFFFFF"/>
<GradientStop Offset="1" Color="#20FFFFFF"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<!-- Bottom highlight -->
<Ellipse Width="20" Height="10"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Ellipse.RenderTransform>
<TransformGroup>
<RotateTransform Angle="-45"/>
<TranslateTransform X="10" Y="24"/>
</TransformGroup>
</Ellipse.RenderTransform>
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="#00FFFFFF"/>
<GradientStop Offset="1" Color="#A0FFFFFF"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</Viewbox>
The button has four main parts:
- A blue background ellipse
- The button's content (an arrow, rectangle, text, or whatever)
- A bright highlight in the top center
- A smaller bright highlight in the bottom right
Those controls are all placed inside a Grid so they are easy to center, and the whole thing is wrapped in a Viewbox so they will all scale depending on how large the button is.
Now that I had a button background, I needed to use it to make a bunch of buttons with different symbols on them. You could copy and paste the basic button code and add the symbols to each copy, but that would mean a lot of duplicated code.
I wouldn't say it's exactly easy, but WPF does allow you to make controls share XAML code to they have a similar appearance. To do that you create a template to determine the control's appearance and behavior.
The first step is getting a default template for the control you want to modify, in this case Button. Microsoft has not been consistent at posting their default templates so it can take a bit of digging to find them. You can find a list of control templates at ControlTemplate Examples.
Click the Button ControlTemplate Example link to see the Button control's template.
For this example my goal was to save the button images so I didn't need the buttons to do anything. To keep the template simple, I removed all of the triggers that define the Button behavior.
The following code shows the template this example uses. I removed the code shown previously that defines the background and highlights.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate TargetType="Button" x:Key="BubbleButtonTemplate">
<Viewbox Stretch="Uniform"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid Width="30" Height="30">
<!-- Outline -->
...
<!-- Content -->
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center"
RecognizesAccessKey="True"/>
<!-- Top highlight -->
...
<!-- Bottom highlight -->
...
</Grid>
</Viewbox>
</ControlTemplate>
</ResourceDictionary>
The most interesting part in this new template is the ContentPresenter. This tells the template where to place whatever content you put inside the button. For example, the following code shows how the main program defines the Play button on the far left. The code that makes the button use the template is highlighted in blue.
<Button Template="{StaticResource BubbleButtonTemplate}"
Name="btnPlay" Click="btn_Click">
<Path Stroke="DarkBlue" StrokeLineJoin="Round"
StrokeThickness="2" Fill="White"
Data="M 0,0 L 12,7 0,14 Z"/>
</Button>
Inside the Button element this code contains a Path object that defines the Play button's arrow. The template inserts that object at the position where the ContentPresenter appears in the template. In this example that means it comes after the background ellipse and before the two highlights. If you look closely at the picture on the right you can see that the upper highlight sits on top of the arrow.
The button template is stored in a separate resource dictionary file named ButtonTemplate.xaml. The main Window1.xaml file uses the following code to include it so its code can use the template.
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ButtonTemplate.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
The last parts to this example are relatively straightforward. If you click any of the buttons, the following code displays the button's name.
private void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
MessageBox.Show(btn.Name);
}
Finally, when the program starts, it uses the following code to save images of the buttons in PNG files.
// Save images of the buttons.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Button[] buttons = new Button[]
{
btnPlay, btnFaster, btnNext, btnStop, btnPause,
btnRestart, btnBack, btnSlower, btnPrevious, btnTest
};
foreach (Button btn in buttons)
{
string filename =
btn.Name.Replace("btn", "").ToLower() + ".png";
SaveControlImage(btn, filename);
}
}
This code creates an array holding all of the buttons. It then loops through the buttons and calls the SaveControlImage method to save their images in PNG files. For information about the SaveControlImage method, see the post Save WPF control images in C#.
My next post will show how you can use these images on buttons that control video playback.
Download the example to experiment with it and to see additional details.
|