|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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.
Creating the project
Creating the custom control
...
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);
}
...
...
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;
}
...
Creating a default template for the custom control
<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>
Using the custom control in an application<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.
Overriding the default templateBy overriding the default template, it is possible to change the look and feel of the control completely.
<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>
<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>
[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:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||