书名:WPF专业编程指南
作者:李应保
出版社:电子工业出版社
出版时间:2010-01
ISBN:9787121100116
一、自定义传递事件
自定义传递事件步骤
声明一个传递事件
- 声明一个传递事件的语法为:
public static readonly RoutedEvent YclickEvent;
public static readonly RoutedEvent PreviewYclickEvent;
- 在这里定义了YclickEvent和PreviewYclickEvent两个传递事件,其类型为RoutedEvent,它们都被说明为static和readonly的,这是必需的。
在WPF传递事件系统中注册传递事件
- 与相关属性需要在相关属性系统中注册一样,也需要向传递系统注册传递事件。
YclickEvent=EventManager.RegisterRoutedEvent("Yclick",
RoutingStrategy.Bubble, typeof(RoutedEventHandler),
typeof( Ybutton) );
PreviewYclickEvent =EventManager.RegisterRoutedEvent("Yclick",
RoutingStrategy.Tunnel, typeof(RoutedEventHandler),
typeof( Ybutton) );
- WPF用RoutingStrategy来定义传递事件的传递方式,它取Tunnel、Bubble和Direct三个值,分别对应于潜入传递、冒泡传递和直接传递(只传递到自身类)三种传递方式。
在这里,我把YclickEvent的传递方式设为冒泡方式,把PreviewYclickEvent设为潜入方式。
第三个参数为传递事件处理函数的类型,第四个参数说明传递事件是在哪个类中定义的。
定义传递事件的接入属性
public event RoutedEventHandler Yclick
{
add {AddHandler(YclickEvent,value);}
remove{RemoveHandler(YclickEvent,value);}
}
public event RoutedEventHandler PreviewYclick
{
add {AddHandler(PreviewYclickEvent,value);}
remove{RemoveHandler(PreviewYclickEvent,value);}
}
- AddHandler和RemoveHandler是DependencyObject中的两个方法,所以为了使用自定义传递事件,Ybutton的继承树上需要有DependencyObject类。
产生传递事件
- 要在WPF传递事件系统中产生传递事件,需要调用UIElement类中的RaiseEvent方法:
RoutedEventArgs argsEvent = new RoutedEventArgs();
argsEvent.RoutedEvent = YButton.PreviewYclickEvent;
argsEvent.Source = this;
RaiseEvent(argsEvent);
好了,在完成了上面四个步骤之后,就可以使用自己定义的事项了。
二、例子
- 我们创建的按钮是由一个StackPanel、两个Ellipse和一个TextBlock组成的,样子有点奇怪。
主要的目的是要展示传递事件如何在元素树中传播。
为了演示如何定义并使用传递事件,在下面的例子中,
还是创建一个按钮,不过这个按钮不只是换一下视觉元素,而是做一点内部较深入的工作,让YButton类从Control类中派生出来:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Yingbao.Chapter7
{
public class YButton : Control
{
FormattedText formtxt;
bool isMouseReallyOver;
public static readonly DependencyProperty TextProperty;
public static readonly RoutedEvent YclickEvent;
public static readonly RoutedEvent PreviewYclickEvent;
static YButton()
{
TextProperty =DependencyProperty.Register("Text",
typeof(string),typeof(YButton),
new FrameworkPropertyMetadata(" ",
FrameworkPropertyMetadataOptions.AffectsMeasure));
YclickEvent = EventManager.RegisterRoutedEvent(
"Yclick", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(YButton));
PreviewYclickEvent = EventManager.RegisterRoutedEvent(
"PreviewYclick",RoutingStrategy.Tunnel,
typeof(RoutedEventHandler), typeof(YButton));
}
public string Text
{
set { SetValue(TextProperty, value == null ? " " :
value); }
get { return (string)GetValue(TextProperty); }
}
public event RoutedEventHandler Yclick
{
add { AddHandler(YclickEvent, value); }
remove { RemoveHandler(YclickEvent, value); }
}
public event RoutedEventHandler PreviewYclick
{
add { AddHandler(PreviewYclickEvent, value); }
remove { RemoveHandler(PreviewYclickEvent, value); }
}
protected override Size MeasureOverride(Size sizeAvailable)
{
formtxt = new FormattedText(Text,
CultureInfo.CurrentCulture, FlowDirection,
new Typeface(FontFamily, FontStyle, FontWeight,
FontStretch), FontSize, Foreground);
Size sizeDesired = new Size(Math.Max(48, formtxt.Width)
+ 4,formtxt.Height + 4);
sizeDesired.Width += Padding.Left + Padding.Right;
sizeDesired.Height += Padding.Top + Padding.Bottom;
return sizeDesired;
}
protected override void OnRender(DrawingContext dc)
{
Brush brushBackground = SystemColors.ControlBrush;
if (isMouseReallyOver && IsMouseCaptured)
brushBackground = SystemColors.ControlDarkBrush;
Pen pen = new Pen(Foreground, IsMouseOver ? 2 : 1);
dc.DrawRoundedRectangle(brushBackground, pen,
new Rect(new Point(0, 0), RenderSize), 4, 4);
formtxt.SetForegroundBrush( IsEnabled ? Foreground :
SystemColors.ControlDarkBrush);
Point ptText = new Point(2, 2);
switch (HorizontalContentAlignment)
{
case HorizontalAlignment.Left:
ptText.X += Padding.Left;
break;
case HorizontalAlignment.Right:
ptText.X += RenderSize.Width - formtxt.Width -
Padding.Right;
break;
case HorizontalAlignment.Center:
case HorizontalAlignment.Stretch:
ptText.X += (RenderSize.Width - formtxt.Width -
Padding.Left - Padding.Right) / 2;
break;
}
switch (VerticalContentAlignment)
{
case VerticalAlignment.Top:
ptText.Y += Padding.Top;
break;
case VerticalAlignment.Bottom:
ptText.Y += RenderSize.Height - formtxt.Height -
Padding.Bottom;
break;
case VerticalAlignment.Center:
case VerticalAlignment.Stretch:
ptText.Y += (RenderSize.Height - formtxt.Height -
Padding.Top - Padding.Bottom) / 2;
break;
}
dc.DrawText(formtxt, ptText);
}
protected override void OnMouseEnter(MouseEventArgs args)
{
base.OnMouseEnter(args);
InvalidateVisual();
}
protected override void OnMouseLeave(MouseEventArgs args)
{
base.OnMouseLeave(args);
InvalidateVisual();
}
protected override void OnMouseMove(MouseEventArgs args)
{
base.OnMouseMove(args);
Point pt = args.GetPosition(this);
bool isReallyOverNow = (pt.X >= 0 && pt.X < ActualWidth
&& pt.Y >= 0 && pt.Y < ActualHeight);
if (isReallyOverNow != isMouseReallyOver)
{
isMouseReallyOver = isReallyOverNow;
InvalidateVisual();
}
}
protected override void OnMouseLeftButtonDown(
MouseButtonEventArgs args)
{
base.OnMouseLeftButtonDown(args);
CaptureMouse();
InvalidateVisual();
args.Handled = true;
}
protected override void OnMouseLeftButtonUp(
MouseButtonEventArgs args)
{
base.OnMouseLeftButtonUp(args);
if (IsMouseCaptured)
{
if (isMouseReallyOver)
{
OnPreviewYclick();
OnYclick();
}
args.Handled = true;
Mouse.Capture(null);
}
}
protected override void OnLostMouseCapture(
MouseEventArgs args)
{
base.OnLostMouseCapture(args);
InvalidateVisual();
}
protected override void OnKeyDown(KeyEventArgs args)
{
base.OnKeyDown(args);
if (args.Key == Key.Space || args.Key == Key.Enter)
args.Handled = true;
}
protected override void OnKeyUp(KeyEventArgs args)
{
base.OnKeyUp(args);
if (args.Key == Key.Space || args.Key == Key.Enter)
{
OnPreviewYclick();
OnYclick();
args.Handled = true;
}
}
protected virtual void OnYclick()
{
RoutedEventArgs argsEvent = new RoutedEventArgs();
argsEvent.RoutedEvent = YButton.YclickEvent;
argsEvent.Source = this;
RaiseEvent(argsEvent);
}
protected virtual void OnPreviewYclick()
{
RoutedEventArgs argsEvent = new RoutedEventArgs();
argsEvent.RoutedEvent = YButton.PreviewYclickEvent;
argsEvent.Source = this;
RaiseEvent(argsEvent);
}
}
}
在YButton中,定义了两个传递事件:一个是Yclick,另一个是PreviewYclick;一个相关属性TextProperty。
为了产生传递事件,在YButton中截获了按下鼠标左键的传递事件,并把RoutedEventArgs类中的Handled的属性设为true,以阻止传递事件的传播。
在释放鼠标左键的时候,首先产生的是PreviewYclick事件,再产生Yclick事件。
类似地,还截获了空格键和回车键,在用户按下或释放这两个键的过程中产生Yclick和PreviewYclick传递事件。使用YButton的程序如下:
<Window x:Class="Yingbao.Chapter7.UsingYButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:Yingbao.Chapter7"
Title="使用自己的按钮" Height="500" Width="700"
src:YButton.PreviewYclick="OnPreviewYclick"
src:YButton.Yclick="OnYclick">
<StackPanel Orientation ="Horizontal"
src:YButton.PreviewYclick="OnPreviewYclick"
src:YButton.Yclick="OnYclick">
<StackPanel src:YButton.PreviewYclick="OnPreviewYclick"
src:YButton.Yclick="OnYclick">
<src:YButton x:Name="myButton1" Text=" 使用自定义按钮1"/>
<src:YButton x:Name="myButton2" Text=" 使用自定义按钮2"/>
<src:YButton x:Name="myButton3" Text=" 使用自定义按钮3"/>
</StackPanel>
<StackPanel Width ="560" Margin ="20" Height ="450">
<Label Name ="EventOutputHeader" FontSize ="12"> </Label>
<ScrollViewer Height ="400">
<StackPanel Name="EventOutput">
</StackPanel>
</ScrollViewer>
</StackPanel>
</StackPanel>
</Window>
- 后台C#程序如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Yingbao.Chapter7
{
public partial class UsingYButton : System.Windows.Window
{
string outputFormat = "{0,-40}{1,-30}{2,-30}{3,-30}";
public UsingYButton()
{
InitializeComponent();
EventOutputHeader.Content = string.Format(outputFormat,
"Routed Event ", "Sender", "Source", "Original Source");
}
private void OnPreviewYclick(object sender,
RoutedEventArgs rea)
{
RoutedEventHandler(sender, rea);
}
private void OnYclick(object sender, RoutedEventArgs rea)
{
RoutedEventHandler(sender, rea);
}
private void RoutedEventHandler(object sender,
RoutedEventArgs rea)
{
TextBlock text = new TextBlock();
text.Text = string.Format(outputFormat,
rea.RoutedEvent.Name, GetTypeName(sender),
GetTypeName(rea.Source),
GetTypeName(rea.OriginalSource));
EventOutput.Children.Add(text);
(EventOutput.Parent as ScrollViewer).ScrollToBottom();
while (EventOutput.Children.Count > 40)
{
EventOutput.Children.RemoveAt(0);
}
}
private string GetTypeName(object obj)
{
string[] name = obj.GetType().ToString().Split('.');
return name[name.Length - 1];
}
}
}
-
这个例子可以考察自定义的传递事件,也可以按照冒泡和潜入两种方式传播。按下按钮,我们可以看到PreviewYclick和Yclick事件的传递过程。图7-8示出了上面程序的运行结果,在XAML中使用YButton按钮,和使用WPF本身所提供的控件几乎没什么区别,这样,就可以根据自己的需要,很方便地对WPF控件进行自己的扩展。
图7-8 使用自定义传递事件
