ToggleButton changing image depending on state
I would like to use ToggleButton in following way: There are 5 different images and each of them should be displayed depending on current state:
- button disabled
- button enabled, unchecked
- button enabled, unchecked, pointed by mouse cursor
- button enabled, checked
- button enabled, checked, pointed by mouse cursor
I've found a simple example with two images here , but how to change the image depending on "checked" property?
The second question: how can I avoid creating different styles for each button in my application? I'm using about 20 different buttons and each of them has different set of icons.
So far I'm using only one icon, below my code. Is it possible to have common code (style and template) and to define the source of images in section where I want to create button (like in section 3 of my code)?
<ControlTemplate x:Key="ToggleButtonTemplate" TargetType="{x:Type ToggleButton}">
<Grid>
开发者_如何学编程 <Border x:Name="ContentBorder" CornerRadius="4" BorderBrush="Transparent" BorderThickness="1" Background="{DynamicResource ButtonOff}">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource ButtonOn}"/>
</Trigger>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource ButtonOn}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource ButtonDisabled}"/>
<Setter Property="Foreground" Value="{DynamicResource BorderDisabled}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="ToggleButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Width" Value="64" />
<Setter Property="Height" Value="64" />
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template" Value="{DynamicResource ToggleButtonTemplate}" />
</Style>
<ToggleButton IsChecked="{Binding Path=IsLectorModeEnabled}" Command="{Binding CmdLector}" Style="{DynamicResource ToggleButtonStyle}">
<Image Source="{DynamicResource LectorImage}" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" />
</ToggleButton>
This solution is a simple one:
<ToggleButton IsChecked="{Binding IsCheckedState}">
<Image Width="24" Height="24" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCheckedState}" Value="true">
<Setter Property="Source" Value="Images/checked.ico"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsCheckedState}" Value="false">
<Setter Property="Source" Value="Images/unchecked.ico"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ToggleButton>
You can get the functionality you want by creating a UserControl that exposes dependency properties for Command, IsChecked, and one for each stateful image. Your user control will contain a toggle button and image.
You can use MultiDataTriggers to detect your state and swtich the image depending on the state.
Because you exposed the DependencyProperties for the stateful images, they can be set using Databinding wherever you declare your control. The triggers will automatically switch the image source for you, once state changes.
[Edit: Added some code to help explain]
Here is a partial example to get you started:
MyToggleButton.xaml:
<UserControl x:Class="ToggleTest.MyToggleButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ToggleButton
IsChecked='{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ToggleButton} },
Path=IsChecked}'>
<Image
x:Name='ButtonImage'>
<Image.Style>
<Style
TargetType='{x:Type Image}'>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ToggleButton} },
Path=IsChecked}'
Value='True' />
<Condition
Binding='{Binding
RelativeSource={RelativeSource Self},
Path=IsEnabled}'
Value='True' />
</MultiDataTrigger.Conditions>
<Setter
Property='Source'
Value='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl} },
Path=EnabledChecked}' />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ToggleButton} },
Path=IsChecked}'
Value='False' />
<Condition
Binding='{Binding
RelativeSource={RelativeSource Self},
Path=IsEnabled}'
Value='True' />
</MultiDataTrigger.Conditions>
<Setter
Property='Source'
Value='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl} },
Path=EnabledUnchecked}' />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ToggleButton} },
Path=IsChecked}'
Value='False' />
<Condition
Binding='{Binding
RelativeSource={RelativeSource Self},
Path=IsEnabled}'
Value='False' />
</MultiDataTrigger.Conditions>
<Setter
Property='Source'
Value='{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl} },
Path=DisabledUnchecked}' />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ToggleButton>
And the cs file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ToggleTest
{
/// <summary>
/// Interaction logic for ToggleButton.xaml
/// </summary>
public partial class MyToggleButton : UserControl
{
public MyToggleButton()
{
InitializeComponent();
}
public static readonly DependencyProperty EnabledUncheckedProperty =
DependencyProperty.Register(
"EnabledUnchecked",
typeof(ImageSource),
typeof(MyToggleButton),
new PropertyMetadata(onEnabledUncheckedChangedCallback));
public ImageSource EnabledUnchecked
{
get { return (ImageSource)GetValue(EnabledUncheckedProperty); }
set { SetValue(EnabledUncheckedProperty, value); }
}
static void onEnabledUncheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
public static readonly DependencyProperty DisabledUncheckedProperty =
DependencyProperty.Register(
"DisabledUnchecked",
typeof(ImageSource),
typeof(MyToggleButton),
new PropertyMetadata(onDisabledUncheckedChangedCallback));
public ImageSource DisabledUnchecked
{
get { return (ImageSource)GetValue(DisabledUncheckedProperty); }
set { SetValue(DisabledUncheckedProperty, value); }
}
static void onDisabledUncheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
public static readonly DependencyProperty EnabledCheckedProperty =
DependencyProperty.Register(
"EnabledChecked",
typeof(ImageSource),
typeof(MyToggleButton),
new PropertyMetadata(onEnabledCheckedChangedCallback));
public ImageSource EnabledChecked
{
get { return (ImageSource)GetValue(EnabledCheckedProperty); }
set { SetValue(EnabledCheckedProperty, value); }
}
static void onEnabledCheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked",
typeof(Boolean),
typeof(MyToggleButton),
new PropertyMetadata(onCheckedChangedCallback));
public Boolean IsChecked
{
get { return (Boolean)GetValue(IsCheckedProperty); }
set { if(value != IsChecked) SetValue(IsCheckedProperty, value); }
}
static void onCheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something, if needed
}
}
}
This control could be used like so:
<local:MyToggleButton
IsChecked='True'
IsEnabled='False'
EnabledChecked='<add your image source here>'
EnabledUnchecked='<add your image source here>'
DisabledUnchecked='<add your image source here>'/>
Sir Ed Gonzalez, thank you for good example.
The only problem is that the binding to the MyToggleButton.IsChecked dependency property doesn't work properly (platform: Windows 7., NET 4.0, VS2010). So I have made some changes in your example.
xaml:
<ToggleButton x:Class="MyApp.ToggleButtonEx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Checked="ToggleButton_CheckedChanged"
Unchecked="ToggleButton_CheckedChanged"
IsEnabledChanged="ToggleButton_IsEnabledChanged"
Loaded="ToggleButton_Loaded">
<Image x:Name='ButtonImage'/>
</ToggleButton>
cs:
public partial class ToggleButtonEx : ToggleButton
{
public ToggleButtonEx()
{
InitializeComponent();
}
public static readonly DependencyProperty EnabledUncheckedProperty =
DependencyProperty.Register(
"EnabledUnchecked",
typeof(ImageSource),
typeof(ToggleButtonEx),
new PropertyMetadata(onEnabledUncheckedChangedCallback));
public ImageSource EnabledUnchecked
{
get { return (ImageSource)GetValue(EnabledUncheckedProperty); }
set { SetValue(EnabledUncheckedProperty, value); }
}
static void onEnabledUncheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
public static readonly DependencyProperty DisabledUncheckedProperty =
DependencyProperty.Register(
"DisabledUnchecked",
typeof(ImageSource),
typeof(ToggleButtonEx),
new PropertyMetadata(onDisabledUncheckedChangedCallback));
public ImageSource DisabledUnchecked
{
get { return (ImageSource)GetValue(DisabledUncheckedProperty); }
set { SetValue(DisabledUncheckedProperty, value); }
}
static void onDisabledUncheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
public static readonly DependencyProperty EnabledCheckedProperty =
DependencyProperty.Register(
"EnabledChecked",
typeof(ImageSource),
typeof(ToggleButtonEx),
new PropertyMetadata(onEnabledCheckedChangedCallback));
public ImageSource EnabledChecked
{
get { return (ImageSource)GetValue(EnabledCheckedProperty); }
set { SetValue(EnabledCheckedProperty, value); }
}
static void onEnabledCheckedChangedCallback(
DependencyObject dobj,
DependencyPropertyChangedEventArgs args)
{
//do something if needed
}
private void ToggleButton_CheckedChanged(object sender, RoutedEventArgs e)
{
ChangeImage();
}
private void ToggleButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ChangeImage();
}
private void ToggleButton_Loaded(object sender, RoutedEventArgs e)
{
ChangeImage();
}
private void ChangeImage()
{
if (IsEnabled)
{
if(IsChecked == true)
ButtonImage.Source = EnabledChecked;
else
ButtonImage.Source = EnabledUnchecked;
}
else
{
ButtonImage.Source = DisabledUnchecked;
}
}
}
Usage pattern remains unchaged:
<local:MyToggleButton
IsChecked='True'
IsEnabled='False'
EnabledChecked='<add your image source here>'
EnabledUnchecked='<add your image source here>'
DisabledUnchecked='<add your image source here>'/>
I did the same for my RibbonToggleButton, but a bit easier I think. I put the style trigger inside the button instead of using an extra image element.
<RibbonToggleButton Label="{x:Static p:Resources.Main_Connect}" Command="{Binding ConnectRemoteCommand}" CommandParameter="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}">
<RibbonToggleButton.Style>
<Style TargetType="{x:Type RibbonToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" Value="true">
<Setter Property="LargeImageSource" Value="../../Resources/Images/GPS-On.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" Value="false">
<Setter Property="LargeImageSource" Value="../../Resources/Images/GPS-Off.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RibbonToggleButton.Style>
</RibbonToggleButton>
Another way to go about it is to use style on the ToggleButton
itself. Add the Image as Content and change the image using triggers on IsChecked
Property of ToggleButton
<ToggleButton Name="ExpandButton" Grid.Row="1" Grid.Column="0" IsChecked="{Binding IsCheckedState}">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Image Name="LogoImage" Stretch="Uniform" Source="checked.png"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="LogoImage" Property="Source" Value="checked.png"/>
</Trigger>
<Trigger Property="IsChecked" Value="false">
<Setter TargetName="LogoImage" Property="Source" Value="unchecked.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
Just because I hate messing up my toggle button template for the content. I prefer just to use bool to visibility converters to achieve the same behavior as above. And it's super simple. Now I use XAML Vector images so I'm currently using content controls, but you can easily swap those out for an Image. This allows me to keep my toggle button styles pure so I can keep them default for the entire application without writing custom styles for every button.
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
</UserControl.Resources>
<ToggleButton>
<Grid>
<ContentControl Visibility="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type ToggleButton}}, Converter={StaticResource BoolToVisConverter}}" Content="{StaticResource Play}" />
<ContentControl Visibility="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type ToggleButton}}, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Content="{StaticResource Pause}" />
</Grid>
</ToggleButton>
<ToggleButton>
<Grid>
<Image Visibility="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type ToggleButton}}, Converter={StaticResource BoolToVisConverter}}" Source="play.png" />
<Image Visibility="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type ToggleButton}}, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Source="pause.png" />
</Grid>
</ToggleButton>
Sir Ed Gonzalez, thank you for good example.
The only problem is that the binding to the MyToggleButton.IsChecked dependency property doesn't work properly (platform: Windows 7., NET 4.0, VS2010). So I have made some changes in your example.
Just remove static on IsChecked DependencyProperty, add your ChangeImage()
in IsChecked()
and the Sir Ed Gonzalez's example work well ^^
public readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register(
"IsChecked" ...
public Boolean IsChecked
... if (value != IsChecked) SetValue(IsCheckedProperty, value); ChangeImage();
精彩评论