Bounty Rewarded for any solid tutorial/learning resources regarding wiring up events with templated controls.
I Have a control template like this:
<Style TargetType="local:DatePicker">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" x:Name="myDatePickerContentArea">
<StackPanel Orientation="Vertical">
<Button x:Name="myTestButton" Content="Test button" />
<telerik:RadDatePicker Style="{StaticResource VisitsReportTextBoxStyle}" Foreground="#FFFFFF" x:Name="startDate" DateTimeWatermarkContent="Start Date"/>
<telerik:RadDatePicker Style="{StaticResource VisitsReportTextBoxStyle}" x:Name="endDate" DateTimeWatermarkContent="End Date"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The C# for this template is:
public class DatePicker : Control
{
public static readonly DependencyProperty StartDateSelectedDateProperty = DependencyProperty.Register("StartDateSelectedDateProperty", typeof(DateTime), typeof(DatePicker), null);
public DateTime? StartDateSelectedDate { get; set; }
public DatePicker()
{
this.DefaultStyleKey = typeof(DatePicker);
}
public override void OnApplyTemplate()
{
RadDatePicker StartDate = this.GetTemplateChild("startDate") as RadDatePicker;
StartDate.SelectionChanged += new Telerik.Windows.Controls.SelectionChangedEventHandler(StartDate_SelectionChanged);
StartDate.SelectedDate = new DateTime(2010, 01, 01);
base.OnApplyTemplate();
}
void StartDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
RadDatePicker temp = (RadDatePicker)sender;
StartDateSelectedDate = temp.SelectedDate;
}
}
My selectionChanged Event Doesn't Fire and I'm not sure why.
Any Ideas ?
Here is an example of using best practice with the sort of control I think you are attempting to build (see notes at end for some explanations):-
[TemplatePart(Name = DatePicker.ElementStartDate, Type = typeof(RadDatePicker))]
[TemplatePart(Name = DatePicker.ElementEndDate, Type = typeof(RadDatePicker))]
public class DatePicker : Control
{
public DatePicker()
{
this.DefaultStyleKey = typeof(DatePicker);
}
#region Template Part Names
private const string ElementStartDate = "startDate";
private const string ElementEndDate = "endDate";
#endregion
#region Template Parts
private RadDatePicker _StartDate;
internal RadDatePicker StartDate
{
get { return _StartDate; }
private set
{
if (_StartDate != null)
{
_StartDate.SelectionChanged -= StartDate_SelectionChanged;
}
_StartDate = value;
if (_StartDate != null)
{
_StartDate.SelectionChanged += StartDate_SelectionChanged;
}
}
}
private RadDatePicker _EndDate;
internal RadDatePicker EndDate
{
get { return _EndDate; }
private set
{
if (_EndDate!= null)
{
_EndDate.SelectionChanged -= EndDate_SelectionChanged;
}
_EndDate= value;
if (_EndDate!= null)
{
_EndDate.SelectionChanged += EndDate_SelectionChanged;
}
}
}
#endregion
public static readonly DependencyProperty StartDateSelectedDateProperty =
DependencyProperty.Register(
"StartDateSelectedDateProperty",
typeof(DateTime?),
typeof(DatePicker),
new PropertyMetaData(new DateTime(2010, 01, 01)));
public DateTime? StartDateSelectedDate
{
get { return (DateTime?)GetValue(StartDateSelectedDateProperty); }
set { SetValue(StartDateSelectedDateProperty)}
}
public static readonly DependencyProperty EndDateSelectedDateProperty =
DependencyProperty.Register(
"EndDateSelectedDateProperty",
typeof(DateTime?),
typeof(DatePicker),
new PropertyMetaData(new DateTime(2010, 01, 01)));
public DateTime? EndDateSelectedDate
{
get { return (DateTime?)GetValue(EndDateSelectedDateProperty); }
set { SetValue(EndDateSelectedDateProperty)}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
StartDate = GetTemplateChild(ElementStartDate) as RadDatePicker;
EndDate = GetTemplateChild(ElementEndDate) as RadDatePicker;
}
void StartDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
// Do stuff with StartDate here
}
void EndDate_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
// Do stuff with EndDate here
}
}
The template Xaml should look like:-
<Style TargetType="local:DatePicker">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" x:Name="myDatePickerContentArea">
<StackPanel Orientation="Vertical">
<Button x:Name="myTestButton" Content="Test button" />
<telerik:RadDatePicker x:Name="startDate"
Style="{StaticResource VisitsReportTextBoxStyle}"
Foreground="#FFFFFF"
DateTimeWatermarkContent="Start Date"
SelectedDate="{TemplateBinding StartDateSelectedDate}"
/>
<telerik:RadDatePicker x:Name="endDate"
Style="{StaticResource VisitsReportTextBoxStyle}"
DateTimeWatermarkContent="End Date"
SelectedDate="{TemplateBinding EndDateSelectedDate}"
/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Some Explanations
A key problem your original code had was that it hadn't implemented the dependency properties correctly. Note that properties now use GetValue and SetValue, also that the property meta data is used to assign the default rather than attempting to set in in onapplytemplate.
With the properties correctly implemented the template binding should work and in fact we're done as far as getting what appears to be your original intent, hence I've left out any actual code in the event handlers.
Create constants in the code to hold the names of key template parts that you want to interact with, this allows for name changes to be less expensive to make.
Add TemplatePart attributes to the class to indicate the key elements that the code expects to find, what their names should be and what base type they are expected to have. This allows for a designer to re-template an existing control, as long the declared template parts are present somewher the control should function correctly even if its UI is radically altered.
If you need to attach event handlers for some elements create a field to hold a reference to the element and then create a property to wrap round it. The property setter should then detach and attach the event handlers as you see in the code.
Make sure bae.OnApplyTemplate is called in the override of OnApplyTemplate then as you can see its quite straight forward to assign the above created properties.
I don't have the RadDatePicker so I can't test, my only outstanding concern is where the DateTime? is the correct type for the SelectedDate property. Certainly if it is its an improvement over the Microsoft offering which seems to have drop the ball on this typical data entry requirement.
I may only guess that the problem is that for OnApplyTemplate method Implementers should always call the base implementation before their own implementation.
The other thing is that from your code it looks like it's better to use TemplateBinding(Archive)(V4) in the template xaml
<telerik:RadDatePicker SelectedDate={TemplateBinding StartDateSelectedDate}
Style="{StaticResource VisitsReportTextBoxStyle}"
Foreground="#FFFFFF" x:Name="startDate"
DateTimeWatermarkContent="Start Date"/>
Related
The TimeSpanUpDown (Extended WPF Toolkit) seems to have a display bug when the number of days changes from 0 to >0.
Here is a simple way to reproduce it:
<Window x:Class="TimeSpanBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Height="100" Width="200">
<StackPanel>
<!-- Bind both to the same TimeSpan property in the ViewModel -->
<xctk:TimeSpanUpDown Value="{Binding TimeSpan}"/>
<xctk:TimeSpanUpDown Value="{Binding TimeSpan}"/>
</StackPanel>
</Window>
Enter a time span close to but below 24h. The number of days is automatically hidden.
Then press the up-arrow on the first control to increase the time span to >24h. The control now updates its display to include the number of days. The second control receives the property changed notification and also tries to update, but ends up in a weird state:
Obviously, this is a bug and should be fixed by Xceed, but does anyone know a quick and easy fix or workaround?
Why not roll your own? Welcome to custom control authoring! A simple starting point without the above bug, which you can customize to your heart's content:
[TemplatePart(Name = "UP", Type = typeof(ButtonBase))]
[TemplatePart(Name = "DOWN", Type = typeof(ButtonBase))]
public class TimeSpinner : Control
{
static TimeSpinner()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeSpinner), new FrameworkPropertyMetadata(typeof(TimeSpinner)));
}
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set { SetValue(TimeProperty, value); }
}
// Using a DependencyProperty as the backing store for Time. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TimeProperty =
DependencyProperty.Register("Time", typeof(TimeSpan), typeof(TimeSpinner), new PropertyMetadata(TimeSpan.Zero));
public TimeSpan Interval
{
get { return (TimeSpan)GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}
// Using a DependencyProperty as the backing store for Interval. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IntervalProperty =
DependencyProperty.Register("Interval", typeof(TimeSpan), typeof(TimeSpinner), new PropertyMetadata(TimeSpan.FromTicks(TimeSpan.TicksPerHour)));
private static readonly TimeSpan OneDay = TimeSpan.FromTicks(TimeSpan.TicksPerDay);
void UpTime()
{
var newTime = Time + Interval;
if (newTime >= OneDay)
Time = newTime - OneDay;
else
Time = newTime;
}
void DownTime()
{
var newTime = Time - Interval;
if (newTime < TimeSpan.Zero)
Time = newTime + OneDay;
else
Time = newTime;
}
public override void OnApplyTemplate()
{
var upButton = GetUIPart<ButtonBase>("UP");
if(upButton != null)
upButton.Click += upButton_Click;
if (_upButton != null)
_upButton.Click -= upButton_Click;
_upButton = upButton;
var downButton = GetUIPart<ButtonBase>("DOWN");
if (downButton != null)
downButton.Click += downButton_Click;
if (_downButton != null)
_downButton.Click -= downButton_Click;
_downButton = downButton;
}
void downButton_Click(object sender, RoutedEventArgs e)
{
DownTime();
}
void upButton_Click(object sender, RoutedEventArgs e)
{
UpTime();
}
private ButtonBase _upButton;
private ButtonBase _downButton;
T GetUIPart<T>(string name) where T : DependencyObject
{
return (T) GetTemplateChild(name);
}
}
and a sample template (which should put in a ResourceDictionary called Generic.xaml in a folder called Themes at the root of your project):
<Style TargetType="{x:Type local:TimeSpinner}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TimeSpinner}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<UniformGrid Columns="1" DockPanel.Dock="Right">
<Button x:Name="UP" Content="+"/>
<Button x:Name="DOWN" Content="-"/>
</UniformGrid>
<TextBox Text="{Binding Time, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a Button containing a Hyperlink, like so:
<Button IsEnabled="False">
<Hyperlink IsEnabled="True">Testing</Hyperlink>
</Button>
I need the Hyperlink to be enabled, however the Button to be disabled. How can I achieve this?
The above simply results in both controls being disabled.
I solved this problem by creating a simple wrapper element that breaks the IsEnabled inheritance chain from the parent.
The framework's default coerce callback checks the parent IsEnabled value and inherits it. This control sets a new coerce callback that just returns the value directly without checking inheritance.
public class ResetIsEnabled : ContentControl
{
static ResetIsEnabled()
{
IsEnabledProperty.OverrideMetadata(
typeof(ResetIsEnabled),
new UIPropertyMetadata(
defaultValue: true,
propertyChangedCallback: (_, __) => { },
coerceValueCallback: (_, x) => x));
}
}
In the example from the question it would be used like this:
<Button IsEnabled="False">
<ResetIsEnabled>
<!-- Child elements within ResetIsEnabled have IsEnabled set to true (the default value) -->
<Hyperlink>Testing</Hyperlink>
</ResetIsEnabled>
</Button>
Control Hyperlink has strangely with the property IsEnabled. In addition to the one that you mentioned, namely, the full value inheritance from a parent, there is another similar.
Hyperlink for the specific control, which has been turned off (IsEnabled="False"), setting (IsEnabled="True") will not update the Hyperlink property. The solution - use a relative source for Hyperlink (more info).
For solving your question, I have decided that it is not the standard way to solve. So I created a Class with its own dependencies properties. It has it's property MyIsEnabled and MyStyle. As you might guess from the title, the first sets its property IsEnabled and MyStyle need to specify the button style, simulating the IsEnabled="False" behavior.
SimulateDisable Style
<Style x:Key="SimulateDisable" TargetType="{x:Type Button}">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="4" BorderThickness="1" BorderBrush="DarkBlue" SnapsToDevicePixels="True">
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Define Button with yours properties:
<Button Name="MyButton" local:MyClass.MyIsEnabled="False" local:MyClass.MyStyle="{StaticResource SimulateDisable}" Width="100" Height="30" Click="Button_Click">
<Hyperlink IsEnabled="True" Click="Hyperlink_Click">Testing</Hyperlink>
</Button>
Listing of MyClass
public class MyClass : DependencyObject
{
public static readonly DependencyProperty MyIsEnabledProperty;
public static readonly DependencyProperty MyStyleProperty;
#region MyIsEnabled
public static void SetMyIsEnabled(DependencyObject DepObject, bool value)
{
DepObject.SetValue(MyIsEnabledProperty, value);
}
public static bool GetMyIsEnabled(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(MyIsEnabledProperty);
}
#endregion MyIsEnabled
#region MyStyle
public static void SetMyStyle(DependencyObject DepObject, Style value)
{
DepObject.SetValue(MyStyleProperty, value);
}
public static Style GetMyStyle(DependencyObject DepObject)
{
return (Style)DepObject.GetValue(MyStyleProperty);
}
#endregion MyStyle
static MyClass()
{
MyIsEnabledProperty = DependencyProperty.RegisterAttached("MyIsEnabled",
typeof(bool),
typeof(MyClass),
new UIPropertyMetadata(false, OnPropertyChanged));
MyStyleProperty = DependencyProperty.RegisterAttached("MyStyle",
typeof(Style),
typeof(MyClass),
new UIPropertyMetadata(OnPropertyChanged));
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Button MyButton = sender as Button;
bool MyBool = GetMyIsEnabled(MyButton);
if (MyBool == false)
{
MyButton.Style = MyClass.GetMyStyle(MyButton);
}
}
}
Plus for the event Hyperlink pointing e.Handled = true, so that the event did not happen next.
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hyperlink Click!");
e.Handled = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button Click! Don't show it's!");
}
Output
P.S. Sorry for late answer :).
I am working a very simple lookless control, and I can't seem to get one of the template bindings to work. In the control I have two Dependency Properties, the one that is a string works, and the one that is an int does not.
The csharp code looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
namespace ControlDemo
{
public class TextControlLookless : Control
{
#region Title
public static readonly DependencyProperty ChartTitleProperty =
DependencyProperty.Register("ChartTitle", typeof(string), typeof(TextControlLookless),
null);
public String ChartTitle
{
get { return (string)GetValue(ChartTitleProperty); }
set
{
SetValue(ChartTitleProperty, value);
}
}
#endregion
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
null);
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
#region ctor
public TextControlLookless()
{
this.DefaultStyleKey = typeof(TextControlLookless);
}
#endregion
}
}
And the xaml for the control looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ControlDemo">
<Style TargetType="local:TextControlLookless">
<Setter Property="ChartTitle" Value="Set Title" />
<Setter Property="ChartValue" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TextControlLookless">
<Grid x:Name="Root">
<Border BorderBrush="Black" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding ChartTitle}" />
<TextBlock Text="{TemplateBinding ChartValue}" Grid.Row="1" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When I put this on a page, I can see the ChartTitle (either Set Title, or whatever I set it to), but the ChartValue never shows up. If I change its type to a string, it does show up, so I must be missing something.
The problem is that TemplateBinding is a far more primitive operation than Binding. Binding is an actual class and includes some helpful features including the implicit conversion of strings back and forth between other data types.
TemplateBinding is purely a markup instruction and crucially in your case does not do type conversion for you. Hence the dependency property being bound to a Text property of a TextBlock must be a string.
You have two choices:-
One choice is instead using TemplateBinding give the TextBlock a name and assign its Text in the ChartValue property changed call back:-
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
new PropertyMetadata(0, OnChartValuePropertyChanged));
private static void OnChartValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextControlLookless source = d as TextControlLookless;
source.Refresh();
}
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
private TextBlock txtChartValue { get; set; }
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
txtChartValue = GetTemplateChild("txtChartValue") as TextBlock;
Refresh();
}
private void Refresh()
{
if (txtChartValue != null)
{
txtChartValue.Text = ChartValue.ToString();
}
}
where the xaml looks like:-
<TextBlock x:Name="txtChartValue" Grid.Row="1" />
The other choice is to create a private dependency property for the value with type of string:-
#region Value
public static readonly DependencyProperty ChartValueProperty =
DependencyProperty.Register("ChartValue", typeof(int), typeof(TextControlLookless),
new PropertyMetadata(0, OnChartValuePropertyChanged));
private static void OnChartValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(ChartValueStrProperty, e.NewValue.ToString());
}
private static readonly DependencyProperty ChartValueStrProperty =
DependencyProperty.Register("ChartValueStr", typeof(string), typeof(TextControlLookless),
new PropertyMetadata("0"));
public int ChartValue
{
get { return (int)GetValue(ChartValueProperty); }
set
{
SetValue(ChartValueProperty, value);
}
}
#endregion
where the xaml looks like:-
<TextBlock Text="{TemplateBinding ChartValueStr}" Grid.Row="1" />
Note that the ChartValueStrProperty is private and I haven't bothered creating a standard .NET property to cover it. TemplateBinding actually takes the property name you assign suffixes is with "Property" then looks for a static field on the target type.
Both approaches have their strengths and weaknesses. The first approach is the more common pattern but takes a little more code and is less flexiable (the control displaying the value must be a TextBlock). The second is more flexiable and uses less code but is somewhat unorthodox.
Using WPF, I want to apply a converter within the binding of the Text property within all my TextBoxes.
The following works for a single TextBox:
<TextBox Style="{StaticResource TextBoxStyleBase2}"
Text="{Binding Text, Converter={StaticResource MyConverter}}">
</TextBox>
However, our TextBoxes uses a style with a Control Template that looks like this:
<Grid>
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{StaticResource DefaultCornerRadius}">
<Grid>
<Border BorderThickness="1">
<ScrollViewer x:Name="PART_ContentHost" Margin="0"/>
</Border>
</Grid>
</Border>
</Grid>
How can I apply my converter using this Template?
Thanks!
Any attempt to modify TextBox properties from inside the ControlTemplate will be messy, so I recommend you do it in the Style instead of the ControlTemplate if at all possible. I'll start by explaining how to do it with a Style then explain how to adapt the technique for use in a ControlTemplate if necessary.
What you need to do is create an attached property that can be used like this:
<Style x:Name="TextBoxStyleBase2" TargetType="TextBox">
<Setter Property="local:ConverterInstaller.TextPropetyConverter"
Value="{StaticResource MyConverter}" />
...
</Style>
The ConverterInstaller class has a simple attached property that installs the converter into any Binding initially set on the TextBox:
public class ConverterInstaller : DependencyObject
{
public static IValueConverter GetTextPropertyConverter(DependencyObject obj) { return (IValueConverter)obj.GetValue(TextPropertyConverterProperty); }
public static void SetTextPropertyConverter(DependencyObject obj, IValueConverter value) { obj.SetValue(TextPropertyConverterProperty, value); }
public static readonly DependencyProperty TextPropertyConverterProperty = DependencyProperty.RegisterAttached("TextPropertyConverter", typeof(IValueConverter), typeof(Converter), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var box = (TextBox)obj;
box.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
var binding = BindingOperations.GetBinding(box, TextBox.TextProperty);
if(binding==null) return;
var newBinding = new Binding
{
Converter = GetTextPropertyConverter(box),
Path = binding.Path,
Mode = binding.Mode,
StringFormat = binding.StringFormat,
}
if(binding.Source!=null) newBinding.Source = binding.Source;
if(binding.RelativeSource!=null) newBinding.RelativeSource = binding.RelativeSource;
if(binding.ElementName!=null) newBinding.ElementName = binding.ElementName;
BindingOperations.SetBinding(box, TextBox.TextProperty, newBinding);
}));
}
});
}
The only complexity here is:
The use of Dispatcher.BeginInvoke to ensure the binding update happens after the XAML finishes loading and all styles are applied, and
The need to copy Binding properties into a new Binding to change the Converter, since the original Binding is sealed
To attach this property to an element inside the ControlTemplate instead of doing it in the style, the same code is used except the original object passed into the PropertyChangedCallback is cast var element = (FrameworkElement)obj; and inside the Dispatcher.BeginInvoke action the actual TextBox is found with var box = (TextBox)element.TemplatedParent;
I am trying to create the simplest Silverlight templated control, and I can't seem to get TemplateBinding to work on the Angle property of a RotateTransform.
Here's the ControlTemplate from generic.xaml:
<ControlTemplate TargetType="local:CtlKnob">
<Grid x:Name="grid" RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{TemplateBinding Angle}"/> <!-- This does not work -->
<!-- <RotateTransform Angle="70"/> --> <!-- This works -->
</TransformGroup>
</Grid.RenderTransform>
<Ellipse Stroke="#FFB70404" StrokeThickness="19"/>
<Ellipse Stroke="White" StrokeThickness="2" Height="16" VerticalAlignment="Top"
HorizontalAlignment="Center" Width="16" Margin="0,2,0,0"/>
</Grid>
</ControlTemplate>
Here's the C#:
using System.Windows;
using System.Windows.Controls;
namespace CtlKnob
{
public class CtlKnob : Control
{
public CtlKnob()
{
this.DefaultStyleKey = typeof(CtlKnob);
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CtlKnob), null);
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty,value); }
}
}
}
Here is a workaround:
public class TemplatedControl1 : Control
{
public TemplatedControl1()
{
this.DefaultStyleKey = typeof(TemplatedControl1);
}
public override void OnApplyTemplate()
{
var transform = this.GetTemplateChild("Transform1") as RotateTransform;
transform.Angle = this.StartAngle;
base.OnApplyTemplate();
}
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(double), typeof(TemplatedControl1), null);
public double StartAngle
{
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
}
And the xaml:
<Style TargetType="local:TemplatedControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TemplatedControl1">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Canvas>
<Polyline Fill="Black" >
<Polyline.RenderTransform>
<RotateTransform x:Name="Transform1" />
</Polyline.RenderTransform>
</Polyline>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Henrik has all ready provided the answer but I'll explain why its necessary.
Currently in Sliverlight 3 this sort of binding requires that object receiving the binding is a FrameworkElement. Its the FrameworkElement that has the SetBinding method that allows this stuff to work.
RotateTransform whilst being a DependencyObject is not a FrameworkElement and therefore cannot participate in this sort of binding. Silverlight 4 allows binding to work on DependencyObject so theoritically this sort of code ought to work in SL4. I'll have to try that to see if that is true.
Thanks Anthony for explaining why it doesn't work.
Thanks Henrik for the workaround, it solves half of the problem: the Angle can now be set from Xaml. However, it still can't be set programmatically, but the missing bit is now easy.
Here's the full solution:
public class CtlKnob : Control
{
public CtlKnob()
{
this.DefaultStyleKey = typeof(CtlKnob);
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(CtlKnob),
new PropertyMetadata(AngleChanged));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty,value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var transform = this.GetTemplateChild("RotateTransform") as RotateTransform;
transform.Angle = this.Angle;
}
private static void AngleChanged( DependencyObject dobj,
DependencyPropertyChangedEventArgs e )
{
var knob = dobj as CtlKnob;
var rotateTransform = knob.GetTemplateChild("RotateTransform") as
RotateTransform;
if (rotateTransform != null)
rotateTransform.Angle = (double)e.NewValue;
}
}
In Silverlight 3, you can only bind to FrameworkElements and not DependencyObjects. RotateTransform is not a FrameworkElement.
Silverlight 4 supports binding to DependencyObjects, so this works in Silverlight 4.
If you can upgrade to Silverlight 4, I'd consider it. Otherwise, a number of people have posted workarounds for Silverlight 3.