How to use the Child-Defined Attached Properties? - wpf

1. Issue
As we know,Attached Property widely expands the property system in wpf.But all the examples which are familiar to us are almost Parent-Defined ones,such as DockPanel.Dock / Grid.Row and so on.But after checking the document in MSDN,I found there are some other usages of Attached Property:
From MSDN:Attached Properties Overview / How Attached Properties Are Used by the Owning Type
1.The type that defines the attached property is designed so that it can be the parent element of the elements that will set values for the attached property. The type then iterates its child objects through internal logic against some object tree structure, obtains the values, and acts on those values in some manner.(Parent-Defined)
2.The type that defines the attached property will be used as the child element for a variety of possible parent elements and content models.(Child-Defined)
3.The type that defines the attached property represents a service. Other types set values for the attached property. Then, when the element that set the property is evaluated in the context of the service, the attached property values are obtained through internal logic of the service class.(Use as a Common Service)
2. Trial
Since Attached Property Can be defined by user,I thoought maybe we can use "CallBackMethod" to handle it.So I have coded some trials to validate my idea (Part 4.Code):
1.Customize a child control ("Son") which defined a attached property named "CellBorderThicknessProperty" and use "PropertyChangedCallBack" to update the layout;
2.Create a parent control ("Father") which's template contains the child control.
3.Use the parent control in a Window and set the child.CellBorderThickness's value;
3. Problem
1.As you see,It's not a good way to expose the "Parent Type" in "Child Type",especially we won't how many Parents there would be...
2.This Trial didnt work well,'Cus when the "PropertyChangedCallBack" was fired,the Father's template was not applied
yet!So,Father.SetBorderThickness() will do nothing!
Is there any example which has used the Child-Defined attached property?And how does it work?
I am so eager to know how MS developers do with the Child-Defined ones.
eg: what about ScrollViwer.VerticalScrollBarVisibility in WPFToolkit:DataGrid?
4. Code
Son Control (Child)
<Style TargetType={x:Type Son}>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness={TemplateBinding CellBorderThickness}>
...
</Border>
</ControlTemplate>
<Setter.Value>
</Setter>
public class Son: Control
{
public static readonly DependencyProperty CellBorderThicknessProperty = DependencyProperty.RegisterAttached("CellBorderThickness", typeof(Thickness), typeof(Son), new FrameworkPropertyMetadata(new Thickness(0.2), FrameworkPropertyMetadataOptions.AffectsRender, CellBorderThicknessProperty_ChangedCallBack));
public static void SetCellBorderThickness(UIElement obj, Thickness value)
{
obj.SetValue(Son.CellBorderThicknessProperty, value);
}
public static Thickness GetCellBorderThickness(UIElement obj)
{
return (Thickness)obj.GetValue(Son.CellBorderThicknessProperty);
}
public Thickness CellBorderThickness
{
//With this, CellBorderThickness can be used as a normal dependency property.
get { return (Thickness)GetValue(CellBorderThicknessProperty); }
set { SetValue(CellBorderThicknessProperty, value); }
}
static void CellBorderThicknessProperty_ChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((d as Father) != null)
{
// Try to update the Son's CellBorderThickness which is the child element of the Father.
d.SetBorderThickness(e.NewValue);
}
}
}
Father Control (Parent)
<Style TargetType={x:Type Father}>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Son></Son>
</Border>
</ControlTemplate>
<Setter.Value>
</Setter>
public class Father:Control
{
private Son childControl;
public void override OnApplyTemplate()
{
childControl=(Son)GetTemplateChild("PART_ChildControl");//Here is a problem!!
}
public void SetBorderThickness(Thickness value)
{
if(childControl==null)
childControl.CellBorderThickness=value;
}
}
Window
<Window>
<Grid>
<Father Son.CellBorderThichness="5"></Father>
</Grid>
<Window>

I took your example as a basis, removed a bit too much, and got this example. I removed them to show a minimal example of work.
First, I removed CellBorderThickness property, as is already attached dependency property.
Son
// Removed
public Thickness CellBorderThickness
{
get { return (Thickness)GetValue(CellBorderThicknessProperty); }
set { SetValue(CellBorderThicknessProperty, value); }
}
In my father control I removed OnApplyTemplate(), and in function SetBorderThickness() use the opportunity of attached dependency properties to set value:
Father
// Removed
OnApplyTemplate() { ... }
// Add
Son.SetCellBorderThickness(childControl, value);
Below is a complete example. The structure of example:
XAML
Styles
Son
<Style TargetType="{x:Type SonNamespace:Son}">
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding SonNamespace:Son.CellBorderThickness}">
<ContentPresenter Content="I'am a Son"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Father
<Style TargetType="{x:Type FatherNamespace:Father}">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding SonNamespace:Son.CellBorderThickness}">
<ContentPresenter Content="I'am a Father"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow
<Grid>
<SonNamespace:Son />
<FatherNamespace:Father SonNamespace:Son.CellBorderThickness="6" />
</Grid>
Code
Son
public class Son : Control
{
public static readonly DependencyProperty CellBorderThicknessProperty =
DependencyProperty.RegisterAttached("CellBorderThickness",
typeof(Thickness), typeof(Son),
new FrameworkPropertyMetadata(new Thickness(2),
FrameworkPropertyMetadataOptions.AffectsRender,
CellBorderThicknessProperty_ChangedCallBack));
public static void SetCellBorderThickness(UIElement obj, Thickness value)
{
obj.SetValue(Son.CellBorderThicknessProperty, value);
}
public static Thickness GetCellBorderThickness(UIElement obj)
{
return (Thickness)obj.GetValue(Son.CellBorderThicknessProperty);
}
private static void CellBorderThicknessProperty_ChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Father father = d as Father;
if (father != null)
{
father.SetBorderThickness((Thickness)e.NewValue);
}
}
}
Father
public class Father : Control
{
private Son childControl;
public void SetBorderThickness(Thickness value)
{
if (childControl != null)
{
Son.SetCellBorderThickness(childControl, value);
}
}
}
Output
Project is available at this link.

Related

sidebar with materialIcons

I have a custom control, that I mad, for hosting the name, and icon of the panel, and made triggers, so it will change colors when I hover the icon or select it
public class NavButton : ListBoxItem {
static NavButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavButton),
new FrameworkPropertyMetadata(typeof(NavButton)));
}
public Uri NavLink {
get { return (Uri)GetValue(NavLinkProperty); }
set { SetValue(NavLinkProperty, value); }
}
// This property will hold specifics on where to navigate within our appreciation
public static readonly DependencyProperty NavLinkProperty =
DependencyProperty.Register(
"NavLink", typeof(Uri),
typeof(NavButton),
new PropertyMetadata(null));
// This property will hold specifics on the icon
public Label Icon {
get { return (Label)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(Label),
typeof(NavButton),
new PropertyMetadata(null));
The problem is, that whenever I call this control in my main, I always get this error
I am calling it like this
<custom:NavButton
Padding="6"
FontFamily="{StaticResource Material}"
Icon="{x:Static fonts:IconFont.VolumeHigh}"/>
And this is the recourse dictionary for the label
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<FontFamily x:Key="Material">/Fonts/Material.ttf#Material Design Icons</FontFamily>
</ResourceDictionary>
<Style TargetType="{x:Type local:NavButton}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NavButton}">
<Border
x:Name="back"
Padding="{TemplateBinding Padding}"
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8">
<Label
x:Name="icon"
Content="{TemplateBinding Icon}"
FontFamily="{StaticResource Material}"
FontSize="48"
Foreground="#7b8793" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="back" Property="Background" Value="#d0ebff" />
<Setter TargetName="icon" Property="Foreground" Value="#2a84f1" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="back" Property="Background" Value="#d0ebff" />
<Setter TargetName="icon" Property="Foreground" Value="#2a84f1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
error message clearly says, that you have a type mismatch. Icon DP should not have type Label. it should have the same type as IconFont.VolumeHigh. or it could simple be object:
// This property will hold specifics on the icon
public object Icon {
get { return (Label)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
// Using a DependencyProperty as the backing store for Icon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon",
typeof(object),
typeof(NavButton),
new PropertyMetadata(null));

Add new brush property in VS WPF designer

I'm creating a new button control, and I wanted to create a new property under the brushes section where I can set the hovercolor. Anyone know how you would do this?
The simplest way to add a property that will appear in the "Brush" section of the Properties panel is to - define a Brush property in control's class code:
public partial class MyFancyControl : UserControl
{
// ...
public Brush FancyBrush
{
get;
set;
}
// ...
}
The property will show in "Brush" section with no further action (at least it does in my VS2013, see below).
While such a property will work just fine in most cases, the proper way to do it is to define it as a DependencyProperty:
public partial class MyFancyControl : UserControl
{
// ...
public Brush FancyBrush
{
get
{
return (Brush)GetValue(FancyBrushProperty);
}
set
{
SetValue(FancyBrushProperty, value);
}
}
public static readonly DependencyProperty FancyBrushProperty =
DependencyProperty.Register("FancyBrush", typeof(Brush), typeof(IntUpDown), new PropertyMetadata(default(Brush)));
// ...
}
Using DependencyProperty will enable binding and other "advanced" stuff.
Tip: Use VS Intellisense helpers to avoid need to type all the surrounding code - type "propdp" and press Tab twice.
To make sure the property will show up in the correct section of the Properties panel, add the Category attribute:
[System.ComponentModel.Category("Brush")]
public Brush FancyBrush
{...
Again, this seems to work automatically for Brush type so it may not be necessary.
You can also add a Description attribute that will show in the tooltip in the Properties panel:
[System.ComponentModel.Description("Gets or sets a brush that defines fancy look of the control.")]
You can just create the brush and add it as a resource something like:
<SolidColorBrush x:Key="MouseOverColor" Color="#FFFFFFF"/>
Then in the template triggers in your button template:
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonBorder" Property="Background" Value="StaticResource MouseOverColor}"/>
</Trigger>
Here is a simple button from in a resource dictionary file that uses defined brushes:
<SolidColorBrush x:Key="SelectionHighlightBrush" Color="#282828"/>
<SolidColorBrush x:Key="SelectionHighlightTextBrush" Color="White"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="#282828"/>
<SolidColorBrush x:Key="ControlBackgroundBrush" Color="White"/>
<SolidColorBrush x:Key="ControlBorderBrush" Color="#C0C0C0" />
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Background" Value="{StaticResource ControlBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="temp" TargetType="ToggleButton">
<Border x:Name="bd" CornerRadius="3"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="contentPresenter" Margin="{TemplateBinding Padding}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{StaticResource SelectionHighlightBrush}"/>
<Setter Property="TextElement.Foreground" Value="{StaticResource SelectionHighlightTextBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

How turn-off tooltips for the whole application

Is it possible to turn-off toooltips for all controls (always or based on some rule) without setting TooltipService.IsEnabled on each control? I mean, going through all logical items takes too much time.
Try this. It hides all tooltips.
<Style TargetType="{x:Type ToolTip}">
<Setter Property="Visibility"
Value="Collapsed" />
</Style>
There are several ways you should be able to use to accomplish this. Marco Zhou outlines two of them in this posting., both of these methods relying on setting TooltipService.IsEnabled to False for a parent control such as a Window. Apparently it inherits to all children, so you can set it just there to disable all tooltips.
You could also set all of your Tooltips to a style which had bindings to a property that would make them invisible or disabled when you wanted.
EDIT
Adding the Code to make it easier to understand:
Create the ToolTipEnabled Attached Property which sets the FrameworkPropertyMetadataOptions.Inherits so that it will be inherited by the children.
public class ToolTipBehavior
{
public static Boolean GetIsToolTipEnabled(FrameworkElement obj)
{
return (Boolean)obj.GetValue(ToolTipEnabledProperty);
}
public static void SetToolTipEnabled(FrameworkElement obj, Boolean value)
{
obj.SetValue(ToolTipEnabledProperty, value);
}
public static readonly DependencyProperty ToolTipEnabledProperty = DependencyProperty.RegisterAttached(
"IsToolTipEnabled",
typeof(Boolean),
typeof(ToolTipBehavior),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, (sender, e) =>
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
element.SetValue(ToolTipService.IsEnabledProperty, e.NewValue);
}
}));
}
You can either use this property in the XAML or codebehind as below:
<Window x:Class="AnswerHarness.ToggleToolTipsDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:AnswerHarness"
Title="ToggleToolTipsDemo" Height="300" Width="300" Name="window">
<StackPanel>
<CheckBox IsChecked="{Binding Path=(cc:ToolTipBehavior.IsToolTipEnabled), ElementName=window}" Content="Enable ToolTip"/>
<Border BorderBrush="Green" BorderThickness="1" Background="Yellow" ToolTip="Border">
<StackPanel>
<Button Width="120" Height="30" Content="Button1" ToolTip="Button1"/>
<Button Width="120" Height="30" Content="Button2" ToolTip="Button2"/>
<Button Width="120" Height="30" Content="Button3" ToolTip="Button3"/>
</StackPanel>
</Border>
</StackPanel>
</Window>
Or
public partial class ToggleToolTipsDemo : Window
{
public ToggleToolTipsDemo()
{
InitializeComponent();
// You can programmatically disable tool tip here.
this.SetValue(ToolTipBehavior.ToolTipEnabledProperty, false);
}
}
Put this style where it is accessible throughout the application(a resourcedictionary or App.xaml) so you won't need to reference this style in any textbox.
<Style BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Microsoft_Windows_Themes:ListBoxChrome x:Name="Bd" ToolTipService.IsEnabled="False" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true">
<ScrollViewer ToolTipService.IsEnabled="False" x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Microsoft_Windows_Themes:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
NOTE
This is the default textbox style generated by Expression blend to which I have added the following trigger which enables tooltips when textbox is not empty and disables them otherwise
<Trigger Property="Text" Value="">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
</Trigger>
I don't know of any global setting, but there is an easy way to 'visit' all of the elements of your visual tree using Linq-to-VisualTree, I utility I wrote a while back that providers a Linq-to-XML style API for the visual tree.
The following should do the trick:
foreach(var element in window.Descendants())
ToolttipService.SetIsEnabled(element, false);
You can try to use
ToolTipService.IsOpenProperty.OverrideMetadata(typeof(DependencyObject),new PropertyMetadata(false));
I don't have an answer for handling the entire app in one statement, but I've been able to centralize a number of UI-specific parameters in a general base class, then create applications which are derived off this base class and inherit the centralized settings. I should mention there's some extra plumbing you have to add to the base class to support MVVM as in the following:
public class MyMainWindowBaseClass : Window, INotifyPropertyChanged
{
...whatever unrelated stuff you need in your class here...
private int m_toolTipDuration = 3000; // Default to 3 seconds
public int MyToolTipDuration
{
get { return m_toolTipDuration; }
set
{
if (m_toolTipDuration != value)
{
bool transition = (value == 0 || m_toolTipDuration == 0);
m_toolTipDuration = value;
NotifyPropertyChanged("MyToolTipDuration");
if (transition)
{
NotifyPropertyChanged("MyToolTipEnabled");
}
}
}
}
public bool MyToolTipEnabled
{
get { return (m_toolTipDuration > 0); }
}
public event PropertyChangedEventHandler PropertyChanged;
... whatever variables, properties, methods, etc., you need here...
///-----------------------------------------------------------------------------
/// <summary>
/// Fires property-changed event notification
/// </summary>
/// <param name="propertyName">The name of the property that changed</param>
///-----------------------------------------------------------------------------
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The XAML code looks like this:
<Button Command="{Binding StartCommand}"
Content="Start"
FontWeight="Bold"
Height="Auto"
HorizontalAlignment="Left"
Margin="20,40,0,0"
Name="ui_StartButton"
ToolTip="Click this button to begin processing."
ToolTipService.IsEnabled="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipEnabled}"
ToolTipService.ShowDuration="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=MyToolTipDuration}"
VerticalAlignment="Top"
Width="90"/>
With the important bindings being those related to ToolTipService.IsEnabled and ToolTipService.ShowDuration.
You can see that if MyToolTipDuration is set to zero, MyToolTipEnabled will return false and this disables the tooltip. In my first attempt I tried simply setting MyToolTipDuration to zero without using the ToolTipService.IsEnabled= in conjunction with the MyToolTipEnabled property, but all that accomplished was flashing, barely-readable tooltips which appear and disappear.
Overall this worked pretty well for me (ymmv), though not as well as a single setting or single call that would have handled the entire app and circumvented the need for distributing these bindings into every item with a tooltip I wanted to be support the ability to disable. Oh well, when in Rome....
In any event, hopefully someone finds this of use.

WPF : give me a best way for icon button

We can easily make an icon button using a control template like the following code:
<Style x:Key="IconButton" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Image x:Name="Background" Source="/UOC;component/TOOLBAR_BUTTON_NORMAL.png"/>
<Image Source="/UOC;component/ICON_SLICER.gif" Width="20" Height="20" Margin="0,-10,0,0"/>
<TextBlock Foreground="White" FontSize="9" Text="{TemplateBinding Button.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,15,0,0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Source" TargetName="Background" Value="/UOC;component/TOOLBAR_BUTTON_OVER.png"/>
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Source" TargetName="Background" Value="/UOC;component/TOOLBAR_BUTTON_CLICK.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But i think it's not a productive way in practice. because i can't make a number of styles for each one of icon buttons. (ex. let's assume three buttons in App:'open' button, 'close' button and 'navigate' button. these buttons have different icon sets. i can't make styles like 'IconButton_Close', 'IconButton_Open', 'IconButton_Nav'. it's too stupid.)
UserControl may be an answer. but i think it's not a smart way for that. because if i make UserControl, it'll be just a wrapper of the Button control. it's not a right way.
So, give me the best way for icon button.
thanks.
The correct way to do this would be to define a custom button class, like so:
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource),
typeof(MyButton), new FrameworkPropertyMetadata(null));
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceHoverProperty = DependencyProperty.Register("ImageSourceHover", typeof(ImageSource),
typeof(MyButton), new FrameworkPropertyMetadata(null));
public ImageSource ImageSourceHover
{
get { return (ImageSource)GetValue(ImageSourceHoverProperty); }
set { SetValue(ImageSourceHoverProperty, value); }
}
public static readonly DependencyProperty ImageSourcePressedProperty = DependencyProperty.Register("ImageSourcePressed", typeof(ImageSource),
typeof(MyButton), new FrameworkPropertyMetadata(null));
public ImageSource ImageSourcePressed
{
get { return (ImageSource)GetValue(ImageSourcePressedProperty); }
set { SetValue(ImageSourcePressedProperty, value); }
}
}
Then define the default Style like so:
<Style x:Key="{x:Type local:MyButton}" TargetType="{x:Type local:MyButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyButton}">
<Grid>
<Image x:Name="Background" Source="{TemplateBinding ImageSource}" />
<Image Source="/UOC;component/ICON_SLICER.gif" Width="20" Height="20" Margin="0,-10,0,0"/>
<TextBlock Foreground="White" FontSize="9" Text="{TemplateBinding Button.Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,15,0,0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Source" TargetName="Background" Value="{TemplateBinding ImageSourceHover}"/>
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="True">
<Setter Property="Source" TargetName="Background" Value="{TemplateBinding ImageSourcePressed}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And you'd use it like so:
<local:MyButton ImageSource="/UOC;component/TOOLBAR_BUTTON_NORMAL.png"
ImageSourceHover="/UOC;component/TOOLBAR_BUTTON_OVER.png"
ImageSourcePressed="/UOC;component/TOOLBAR_BUTTON_CLICK.png" />
I did something similar to this for a custom control a while back using the TemplatePart attribute. This displays an icon and some text in a panel. If the icons is the error or fail icon, it turns the text red. There is a dependency property called "Type" which is really just the image file name without the extension. Here's the code, I bet you can adapt this for a custom Button where you can set the source and still have your customization to the template.
[TemplatePart(Name = "PART_Image", Type = typeof(Image))]
public class IconPanel : ContentControl
{
static IconPanel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconPanel), new FrameworkPropertyMetadata(typeof(IconPanel)));
}
public string Type
{
get { return (string)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.Register("Type", typeof(string), typeof(IconPanel),
new UIPropertyMetadata("warning", TypeChangedCallback));
static void TypeChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
IconPanel panel = obj as IconPanel;
panel.UpdateImage();
}
void UpdateImage()
{
Image img = GetTemplateChild("PART_Image") as Image;
if (img == null) return;
string ImagePath = String.Format("pack://application:,,,/Resources/{0}.png", this.Type);
Uri uri = new Uri(ImagePath, UriKind.RelativeOrAbsolute);
BitmapImage bmp = new BitmapImage(uri);
img.Source = bmp;
if ( String.Compare(Type, "error", true) == 0 ||
String.Compare(Type, "fail", true) == 0 )
{
this.Foreground = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0x00));
}
}
public override void OnApplyTemplate()
{
UpdateImage();
base.OnApplyTemplate();
}
}
XAML:
<Style TargetType="{x:Type local:IconPanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:IconPanel}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="7">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image
x:Name="PART_Image"
Margin="0,0,5,5"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="16"
Height="16" />
<ContentPresenter Grid.Column="1"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF TriState Image Button

Does anyone have any pointers for creating a tristate image button?
I have the following but what I really want to do is have a control with multiple ImageSource properties like <Controls.TristateButton Image="" HoverImage="" PressedImage="" />
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<StackPanel Orientation="Horizontal" >
<Image Name="PART_Image" Source="path to normal image" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="path to mouse over image" TargetName="PART_Image"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Source" Value="path to pressed image" TargetName="PART_Image"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have run into the same problem myself. I have created an open source project here http://imagebuttonwpf.codeplex.com where you can get the latest version of the Image Button.
I don't like the "accepted" solution provided for several reasons (Although it is a lighter solution and has its own advantages)
Blockquote The accepted answer to this StackOverflow question shows an easy way to do this: WPF - How to create image button with template
Mainly I don't think its correct to override the control template for every button you would like to change the image for so I have created a custom control called ImageButton. It extends from button so as to have any of its functionality (though it may be able to extend from content control just as easily) but also contains an Image which can be styled without rewriting the entire control template.
Another reason why I don't like rewriting the entire control template for my button each time is that my base button style contains several borders and animation effects for mouse over, is pressed etc. Rewriting these each time obviously has its own redundancy problems.
Anyway here is the ImageButton class
public class ImageButton : Button
{
static ImageButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
#region Dependency Properties
public double ImageSize
{
get { return (double)GetValue(ImageSizeProperty); }
set { SetValue(ImageSizeProperty, value); }
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.Register("ImageSize", typeof(double), typeof(ImageButton),
new FrameworkPropertyMetadata(30.0, FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string NormalImage
{
get { return (string)GetValue(NormalImageProperty); }
set { SetValue(NormalImageProperty, value); }
}
public static readonly DependencyProperty NormalImageProperty =
DependencyProperty.Register("NormalImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender,ImageSourceChanged));
public string HoverImage
{
get { return (string)GetValue(HoverImageProperty); }
set { SetValue(HoverImageProperty, value); }
}
public static readonly DependencyProperty HoverImageProperty =
DependencyProperty.Register("HoverImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string PressedImage
{
get { return (string)GetValue(PressedImageProperty); }
set { SetValue(PressedImageProperty, value); }
}
public static readonly DependencyProperty PressedImageProperty =
DependencyProperty.Register("PressedImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
public string DisabledImage
{
get { return (string)GetValue(DisabledImageProperty); }
set { SetValue(DisabledImageProperty, value); }
}
public static readonly DependencyProperty DisabledImageProperty =
DependencyProperty.Register("DisabledImage", typeof(string), typeof(ImageButton),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, ImageSourceChanged));
private static void ImageSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Application.GetResourceStream(new Uri("pack://application:,,," + (string) e.NewValue));
}
#endregion
Next up we need to provide a default control template for our button ive taken most of my borders etc out of this one, bar one so you can see that it is inherited throughout all our styles
<ControlTemplate x:Key="ImageButtonTemplate" TargetType="{x:Type Controls:ImageButton}">
<Grid x:Name="Grid">
<Border x:Name="Background" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3" />
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image x:Name="ButtonImage" Source="{Binding NormalImage, RelativeSource={RelativeSource TemplatedParent}}"
Height="{Binding ImageSize, RelativeSource={RelativeSource TemplatedParent}}"
Width="{Binding ImageSize, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding HoverImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding PressedImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="ButtonImage" Property="Source" Value="{Binding DisabledImage, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
then of course we need a default style for our new image button
<Style TargetType="{x:Type Controls:ImageButton}" BasedOn="{x:Null}">
<Setter Property="Template" Value="{StaticResource ImageButtonTemplate}" />
</Style>
And of course the benefits of using this method i have created a style based on the parent style which uses a Setter to change the dependency properties (instead of needed to override the control template - the goal)
<Style x:Key="TestImageButton" TargetType="{x:Type Controls:ImageButton}" BasedOn="{StaticResource {x:Type Controls:ImageButton}}">
<Setter Property="NormalImage" Value="/ImageButton;component/Resources/clear.png"/>
<Setter Property="HoverImage" Value="/ImageButton;component/Resources/clear_green.png" />
<Setter Property="PressedImage" Value="/ImageButton;component/Resources/clear_darkgreen.png" />
<Setter Property="DisabledImage" Value="/ImageButton;component/Resources/clear_grey.png" />
</Style>
and finally this means that one can declare the button in a few different ways either declare the image path in the XAML
<Controls:ImageButton
Content="Test Button 1"
NormalImage="/ImageButton;component/Resources/edit.png"
HoverImage="/ImageButton;component/Resources/edit_black.png"
PressedImage="/ImageButton;component/Resources/edit_darkgrey.png"
DisabledImage="/ImageButton;component/Resources/edit_grey.png"/>
Or alternatively use the style
<Controls:ImageButton
Content="Test Button 2"
Style="{DynamicResource TestImageButton}"/>
Hope it helps
The accepted answer to this StackOverflow question shows an easy way to do this:
WPF - How to create image button with template
You create property triggers on the IsEnabled and IsPressed properties and show or hide the images as needed.
As Avanka noted in his answer, you'll need to create dependency properties to set the paths to the images.
Ideally, you have to create a custom control, inherited from Button. Add three dependency properties, and create default style for new control.
You can check ImageButton class from FluidKit library - it does exactly what you want.

Resources