![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
Creating a look-less custom control in WPFBy Karin HuberIn WPF, custom controls should be look-less. That means, that business logic and UI are strongly separated. When you create a control, you can provide a default template for the control, but anyone who uses the control can override it without touching the business logic. |
C#, Windows, .NET, Visual Studio, XAML, WPF, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
In WPF, custom controls should be look-less. That means, that business logic and UI are strongly separated. When you create a control, you can provide a default template for the control, but anyone who uses the control can override it without touching the business logic.
In my sample, I will create a custom control for a simple clock, with a default template for a digital clock. In the application that uses the control, I will overwrite the template for the clock so that it will be displayed as an analog clock.
This sample was created with the February CTP.
|
|
|
|
Default template |
Customized template |
DependencyProperty "DateTime" to the custom control. This property will always contain the actual date and time. Templates for this control can bind to this property. The PropertyChangedCallback is required to notify UI elements that the property has changed. Without this callback, bound controls would not be updated. ...
public DateTime DateTime
{
get
{
return (DateTime)GetValue(DateTimeProperty);
}
private set
{
SetValue(DateTimeProperty, value);
}
}
public static DependencyProperty DateTimeProperty =
DependencyProperty.Register(
"DateTime",
typeof(DateTime),
typeof(Clock),
new PropertyMetadata(
DateTime.Now,
new PropertyChangedCallback(OnDateTimeInvalidated)));
public static readonly RoutedEvent DateTimeChangedEvent =
EventManager.RegisterRoutedEvent(
"DateTimeChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<DateTime>),
typeof(Clock));
protected virtual void OnDateTimeChanged(DateTime oldValue,
DateTime newValue)
{
RoutedPropertyChangedEventArgs<DateTime> args = new
RoutedPropertyChangedEventArgs<DateTime>(oldValue, newValue);
args.RoutedEvent = Clock.DateTimeChangedEvent;
RaiseEvent(args);
}
private static void OnDateTimeInvalidated(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Clock clock = (Clock)d;
DateTime oldValue = (DateTime)e.OldValue;
DateTime newValue = (DateTime)e.NewValue;
clock.OnDateTimeChanged(oldValue, newValue);
}
...
Timer to the control to keep the "DateTime" property up to date.
If you want to be able to update UI elements in your TimerElapsedEvent, you should use the DispatcherTimer class from the System.Windows.Threading namespace. Another option would be to invoke a delegate from the Control.Dispatcher object (this.Dispatcher.Invoke(...);).
...
using System.Windows.Threading;
namespace CustomControlLibrary
{
public class Clock : Control
{
private DispatcherTimer timer;
static Clock()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(Clock),
new FrameworkPropertyMetadata(typeof(Clock)));
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
UpdateDateTime();
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000 -
DateTime.Now.Millisecond);
timer.Tick += new EventHandler(Timer_Tick);
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
UpdateDateTime();
timer.Interval = TimeSpan.FromMilliseconds(1000 -
DateTime.Now.Millisecond);
timer.Start();
}
private void UpdateDateTime()
{
this.DateTime = System.DateTime.Now;
}
...
DateTime is displayed in a TextBlock control. Optionally, you could add a converter to the binding to format the DateTime property. <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" target=_blank>http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" target=_blank>http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControlLibrary"
>
<Style TargetType="{x:Type local:Clock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Clock}">
<TextBlock Name="tblClock" Text="{Binding Path=DateTime,
RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
<Window x:Class="WindowsApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" target=_blank>http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" target=_blank>http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customControl="clr-namespace:CustomControlLibrary;
assembly=CustomControlLibrary"
Title="WindowsApplication" Height="487" Width="412"
>
<StackPanel HorizontalAlignment="Center">
<customControl:Clock Name="customControlClock" />
</StackPanel>
</Window>
Now the custom control is working.

By overriding the default template, it is possible to change the look and feel of the control completely.
StackPanel in "Window1.xaml". Furthermore, you will need some converters to convert the seconds, minutes, and hours of the actual date to an angle for the watch hands. <StackPanel.Resources>
<local:SecondsConverter x:Key="secondsConverter"/>
<local:MinutesConverter x:Key="minutesConverter"/>
<local:HoursConverter x:Key="hoursConverter"/>
<local:WeekdayConverter x:Key="weekdayConverter"/>
<Style x:Key="AnalogClock" TargetType="{x:Type customControl:Clock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customControl:Clock}">
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
DateTime DependencyProperty of the custom control. As an angle of a RotateTransform cannot be bound to a DateTime datatype, you have to use a converter object. <Rectangle x:Name="SecondHand" Canvas.Top="4"
Canvas.Left="49" Fill="Red" Width="1" Height="47">
<Rectangle.RenderTransform>
<RotateTransform Angle="{Binding Path=DateTime,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource secondsConverter}}"
CenterX="0.5" CenterY="47"></RotateTransform>
</Rectangle.RenderTransform>
</Rectangle>
SecondsConverter, for instance, uses the seconds date part of the DateTime property, and converts it to an angle for the second hand of the clock. [ValueConversion(typeof(DateTime), typeof(int))]
public class SecondsConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.Second * 6;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
<customControl:Clock Name="customControlAnalogClock"
Style="{StaticResource AnalogClock}" />
Now, the custom control is customized:
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 4 Jun 2006 Editor: Chris Maunder |
Copyright 2006 by Karin Huber Everything else Copyright © CodeProject, 1999-2009 Web21 | Advertise on the Code Project |