Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

03 Nov 2019

XAML API 学习笔记:UIElement

DO(DependencyObject伴对象)以及DP(DependencyProperty伴辖属)对应了XAML语法中的元素和属性,以及树形结构中的节点。那么从DO派生出来的UIElement进一步丰富了DO的语义。为DO添加UI构件的响应性。

你无法实例化一个UIElement,因为UIElement被设计无法直接实例化。UIElement基本上是所有UI构件的基本类型,所以可以使用UIElement来进行多态引用UI构件的实例。在UIElement这一级,主要是定义了事件响应机制,只有指定很少的和绘制相关的操作或者特性。绘制主要在UIElement的下一级,也就是FrameworkElement中定义。

在GUI中,最重要的细节是如何在屏幕上绘制出图形。在XAML中,这些细节被推迟到允许的最底层来处理。比如作为Control的Button本身依然可以不用定义自己的绘制细节,而是通过一个ControlTemplate来给出其细节。

前面提到UIElement定义的是事件响应机制。事件响应机制本身是抽象,一个UI构件即便不在屏幕上显示,也可以有事件响应机制,只是接收不到事件。

UIElement支持以下事件处理:

  • Pointer events: PointerPressed, PointerReleased, PointerMoved, PointerEntered, PointerExited
  • Key handling events: KeyDown, KeyUp
  • Focus: GotFocus, LostFocus
  • Pointer capture: CapturePointer, PointerCanceled, PointerCaptureLost, ReleasePointerCapture, PointerCaptures
  • Drag-drop: DragOver, Drop, DragEnter, DragLeave, AllowDrop
  • Properties that influence how basic input is processed: IsHitTestVisible, AllowDrop
  • Gesture events: DoubleTapped, Holding, RightTapped, Tapped
  • Manipulation events: ManipulationCompleted, ManipulationDelta, ManipulationInertiaStarting, ManipulationStarted, ManipulationStarting
  • Properties that influence how gestures and manipulations are processed: IsHoldingEnabled and other Is*Enabled, ManipulationMode

UIElement.AddHandlerUIElement.RemoveHandler可以用来在UIElement上添加或者删除事件处理器。添加的时候可以告诉事件处理器是否忽略已经被处理过的事件。事件处理本身是一个顶层的通用对象,比如C#的object或者C++WinRT中的IInspectable。但是,具体的事件处理器必须能够接受目标事件所提供的参数。

事件处理Events and routed events overview

下面是一个按键点击的例子

<Button x:Name="showUpdatesButton"
  Content="{Binding ShowUpdatesText}"
  Click="ShowUpdatesButton_Click"/>
private void ShowUpdatesButton_Click (object sender, RoutedEventArgs e) 
{
    Button b = sender as Button;
    //more logic to do here...
}

C#支持partial class,所以可以把事件处理方法申明为private的。XAML会生成partial class的调用事件处理方法的代码,所以事件处理方法和调用者,其实在一个类中。

事件处理器获得的第一个参数是事件目标。传进来的是一个通用类型的对象,需要转成具体的类型(大部分情况下你知道事件是在哪个对象上触发的,可以直接转,而不用担心类型转换错误)。事件处理器接受的第二个参数是事件参数。如果需要的话,第二个参数可以指定具体的事件参数类型,而不是一个通用的类型。

RoutedEventArgs是大部分事件参数类型的基类。它只有一个辖属:OriginalSource,用来表明事件的原始发生地。在WPF中,Handled辖属也是附着在RoutedEventArgs上,但是在UWP中,Handled归属于某些从RoutedEventArgs派生的类型。比如对于KeyUpKeyDown事件,他们的事件参数类型是KeyRoutedEventArgs,具有如下辖属:

  • DeviceId
  • Handled
  • Key
  • KeyStatus
  • OriginalKey
  • OriginalSource (从基类继承出来的)

注意:事件处理器也可以是异步的。

可以直接在代码,而不是XAML上添加事件处理器,比如:

// C++/WinRT
textBlock1().PointerEntered({this, &MainPage::TextBlock1_PointerEntered });

通常不需要删除添加的事件处理器,如果在特殊情况下需要这么做,可以使用相编程语言提供的方法。

事件传导

如果注意到事件的名字中都带有Routed字样,这个意思是,事件可以由子对象往父对象传播。这有点像HTML DOM中事件的Bubbling过程。传到的过程事件的数据是共享的,某个事件处理器如果修改了事件数据,后续的事件处理器可以见到修改。在事件传到过程中,事件的sender会发生改变,变为捕获该事件的对象。如果想知道事件的原始发生地,可以使用事件的OriginalSource辖属。

OnApplyTemplate,可以用来模板初始化时的事件转发。

有些事件带有Handled辖属,可以用来避开那些不处理Handled事件的事件处理器。有些派生的事件,比如click,是由原生事件KeyDown和KeyUp转化而成了。这时候原生事件会被设置为Handled。可以通过修改控件的OnEvent方法,来改变这种处理方式。比如在TextBox的派生类中重定义Control.OnKeyDown。

Popup和Tooltip由于不在XAML VisualTree之中,所以他们不支持事件传导。只能在Popup或者Tooltip之内的元素上放置事件处理器。

XAML还支持自定义事件,不过这跟具体使用的语言有密切关系。

Hit Testing

判断能够响应鼠标、触摸手势以及触摸笔的过程叫做Hit Testing。一个元素需要具有若干特性才能进行Hit Testing:

  • The element’s Visibility property value is Visible.
  • The element’s Background or Fill property value is not null. A null Brush value results in transparency and hit test invisibility. (To make an element transparent but also hit testable, use a Transparent brush instead of null.)
  • If the element is a control, its IsEnabled property value must be true.
  • The element must have actual dimensions in layout. An element where either ActualHeight and ActualWidth are 0 won’t fire input events.

简单而言,元素必须有形且可用才可以进行Hit Testing。有一些元素由特殊的规则。通过FindElementsInHostCoordinates可以找到Hit Testing发生的同一个Host下的其他元素。

跟绘制以及布局相关的

Visibility和Opacity可以用来调整显示效果。Clip、RenderTransform以及Transistion用于交互时的UI效果。

DesiredSize、Arrange以及Measure是和布局相关的几个辖属。

其他参考

(完)

comments powered by Disqus