行为架构行为架构
行为体系结构是Microsoft Blend引入的一个新概念,用于将功能部件封装到可重用的组件中。这些组件可以附加到控件以使其具有其他行为。
行为背后的想法是使设计人员在无需编写任何代码的情况下设计复杂的用户交互时具有更大的灵活性。开发人员可以提供编写触发器,动作和行为的功能,这些触发器,动作和行为可以被开发人员和设计人员完全封装并重用。同样,设计人员有权在工作中添加新级别的交互功能,而无需触摸代码文件。
行为的示例是拖放,输入验证,平移和缩放或元素的重新****放置。可能的行为列表很长。
注意
行为的实现是应用程序框架的一部分。
触发和动作
对于具有WPF背景的任何人,触发器和操作听起来应该都很熟悉。行为体系结构引入了类似的模型,并允许您编写自己的触发器和动作-为可以在自己的应用程序中创建和重用的功能种类开辟了一个全新的世界。
的动作是可以被调用来执行操作的对象。如果您认为这听起来很含糊,那是对的。动作的范围不受限制:如果可以编写代码来执行某项操作,则可以编写一个动作来执行相同的事情。也就是说,最好编写动作来执行本质上是原子的操作。也就是说,当动作不依赖于在动作调用之间需要保持的外部状态,并且不依赖于相对于其调用以特定顺序存在或以其他顺序运行的其他动作时,它们的工作效果最佳。
例如:更改属性,调用方法,打开窗口,导航到页面或设置焦点。
动作本身并不是特别有用:它们提供执行某项功能的功能,但无法激活该功能。为了调用一个动作,我们需要一个Trigger。触发器是包含一个或多个动作并响应某些刺激而调用这些动作的对象。一种非常常见的触发器是响应事件而触发的触发器(EventTrigger)。其他示例可能包括在计时器上触发的触发器,或在某些数据更改时触发的触发器。
注意
需要注意的重要一件事是,触发器和动作通常应任意一起使用。换句话说,应该避免编写对调用它的触发器的类型进行假设的操作,或者对与其所属的操作进行假设的触发器。如果发现自己需要触发器和动作之间的紧密结合,则应该考虑一种行为。
它是如何工作的?
要将触发器和动作添加到元素,您需要某种扩展点:名为Interaction.Triggers的附加属性。
此附加属性保存该元素的触发器列表,并将对该元素的引用传递到触发器中。然后,触发器可以将自身注册到事件或属性更改,以作出反应并调用其包含的动作。
这个想法很简单,但是非常聪明。我们不需要任何新的基础架构,我们只需重用现有的基础架构即可。
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:noesis="clr-namespace:NoesisGUIExtensions">
<TextBox Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<noesis:SetFocusAction/>
</i:EventTrigger>
</i:Interaction.Trigger>
</TextBox>
</Border>
C ++
class SetFocusAction final: public NoesisApp::TargetedTriggerActionT<Noesis::UIElement>
{
void Invoke(Noesis::BaseComponent* parameter) override
{
UIElement* element = GetTarget();
if (element != nullptr)
{
element->Focus();
}
}
//...
};
C#
public class SetFocusAction: public NoesisApp.TargetedTriggerActionT<Noesis.UIElement>
{
protected override void Invoke(object parameter)
{
UIElement element = Target;
if (element != null)
{
element.Focus();
}
}
};
注意
Microsoft Blend行为体系结构类映射到以下名称空间:
- http://schemas.microsoft.com/expression/2010/interactivity
- http://schemas.microsoft.com/expression/2010/interactions
这些名称空间通常分别与i:和ei:前缀关联。将动作或行为拖动到Blend中的任何控件时,它们会自动添加到您的xaml文件中。
由Noesis开发的扩展在NoesisGUIExtensions命名空间中定义,您将找到带有noesis:前缀的扩展。
支持的触发器
<colgroup style="box-sizing: inherit;"><col width="7%" style="box-sizing: inherit;"><col width="93%" style="box-sizing: inherit;"></colgroup>
字首 | 名称 |
---|---|
一世: | 事件触发 |
ei: | 定时器触发 |
ei: | 按键触发 |
ei: | PropertyChangedTrigger |
ei: | 数据触发 |
ei: | 故事板完成触发 |
支持的动作
<colgroup style="box-sizing: inherit;"><col width="8%" style="box-sizing: inherit;"><col width="92%" style="box-sizing: inherit;"></colgroup>
字首 | 名称 |
---|---|
一世: | InvokeCommandAction |
ei: | ChangePropertyAction |
ei: | GoToStateAction |
ei: | RemoveElementAction |
ei: | ControlStoryboardAction |
ei: | LaunchUriOrFileAction |
ei: | PlaySoundAction |
ei: | LaunchUriOrFileAction |
鼻息: | SetFocusAction |
鼻息: | SelectAction |
鼻息: | SelectAll动作 |
行为举止
尽管WPF之前已经建立了触发器和动作的概念,但是“ 行为”的概念却是一个新概念。一目了然,行为看起来类似于动作:功能齐全的独立单元。主要区别在于动作期望被调用,并且在被调用时,它们将执行某些操作。行为没有调用的概念。相反,它更多地充当对象的附加组件:如果需要,可以将附加功能附加到对象。它可以响应来自环境的刺激而做某些事情,但是不能保证用户可以控制这种刺激是什么:行为作者可以决定哪些可以自定义,哪些不能自定义。
例如,请考虑一种行为,该行为允许用户拖动鼠标将该行为附加到的对象。该行为需要侦听附加对象上的mouse down,mouse move和mouse up事件。响应鼠标按下,行为将记录鼠标位置,挂钩鼠标移动和鼠标向上处理程序并捕获鼠标输入。在鼠标移动时,将更新对象的位置以及鼠标的位置。在鼠标了,它会释放鼠标捕获和脱钩鼠标移动和鼠标了处理程序。
一种方法可能是尝试对每个事件使用EventTriggers,然后编写StartDragAction,MoveDragAction和StopDragAction分别在每种情况下调用。但是,很快就会发现,这种情况不能通过操作很好地解决,因为它需要在调用之间存储状态(先前的鼠标位置和拖动状态),并且操作不是原子的。相反,我们可以编写一个行为,将上面概述的确切功能包装到可重用的组件中。
它是如何工作的?
为了向元素添加行为,我们还将使用一个称为Interaction.Behaviors的附加属性。
此附加属性保存该元素的行为列表,并将对该元素的引用传递到行为中。然后,行为可以将自身注册到事件和属性更改,以扩展元素的功能。
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
<Border Background="Silver" Margin="100">
<Rectangle Width="200" Height="100" Fill="Red">
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
</Rectangle>
</Border>
</Grid>
C ++
class MouseDragElementBehavior: public NoesisApp::BehaviorT<Noesis::FrameworkElement>
{
public:
float GetX() const { return GetValue<float>(XProperty); }
void SetX(float x) { SetValue<float>(XProperty); }
float GetY() const { return GetValue<float>(YProperty); }
void SetY(float y) { SetValue<float>(YProperty); }
bool GetConstrainToParentBounds() const { return GetValue<bool>(ConstrainToParentBoundsProperty); }
void SetConstrainToParentBounds(bool value) { SetValue<bool>(ConstrainToParentBoundsProperty); }
protected:
void OnAttached()
{
FrameworkElement* obj = GetAssociatedObject();
_transform = *new Noesis::TranslateTransform();
obj->SetRenderTransform(_transform);
obj->MouseLeftButtonDown() += MakeDelegate(this, &MouseDragElementBehavior::OnMouseLeftButtonDown);
}
void OnDetaching()
{
FrameworkElement* obj = GetAssociatedObject();
_transform = 0;
obj->SetRenderTransform(0);
obj->MouseLeftButtonDown() -= MakeDelegate(this, &MouseDragElementBehavior::OnMouseLeftButtonDown);
}
// ...
};
C#
public class MouseDragElementBehavior: NoesisApp.Behavior<Noesis.FrameworkElement>
{
public float X
{
get { return (float)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public float Y
{
get { return (float)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
public bool ConstrainToParentBounds
{
get { return (bool)GetValue(ConstrainToParentBoundsProperty); }
set { SetValue(ConstrainToParentBoundsProperty, value); }
}
protected override void OnAttached()
{
FrameworkElement associatedObject = AssociatedObject;
_transform = new TranslateTransform();
associatedObject.RenderTransform = _transform;
associatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;
}
protected override void OnDetaching()
{
FrameworkElement associatedObject = AssociatedObject;
_transform = null;
associatedObject.RenderTransform = null;
associatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
}
// ...
}
支持的行为
<colgroup style="box-sizing: inherit;"><col width="7%" style="box-sizing: inherit;"><col width="93%" style="box-sizing: inherit;"></colgroup>
字首 | 名称 |
---|---|
ei | 条件行为 |
ei | MouseDragElementBehavior |
ei | TranslateZoomRotateBehavior |
款式
有时,为一种控件定义动作和行为很有用。与其为该控件的每个实例添加相应的交互附加属性,我们不希望在Style中对其进行一次定义,以便将其自动应用于所有它们。
我们将发现的问题是Interaction.Behaviors和Interaction.Triggers是不能从XAML直接设置的私有附加属性。
要解决此问题,我们创建了一对新的附加属性,使您可以定义样式中的动作和行为的集合。它们分别称为StyleInteraction.Triggers和StyleInteraction.Behaviors,并且在名称空间NoesisGUIExtensions内部定义。它们可以这样使用:
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:noesis="clr-namespace:NoesisGUIExtensions">
<Grid.Resources>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="noesis:StyleInteraction.Triggers">
<Setter.Value>
<noesis:StyleTriggerCollection>
<i:EventTrigger EventName="Click">
<ei:PlaySoundAction Source="buttonClick.wav"/>
</i:EventTrigger>
</noesis:StyleTriggerCollection>
</Setter.Value>
</Setter>
<Setter Property="noesis:StyleInteraction.Behaviors">
<Setter.Value>
<noesis:StyleBehaviorCollection>
<ei:MouseDragElementBehavior/>
</noesis:StyleBehaviorCollection>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Start"/>
<Button Content="Options"/>
<Button Content="Exit"/>
</StackPanel>
</Grid>
将样式应用于控件后,附加属性将从控件中获取Interaction.Triggers或Interaction.Behaviors集合,并在其中克隆动作或行为,其结果与直接在控件中指定动作或行为的结果相同。
网友评论