WPF之控件开发

原创 2007年09月17日 11:07:00

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="#FF6A76C6" 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 L312,80"/>

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

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

            <Path Fill="#FF3121B3" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="24,0,0,221.5" VerticalAlignment="Bottom" Width="48.5" Height="1" Data="M32,224 L64,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="#FF6A76C6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,120,310.635,222.793" Width="0.361" Data="M344,192 L472,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="#FF6A76C6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,160.5,310,222" Width="2" Data="M320,192 L272,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="#FF6A76C6" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Right" Margin="0,94.5,311,222.5" Width="1" Data="M344,224 L344,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>

 

WPF控件开发之自定义控件(1)

Windows Presentation Foundation (WPF) 控件模型的扩展性极大减少了创建新控件的需要。但在某些情况下,仍可能需要创建自定义控件。本主题讨论可最大限度减少在 Windo...
  • a7618317
  • a7618317
  • 2014年01月23日 14:31
  • 1295

WPF 定义自己的控件

最近在研究怎么制作自己的控件,制作好了之后给别人用,别人只需要修改控件的相关属性就可以适应他的需求,而无需去修改控件的模板。先上图再解释。 以下是我自己制作的控件截图: 温度计: 模拟微信文...
  • lishuangquan1987
  • lishuangquan1987
  • 2017年05月18日 19:41
  • 567

WPF之数据绑定总结

最近几天高强度开发,暴露出不少问题,WPF还达不到信手拈来的地步,好些东西还要去看看以前的项目。平时还是要多总结的,层次高了之后关注的知识点才会更深入。下面总结下WPF的绑定相关,总结之前又看了一遍深...
  • pfe_Nova
  • pfe_Nova
  • 2014年02月15日 18:00
  • 21931

【WPF】查找父/子控件(元素、节点)

找父/子控件
  • qq_18995513
  • qq_18995513
  • 2016年12月13日 09:22
  • 2476

在WPF中的Canvas上实现控件的拖动、缩放

如题,项目中需要实现使用鼠标拖动、缩放一个矩形框,WPF中没有现成的,那就自己造一个轮子:)    造轮子前先看看Windows自带的画图工具中是怎样做的,如下图:      在被拖动的矩形框四周有9...
  • jtl309
  • jtl309
  • 2016年02月11日 21:31
  • 6050

SplitContainer控件扩展之收缩面板

 转自http://www.cnblogs.com/cxwx/archive/2011/01/11/1932620.html 补充说明,尤其一开始自己没注意到 “程序员之窗”网站提供了示例下载...
  • zep1168
  • zep1168
  • 2016年07月22日 18:26
  • 759

WPF/Blend4之自定义控件——制作自己的Button

先展示一下效果图 先用Ellipse控件画一个30*30的椭圆,找到画笔属性Fill,选择下面的渐变画笔,左下角选择径向渐变,其实颜色设成#FFFF0000,结束颜色设成#FFFFC8C8,效果如图 ...
  • danding_ge
  • danding_ge
  • 2013年12月23日 14:54
  • 2824

【WPF开发】WpfGauge:开源仪表盘(Gauge)的使用

WpfGauge:WPF版本开源仪表盘(Gauge)的使用wpfgauge是一款.net平台WPF版本的开源仪表盘,包括了源码和使用实例。官网地址是: https://wpfgauge.codepl...
  • BrilliantEagle
  • BrilliantEagle
  • 2016年05月20日 20:41
  • 2888

潘鹏整理WPF(9)基于范围的控件Slider&&PrograssBar

Slider刻度条媒体播放器的音量、进度都是基于这个,和用于交互 Value设置指示的刻度 Minimum =0 Maximum = 100设置总刻度 TickPlacement=”Bott...
  • PanPen120
  • PanPen120
  • 2015年09月22日 12:58
  • 1083

[WPF] 文件路径选择控件

1、创建一个WPF的自定义控件,SelectPathControl。 2、修改Style
  • wushang923
  • wushang923
  • 2013年07月02日 17:19
  • 5795
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:WPF之控件开发
举报原因:
原因补充:

(最多只允许输入30个字)