自定义控件与模板分离的实践

在现代软件开发中,将业务逻辑与用户界面(UI)分离是一种常见的设计模式。这种分离有助于提高代码的可维护性和可扩展性。本文将通过一个简单的示例,展示如何在WinFX框架下创建一个自定义控件,并实现模板的自定义,从而将业务逻辑与UI分离。

创建项目

首先,需要创建两个项目:一个WinFX Windows应用程序和一个WinFX自定义控件库。

WinFX Windows应用程序:这个应用程序将用于测试自定义控件的功能。

WinFX自定义控件库:这个库将包含自定义的时钟控件。

接下来,需要在自定义控件库中添加一个新的自定义控件,并删除默认的UserControl1.xaml文件。同时,需要在Windows应用程序项目中添加对自定义控件库的引用。

创建自定义控件

自定义控件的核心是添加一个依赖属性(DependencyProperty),该属性将始终包含当前的日期和时间。控件的模板可以绑定到这个属性上。

public class Clock : Control { public static readonly DependencyProperty DateTimeProperty = DependencyProperty.Register("DateTime", typeof(DateTime), typeof(Clock), new PropertyMetadata(DateTime.Now, OnDateTimeInvalidated)); public DateTime DateTime { get { return (DateTime)GetValue(DateTimeProperty); } private set { SetValue(DateTimeProperty, value); } } public static readonly RoutedEvent DateTimeChangedEvent = EventManager.RegisterRoutedEvent("DateTimeChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(Clock)); protected virtual void OnDateTimeChanged(DateTime oldValue, DateTime newValue) { RoutedPropertyChangedEventArgs args = new RoutedPropertyChangedEventArgs(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); } 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; } }

在上述代码中,定义了一个名为Clock的自定义控件类。这个类包含一个DateTime依赖属性,该属性用于存储当前的日期和时间。同时,使用DispatcherTimer定时器来更新这个属性的值。

创建默认模板

接下来,需要为自定义控件创建一个默认模板。这个模板将在themes文件夹中的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: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>

在上述XML代码中,定义了一个TextBlock控件,用于显示当前的DateTime属性值。这个控件将作为Clock控件的默认模板。

在应用程序中使用自定义控件

现在,可以在Windows应用程序中使用自定义控件了。

<Window x:Class="WindowsApplication.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="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>

在上述XML代码中,定义了一个名为Window1的窗口,并在其中添加了一个Clock控件。这个控件将使用在generic.xaml文件中定义的默认模板。

覆盖默认模板

通过覆盖默认模板,可以完全改变控件的外观和感觉。

<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>

在上述XML代码中,定义了一些转换器(Converter),用于将当前的DateTime属性值转换为模拟时钟的指针角度。然后,定义了一个名为AnalogClock的样式,并将其应用于Clock控件。

绘制时钟模板

模板中,使用了一些椭圆和矩形来绘制时钟的表盘。然后,将时钟的指针绑定到自定义控件的DateTime依赖属性上。由于RotateTransform的角度不能直接绑定到DateTime数据类型,需要使用转换器对象。

<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" /> </Rectangle.RenderTransform> </Rectangle>

在上述XML代码中,定义了一个名为SecondHand的矩形控件,并将其渲染转换(RotateTransform)的角度绑定到DateTime属性上。使用了一个名为secondsConverter的转换器,将DateTime属性的秒部分转换为秒针的角度。

添加转换器

最后,需要在Window1.xaml的代码隐藏文件中添加转换器。

[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; } }

在上述C#代码中,定义了一个名为SecondsConverter的转换器类。这个类实现了IValueConverter接口,并定义了Convert方法,用于将DateTime属性的秒部分转换为秒针的角度。

应用新模板

最后,需要将新模板应用到Clock控件上。

<customControl:Clock Name="customControlAnalogClock" Style="{StaticResource AnalogClock}" />
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485