Creating an image+text button with a control template? - wpf

I am tired of creating the same image+text button over and over again, and I would like to move the markup to a control template. Here is my problem: I need to provide template bindings to add the image and text to the templated button, and the Button control doesn't seem to have properties that I can bind to.
My template looks like this so far (with '???' for the unknown template bindings):
<ControlTemplate x:Key="ImageButtonTemplate" TargetType="{x:Type Button}">
<StackPanel Height="Auto" Orientation="Horizontal">
<Image Source="{TemplateBinding ???}" Width="24" Height="24" Stretch="Fill"/>
<TextBlock Text="{TemplateBinding ???}" HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" />
</StackPanel>
</ControlTemplate>
Is it possible to create this image+text button using a control template, or do I have to go to a user control to do it? If it can be done with a control template, how do I set up the template bindings?

Define a CustomControl like this
in .cs
public class MyButton : Button
{
static MyButton()
{
//set DefaultStyleKeyProperty
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ImageSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(MyButton), new UIPropertyMetadata(null));
}
in generic.xaml of themes folder
<Style TargetType="{x:Type MyButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyButton}">
<StackPanel Height="Auto" Orientation="Horizontal">
<Image Source="{TemplateBinding ImageSource}" Width="24" Height="24" Stretch="Fill"/>
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Left" Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold" Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

ContextMenu - Strange behavior when using ItemContainerStyle

I'm having a strange issue with my ContextMenu. The ItemsSource is a List<Layer> Layers;, where the Layer class overrides the ToString() to return the Name property.
If I don't use any ItemContainerStyle for the context menu, it all works fine - it takes the Layer object and displays the ToString() of that object, like it should. When I add the ItemContainerStyle, it shows an empty string.
Here's the XAML:
<Style x:Key="myItemControlTemplate" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border SnapsToDevicePixels="True" Height="32" Width="200" Background="White">
<Grid VerticalAlignment="Center" Height="Auto" Width="Auto" Background="{x:Null}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{TemplateBinding Header}" VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button ContextMenuService.IsEnabled="False">
<Button.ContextMenu>
<ContextMenu FontFamily="Global Sans Serif" Height="Auto" Width="200" Padding="0,6" VerticalOffset="5" BorderThickness="0"
HasDropShadow="True" ItemContainerStyle="{StaticResource myItemControlTemplate}">
</ContextMenu>
</Button.ContextMenu>
</Button>
And here's how I fire it:
private void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
ContextMenu ctm = btn.ContextMenu;
ctm.ItemsSource = Layers;
ctm.PlacementTarget = btn;
ctm.Placement = PlacementMode.Bottom;
ctm.IsOpen = true;
}
Could it be that for some reason this binding gets busted somehow?
Text="{TemplateBinding Header}"
BTW, if I change the layers list to be a List<string> and just feed it the names of the layers, it works correctly with the ItemContainerStyle.
What am I missing?
The issue is the TemplateBinding. This is a less powerful but optimized variant of a relative source binding to a templated parent, but it comes at the expense of several limitations, like not supporting two-way binding. Although not officially stated in the documentation, it seems that this binding does not support conversion of the underlying type to string, as ToString is never called in this case.
Replace the TemplateBinding with a binding to the templated parent using relative source.
<TextBlock x:Name="textBlock"
HorizontalAlignment="Left" TextWrapping="Wrap"
Text="{Binding Header, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>

WPF XAML Change Style Template Property for Some Elements

In a WPF application I have a style used for buttons:
<Style TargetType="Button" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="/MyProject;component/Images/ButtonEllipse.png"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
For most buttons that is OK but for one specific instance I want to use the same template but change the image to ButtonEllipseNew.png (which is the value of a view model property). The button is defined like this:
<Button Content="Test" Style="{StaticResource ButtonEllipse}">
</Button>
How can I change the value of the image source in the template only for this specific button? I want to bind the source to a property in the view model.
I am afraid you can't reuse only a part of a ControlTemplate. You must define the template as a whole:
WPF: Is there a way to override part of a ControlTemplate without redefining the whole style?
What you could do is to bind the Source property of the Image in the template to some property that you can then set individually for each control to which the template is applied:
<Style TargetType="Button" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None"
Source="{Binding Tag, RelativeSource={RelativeSource AncestorType=Button}}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
<Button Content="Test" Style="{StaticResource ButtonEllipse}" Tag="image.png" />
I would write a custom control that looks something like this:
internal class IconButton : Button
{
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(ImageSource), typeof(IconButton), new PropertyMetadata(null));
}
Then edit your style to accommodate it:
<Style TargetType="{x:Type location:IconButton}" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type location:IconButton}">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="{TemplateBinding Source"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
Where location is a xaml-defined namespace where the IconButton class is placed.
Then just set the Source property of your button. You can mess around with the Source property to set a default as well.
In order to bind a property to your style you should do the following:
Create a user control let's name it ButtonImage.cs
public class ButtonImage : Button
{
public ImageSource Source
{
get
{
return (ImageSource)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source",
typeof(ImageSource),
typeof(ButtonImage),
new PropertyMetadata(null));
}
I would create a ResourceDictionary so you can use it for all your styles. Let's name it Dictionary.xaml. An there you define your style like:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4">
<Style TargetType="{x:Type local:ButtonImage}" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonImage}">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="{TemplateBinding Source}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Then in your view you can do:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication4="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="Dictionary.xaml">
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<wpfApplication4:ButtonImage Margin="0,50,0,0" Content="Test" Source="{Binding Name}" Style="{StaticResource ButtonEllipse}" />
</Grid>
</Window>

WPF Dependency Property Bound To User Control

I have a button subclass called MenuButton.
public class MenuButton : Button
{
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MenuButton), new UIPropertyMetadata(null));
public UserControl Icon
{
get { return (UserControl)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(UserControl), typeof(MenuButton),
new PropertyMetadata(null));
}
In the style I want to show an icon that was created using paths from SVG files. I have created a User Control containing the XAML for the icon:
<UserControl x:Class="WpfApplication1.Views.ScopeIcon"
.
.
.
>
<Viewbox Height="55"
Width="55">
<Grid>
<Path Fill="LightBlue" Data="M98.219,48.111C97..."/>
<Path Fill="LightBlue" Data="M98.219,46.948C97...."/>
</Grid>
</Viewbox>
</UserControl>
And here's the style:
<Style TargetType="Button"
x:Key="TestButtonStyle">
<Setter Property="Height" Value="140"/>
<Setter Property="Width" Value="195"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MenuButton}">
<Border x:Name="TheBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Row="0"/> <===== THE USER CONTROL WILL GO HERE
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="{TemplateBinding Caption}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="5"
Foreground="White"
FontSize="14"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I will set it here:
<ListBox Grid.Row="0"
ItemsSource="{Binding MainTools}" >
<ListBox.ItemTemplate>
<DataTemplate>
<controls:MenuButton Caption="{Binding Caption}"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}"
Icon=""/> <============= HOW DO I PUT THE SCOPEICON USER CONTROL HERE?
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm essentially trying to nest user controls, but I want to tell the button, in XAML, what UserControl to use for its icon.
Thank you
First, you need to update your style to set Content property on your ContentPresenter the same way you did for Text property on TextBlock.
<ContentPresenter Grid.Row="0" Content="{TemplateBinding Icon}"/>
And, then to set Icon on the MenuButton in your DataTemplate update your MenuButton element in DataTemplate to something like:
<controls:MenuButton Caption="Caption"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}">
<controls:MenuButton.Icon>
<views:ScopeIcon />
</controls:MenuButton.Icon>
</controls:MenuButton>

Configurable HeaderTemplate

I have about 12 Expanders that need to have a custom HeaderTemplate. The HeaderTemplate has a textblock to display the header text, along with a button. The button has a custom control template so that I can display the button as a Path within a VisualBrush.
<Expander VerticalAlignment="Top"
Header="Fields"
IsExpanded="True">
<Expander.HeaderTemplate>
<DataTemplate>
<DockPanel LastChildFill="False">
<TextBlock VerticalAlignment="Center"
DockPanel.Dock="Left"
FontSize="14"
Foreground="{DynamicResource IdealForegroundColorBrush}"
Text="{Binding}"/>
<Button DockPanel.Dock="Right" Margin="0 0 10 0"
Command="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=DataContext.AddFieldCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="Transparent" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Button.Template>
<Rectangle Width="20" Height="20" Fill="{DynamicResource EditIconBrush}" />
</Button>
</DockPanel>
</DataTemplate>
</Expander.HeaderTemplate>
I would like to re-use this template across all 12 Expanders, but need to specify what icon and Command the button within the template will use. How would I go about doing that? Can this be achieved by breaking the data template up in to a series of templates? What I want to do is move the template into a static resource
<Expander VerticalAlignment="Top"
Header="Fields"
IsExpanded="True"
HeaderTemplate="{StaticResource IconHeader}">
I'm just not sure how to provide the template what Command and Icon resource it should use. Would a Dependency property make sense here?
Thanks.
I believe that there are at least to ways to achieve this. First is to inherit from Expander, extend it with your Dependency properties and bind to them inside DataTemplate.
Another way to do this is to create some attached properties, and bind to them inside DataTemplate as below:
public class TemplateConfig
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof (ICommand), typeof (TemplateConfig), new PropertyMetadata(default(ICommand)));
public static void SetCommand(DependencyObject element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject element)
{
return (ICommand) element.GetValue(CommandProperty);
}
public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof (VisualBrush), typeof (TemplateConfig), new PropertyMetadata(default(VisualBrush)));
public static void SetIcon(DependencyObject element, VisualBrush value)
{
element.SetValue(IconProperty, value);
}
public static VisualBrush GetIcon(DependencyObject element)
{
return (VisualBrush) element.GetValue(IconProperty);
}
}
Header template:
<DataTemplate x:Key="ExpanderHeaderTemplate">
<DockPanel LastChildFill="False">
<TextBlock VerticalAlignment="Center"
DockPanel.Dock="Left"
FontSize="14"
Text="{Binding}" />
<Button Margin="0 0 10 0"
Command="{Binding Path=(test:TemplateConfig.Command),
RelativeSource={RelativeSource AncestorType=Expander}}"
DockPanel.Dock="Right">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="Transparent" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Button.Template>
<Rectangle Width="20"
Height="20"
Fill="{Binding Path=(test:TemplateConfig.Icon),
RelativeSource={RelativeSource AncestorType=Expander}}" />
</Button>
</DockPanel>
</DataTemplate>
and finally expander:
<Expander VerticalAlignment="Top"
Header="Fields"
HeaderTemplate="{StaticResource ExpanderHeaderTemplate}"
IsExpanded="True"
test:TemplateConfig.Command="{Binding SomeCommand}"
test:TemplateConfig.Icon="{StaticResource VisualBrush}" />
Hope this helps.

ListView like WindowsLiveMessenger

<ListView Margin="0" Background="White" ItemContainerStyle="{DynamicResource ListViewItemStyle}" ItemTemplate="{DynamicResource DataTemplate}">
<ListView.Resources>
<Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DataTemplate">
<Grid Width="Auto">
<Label Content="" HorizontalAlignment="Center" Margin="62,8,8,8" Width="100" VerticalAlignment="Center" Height="30"/>
<Image HorizontalAlignment="Left" Margin="8,8,0,8" Width="50" VerticalAlignment="Stretch" Height="30"/>
</Grid>
</DataTemplate>
</ListView.Resources>
<ListViewItem>
</ListViewItem>
</ListView>
i'm new to blend !!
how to add items from code that contain those 2 controls
this question is similar to
How to write ListViewItem that contain image , text and button?
You are using a DataTemplate that should be applied to the items, but you do not bind the relevant properties at all.
i.e.
<DataTemplate x:Key="DataTemplate">
<Grid Width="Auto">
<Label Content="{Binding Name}"
HorizontalAlignment="Center" Margin="62,8,8,8" Width="100" VerticalAlignment="Center" Height="30"/>
<Image Source="{Binding ImageUrl}"
HorizontalAlignment="Left" Margin="8,8,0,8" Width="50" VerticalAlignment="Stretch" Height="30"/>
</Grid>
</DataTemplate>
See those two bindings? Your data-objects need to provide those public properties, i named them Name and ImageUrl here.
// Data-object class example
public class Contact
{
public string Name { get; set; }
public string ImageUrl { get; set; }
//...
}
If your data-object supports that you can just add items of that type to the ListView and it should display as you want it to.
This might not make much sense to you, if so, make sure to read introductory material first:
Data Binding Overview
Data Templating Overview

Resources