细说WPF自定义路由事件

WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件。

1.WPF内置路由事件  

WPF中的大多数事件都是路由事件,WPF有3中路由策略:

具体不多讲,单需要注意的是WPF路由事件是沿着VIsualTree传递的。VisualTree与LogicalTree的区别在于:LogicalTree的叶子节点是构成用户界面的控件,而VisualTree要连控件中的细微结构也算上。VisualTree是LogicalTree的扩展。

下面给出一个使用WPF内置路由事件的例子:

<Window x:Class=WPFRoutedEvent.MainWindow
xmlns
=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x
=http://schemas.microsoft.com/winfx/2006/xaml
Title
=MainWindow Height=350 Width=525 >
<Grid x:Name=Grid1 Margin=10 Background=AliceBlue MouseLeftButtonDown=Grid1_MouseLeftButtonDown> 
<StackPanel Background=BurlyWood Height=200 x:Name=StackPanel1 Button.Click=ButtonInStackPanel_ClickMouseLeftButtonDown=StackPanel1_MouseLeftButtonDown>
<Button x:Name=Button1 Content=RoutedEvent Click=Button1_Click />
</StackPanel>
</Grid>
</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WPFRoutedEvent
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Grid订阅Button的Click事件
Grid1.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonInGrid_Click));
}

private void Button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(
Button Clicked.);
//
//e.Handled = true;
}

private void ButtonInStackPanel_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(
StackPanel Clicked.);
}

private void ButtonInGrid_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(
Grid Clicked.);
}

private void Grid1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(
Grid Mouse Left button down.);
}

private void StackPanel1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(
StackPanel Mouse Left button down.);
}
}
}

Button的Click事件是一个路由事件,分别在StackPanel中和Grid中订阅这个事件并进行相应的处理,分别用xaml代码和C#代码如下:

Click=Button1_Click
Button.Click=ButtonInStackPanel_Click
Grid1.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonInGrid_Click));

StackPanel的MouseLeftButtonDown也是一个路由事件,也可以叫“附加事件”。其实“附加事件”也是路由事件,只是个文字游戏,为什么还要另外起个名字呢?原来路由事件的宿主都是那些拥有可视化实体的界面元素;而附加事件则不具备显示在用户界面上的能力。

常见的附加事件有:

Binding类:SourceUpdated事件、TargetUpdated事件。

Mouse类:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件等。

Keyboard类:KeyDown事件、KeyUp事件等。

Grid和StackPanel中均如下订阅:

MouseLeftButtonDown=StackPanel1_MouseLeftButtonDown

程序运行如下:

 2.自定义路由事件    

 前面DebugLZQ写过一篇博文,内容是关于自定义CLR事件的。下面来自定义一个WPF路由事件,各位博友可以比较下两者的异同。

创建自定义路由事件大体可以分为三个步骤:

(1)声明并注册路由事件

(2)为路由事件添加CLR事件包装

(3)创建可以激发路由事件的方法

下面我们自定义一个WPF路由事件,我们给事件携带个参数,为此需要创建一个RoutedEventArgs类的派生类。如下:

using System;
using System.Windows;

namespace MyRoutedEvent
{
//事件参数
class ReportTimeRoutedEventArgs:RoutedEventArgs
{
public ReportTimeRoutedEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }

public DateTime ClickTime { get; set; }
}
}

然后,创建一个Button类的派生类并按前面的步骤为其添加路由事件:

using System;
using System.Windows.Controls;
using System.Windows;

namespace MyRoutedEvent
{
class TimeButton:Button
{
//声明和注册路由事件\
public static readonly RoutedEvent ReportTimeRoutedEvent =
EventManager.RegisterRoutedEvent(
ReportTime, RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeRoutedEventArgs>),typeof(TimeButton));
//CLR事件包装
public event RoutedEventHandler ReportTime
{
add { 
this.AddHandler(ReportTimeRoutedEvent, value); }
remove { 
this.RemoveHandler(ReportTimeRoutedEvent, value); }
}
//激发路由事件,借用Click事件的激发方法

protected override void OnClick()
{
base.OnClick();//保证Button原有功能正常使用,Click事件被激发

ReportTimeRoutedEventArgs args 
= new ReportTimeRoutedEventArgs(ReportTimeRoutedEvent, this);
args.ClickTime 
= DateTime.Now;
this.RaiseEvent(args);//UIElement及其派生类 
}

}
}

下面是程序界面的XAML代码,看下如何消费这个路由事件:

<Window x:Class=MyRoutedEvent.MainWindow
xmlns
=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x
=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:local
=clr-namespace:MyRoutedEvent 
Title
=MainWindow Height=350 Width=525>
<Grid x:Name=grid1 local:TimeButton.ReportTime=TimeButton_ReportTime><!—->
<Grid x:Name=grid2>
<Grid x:Name=grid3>
<StackPanel x:Name=stackPanel1>
<ListBox x:Name=listBox1/>
<local:TimeButton Width=200 Height=200 Background=Aquamarine ReportTime=TimeButton_ReportTime /><!—->
</StackPanel>
</Grid>
</Grid> 
</Grid>
</Window>

事件处理的后台代码如下:

using System.Windows;

namespace MyRoutedEvent
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void TimeButton_ReportTime(object sender, ReportTimeRoutedEventArgs e)//注意参数
{
listBox1.Items.Add(e.ClickTime.ToLongTimeString()
+DebugLZQ);
}
}
}

程序运行效果如下:

小结:UIElement类是路由事件和附加事件的分水岭,因为从UIElement类开始才具备了再界面上显示的能力,也因为RaiseEvent、AddHandler和RemoveHandler这些方法也定义在UIElement类中。附加事件也只能算是路由事件的一种用法而不是一个新的概念,其本质还是路由事件。

 

文章来源:http://www.toolscreator.com/?p=1332

 

 

     路由事件还提供了更为丰富的功能。首先,路由事件允许软件开发人员通过EventManager.RegisterClassHandler()函数使用由类定义的静态处理程序。这个类定义的静态处理程序与类型的静态构造函数有些类似:在路由事件到达路由中的元素实例时,WPF都会首先调用该类处理程序,然后再执行该实例所注册的侦听函数。这种控件编写方式在WPF的内部实现中经常使用。另外,通过对路由事件进行管理的类型EventManager,我们可以通过函数调用GetRoutedEvents()得到相应的路由事件,而不再需要运用反射等较为耗时的方法。

  路由事件一般使用以下三种路由策略:1) 冒泡:由事件源向上传递一直到根元素。2) 直接:只有事件源才有机会响应事件。3) 隧道:从元素树的根部调用事件处理程序并依次向下深入直到事件源。一般情况下,WPF提供的输入事件都是以隧道/冒泡对实现的。隧道事件常常被称为Preview事件。

  您可能会想,路由事件的直接路由方式与普通CLR事件的处理方式有什么不同呢?实际上并没有什么不同。但是路由事件为WPF提供了更好的支持。例如触发器等功能需要事件是路由事件。同时路由事件还提供了类处理机制,从而为WPF提供了更灵活的执行方式。在后面的章节中,您将看到WPF是如何通过类处理函数完成一些WPF常见功能的。

(或者可以这么解释:

  • 直接:只有源元素本身才有机会调用处理程序以进行响应。 这与 Windows 窗体用于事件的“路由”相似。 但是,与标准 CLR 事件不同的是,直接路由事件支持类处理(类处理将在下一节中介绍)而且可以由 EventSetter  EventTrigger 使用。

)

 

 

路由事件编程Example2

  与依赖项属性类似,WPF也为路由事件提供了WPF事件系统这一组成。为一个类型添加一个路由事件的方式与为类型添加依赖项属性的方法类似:软件开发人员需要通过EventManager的RegisterRoutedEvent()函数向事件系统注册路由事件。该函数的签名如下所示:

复制代码
1 public static RoutedEvent RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, 
2     Type handlerType, Type ownerType);
复制代码

  该函数带有四个参数:第一个参数name表示事件在WPF事件系统中的名称,而第二个参数routingStrategy则标明了路由事件的路由原则。第三个参数handlerType用来标明事件处理函数的类型,而最后一个参数ownerType则用来标明拥有该路由事件的类型。例如,下面就是Control类注册MouseDoubleClick事件的代码:

复制代码
1 public static readonly RoutedEvent MouseDoubleClickEvent = 
2     EventManager.RegisterRoutedEvent("MouseDoubleClick", RoutingStrategy.Direct, 
3         typeof(MouseButtonEventHandler), typeof(Control));
复制代码

  该函数返回一个RoutedEvent类型的实例。一般情况下,该实例将由一个public static readonly字段所保存,并可以通过add和remove访问符模拟为CLR事件。仍让我们以MouseDoubleClick事件为例。Control类中的MouseDoubleClick事件的实现如下所示:

复制代码
 1 public event MouseButtonEventHandler MouseDoubleClick
 2 {
 3     add
 4     {
 5         base.AddHandler(MouseDoubleClickEvent, value);
 6     }
 7     remove
 8     {
 9         base.RemoveHandler(MouseDoubleClickEvent, value);
10     }
11 }
复制代码

  在前面的讲解中我们已经提到过,EventManager类还提供了一个RegisterClassHandler()函数,以为特定路由事件注册类处理程序。该函数的原型如下所示:

复制代码
1 public static void RegisterClassHandler(Type classType, RoutedEvent routedEvent, 
2     Delegate handler, bool handledEventsToo);
复制代码

  该函数的第一个参数用来指定注册类处理函数的类型,而第二个参数则用来指定类处理函数所需要侦听的事件。第三个参数则指明了类处理函数,而将最后一个参数设置为true则允许类处理函数能够处理被标记为已处理的路由事件。

  由RegisterClassHandler()函数所注册的类处理程序可以在各个实例的事件处理程序运行之前运行。在该类处理程序中,软件开发人员可以选择将事件标记为已处理,或将当前事件转化为另一个事件。就仍以Control类的DoubleClick事件为例。Control类的静态构造函数通过RegisterClassHandler()函数首先注册了一个类处理程序:

复制代码
EventManager.RegisterClassHandler(typeof(Control), UIElement.MouseLeftButtonDownEvent, 
    new MouseButtonEventHandler(Control.HandleDoubleClick), true);
复制代码

  接下来,在类处理程序HandleDoubleClick()中,其将会在用户双击时将MouseLeftButtonDown事件转化为双击事件:

复制代码
 1 private static void HandleDoubleClick(object sender, MouseButtonEventArgs e)
 2 {
 3     if (e.ClickCount == 2) // 对双击进行处理
 4     {
 5         Control control = (Control)sender;
 6         MouseButtonEventArgs args = new MouseButtonEventArgs(e.MouseDevice, 
 7             e.Timestamp, e.ChangedButton, e.StylusDevice);
 8         if ((e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent) 
 9             || (e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent))
10         {
11             args.RoutedEvent = PreviewMouseDoubleClickEvent;
12             args.Source = e.OriginalSource;
13             args.OverrideSource(e.Source); // 注意这里对Source的处理
14             control.OnPreviewMouseDoubleClick(args); // 发出双击的Preview消息
15         }
16         else
17         {
18             args.RoutedEvent = MouseDoubleClickEvent;
19             args.Source = e.OriginalSource;
20             args.OverrideSource(e.Source);
21             control.OnMouseDoubleClick(args); // 发出双击消息
22         }
23         if (args.Handled)
24             e.Handled = true; // 将Handled设置为true,从而使该消息被隐藏
25     }
26 }
复制代码

  需要注意的是,RegisterClassHandler()函数所注册的类处理函数需要是静态成员函数,因此您需要从参数中得到发出路由事件的类型实例。例如在上面的函数中,类处理函数就是通过参数sender得到实际发出路由事件的类型实例的。

  同时,上面的代码还向您展示了在组件编程过程中隐藏消息的方法及实现自定义输入事件的方法。在上面的代码中,路由事件的响应函数会手动发出双击的消息,从而使控件的PreviewDoubleClick以及DoubleClick事件被触发。接下来,路由事件的响应函数会将原事件的Handled属性设置为true,进而使原本的低级输入事件被隐藏。这种将低级事件隐藏并转化为高级事件的方法在WPF中非常常见。就以我们常用的Button类为例。在鼠标按下的时候,我们会接收到PreviewMouseDown事件,却不能接收到MouseDown事件。您一方面需要理解并掌握该方法,另一方面,您在遇到该情况时应能估计到产生该情况的原因,更能使用相应的解决方案:Preview-事件。

  除了通过RegisterRoutedEvent()函数之外,软件开发人员还可以通过RoutedEvent的AddOwner()函数将其它类型所定义的路由事件作为自身的路由事件。RoutedEvent的成员函数AddOwner()函数的原型如下:

复制代码
1 public RoutedEvent AddOwner(Type ownerType)
复制代码

  该函数同样返回一个RoutedEvent实例并可以在CLR包装中使用。就以UIElement类所提供的MouseMove事件为例。WPF首先通过AddOwner()函数添加了对事件的引用:

复制代码
1 public static readonly RoutedEvent MouseMoveEvent = 
2     Mouse.MouseMoveEvent.AddOwner(typeof(UIElement));
复制代码

  接下来,您仍需要按照通常的方式为该附加事件添加一个CLR事件包装:

复制代码
 1 public event MouseEventHandler MouseMove
 2 {
 3     add
 4     {
 5         this.AddHandler(Mouse.MouseMoveEvent, value, false);
 6     }
 7     remove
 8     {
 9         this.RemoveHandler(Mouse.MouseMoveEvent, value);
10     }
11 }
复制代码

  最后要说的则是如何处理Handled已经被设置为true的路由事件。在需要处理这种类型事件的时候,您首先需要考虑的是,您当前的解决方案是否有略欠妥当的地方。如果您有足够强的理由证明自己对Handled属性已经被设置为true的路由事件的处理是有必要的,您需要通过AddHandler函数添加对路由事件的侦听,并在该函数调用中设置属性handledEventsToo参数的值为true。除此之外,软件开发人员还可以通过EventSetter中的HandledEventsToo属性实现相同的功能。

 

附加事件

  和附加属性与依赖项属性之间的关系相对应,WPF的事件系统也支持普通的路由事件以及附加事件。与附加属性具有完全不同的语法实现不同,附加事件所使用的语法与普通的路由事件没有什么不同。例如,下面的代码中,对Image.MouseDown事件的使用就是对普通路由事件的使用:

复制代码
1 <StackPanel Image.MouseDown=…>
复制代码

  而对Mouse.MouseDown事件的使用就是对附加路由事件的使用:

复制代码
1 <StackPanel Mouse.MouseDown=…>
复制代码

  在上面的两段代码中,我们都使用了“类型名称.事件名称”的限定事件语法。但是它们一个属于普通的路由事件,一个是附加事件。那到底怎样辨别哪些是路由事件,哪些是附加事件呢。实际上,附加事件更主要的是其所具有的语义特征。与附加属性所拥有的服务特性类似,附加事件也常常对应着一个全局服务,例如Mouse类所对应的鼠标输入服务。这些服务可能并不会在XAML中作为当前元素的子元素存在。

  我们前面已经看到了,WPF的众多类型都通过AddOwner()函数调用等一系列方法将一些服务所提供的路由事件整合进类型定义中,例如UIElement类对Mouse类所提供的各个路由事件的集成。这常常是控件编写者所采用的一种控件编写策略。毕竟系统服务常常是较为低级的API。将其集成到类型中一方面可以直接使用该事件,而不是路由事件的限定形式,另一方面也令XAML对事件的表示更为直观。

  当然,您不要以为仅仅是输入等底层组成需要创建服务,其实在控件开发过程中,对这种服务的使用也是常常出现的。就以Selector为例。在您查看TreeViewItem,ListBoxItem等组成的实现时,您就会发现它们通过AddOwner()函数添加了对Selector.Selected事件的使用,而Selector自身则添加了对该事件的侦听,并最终转化为路由事件SelectionChanged。这是一个非常明显的对附加路由事件的使用。之所以将选中事件实现为一个服务则是因为对各个项目的选中操作常常发生在各个条目的内部,如鼠标的点击,因此在这些条目中处理选中事件并将该事件路由至Selector是一种较好的解决方法。

  那如何创建一个路由事件呢?答案就是为控件添加AddYourEventHandler()以及RemoveYourEventHandler()两个函数。这两个函数的第一个参数都需要标明需要操作的事件,事件的名称需要与YourEvent所代表的名称相匹配。而第二个参数则是需要为该附加事件指定的处理程序。就以Mouse类所提供的函数AddMouseDownHandler()以及RemoveMouseDownHandler()为例:

复制代码
 1 public static void AddMouseDownHandler(DependencyObject element, 
 2     MouseButtonEventHandler handler)
 3 {
 4     UIElement.AddHandler(element, MouseDownEvent, handler);
 5 }
 6 
 7 public static void RemoveMouseDownHandler(DependencyObject element, 
 8     MouseButtonEventHandler handler)
 9 {
10     UIElement.RemoveHandler(element, MouseDownEvent, handler);
11 }
复制代码

  这样,您就可以在XAML中通过Mouse.MouseDown引用Mouse类所提供的MouseDown附加事件了。

 

转载请注明原文地址:http://www.cnblogs.com/loveis715/archive/2012/04/09/2439803.html

 

 

附整个实例的代码:

后台代码:

using System;
using System.Collections.Generic;
using System.Linq;
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.Navigation;
using System.Windows.Shapes;

namespace WpfApp2
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TimeButton_ReportTime(object sender, ReportTimeRoutedEventArgs e)//注意参数
        {
            listBox1.Items.Add(e.ClickTime.ToString() + "   ---   " + sender.GetType().ToString());
        }

        private void FuncByAddOwner(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Add("+++++++++++++++++++++  " + sender.GetType());
        }
    }

    //事件参数
    class ReportTimeRoutedEventArgs : RoutedEventArgs
    {
        public ReportTimeRoutedEventArgs(RoutedEvent routedEvent, object source) :
            base(routedEvent, source)
        {
        }

        public DateTime ClickTime { get; set; }
    }

    /// <summary>
    /// 自定义控件  路由事件一般为UIElement下的控件  可视化
    /// </summary>
    class TimeButton : Button
    {
        //声明和注册路由事件
        public static readonly RoutedEvent ReportTimeRoutedEvent =
        EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeRoutedEventArgs>), typeof(TimeButton));
        //CLR事件包装
        public event RoutedEventHandler ReportTime
        {
            add { this.AddHandler(ReportTimeRoutedEvent, value); }
            remove { this.RemoveHandler(ReportTimeRoutedEvent, value); }
        }

        #region 通过AddOwner方法 借用KnockButton的路由事件

        public static readonly RoutedEvent PreviewKnockEvent;
        public static readonly RoutedEvent KnockEvent;

        public event RoutedEventHandler Knock
        {
            add { this.AddHandler(KnockEvent, value); }
            remove { this.RemoveHandler(KnockEvent, value); }
        }
        public event RoutedEventHandler PreviewKnock
        {
            add { this.AddHandler(PreviewKnockEvent, value); }
            remove { this.RemoveHandler(PreviewKnockEvent, value); }
        }

        #endregion

        //激发路由事件,借用Click事件的激发方法
        protected override void OnClick()
        {
            base.OnClick();//保证Button原有功能正常使用,Click事件被激发
            ReportTimeRoutedEventArgs args = new ReportTimeRoutedEventArgs(ReportTimeRoutedEvent, this);
            //以下两个参数此程序无用,但在真实的项目中,使用前请赋值,保证参数无误
            args.RoutedEvent = ReportTimeRoutedEvent;
            args.Source = this;
            args.ClickTime = DateTime.Now;
            this.RaiseEvent(args);//UIElement及其派生类 


            //激发路由事件 自身拥有的每一个路由控件 都需要调用一下RaiseEvent方法
            RoutedEventArgs args2 = new RoutedEventArgs();
            args2.RoutedEvent = PreviewKnockEvent;
            args2.Source = this;
            this.RaiseEvent(args2);//UIElement及其派生类 
            args2.RoutedEvent = KnockEvent;
            args2.Source = this;
            this.RaiseEvent(args2);//UIElement及其派生类 
        }

        static TimeButton()
        {
            //添加类处理事件
            EventManager.RegisterClassHandler(typeof(TimeButton), Button.ClickEvent, new RoutedEventHandler(FirstRunFunc), true);
            //使用AddOwner方法将其他类型的路由事件作为自身的路由事件 example
            PreviewKnockEvent = KnockButton.PreviewKnockEvent.AddOwner(typeof(TimeButton));
            KnockEvent = KnockButton.KnockEvent.AddOwner(typeof(TimeButton));
        }

        //类处理程序 每次执行一次, 先于路由事件执行
        private static void FirstRunFunc(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("First Run ?");
        }
    }

    /// <summary>
    /// 另一自定义控件 为TimeButton控件提供其路由事件KnockEvent 和 PreviewKnockEvent   --example
    /// </summary>
    class KnockButton : Button
    {
        public static readonly RoutedEvent PreviewKnockEvent;
        public static readonly RoutedEvent KnockEvent;

        static KnockButton()
        {
            PreviewKnockEvent = EventManager.RegisterRoutedEvent("PreviewKnock", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(KnockButton));
            KnockEvent = EventManager.RegisterRoutedEvent("Knock", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(KnockButton));
        }
        public event RoutedEventHandler Knock
        {
            add { this.AddHandler(KnockEvent, value); }
            remove { this.RemoveHandler(KnockEvent, value); }
        }
        public event RoutedEventHandler PreviewKnock
        {
            add { this.AddHandler(PreviewKnockEvent, value); }
            remove { this.RemoveHandler(PreviewKnockEvent, value); }
        }

        //激发路由事件,借用Click事件的激发方法 (但此例由于没有触发KnockButton控件的Click方法,故此例此方法将不会被调用)
        protected override void OnClick()
        {
            RoutedEventArgs args = new RoutedEventArgs();
            args.RoutedEvent = PreviewKnockEvent;
            args.Source = this;
            this.RaiseEvent(args);//UIElement及其派生类 
            args.RoutedEvent = KnockEvent;
            args.Source = this;
            this.RaiseEvent(args);//UIElement及其派生类 
        }
    }
}

 

XAML代码:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp2"
        Title="MainWindow" Width="500" Height="500" >
    <Grid  Name="grid" local:TimeButton.ReportTime="TimeButton_ReportTime" local:TimeButton.Knock="FuncByAddOwner">
        <Grid x:Name="grid2">
            <Grid x:Name="grid3">
                <StackPanel x:Name="stackPanel1" >
                    <ListBox x:Name="listBox1" Height="300"/>
                    <local:TimeButton Width="100" Height="50" Content="TimeButton"  ReportTime="TimeButton_ReportTime"  Knock="FuncByAddOwner"/>
                </StackPanel>
            </Grid>
        </Grid>
    </Grid>
</Window>

 

 

多谢网友的分享~ 多谢支持!

posted @ 2013-08-20 17:41  三叶草╮  阅读(2120)  评论(0编辑  收藏  举报