How can i pass properties to WPF style with DependencyProperty? - wpf

Im trying to create a reusable style for a button with WPF, and i want to pass parameters from .xaml but it's not working even when i insert a breakpoint in the ButtonStyle Class while debugging he don't enter in it, i miss something and i didn't find it.
Thank you.
ButtonStyle Class :
Public Class ButtonStyle
Inherits System.Windows.Controls.Button
Public Sub New()
End Sub
Public Shared ReadOnly BackgroundColorProperty As DependencyProperty =
DependencyProperty.Register("BackgroundColor", GetType(Brush),
GetType(ButtonStyle))
Public Property BackgroundColor As Brush
Get
Return CType(GetValue(BackgroundColorProperty), Brush)
End Get
Set(value As Brush)
SetValue(BackgroundColorProperty, value)
End Set
End Property
Public Shared ReadOnly BackgroundColorHoverProperty As DependencyProperty
= DependencyProperty.Register("BackgroundColorHover", GetType(Brush),
GetType(ButtonStyle))
Public Property BackgroundColorHover As Brush
Get
Return CType(GetValue(BackgroundColorHoverProperty), Brush)
End Get
Set(value As Brush)
SetValue(BackgroundColorHoverProperty, value)
End Set
End Property
Public Shared ReadOnly BorderColorProperty As DependencyProperty =
DependencyProperty.Register("BorderColor", GetType(Brush),
GetType(ButtonStyle))
Public Property BorderColor As Brush
Get
Return CType(GetValue(BorderColorProperty), Brush)
End Get
Set(value As Brush)
SetValue(BorderColorProperty, value)
End Set
End Property
Public Shared ReadOnly BorderColorHoverProperty As DependencyProperty =
DependencyProperty.Register("BorderColorHover", GetType(Brush),
GetType(ButtonStyle))
Public Property BorderColorHover As Brush
Get
Return CType(GetValue(BorderColorHoverProperty), Brush)
End Get
Set(value As Brush)
SetValue(BorderColorHoverProperty, value)
End Set
End Property
Public Shared ReadOnly IconeProperty As DependencyProperty =
DependencyProperty.Register("Icone", GetType(ImageSource),
GetType(ButtonStyle))
Public Property Icone As ImageSource
Get
Return CType(GetValue(IconeProperty), ImageSource)
End Get
Set(value As ImageSource)
SetValue(IconeProperty, value)
End Set
End Property End Class
MainWindow.xaml :
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DictionaryResources.xaml"/>
</ResourceDictionary.MergedDictionaries >
</ResourceDictionary>
</Window.Resources>
<Grid>
<local:ButtonStyle Width="150" Height="50" Style="{StaticResource
StyleBoutonHello}" Icone="img.png" BorderColor="red"
BackgroundColor="red" BackgroundColorHover="Blue" BorderColorHover="blue"
Content="Hello"></local:ButtonStyle>
</Grid>
DictionnaryResource.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<Style x:Name="StyleBoutonHello" x:Key="StyleBoutonHello" TargetType="
{x:Type local:ButtonStyle}">
<Setter Property="BorderBrush" Value="{Binding BorderColor}" />
<Setter Property="Background" Value="red" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Content" Value="Hello" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Image Name="Img" Width="30" Height="30" Source="{Binding
Icone, RelativeSource={RelativeSource TemplatedParent}}" Stretch="None"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" TargetName="Img"
Value="{Binding Icone, RelativeSource={RelativeSource TemplatedParent}}"
/>
<Setter Property="Background" Value="{Binding
BackgroundColorHover}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

For Example:-
1) Implement INotifyPropertyChanged Interface.
Class MainWindow
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Shared ReadOnly MyProperty1 As DependencyProperty = DependencyProperty.Register("UserName", GetType(String), GetType(MainWindow), New UIPropertyMetadata(String.Empty))
Public Property UserName() As String
Get
Return DirectCast(GetValue(MyProperty1), String)
End Get
Set
SetValue(MyProperty1, Value)
OnPropertyChanged("Name")
End Set
End Property
End Class
2) Now bind the value in XAML
<TextBlock Text="{Binding UserName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
Now whenever you change UserName it will reflect to UI!!!.

Related

WPF DependencyProperty Within a UserControl Bound to another UserControl not updating on coerce

I have a parent usercontrol(NewSymbologyComboBox) with a contentpresenter that displays one of two controls.
The NewSymbologyComboBox control has a "SelectedOption" dependency property
<ContentPresenter x:Name="Presenter" >
<!-- Presentations -->
<ContentPresenter.Resources>
<DataTemplate x:Key="EditablePresenter">
<editableCombobox:EditableSymbologyComboBox SelectedOption="{Binding SelectedOption, ElementName=UserControl}" />
</DataTemplate>
<DataTemplate x:Key="DropdownPresenter">
<dropdownCombobox:DropdownSymbologyComboBox SelectedOption="{Binding SelectedOption, ElementName=UserControl}" />
</DataTemplate>
</ContentPresenter.Resources>
<!-- Triggers -->
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEditable, ElementName=UserControl}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource DropdownPresenter}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsEditable, ElementName=UserControl}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditablePresenter}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
/// <summary>
/// Property for the symbology setter to use on the plotted elements.
/// </summary>
public static readonly DependencyProperty SelectedOptionProperty = DependencyProperty.Register("SelectedOption", typeof(ISymbologyOption), typeof(NewSymbologyComboBox), new FrameworkPropertyMetadata(new UserDefinedSymbologyOption(""), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is ISymbologyOption option)
{
System.Diagnostics.Debug.Print(option.DisplayName); //This is not called on the coerced initial change.
}
}
Both the EditableSymbologyComboBox and the DropdownSymbologyComboBox have a dependencyproperty SelectedOption in them as well.
public static readonly DependencyProperty SelectedOptionProperty = DependencyProperty.Register("SelectedOption", typeof(ISymbologyOption), typeof(DropdownSymbologyComboBox), new FrameworkPropertyMetadata(new UserDefinedSymbologyOption(""), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback, CoerceValueCallback));
The DropDownSymbologyComboBox has a coerce callback which modifies the value if it is not in the available options.
private static object CoerceValueCallback(DependencyObject d, object basevalue)
{
if (d is DropdownSymbologyComboBox comboBox)
{
if (!comboBox._isInitialized)
comboBox.PopulateOptions();
if (basevalue is UserDefinedSymbologyOption userDefinedSymbology)
{
var matchingOption = comboBox.AvailableOptions.FirstOrDefault(option => string.Equals(userDefinedSymbology.DisplayName, option.DisplayName, StringComparison.InvariantCultureIgnoreCase));
if (matchingOption != null)
return matchingOption;
return comboBox.AvailableOptions.FirstOrDefault();
}
}
return basevalue;
}
On My window I am displaying the selection as shown
<combobox:NewSymbologyComboBox Margin="10 5 0 0" SelectedOption="{Binding PlotOptions.SelectedSymbology}" IsEditable="False"/>
<TextBlock Text="{Binding PlotOptions.SelectedSymbology.DisplayName}"/>
My default value in my view model is set to "My First Selection" which is not available in the options and should be coerced to the first item in the list... The coerce function finds this and sets it to the first available option...
The problem I am having is that my window is not updating with this change. It is displaying "My First Selection" until I make a change in the combobox manually.
Any help would be great! Thanks

How to dynamically set style of an element in a User Control

In a WPF project, I have a user control (Valve.xaml) that defines a Polygon shape.
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
</Grid>
I am displaying the Valve user control in a window xaml (FFG.xaml) file, like such:
<Window
<!-- removed other namespaces for brevity -->
xmlns:cl="clr-namespace:FFG.Controls;assembly=PID.Controls">
<Grid>
<cl:Valve x:Name="valve201A"></cl:Valve>
</Grid>
</Window>
I am setting the DataContext of FFG.xaml to class FFG_ViewModel.cs, and it contains an instance of the Valve_Model class. Valve_Model essentially represents the valve that is drawn on the window in FFG.xaml.
public class FFG_ViewModel : ViewModelBase {
public Valve_Model Valve201A { get; set; }
// There are other properties and methods, but I've removed them for brevity also
}
Here is the Valve_Model class:
public class Valve_Model INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private bool _isValveOpen { get; set; }
public bool IsValveOpen {
get {
return _isValveOpen;
}
set {
_isValveOpen = value;
OnPropertyChanged("IsValveOpen");
}
}
#region INotifyPropertyChanged
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
}
QUESTION:
What I want is for the Style property in the Valve.xaml to change when the IsValveOpen property changes.
So if the valve is open then it would be:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}"/>
and when the property is changed to false then I need the style of the polygon to be changed to:
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Closed}"/>
How do I go about implementing this exactly?
You could use an IMultiValueConverter.
First, let's simplify the use case. Basicly you want to swap Styles based on a given state object, which I'll represent by a ToggleButton. The fact that you're wrapping everything in a UserControl also has no influence on the underlying concept.
Demo:
Starting a fresh project
Declaring our Resources
Feeding the Window and the state to the Converter.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<local:ToStyleConverter x:Key="ToStyleConverter"/>
<Style x:Key="Valve_Open" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style x:Key="Valve_Closed" TargetType="{x:Type Polygon}">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="butt" DockPanel.Dock="Bottom">Switch</ToggleButton>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Stretch="Uniform">
<Polygon.Style>
<MultiBinding Converter="{StaticResource ToStyleConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
<Binding ElementName="butt" Path="IsChecked"/>
</MultiBinding>
</Polygon.Style>
</Polygon>
</DockPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ToStyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Window)
{
Window win = (Window)values[0];
if ((bool)values[1])
return win.FindResource("Valve_Open");
if (!(bool)values[1])
return win.FindResource("Valve_Closed");
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object values, Type[] targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Changing to any specific use case means:
Pointing the relativesource binding to the Control that holds the Resources (Styles)
Using the second binding to add the state to the Converter (DP/INPC)
Implementing Converter logic
You can (should as much as I know) use a DataTrigger, within a ControlTemplate. Assuming that these two are your Styles:
<Window.Resources>
<Style TargetType="Polygon" x:Key="Valve_Open">
<Setter Property="Fill" Value="Red"/>
</Style>
<Style TargetType="Polygon" x:Key="Valve_Close">
<Setter Property="Fill" Value="Green"/>
</Style>
</Window.Resources>
You should add this style to the resources:
<Style x:Key="changeStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20" Style="{StaticResource Valve_Open}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Valve201A.IsValveOpen}" Value="true">
<Setter TargetName="pValve" Property="Style" Value="{StaticResource Valve_Close}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and use them in your views:
<Control DataContext="{Binding}" Style="{StaticResource changeStyle}" />
Instead of setting the actual Style property to a new value, you could add a DataTrigger to the Style itself that changes the properties of the Polygon based on the value of the IsValveOpen source property.
Valve.xaml:
<Grid>
<Polygon Name="pValve" Points="0,50 0,20 50,50 50,20">
<Polygon.Style>
<Style TargetType="Polygon">
<!-- Copy the setters from the Valve_Closed style here -->
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValveOpen}" Value="True">
<!-- Copy the setters from the Valve_Open style here -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Polygon.Style>
</Polygon>
</Grid>
FFG.xaml:
<Grid>
<cl:Valve x:Name="valve201A" DataContext="{Binding Valve201A}" />
</Grid>

WPF button property binding to code

I am trying to bind ribbon buttons property IsEnabled to code property so I can make it available or not to the user depending on a certain situation.
I don't know what I am doing wrong and I can't figure it out
Currently I have the following code.
The class ViewModel for the property and the event
Public Class ViewModel
Implements INotifyPropertyChanged
Private _btnStart As Boolean
Public Property btnStartVM() As Boolean
Get
Return _btnStart
End Get
Set(ByVal value As Boolean)
_btnStart = value
NotifyPropertyChanged("btnStartVM")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Button declaration on the xaml
<RibbonButton x:Name="btnStart" Style="{StaticResource btnTriggers}" SmallImageSource="Images/start.ico" Label="Start"/>
and the Style
<Style x:Key="btnTriggers" TargetType="{x:Type RibbonButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=btnStartVM , ElementName=btnStart}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=btnStartVM, ElementName=btnStart}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
Public Sub Window_Loaded(ByVal sender As System.Object, ByVal e As RoutedEventArgs) Handles MyBase.Loaded
InitializeComponent()
DataContext = New ViewModel()
DataContext.btnStartVM = True
End Sub
The DataContext.btnStartVM = True does the job and it triggers the NotifyPropertyChanged but it dosen`t reflect back to the UI.
Your property is named differently from the property you are reporting in your notification.
The property is named btnStartVM but you are reporting btnStart. Change the call to this:
NotifyPropertyChanged("btnStartVM")
Additionally, the binding looks very off. You should probably remove the , ElementName=btnStart part.
Finally, you don't need to work with triggers here. The following style should work just as well:
<Style x:Key="btnTriggers" TargetType="{x:Type RibbonButton}">
<Setter Property="IsEnabled" Value="{Binding btnStartVM}" />
</Style>

WPF Custom Control Property Setter

I have a very simple Button-based control that displays an ellipse with color taken from custom dependency control called "Brush".
The template displays ellipse with proper color, but the Setters in Trigger do not recognize the "Brush" property (errors highlighted in the XAML file below).
How to access the "Brush" property in the setter so I can change its value on MouseOver?
XAML:
<Button x:Class="WpfTest.EllipseButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTest"
Style="{DynamicResource localStyle}"
Name="ellipseButton">
<Button.Resources>
<Style x:Key="localStyle"
TargetType="local:EllipseButton">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Fill="{Binding ElementName=ellipseButton, Path=Brush}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<!-- ERROR HERE: The property "Brush" is not a dependency property. -->
<Setter Property="Brush"
Value="Blue" />
<!-- ERROR HERE: The "BrushProperty" is not recognized or is not accessible. -->
<Setter Property="BrushPropety"
Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Resources>
<Grid>
</Grid>
</Button>
code-behind:
public partial class EllipseButton : Button
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
public Brush Brush
{
get
{
return (Brush)GetValue(BrushProperty);
}
set
{
SetValue(BrushProperty, value);
}
}
public EllipseButton()
{
InitializeComponent();
}
}
Your property is called "Fill" not "Brush":
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Fill", //Error is here
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
Change that to:
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Brush",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Gray)));
Okay, several things needed to be done:
1) Rename dependency property name to "Brush" (it was wrongly named "Fill") - thanks to HighCore
2) When the control is used in code, remove setting the "Brush" property - the local value overriden the setters from style.
3) Move the style from custom control to higher level (e.g. under "Themes\Generic.xaml")
4) Remove x:Key attribute from the style and keep just the type name (still don't know why...)
5) Add default value of the "Brush" property to the style setter (again, not sure why...)
Fixed EllipseButton.xaml:
<Button x:Class="WpfTest.EllipseButton" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid/>
</Button>
fixed code-behind:
public partial class EllipseButton
{
public static readonly DependencyProperty BrushProperty = DependencyProperty.Register(
"Brush",
typeof(Brush),
typeof(EllipseButton),
new FrameworkPropertyMetadata(null));
public Brush Brush
{
get
{
return (Brush)GetValue(BrushProperty);
}
set
{
SetValue(BrushProperty, value);
}
}
public EllipseButton()
{
InitializeComponent();
}
}
fixed style (Generic.xaml):
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTest">
<Style TargetType="local:EllipseButton">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Fill="{Binding ElementName=ellipseButton, Path=Brush}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Brush" Value="Pink"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Brush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>

WPF: Using a button as a DataTemplate, how to add click event

I have a ItemsControl with a collection of objects. I wan't to be able to click the object and then get a panels with more info.
So I decided to style the DataTemplate for the items in the ItemsControl as a button and that seems to work fine. However I have no idea how to set the click event of this button in the style. It says I should use a EventSetter but I can't get that to work.
Here is the code:
<Style TargetType="Expander" >
<Style.Resources>
<Style TargetType="ItemsControl" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border BorderThickness="0,1,0,1" BorderBrush="{StaticResource DarkColorBrush}" >
<ScrollViewer Margin="0" VerticalScrollBarVisibility="Auto"
Focusable="false">
<StackPanel Margin="2" IsItemsHost="True" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type data:CompanyViewModel}" >
<Button>
<Button.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="Bd" BorderBrush="{StaticResource DarkColorBrush}"
BorderThickness="1"
Margin="5"
CornerRadius="8">
<Border.Background>
<!-- Removed for brevity -->
</Border.Background>
<StackPanel Orientation="Vertical">
<TextBlock Margin="5" Text="{Binding Path=Name}" Style="{StaticResource MenuText}" FontSize="16" HorizontalAlignment="Center" />
<TextBlock Margin="5,0,5,5" Text="{Binding Path=Code, StringFormat=Kt. {0}}" Style="{StaticResource MenuText}" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Resources>
</Button>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="Expander">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="1"
IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter Name="Content" Grid.Column="0" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="Content" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Decided to add what I wanted to accomplish with the button click:
<Button Click="CompanyClick" />
CompanyClick being defined in the code behind.
Change
<Button>
To...
<Button Command="{Binding OnClick}" />
On the class you use as an item in this ItemsControl, implement a read-only property which returns an ICommand for the button to use.
EDIT:
For this example, I made use of an implementation of ICommand called RelayCommand, which is available at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx. See figure 3 of that article for the full RelayCommand class in C#. I converted it to Visual Basic for my use, that code is below. It does nothing more than automate the registration of commands with the WPF system, and provides you with a convenient constructor:
''' <summary>
''' Implements the ICommand interface
''' </summary>
''' <remarks>
''' Thanks to Josh Smith for this code: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
''' </remarks>
Public Class RelayCommand
Implements ICommand
#Region "Fields"
Private ReadOnly _execute As Action(Of Object)
Private ReadOnly _canExecute As Predicate(Of Object)
#End Region ' Fields
#Region "Constructors"
Public Sub New(ByVal execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
#End Region ' Constructors
#Region "ICommand Members"
<DebuggerStepThrough()>
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_execute(parameter)
End Sub
#End Region ' ICommand Members
End Class
Using that class, you can then implement ICommands on your ViewModel, by exposing an ICommand as a read-only property in that class, along with a backing field to store the RelayCommand, which, don't forget, implements ICommand. Here's a truncated sample:
Public Class CompanyViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _OnClick As RelayCommand
Public ReadOnly Property OnClick As ICommand
Get
If _OnClick Is Nothing Then
_OnClick = New RelayCommand(Sub()
Me.OnClickExecute()
End Sub,
Function()
Return Me.OnClickCanExecute()
End Function)
End If
Return _OnClick
End Get
End Property
Private Function OnClickCanExecute() As Boolean
' put a test here to tell the system whether conditions are right to execute your command.
' OR, just return True and it will always execute the command.
End Function
Private Sub OnClickExecute()
' put the processing for your command here; THIS IS YOUR EVENT HANDLER
End Sub
' .... implement the rest of your ViewModel
End Class
The "OnClick" name is not required; the commands can take any name, because the system is not convention-based the way that VB6 was with its event handlers.
There is more than one way to do this. I'm intrigued by the "Caliburn.Micro" implementation of ICommand, which is convention-based and might make things more readable, depending on your style. But, Caliburn is an open-sourced effort by an enthusiast, albeit a very competent and qualified enthusiast. Google or Bing "Caliburn.Micro" for more information on that.
There's also this:
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<!-- Animations manipulating the button here -->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- The rest of your triggers here -->
</ControlTemplate.Triggers>
This kind of mechanism in your template will give you control over the properties of the button, and possibly properties in other parts of the visual tree, depending on where you put the definiton.
You might also consider architecting things a bit differently. I wouldn't necessarily pile all the definitions into the style in quite the same way.

Resources