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();
Related
I have some problem with Radiobutton binding to show validation.
I create two RadioButtons
<StackPanel Grid.Column="2" Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Top" Margin="0,13,0,0">
<RadioButton IsChecked="{Binding EndUser, Mode=TwoWay}" Content="End User" />
<RadioButton IsChecked="{Binding AppDeveloper, Mode=TwoWay}" Margin="15,0,0,0" Content="App Developer"/>
</StackPanel>
for my logic I should take 1 name from 2.
I write the logic
[Required]
public string Role
{
get => role;
set
{
Set(ref role, value);
RaisePropertyChanged("EndUser");
RaisePropertyChanged("AppDeveloper");
}
}
public bool EndUser
{
get => Role.Contains("EndUser");
set => Role = "EndUser";
}
public bool AppDeveloper
{
get => Role.Contains("AppDeveloper");
set => Role = "AppDeveloper";
}
the problem is how show [Required] in a form, and if i choose 1 of them, the validation will be true (validation work right if it's need i show the validation code)
I find this Validation Rule for Radio buttons wpf ,but this example doesn't work for me, broke all logic(doesn't send to me anything) and doesn't mark my field.
how write the
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
for radiobutton fields like in TextBox and mark it red?
my button which disable if the fields is not valid
<Button x:Name="SignInButton" Command="{Binding SignInCommand}" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Content="Sign In" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="MidnightBlue"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Margin="24,5,24,4"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Password}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Login}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Role}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF280895"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
in MultiDataTrigger i write the rule, if Validation.HasError for the field the button is disable
You could for example put a Border around the RadioButtons and use a Style with a DataTrigger that binds to the Role property:
<Border Grid.Column="2" Grid.Row="1" BorderBrush="Red" Margin="0,13,0,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<RadioButton IsChecked="{Binding EndUser, Mode=TwoWay}" Content="End User" />
<RadioButton IsChecked="{Binding AppDeveloper, Mode=TwoWay}" Margin="15,0,0,0" Content="App Developer"/>
</StackPanel>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Role}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
You should then make the setter of the Role private and make sure to raise the PropertyChanged event for the involved properties:
private string role;
[Required]
public string Role
{
get => role;
private set
{
role = value;
RaisePropertyChanged("Role");
}
}
public bool EndUser
{
get => Role == "EndUser";
set
{
Role = "EndUser";
RaisePropertyChanged("EndUser");
RaisePropertyChanged("AppDeveloper");
}
}
public bool AppDeveloper
{
get => Role == "AppDeveloper";
set
{
Role = "AppDeveloper";
RaisePropertyChanged("AppDeveloper");
RaisePropertyChanged("EndUser");
}
}
this is my button code
public class MetroButton : Button
{
public static readonly DependencyProperty MoseOverBrushProperty;
public static readonly DependencyProperty PressedBrushProperty;
public MetroButton():base()
{
var resource = new ResourceDictionary
{
Source = new Uri("/Parking.Component.Ui;component/Styles/ButtonMetro.xaml",
UriKind.RelativeOrAbsolute)
};
Style = resource["ButtonMetro"] as Style;
//SetResourceReference(StyleProperty, Style);
}
static MetroButton()
{
MoseOverBrushProperty = DependencyProperty.Register("MoseOverBrush", typeof(Brush), typeof(MetroButton));
PressedBrushProperty = DependencyProperty.Register("PressedBrush", typeof(Brush), typeof(MetroButton));
}
public Brush MoseOverBrush
{
get { return (Brush)base.GetValue(MoseOverBrushProperty); }
set { base.SetValue(MoseOverBrushProperty, value); }
}
public Brush PressedBrush
{
get { return (Brush)base.GetValue(PressedBrushProperty); }
set { base.SetValue(PressedBrushProperty, value); }
}
}
and I use this style for my button
<Style x:Key="ButtonMetro" TargetType="{ x:Type LochalUI:MetroButton}">
<Setter Property="Foreground" Value="White" />
<Setter Property="MoseOverBrush" Value="#FF3F62FD"/>
<Setter Property="PressedBrush" Value="#FF000099"/>
<Setter Property="Background" Value="#FF6B9AFF"/>
<Setter Property="FontSize" Value="15" />
<Setter Property="FontFamily" Value="B Yekan" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type LochalUI:MetroButton}">
<Border x:Name="border" CornerRadius="4" Background="{TemplateBinding Background}">
<Grid>
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding Path=MoseOverBrush , RelativeSource={RelativeSource Self}}" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{Binding Path=PressedBrush , RelativeSource={RelativeSource Self}}" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
but the problem is there when i put color for my button background like below code:
<UI:MetroButton HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="132" Height="107" Background="#FF09CD00" >
<Grid>
<Label Content="تنظیمات" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5,5,5,12" Foreground="White" Margin="5"/>
</Grid>
</UI:MetroButton>
the IsMouseOver changer color and IsPressed Triggers not work.
(I don't want use static resource in my setters)
Changing other properties has no effect just changing background made this problem.
I found the answer problem was in 2 place:
first one when we use trigger in
<ControlTemplate.Triggers/>
you have sure you set your setter set property on the currect object
and the secend one is in the binding we have change
Value="{Binding Path=MoseOverBrush , RelativeSource={RelativeSource Self}}"
to
Value="{Binding Path=MoseOverBrush , RelativeSource={RelativeSource TemplatedParent}}"
because MoseOverBrush is parrent property not the ControlTemplate property
I have this Border style:
<Border.Style>
<Style x:Uid="Style_36" TargetType="Border">
<Setter x:Uid="Setter_94" Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger x:Uid="DataTrigger_36" Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}"
Value="{x:Static StudentInfoEnums:StudentAssignmentType.Student1Main}">
<Setter x:Uid="Setter_95" Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
I know how to place a style into the Window.Resources and then apply it to any control. But I need to tweak each instance. In the text:
<Border.Style>
<Style x:Uid="Style_36" TargetType="Border">
<Setter x:Uid="Setter_94" Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger x:Uid="DataTrigger_36" Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}"
Value="{x:Static StudentInfoEnums:StudentAssignmentType.Student1Main}">
<Setter x:Uid="Setter_95" Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
This bit:
Value="{x:Static StudentInfoEnums:StudentAssignmentType.Student1Main}">
needs to change for each Border on the window. So, how can I set up a style to simplify my code but allow this property to change?
Possible?
Update
Since each border should only diaplay when the combo is a certain value, and the suggestion was to put all the data triggers into the style template, I began by trying:
<Style x:Uid="Style_38" x:Key="StudentAssignmentFocusedBorder" TargetType="Border">
<Setter x:Uid="Setter_94" Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<MultiDataTrigger x:Uid="MultiDataTrigger_5">
<MultiDataTrigger.Conditions>
<Condition x:Uid="Condition_11" Binding="{Binding SelectedtItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.Student1Main}"/>
<Condition x:Uid="Condition_12" Binding="{Binding Name, Mode=OneWay, RelativeSource={RelativeSource Self}}" Value="borderMainHallStudent1"/>
</MultiDataTrigger.Conditions>
<Setter x:Uid="Setter_95" Property="BorderBrush" Value="Red"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
But that doesn't work.
This NOT beautiful but enhancing your possibilities, since you can bind your StudentInfoEnums:StudentAssignmentType.Student1Main-Enum.
Some random demo-XAML to Test:
<Window x:Class="SelectButtonSample.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:local="clr-namespace:SelectButtonSample"
mc:Ignorable="d"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="MainWindow" >
<Grid Height="200">
<StackPanel>
<Button Content="Click me" Width="80" Height="20" >
<i:Interaction.Behaviors>
<local:MyBorderBehavior MyEnumPropery="Two"/>
</i:Interaction.Behaviors>
</Button>
<CheckBox Content="Click me" x:Name="chk">
<i:Interaction.Behaviors>
<local:MyBorderBehavior MyEnumPropery="Three"/>
</i:Interaction.Behaviors>
</CheckBox>
<ListView>
<i:Interaction.Behaviors>
<local:MyBorderBehavior MyEnumPropery="One"></local:MyBorderBehavior>
</i:Interaction.Behaviors>
<ListViewItem Content="Item1">
<i:Interaction.Behaviors>
<local:MyBorderBehavior MyEnumPropery="Four"></local:MyBorderBehavior>
</i:Interaction.Behaviors>
</ListViewItem>
<ListViewItem>Item 2</ListViewItem>
<ListViewItem>Item 3</ListViewItem>
<ListViewItem>Item 4</ListViewItem>
</ListView>
</StackPanel>
</Grid>
</Window>
My Demo-Enum:
public enum MyEnum
{
One,
Two,
Three,
Four
}
The Magic:
public class MyBorderBehavior : Behavior<Control>
{
public MyEnum MyEnumPropery {
get { return (MyEnum) GetValue(MyEnumProperyProperty); }
set { SetValue(MyEnumProperyProperty, value); }
}
public static readonly DependencyProperty MyEnumProperyProperty = DependencyProperty.Register("MyEnumPropery", typeof(MyEnum), typeof(MyBorderBehavior), new PropertyMetadata(PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dO, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var self = dO as MyBorderBehavior;
if (self != null && self._controlToColorBorder != null)
self.SetColor();
}
private Control _controlToColorBorder;
private void SetColor()
{
switch (this.MyEnumPropery)
{
case MyEnum.One:
this._controlToColorBorder.BorderBrush = Brushes.Yellow;
break;
case MyEnum.Two:
this._controlToColorBorder.BorderBrush = Brushes.Red;
break;
case MyEnum.Three:
this._controlToColorBorder.BorderBrush = Brushes.Green;
break;
case MyEnum.Four:
this._controlToColorBorder.BorderBrush = Brushes.DeepPink;
break;
}
}
protected override void OnAttached()
{
this._controlToColorBorder = this.AssociatedObject;
this._controlToColorBorder.Loaded += ControlToColorBorderLoaded;
base.OnAttached();
}
private void ControlToColorBorderLoaded(object sender, RoutedEventArgs e)
{
this.SetColor();
}
}
Notes:
As you can see, you have to use the
System.Windows.Interactivity-Assembly
This little Behavior can applied to everything of type Control
(Since Control has BorderBrush-Property)
I've implemented a DependencyProperty to make things bindable.
Four your purpose, you sure have to replace MyEnum with yours and
adjust the colors. Furthermore, you might have to implement another
DependencyProperty to bring your 2nd condition to the Behavior.
Hope this gives you a clue, on how you can get ahead with your code.
Sure Im missing something simple but I have overridden a checkbox as a user control so I can use different images as its bullet decorators. Seems to work ok but doesnt respond to any click or checked events. I need it to display the bound popup upon the click evnt but not change the bullet decorator images.
Also my binding seems to be broken as when the click even fires (once it works) it should present a popup with the bound data. WarningList is my data to bind and when debugging it shows its populated correctly, just not bound correctly I believe
<UserControl x:Class="Dev.App.Views.Controls.StatusResultCheckBox"
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:converters="clr-namespace:Dev.App.Views.Converters"
x:Name="Root"
d:DesignHeight="50"
d:DesignWidth="50"
mc:Ignorable="d">
<UserControl.Resources>
<converters:WarningListFilterByOperationTypeConverter x:Key="WarningListFilterByOperationTypeConverter" />
<DataTemplate x:Key="StatusResultNone">
<Viewbox Width="20" Height="20">
<Grid/>
</Viewbox>
</DataTemplate>
<DataTemplate x:Key="StatusResultWarning">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
</Grid>
</Viewbox>
</BulletDecorator.Bullet>
<Popup Name="WarningMessagePopup"
Width="{Binding ElementName=ListBox, Path=ActualWidth}"
Height="200"
HorizontalAlignment="Center"
HorizontalOffset="50"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding Path=IsChecked}"
StaysOpen="False"
VerticalOffset="10">
<TextBox VerticalScrollBarVisibility="Auto">
<TextBox.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="OperationType" />
<Binding Path="DataContext.WarningList" RelativeSource="{RelativeSource AncestorType=ListBox}" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</Popup>
</BulletDecorator>
</DataTemplate>
<ControlTemplate x:Key="CheckBoxTemplate" TargetType="ContentControl">
<ContentControl Name="Content" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="None">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultNone}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="Warning">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultWarning}" />
<Setter Property="Cursor" Value="Hand" />
</DataTrigger>
<DataTrigger Binding="{Binding HasStatus, ElementName=Root}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="StatusResultVisibilityStyle" TargetType="CheckBox">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid />
</Viewbox>
</BulletDecorator.Bullet>
</BulletDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsInStatusNone.Value}" Value="False" />
<Condition Binding="{Binding IsInStatusWarning.Value}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="True"
Style="{StaticResource StatusResultVisibilityStyle}"/>
<ContentControl Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="20" Height="20"
Template="{StaticResource CheckBoxTemplate}" />
</Grid>
</UserControl>
Called from my main page as follows. (nested inside a Listbox)
<controls:StatusResultCheckBox Grid.Column="3"
IsInStatusWarning="{Binding Path=IsInStatusWarning.Value}"
IsInStatusNone="{Binding Path=IsInStatusNone.Value}"
HasStatus="{Binding Path=HasStatus.Value}"
IsRunning="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.IsRunning.Value}"/>
The code behind the user control...
using System.Windows;
using System.Windows.Controls;
namespace Dev.App.Views.Controls
{
/// <summary>
/// Interaction logic for StatusResultCheckBox.xaml
/// </summary>
public partial class StatusResultCheckBox : UserControl
{
public static readonly DependencyProperty StatusResultCheckModeProperty =
DependencyProperty.Register("StatusResultCheckMode", typeof(StatusResultCheckMode), typeof(StatusResultCheckBox), new PropertyMetadata(StatusResultCheckMode.None));
public static readonly DependencyProperty IsRunningProperty =
DependencyProperty.Register("IsRunning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty HasStatusProperty =
DependencyProperty.Register("HasStatus", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusWarningProperty =
DependencyProperty.Register("IsInStatusWarning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusNoneProperty =
DependencyProperty.Register("IsInStatusNone", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
private static void IsRunningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var statusResultCheckBox = d as StatusResultCheckBox;
if (statusResultCheckBox != null)
{
if (statusResultCheckBox.IsRunning)
{
if (statusResultCheckBox.IsInStatusWarning)
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.Warning;
statusResultCheckBox.HasStatus = true;
}
else
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.None;
statusResultCheckBox.HasStatus = false;
}
}
}
}
public StatusResultCheckBox()
{
InitializeComponent();
}
public StatusResultCheckMode StatusResultCheckMode
{
get { return (StatusResultCheckMode)GetValue(StatusResultCheckModeProperty); }
set { SetValue(StatusResultCheckModeProperty, value); }
}
public bool IsRunning
{
get { return (bool)GetValue(IsRunningProperty); }
set { SetValue(IsRunningProperty, value); }
}
public bool HasStatus
{
get { return (bool)GetValue(HasStatusProperty); }
set { SetValue(HasStatusProperty, value); }
}
public bool IsInStatusWarning
{
get { return (bool)GetValue(IsInStatusWarningProperty); }
set { SetValue(IsInStatusWarningProperty, value); }
}
public bool IsInStatusNone
{
get { return (bool)GetValue(IsInStatusNoneProperty); }
set { SetValue(IsInStatusNoneProperty, value); }
}
}
public enum StatusResultCheckMode
{
None,
Warning
}
}
If you want to do something like this, it probably will be easier if you just apply a style to a existing checkbox.
You can get the default style from here and change what you need.
Ended up refactoring all this code and simplifying. Basically styled a togglebutton.
<ToggleButton Grid.Column="3" Style="{StaticResource WarningStyle}"/>
And in the resource file...
<Style x:Key="WarningStyle" TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Popup Name="WarningMessagePopup"
Width="425"
Height="150"
HorizontalAlignment="Center"
HorizontalOffset="22"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked}"
StaysOpen="False"
VerticalOffset="7">
<ListBox ItemsSource="{Binding Path=WarningList}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Background" Value="#F8FCFF"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="Count" />
<Binding Path="Message" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsInStatusWarning.Value}" Value="True" >
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
I have simple custom control that shows a message to user (something like browser's Info bar).
I have added a Boolean Dependency Property that indicate an error message. If flag is set the background color of control should be red otherwise yellow.
Here is style for the control(in Themes\Generic.xaml):
<Style TargetType="{x:Type local:InfoBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:InfoBar}">
<ControlTemplate.Triggers>
<Trigger Property="IsError" Value="True" >
<Setter Property="Background" Value="LightPink" />
</Trigger>
<Trigger Property="IsError" Value="False" >
<Setter Property="Background" Value="LightYellow" />
</Trigger>
</ControlTemplate.Triggers>
<Grid Margin="4,0,4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{TemplateBinding Message}" Padding="5" FontWeight="Normal" TextWrapping="Wrap" Grid.Column="0"/>
<Button x:Name="PART_CloseButton" Grid.Column="1" VerticalAlignment="Top" >
<Button.Template>
<ControlTemplate>
<Border HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="Transparent">
<Image Height="16" Width="16" Source="/QOffice.Common.Controls;component/Images/icons/Close.png" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is The control itself:
[TemplatePart(Name = PART_CloseButton, Type = typeof(ButtonBase))]
public class InfoBar : Control
{
private const string PART_CloseButton = "PART_CloseButton";
static InfoBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InfoBar), new FrameworkPropertyMetadata(typeof(InfoBar)));
}
#region CloseButton
private ButtonBase _closeButton;
/// <summary>
/// Gets or sets the CloseButton template part.
/// </summary>
private ButtonBase CloseButton
{
get
{
return _closeButton;
}
set
{
if (_closeButton != null)
{
_closeButton.Click -= OnButtonClick;
}
_closeButton = value;
if (_closeButton != null)
{
_closeButton.Click += OnButtonClick;
}
}
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
this.Visibility = System.Windows.Visibility.Collapsed;
}
#endregion
public override void OnApplyTemplate()
{
CloseButton = GetTemplateChild(PART_CloseButton) as ButtonBase;
}
#region DependencyProperty Message of InfoBar
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(InfoBar),
new UIPropertyMetadata());
#endregion
#region DependencyProperty IsError of InfoBar
public bool IsError
{
get { return (bool)GetValue(IsErrorProperty); }
set { SetValue(IsErrorProperty, value); }
}
public static readonly DependencyProperty IsErrorProperty =
DependencyProperty.Register("IsError", typeof(bool), typeof(InfoBar),
new UIPropertyMetadata());
#endregion
}
As you can see I have defined a property IsError and a trigger to set the background of the control.
But the background is always transparent. Other than that the control if functional.
What is wrong?
It seems that your Custom Control is not setting Background Color properly even if I add Background color manually. I am not sure why this is, hopefully someone can elaborate. I did fix your issue though by changing the color of the Grid in your style using:
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:InfoBar}}, Path=IsError}" Value="True">
<Setter Property="Background" Value="LightPink" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:InfoBar}}, Path=IsError}" Value="False">
<Setter Property="Background" Value="LightYellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
This triggers the background color of the grid based on the IsError value in your InfoBar control.
You give your Control a Background but no child is using it. Two possible solutions:
TemplateBinding
<Grid Margin="4,0,4,0" Background="{TemplateBinding Background}">
DataTrigger with TargetName
<ControlTemplate TargetType="{x:Type local:InfoBar}">
<Grid Name="grid" Margin="4,0,4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
...
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsError" Value="True" >
<Setter TargetName="grid" Property="Background" Value="LightPink" />
</Trigger>
<Trigger Property="IsError" Value="False" >
<Setter TargetName="grid" Property="Background" Value="LightYellow" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
In this solution you have to change the order: ControlTemplate.Triggers after Grid declaration.
Try this (Since IsError is DP in your InfoBar and not property of your ControlTemplate)
<DataTrigger Property="{Binding IsError, RelativeSource={RelativeSource
Mode=TemplatedParent}" Value="True">
<Setter Property="Background" Value="LightPink" />
</DataTrigger>
<DataTrigger Property="{Binding IsError, RelativeSource={RelativeSource
Mode=TemplatedParent}" Value="False">
<Setter Property="Background" Value="LightYellow" />
</DataTrigger>