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

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.

Related

Command binding issue in CustomControl: CanExecute() fires, Execute() does not

I'm binding a button command, in a ControlTemplate, to an Execute() method in a CustomControl. I'm using a RoutedCommand, the CanExecute() fires, but the Execute() never does. When the CustomControl is placed in the main window, the code works as expected. When it is placed in a Usercontrol, I have this issue. I have tried several ways to wire up the buttons Command (RelayCommand etc) but can't seem to figure out what's wrong. Any help is appreciated.
For context, this is a TokenizingTextBox control - an early fork of the Xceed open source version. The button is for deleting the token from the list of tokens.
The complete style of a TokenIten (which contains the button of interest):
<Style TargetType="{x:Type local:TokenItem}">
<Setter Property="Background" Value="#F3F7FD" />
<Setter Property="BorderBrush" Value="#BBD8FB" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Padding" Value="2,1,1,1" />
<Setter Property="Margin" Value="1,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TokenItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
CornerRadius="0,0,5,5"
Margin="{TemplateBinding Margin}"
>
<StackPanel Orientation="Horizontal" Margin="1" x:Name="myRoot">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" />
<Button Margin="3,0,0,0" Cursor="Hand"
Command="{x:Static local:TokenizedTextBoxCommands.Delete}" CommandParameter="{TemplateBinding TokenKey}"
PresentationTraceSources.TraceLevel="High">
<!--<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter />
</ControlTemplate>
</Button.Template>-->
<Image Source="/Resources/delete8.png" Width="8" Height="8" />
</Button>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The static Command:
public static class TokenizedTextBoxCommands
{
private static RoutedCommand _deleteCommand = new RoutedCommand();
public static RoutedCommand Delete => _deleteCommand;
}
The Custom Control inherits from ItemsControl. In the non-static constructor, we wire up the static delete command to the DeleteToken method:
public TokenizedTextBox()
{
CommandBindings.Add(new CommandBinding(TokenizedTextBoxCommands.Delete, DeleteToken, CanDelete));
}
Finally CanDelete which just sets CanExecute to true:
private void CanDelete(object sender, CanExecuteRoutedEventArgs canExecuteRoutedEventArgs)
{
canExecuteRoutedEventArgs.CanExecute = true;
}
And DeleteToken - functionality omitted, signature is really only important thing here:
private void DeleteToken(object sender, ExecutedRoutedEventArgs e)
{
...
}
So, hopefully this is enough information for anyone interested in providing guidance/suggestions. Thanks.
Little interest here so I hired a Mentor through Pluralsight. The bindings were correct, but the CustomControl had a RichTextBox which was capturing the Mouse Click. We fixed the issue using a Behavior targeting the Button's PreviewMouseDown.

How to Retrieve Input from User Control without using Events

I have a very Unusual situation. I need to show a User Control on a Form to get the users input. But I would like to not have to try and retrieve the information through events. The reason is that it splits up my code to do one thing into three different methods and I would like to try and prevent that. If there is a way I can do this, that would be awesome.
I have tried using byRef variables but because they are both in the UI thread my UserControl does not get shown.
Thank you in advance
Window
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel >
<Button Name="btnTest" Click="btnTest_Click" Content="Click Me!" />
<Button Name="btnTest2" Click="btnTest2_Click" Content="Click Me Too!" />
</StackPanel>
<me:ucInputBox x:Name="Input" Visibility="Collapsed" />
</Grid>
User Control
<UserControl x:Class="ucInputBox"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" x:Name="InputBox">
<UserControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border" Background="{TemplateBinding Background}" CornerRadius="4" BorderThickness="2">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#ECECEC" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background" Value="#CECECE" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Opacity" Value="0.7" />
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid >
<Grid Background="Black" Opacity="0.5"/>
<Border
MinWidth="250"
Background="WhiteSmoke"
BorderBrush="Black"
BorderThickness="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel >
<TextBlock Name="txbInput" Margin="10" Text="Input Box:" FontWeight="Bold" FontFamily="Cambria" />
<TextBox MinWidth="150" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="InputTextBox" Width="210" TextWrapping="WrapWithOverflow" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5,0,0">
<Button x:Name="OKButton" Content="Ok" Margin="5" Click="OKButton_Click" IsDefault="True" Height="30" Width="50"/>
<Button x:Name="CancelButton" Content="Cancel" Margin="5" Click="CancelButton_Click" IsCancel="True" Height="30" Width="50" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
Code Behind
Class MainWindow
#Region " Split Method"
Private Sub btnTest_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
'\\\\\\\\\ Button using split method
Input.Visibility = Windows.Visibility.Visible
End Sub
Private Sub Input_Submit(Response As String) Handles Input.Submit
'\\\\\\\ since this is the event all code that depends on the input goes here.
'\\\\\\\ If Multiple methods need input this blows up
End Sub
#End Region
Private Sub btnTest2_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Input.HandledLocally = True
'\\\\\\\\\ This method gets stuck and never shows becuase the while loop is in the UI thread
Dim holder = Input.GetInput()
End Sub
End Class
Code Behind User Control
Public Class ucInputBox
Public Event Submit(ByVal Response As String)
Private _TextBoxString As String
Public Property TextBoxString() As String
Get
Return _TextBoxString
End Get
Set(ByVal value As String)
_TextBoxString = value
txbInput.Text = _TextBoxString
End Set
End Property
Private _WaitingForInput As Boolean = True
Public Property WaitingForInput() As Boolean
Get
Return _WaitingForInput
End Get
Set(ByVal value As Boolean)
_WaitingForInput = value
End Set
End Property
Private _HandledLocally As Boolean
Public Property HandledLocally() As Boolean
Get
Return _HandledLocally
End Get
Set(ByVal value As Boolean)
_HandledLocally = value
End Set
End Property
Public Function GetInput() As String
InputBox.Visibility = Windows.Visibility.Visible
While Not WaitingForInput
'\\\\\\ Wait for input
End While
Dim ret As String
ret = InputTextBox.Text
InputTextBox.Text = String.Empty
Return ret
End Function
Private Sub CancelButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
InputBox.Visibility = Windows.Visibility.Collapsed
InputTextBox.Text = String.Empty
If HandledLocally Then
WaitingForInput = False
Else
RaiseEvent Submit(String.Empty)
End If
End Sub
Private Sub OKButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
InputBox.Visibility = Windows.Visibility.Collapsed
If HandledLocally Then
WaitingForInput = False
Else
RaiseEvent Submit(String.Empty)
InputTextBox.Text = String.Empty
End If
'RaiseEvent Submit(String.Empty)
'InputTextBox.Text = String.Empty
End Sub
Private Sub UserControl_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
txbInput.Text = _TextBoxString
InputTextBox.Focus()
OKButton.IsEnabled = False
End Sub
Private Sub InputTextBox_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles InputTextBox.TextChanged
If InputTextBox.Text.Trim <> String.Empty Then
OKButton.IsEnabled = True
Else
OKButton.IsEnabled = False
End If
End Sub
End Class

WPF custom shaped button template using path as attached property

I'm still pretty new to WPF. I'm trying to create a button template that uses path data for the button shape. My application has several buttons that will perform similar tasks, but will need different shapes. I'm trying to create an attached property that will pass the path data to my template. What I've got so far:
The attached property:
Public Class CustomShapeButton
Inherits Button
Public Shared PathDataProperty As DependencyProperty = DependencyProperty.RegisterAttached("PathData", GetType(Path), GetType(CustomShapeButton), New PropertyMetadata(Nothing))
Public Shared Sub SetPathData(obj As DependencyObject, value As Path)
obj.SetValue(PathDataProperty, value)
End Sub
Public Shared Function GetPathData(obj As DependencyObject) As Path
Return DirectCast(obj.GetValue(PathDataProperty), Path)
End Function
End Class
And the button template, located in my resource dictionary:
<Style x:Key="TransparentNavButton" TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Path x:Name="pth" Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(hmi:CustomShapeButton.PathData)}" Stroke="Black" Stretch="Fill"/>
<ContentPresenter x:Name="ButtonContentPresenter" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="pth" Property="Fill" Value="#60FF0000"/>
<Setter TargetName="pth" Property="Stroke" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then trying to implement it something like this:
<Button Content="Button" Width="60" Height="50" Style="{StaticResource TransparentNavButton}">
<hmi:CustomShapeButton.PathData>
<Path Data="M1 1 L20 0 L20 20 L0 20 Z"/>
</hmi:CustomShapeButton.PathData>
</Button>
I can't seem to get the path data to show up on the button. What am I missing?
Your problem is that you're creating an attached property of type Path but assigning it to the PathData property in your template, which is of type Geometry. To fix this, change the attached property declaration like so:
Public Class CustomShapeButton
Public Shared PathDataProperty As DependencyProperty = DependencyProperty.RegisterAttached("PathData", GetType(Geometry), GetType(CustomShapeButton), New PropertyMetadata(Nothing))
Public Shared Sub SetPathData(obj As DependencyObject, value As Geometry)
obj.SetValue(PathDataProperty, value)
End Sub
Public Shared Function GetPathData(obj As DependencyObject) As Geometry
Return DirectCast(obj.GetValue(PathDataProperty), Geometry)
End Function
End Class
Then use it like this:
<Button Content="Button" Width="60" Height="50"
hmi:CustomShapeButton.PathData="M1 1 L20 0 L20 20 L0 20 Z"
Style="{StaticResource TransparentNavButton}" />
One more thing to note: your CustomShapeButton class doesn't need to inherit from Button - in fact, it doesn't have to inherit from anything at all. If however you'd like to create your own derived button class that has the PathData property, then you could inherit from Button, in which case you would declare PathData as a regular dependency property and not as an attached property.

Custom button user control style overwritten when content is set

I am currently using Blend and some tutorials I found online to try to make my own button user control. Normally, I would just use a custom style and apply it to the controls I want, but I wrote in some dependency properties as well so I decided to write my own user control.
Something that I can't get my head around is how to set the Content property without overwriting the styling of the button control. I thought it might have been my code, but I started brand new with another button and I have the same thing happen - when the Content property is set, the button simply turns white and has black text in the upper left corner.
Here is the XAML code that Blend generated for me:
<UserControl x:Class="MyUI.Controls.MyButton"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="100">
<UserControl.Resources>
<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle/>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True"/>
<Trigger Property="IsDefaulted" Value="True"/>
<Trigger Property="IsMouseOver" Value="True"/>
<Trigger Property="IsPressed" Value="True"/>
<Trigger Property="IsEnabled" Value="False"/>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Button Content="Button" Height="25" Style="{DynamicResource MyButtonStyle}" Width="100"/>
</Grid>
Now, if I reference the button in my main window like this, it reverts any styling that was done within the user control:
<local:MyButton Content="test" />
What do I need to change in order to make the button accept a different Content tag?
What you need is to connect your UserControl level Content property to your Button level Content property. By default UserControl's Content property is its only child element, which is Grid in your case. You can either create your own Content property or another one with different name. In your UserControl's .cs file:
/// <summary>
/// Interaction logic for MyButton.xaml
/// </summary>
public partial class MyButton : UserControl
{
public new static DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof (object),
typeof (MyButton));
public new object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public MyButton()
{
InitializeComponent();
}
}
On your UnserControl's Xaml:
<Button Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="25" Style="{DynamicResource MyButtonStyle}" Width="100"/>
Now Button's Content binds to your UserControl level Content property.

How to bind Fill property to a custom property into a controltemplate

I have a button control which its template is stilyzed in an external resource Theme.xaml. Below the controltemplate definition:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Grid x:Name="Grid">
<Border x:Name="Background" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="2,2,2,2" CornerRadius="2,2,2,2">
<Border x:Name="Hover" Background="{StaticResource HoverBrush}" CornerRadius="1,1,1,1" Height="Auto" Width="Auto" Opacity="0"/>
</Border>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
...
Now I added an item which is an ellipse that must be filled with red or green color (as a semaphore) depending on a custom property defined into my usercontrol:
<UserControl.Resources>
<ResourceDictionary Source="Themes/theme.xaml"/>
</UserControl.Resources>
<Grid>
<Button Click="Button_Click"></Button>
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5"></Ellipse>
</Grid>
and in the behind code I have:
private SolidColorBrush ButtonValue_;
public SolidColorBrush ButtonValue {
get { return ButtonValue_; }
set {
ButtonValue_ = value;
}
}
I'm trying to put into the CONTROLTEMPLATE this ellipse item, but i have some problems regarding how to BIND the Fill property of the ellipse with the ButtonValue custom property into the controlTemplate.
Any hints??
Thanks in advance
You can go to several directions:
Implement a custom control, that is your own class derived from an existing control (Button in your case). Add a dependency property (e.g. ButtonValue). Note - dependency property aren't standard .NET property - they have much more. Check out the following sample: http://msdn.microsoft.com/en-us/library/cc295235(v=expression.30).aspx (A custom button), or here: http://wpftutorial.net/HowToCreateACustomControl.html (A simpler sample, but without a property.
Have a data context for the control. Typically the data context is a separate class (a.k.a. the "View Model"), but if you aren't following the mvvm paradigm, it is OK the data context is self. Whatever data context you are using, it must derived from INotifyPropertyChanged, and it must file PropertyChanged event.
(Recommended!) Create a Control Template for CheckBox. When you come to think about it, logically your control is really a button with a binary state. Red/Green in your case, Checked/Unchecked for a CheckBox. So logically, you are looking for a checkbox, but you just want to present it differently.
So in your control template, draw the ellipse, and add a trigger for the IsChecked property:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type CheckBox}">
<Grid>
... everything else in the control ...
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ellipse1" Property="Fill" Value="Red" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="ellipse1" Property="Fill" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This is a nice example for the difference between behavior and presentation of WPF.
While your control may look like a button, it behaves like a CheckBox, in the sense that it has two states.
EDIT: Use ToggleButton - this is the base class of CheckBox (and RadioButton), and it has exactly the functionality that you need, including the IsChecked property.
You have a couple of options:
1.The easiest one is to re-purpose an unused Brush or Color(with a converter) Button existing property:
<Window.Resources>
<ControlTemplate x:Key="repurposedProperty" TargetType="Button">
<Border Background="{TemplateBinding BorderBrush}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource repurposedProperty}">Button</Button>
2.Other option is to define an attached property and use it in the ControlTemplate. On any Button that you apply the template to you have to set the attached property:
public static readonly DependencyProperty AttachedBackgroundProperty =
DependencyProperty.RegisterAttached("AttachedBackground", typeof (Brush), typeof (MainWindow),
new PropertyMetadata(default(Brush)));
public static void SetAttachedBackground(UIElement element, Brush value)
{
element.SetValue(AttachedBackgroundProperty, value);
}
public static Brush GetAttachedBackground(UIElement element)
{
return (Brush) element.GetValue(AttachedBackgroundProperty);
}
...
<
Window.Resources>
<ControlTemplate x:Key="attachedProperty" TargetType="Button">
<Border Background="{TemplateBinding WpfApplication1:MainWindow.AttachedBackground}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource attachedProperty}">
<WpfApplication1:MainWindow.AttachedBackground>
<SolidColorBrush Color="Pink"></SolidColorBrush>
</WpfApplication1:MainWindow.AttachedBackground>
Button</Button>
PS: you can use a binding to set the value of the attached property.

Resources