WPF之控件开发

1 概述

  WPF提供了创建控件的3种通用模式,提供了 不同的特性和灵活性。在开发一个新控件之前,理解这些模式是非常重要的。这将有助于你使用适当的模式来开发控件。

  1.1 何时需要开发一个控件

   当你需要一个表现丰富的的控件,而已有控件又不能满足需要,这个时候就需要开发一个定制控件了。

  1.2 控件特性

       WPF的控件支持 Rich Content,Styles, Triggers 以及templates,在大多数情况下你将不需要编写一个新的控件,而仅仅通过这些特性来定制表现丰富的控件

 

Rich Content:它支持任意的内容显示

Styles      values的集合,通过它你可以改变已有控件界面UI,实现定制界面显示

Trigger.     通过它你可以改变已有控件的显示和行为。

Templates   通过它可以不需要编写代码就实现扩充和定制界面显示。

2 控件开发

  2.1 创建一个UserControl控件

这是WPF中创建控件最简单的方法,通过继承UserControl类实现

 

      2.1.1 UserControl特性

       持有特性:Rich ContentstylesTriggers

       非持有特性:Templates

 

      2.1.2 创建UserControl的好处

       可以像创建Application一样来创建控件

       控件仅仅由已有组件(控件)组成

不需要支持复杂的定制

  2.2 创建一个继承自Control的控件

继承自Control类的控件开发模式是大部分WPF中的控件采用的开发模式。自定义控件通过模板实现运行逻辑和界面显示的分离。比起UserControl它提供了更多的灵活性

 

2.2.1 Control特性

持有特性:Rich Content Styles Triggers, templates

非持有特性:无

 

2.2.2 创建Control的好处

通过controltemplate定制控件的显示

支持theme

 2.3 创建一个继承自FrameworkElement的控件

     无论是继承自UserControl还是Control的控件都将依赖已有的元素(Elements),如果构成它们的简单元素不能满足当前需要,那么就需要通过重载OnRender并由DrawingContext操作来重绘界面

  

2.3.1 创建继承自FrameworkElement的好处

     实现当前控件元素不具有的特定显示

通过定义自己的Render逻辑来显示界面

以新奇的方式组合现有控件元素

3 创建自定义控件

   由于UserControl相对简单,这里仅介绍如何创建自定义控件,本节假定.NET3.0环境已经搭建完毕,采用Feb 2006 CTP版。

     以创建一个时钟控件为例

  3.1 打开VS2005,在左边的项目类型中选择Visual C# -- Net Framework3.0,在右边Vs已经安装的模板中选择CustomControlLibraryWPF),并在下面项目名称处填入项目名称Clock,选择项目要生成到的目录,点击确定。过程如下图所示

      3.2工程创建完毕之后,在解决方案中将VS自动生成的UserControl1.xaml删除。然后给工程MyClock加入新建项Custom Control(WPF),如下图所示

      3.3 此时,工程已经创建完毕,开始编写代码。基本框架如下

      

public class MyClock : System.Windows.Controls.Control

    {

        static MyClock()

        {

            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.

            //This style is defined in themes/generic.xaml

            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyClock), new FrameworkPropertyMetadata(typeof(MyClock)));

        }

    }

      3.3.1 定义一个计时器

    private DispatcherTimer timer; // NameSpace:System.Windows.Threading

     //重载初始化函数OnInitialized,加入初始化计时器代码

protected override void OnInitialized(EventArgs e)

        {

            base.OnInitialized(e);

            UpdateDateTime();

            timer = new DispatcherTimer();

            timer.Interval = TimeSpan.FromMilliseconds(1000);

            timer.Tick += new EventHandler(timer_Tick);

            timer.Start();

        }

     //计时器事件函数

       private void timer_Tick(Object sender, EventArgs e)

        {

            UpdateDateTime();

       }

     //编写UpdateDateTime函数,用于更新时间

         private void UpdateDateTime()

        {

            Time = DateTime.Now;

       }

      3.3.2 添加依赖属性Time

         public DateTime Time

        {

            get{ return (DateTime)GetValue(TimeProperty);}

            private set SetValue(TimeProperty, value);}

         }

        public static DependencyProperty TimeProperty = DependencyProperty.Register(

            "Time",peof(DateTime),

            typeof(MyClock),

            new PropertyMetadata(DateTime.Now,new PropertyChangedCallback(OnDateTimeInvalidated)));

     //添加依赖属性值改变后触发的事件代码

       public static readonly RoutedEvent DateTimeChangedEvent =

            EventManager.RegisterRoutedEvent("DateTimeChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime>),

            typeof(MyClock));

private static void OnDateTimeInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            MyClock lock = (MyClock)d;

 

            DateTime oldValue = (DateTime)e.OldValue;

            DateTime newValue = (DateTime)e.NewValue;

 

            clock.OnDateTimeChanged(oldValue, newValue);

 

        }

        protected virtual void OnDateTimeChanged(DateTime oldValue, DateTime newValue)

        {

            RoutedPropertyChangedEventArgs<DateTime> args = new RoutedPropertyChangedEventArgs<DateTime>(oldValue, newValue);

 

            args.RoutedEvent = MyClock.DateTimeChangedEvent;

            RaiseEvent(args);

        }

至此代码编写基本完毕

3.4 打开解决方案theme文件夹中的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:MyClock"

    >

 

    <Style TargetType="{x:Type local:MyClock}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type local:MyClock}">

                   <!--在这里加入特定显示代码-->

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

</ResourceDictionary>

打开Express Blend,新建一个windosw app工程,将TextBox拖放到窗体中,位置大小设置好之后,打开对应的xaml文件,内容如下:

<Window

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  x:Class="test.Window1"

  x:Name="Window"

  Title="Window1"

  Width="640" Height="480">

 

  <Grid x:Name="LayoutRoot">

         <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="TextBox" TextWrapping="Wrap"/>

  </Grid>

</Window>

<Grid></Grid>以及它们包含的内容copygeneric.xmal的注释处,并加入绑定信息,最终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:MyClock"

    >

 

    <Style TargetType="{x:Type local:MyClock}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type local:MyClock}">

                  <!--在这里加入特定显示代码-->

                  <Grid x:Name="LayoutRoot">

                    <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

                             Text="{Binding  Path=Time, RelativeSource={RelativeSource TemplatedParent}}"

                             TextWrapping="Wrap"/>

                  </Grid>

 

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

</ResourceDictionary>

至此一个简单的时钟控件已经创建完毕,打开Blend,创建一工程,将MyClock拖放到窗体,结果如下:

 

4 为自定义控件创建控件模板

MyClock为例,如果希望时间以模拟时钟的方式显示,就需要重写ControlTemplate

        

4.1打开Blend,绘制希望显示的界面。如下图

 

 

 

         4.2 theme文件 夹中新建一字典资源文件Classic.xaml,将<Grid></Grid>Window下一级)以及它所包含的内容拷贝到<ControlTemplate></ControlTemplate>中。

       4.3 因为模拟时钟需要角度数据,所在在这里需要定义4个转换类,分别用于转换时分秒和星期,如下所示:

         #region Converter

    [ValueConversion(typeof(DateTime), typeof(int))]

    public class SecondsConverter : IValueConverter

    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            DateTime date = (DateTime)value;

            return date.Second * 6;

        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            return null;

        }

    }

    [ValueConversion(typeof(DateTime), typeof(int))]

    public class MinutesConverter : IValueConverter

    {

        public object Convert(Object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            DateTime date = (DateTime)value;

            return date.Minute * 6;

        }

        public object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)

        {

            return null;

        }

    }

    [ValueConversion(typeof(DateTime), typeof(int))]

    public class HoursConverter : IValueConverter

    {

        public object Convert(Object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

            DateTime date = (DateTime)value;

            return (date.Hour * 30) + (date.Minute / 2);

        }

        public object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)

        {

            return null;

        }

    }

    [ValueConversion(typeof(DateTime), typeof(string))]

    public class WeekDayConverter : IValueConverter

    {

        public object Convert(Object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

        {

          

            DateTime date = (DateTime)value;

            return date.DayOfWeek.ToString();

        }

        public object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)

        {

            return null;

        }

    }

#endregion

4.4 修改classic.xmal文件,结果如下

通过添加数据绑定到表指针的角度,实现模拟时钟

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

     xmlns:local="clr-namespace:MyClock"

    >

 

 

  <local:SecondsConverter x:Key="SecondsConverter"/>

  <local:MinutesConverter x:Key="MinutesConverter"/>

  <local:HoursConverter x:Key="HoursConverter"/>

  <local:WeekDayConverter x:Key="WeekDayConverter"/>

 

  <Style TargetType="{x:Type local:MyClock}">

    <Setter Property="Template">

      <Setter.Value>

        <ControlTemplate TargetType="{x:Type local:MyClock}">

          <!--在这里加入特定显示代码-->

          <Grid x:Name="LayoutRoot">

            <Ellipse Fill="#FF3121B3" Stroke="#FF000000"/>

            <Ellipse RenderTransformOrigin="0.507,0.5" Fill="#FF 6A 76C 6" Stroke="#FF000000" Margin="24,24,24,22"/>

            <Path Fill="#FF3121B3" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,24,311,0" VerticalAlignment="Top" Width="1" Height="57" Data="M312, 24 L 312,80"/>

            <Path Fill="#FF3121B3" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,0,311,22" VerticalAlignment="Bottom" Width="1" Height="57" Data="M 320,360 L 320,416"/>

            <Path Fill="#FF3121B3" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,0,24,221.5" VerticalAlignment="Bottom" Width="48.5" Height="1" Data="M 600,224 L 560,224"/>

            <Path Fill="#FF3121B3" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="24,0,0,221.5" VerticalAlignment="Bottom" Width="48.5" Height="1" Data="M 32,224 L 64,224"/>

            <Label  HorizontalAlignment="Left" Margin="288,0,280,118" VerticalAlignment="Bottom" Height="24" Width="50"

                    Content="{Binding Path=Time, Converter={StaticResource WeekDayConverter}, RelativeSource={RelativeSource TemplatedParent}}"/>

            <Path RenderTransformOrigin="-0.062,1.004" Fill="#FF 6A 76C 6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,120,310.635,222.793" Width="0.361" Data="M 344,192 L 472,112">

              <Path.RenderTransform>

                <TransformGroup>

                  <ScaleTransform ScaleX="1" ScaleY="1"/>

                  <SkewTransform AngleX="0" AngleY="0"/>

                  <RotateTransform Angle="{Binding Path=Time, Converter={StaticResource MinutesConverter}, RelativeSource={RelativeSource TemplatedParent}}"/>

                  <TranslateTransform X="0.914" Y="0.296"/>

                </TransformGroup>

              </Path.RenderTransform>

            </Path>

            <Path RenderTransformOrigin="0,1.008" Fill="#FF 6A 76C 6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,160.5,310,222" Width="2" Data="M 320,192 L 272,136">

              <Path.RenderTransform>

                <TransformGroup>

                  <ScaleTransform ScaleX="-1" ScaleY="1"/>

                  <SkewTransform AngleX="0" AngleY="0"/>

                  <RotateTransform Angle="{Binding Path=Time, Converter={StaticResource HoursConverter}, RelativeSource={RelativeSource TemplatedParent}}"/>

                  <TranslateTransform X="0" Y="0"/>

                </TransformGroup>

              </Path.RenderTransform>

            </Path>

            <Path RenderTransformOrigin="0.5,1" Fill="#FF 6A 76C 6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,94.5,311,222.5" Width="1" Data="M 344,224 L 344,96">

              <Path.RenderTransform>

                <TransformGroup>

                  <ScaleTransform ScaleX="1" ScaleY="1"/>

                  <SkewTransform AngleX="0" AngleY="0"/>

                  <RotateTransform Angle="{Binding Path=Time, Converter={StaticResource SecondsConverter}, RelativeSource={RelativeSource TemplatedParent}}"/>

                  <TranslateTransform X="0" Y="0"/>

                </TransformGroup>

              </Path.RenderTransform>

            </Path>

          </Grid>

 

        </ControlTemplate>

      </Setter.Value>

    </Setter>

  </Style>

</ResourceDictionary>

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值