0614-Interactivity 相互作用/Behavior

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

<!-- TextBox控件的获得焦点、失去焦点事件 -->
<TextBox Text="Test">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="LostFocus">
            <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.OnTextLostFocus}"
                                   CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type TextBox}}}"/>
        </i:EventTrigger>
        <i:EventTrigger EventName="GotFocus">
            <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.OnTextGotFocus}"
                                   CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type TextBox}}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

Events

ContextMenuClosing

Occurs just before any context menu on the element is closed.

(Inherited from FrameworkElement)
ContextMenuOpening

Occurs when any context menu on the element is opened.

(Inherited from FrameworkElement)
DataContextChanged

Occurs when the data context for this element changes.

(Inherited from FrameworkElement)
DragEnter

Occurs when the input system reports an underlying drag event with this element as the drag target.

(Inherited from UIElement)
DragLeave

Occurs when the input system reports an underlying drag event with this element as the drag origin.

(Inherited from UIElement)
DragOver

Occurs when the input system reports an underlying drag event with this element as the potential drop target.

(Inherited from UIElement)
Drop

Occurs when the input system reports an underlying drop event with this element as the drop target.

(Inherited from UIElement)
FocusableChanged

Occurs when the value of the Focusable property changes.

(Inherited from UIElement)
GiveFeedback

Occurs when the input system reports an underlying drag-and-drop event that involves this element.

(Inherited from UIElement)
GotFocus

Occurs when this element gets logical focus.

(Inherited from UIElement)
GotKeyboardFocus

Occurs when the keyboard is focused on this element.

(Inherited from UIElement)
GotMouseCapture

Occurs when this element captures the mouse.

(Inherited from UIElement)
GotStylusCapture

Occurs when this element captures the stylus.

(Inherited from UIElement)
GotTouchCapture

Occurs when a touch is captured to this element.

(Inherited from UIElement)
Initialized

Occurs when this FrameworkElement is initialized. This event coincides with cases where the value of the IsInitialized property changes from false (or undefined) to true.

(Inherited from FrameworkElement)
IsEnabledChanged

Occurs when the value of the IsEnabled property on this element changes.

(Inherited from UIElement)
IsHitTestVisibleChanged

Occurs when the value of the IsHitTestVisible dependency property changes on this element.

(Inherited from UIElement)
IsKeyboardFocusedChanged

Occurs when the value of the IsKeyboardFocused property changes on this element.

(Inherited from UIElement)
IsKeyboardFocusWithinChanged

Occurs when the value of the IsKeyboardFocusWithin property changes on this element.

(Inherited from UIElement)
IsMouseCapturedChanged

Occurs when the value of the IsMouseCaptured property changes on this element.

(Inherited from UIElement)
IsMouseCaptureWithinChanged

Occurs when the value of the IsMouseCaptureWithinProperty changes on this element.

(Inherited from UIElement)
IsMouseDirectlyOverChanged

Occurs when the value of the IsMouseDirectlyOver property changes on this element.

(Inherited from UIElement)
IsStylusCapturedChanged

Occurs when the value of the IsStylusCaptured property changes on this element.

(Inherited from UIElement)
IsStylusCaptureWithinChanged

Occurs when the value of the IsStylusCaptureWithin property changes on this element.

(Inherited from UIElement)
IsStylusDirectlyOverChanged

Occurs when the value of the IsStylusDirectlyOver property changes on this element.

(Inherited from UIElement)
IsVisibleChanged

Occurs when the value of the IsVisible property changes on this element.

(Inherited from UIElement)
KeyDown

Occurs when a key is pressed while focus is on this element.

(Inherited from UIElement)
KeyUp

Occurs when a key is released while focus is on this element.

(Inherited from UIElement)
LayoutUpdated

Occurs when the layout of the various visual elements associated with the current Dispatcher changes.

(Inherited from UIElement)
Loaded

Occurs when the element is laid out, rendered, and ready for interaction.

(Inherited from FrameworkElement)
LostFocus

Occurs when this element loses logical focus.

(Inherited from UIElement)
LostKeyboardFocus

Occurs when the keyboard is no longer focused on this element,.

(Inherited from UIElement)
LostMouseCapture

Occurs when this element loses mouse capture.

(Inherited from UIElement)
LostStylusCapture

Occurs when this element loses stylus capture.

(Inherited from UIElement)
LostTouchCapture

Occurs when this element loses a touch capture.

(Inherited from UIElement)
ManipulationBoundaryFeedback

Occurs when the manipulation encounters a boundary.

(Inherited from UIElement)
ManipulationCompleted

Occurs when a manipulation and inertia on the UIElement object is complete.

(Inherited from UIElement)
ManipulationDelta

Occurs when the input device changes position during a manipulation.

(Inherited from UIElement)
ManipulationInertiaStarting

Occurs when the input device loses contact with the UIElement object during a manipulation and inertia begins.

(Inherited from UIElement)
ManipulationStarted

Occurs when an input device begins a manipulation on the UIElement object.

(Inherited from UIElement)
ManipulationStarting

Occurs when the manipulation processor is first created.

(Inherited from UIElement)
MouseDown

Occurs when any mouse button is pressed while the pointer is over this element.

(Inherited from UIElement)
MouseEnter

Occurs when the mouse pointer enters the bounds of this element.

(Inherited from UIElement)
MouseLeave

Occurs when the mouse pointer leaves the bounds of this element.

(Inherited from UIElement)
MouseLeftButtonDown

Occurs when the left mouse button is pressed while the mouse pointer is over this element.

(Inherited from UIElement)
MouseLeftButtonUp

Occurs when the left mouse button is released while the mouse pointer is over this element.

(Inherited from UIElement)
MouseMove

Occurs when the mouse pointer moves while over this element.

(Inherited from UIElement)
MouseRightButtonDown

Occurs when the right mouse button is pressed while the mouse pointer is over this element.

(Inherited from UIElement)
MouseRightButtonUp

Occurs when the right mouse button is released while the mouse pointer is over this element.

(Inherited from UIElement)
MouseUp

Occurs when any mouse button is released over this element.

(Inherited from UIElement)
MouseWheel

Occurs when the user rotates the mouse wheel while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewDragEnter

Occurs when the input system reports an underlying drag event with this element as the drag target.

(Inherited from UIElement)
PreviewDragLeave

Occurs when the input system reports an underlying drag event with this element as the drag origin.

(Inherited from UIElement)
PreviewDragOver

Occurs when the input system reports an underlying drag event with this element as the potential drop target.

(Inherited from UIElement)
PreviewDrop

Occurs when the input system reports an underlying drop event with this element as the drop target.

(Inherited from UIElement)
PreviewGiveFeedback

Occurs when a drag-and-drop operation is started.

(Inherited from UIElement)
PreviewGotKeyboardFocus

Occurs when the keyboard is focused on this element.

(Inherited from UIElement)
PreviewKeyDown

Occurs when a key is pressed while focus is on this element.

(Inherited from UIElement)
PreviewKeyUp

Occurs when a key is released while focus is on this element.

(Inherited from UIElement)
PreviewLostKeyboardFocus

Occurs when the keyboard is no longer focused on this element.

(Inherited from UIElement)
PreviewMouseDown

Occurs when any mouse button is pressed while the pointer is over this element.

(Inherited from UIElement)
PreviewMouseLeftButtonDown

Occurs when the left mouse button is pressed while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseLeftButtonUp

Occurs when the left mouse button is released while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseMove

Occurs when the mouse pointer moves while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseRightButtonDown

Occurs when the right mouse button is pressed while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseRightButtonUp

Occurs when the right mouse button is released while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseUp

Occurs when any mouse button is released while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewMouseWheel

Occurs when the user rotates the mouse wheel while the mouse pointer is over this element.

(Inherited from UIElement)
PreviewQueryContinueDrag

Occurs when there is a change in the keyboard or mouse button state during a drag-and-drop operation.

(Inherited from UIElement)
PreviewStylusButtonDown

Occurs when the stylus button is pressed while the pointer is over this element.

(Inherited from UIElement)
PreviewStylusButtonUp

Occurs when the stylus button is released while the pointer is over this element.

(Inherited from UIElement)
PreviewStylusDown

Occurs when the stylus touches the digitizer while it is over this element.

(Inherited from UIElement)
PreviewStylusInAirMove

Occurs when the stylus moves over an element without actually touching the digitizer.

(Inherited from UIElement)
PreviewStylusInRange

Occurs when the stylus is close enough to the digitizer to be detected, while over this element.

(Inherited from UIElement)
PreviewStylusMove

Occurs when the stylus moves while over the element. The stylus must move while being detected by the digitizer to raise this event, otherwise, PreviewStylusInAirMove is raised instead.

(Inherited from UIElement)
PreviewStylusOutOfRange

Occurs when the stylus is too far from the digitizer to be detected.

(Inherited from UIElement)
PreviewStylusSystemGesture

Occurs when a user performs one of several stylus gestures.

(Inherited from UIElement)
PreviewStylusUp

Occurs when the user raises the stylus off the digitizer while the stylus is over this element.

(Inherited from UIElement)
PreviewTextInput

Occurs when this element gets text in a device-independent manner.

(Inherited from UIElement)
PreviewTouchDown

Occurs when a finger touches the screen while the finger is over this element.

(Inherited from UIElement)
PreviewTouchMove

Occurs when a finger moves on the screen while the finger is over this element.

(Inherited from UIElement)
PreviewTouchUp

Occurs when a finger is raised off of the screen while the finger is over this element.

(Inherited from UIElement)
QueryContinueDrag

Occurs when there is a change in the keyboard or mouse button state during a drag-and-drop operation.

(Inherited from UIElement)
QueryCursor

Occurs when the cursor is requested to display. This event is raised on an element each time that the mouse pointer moves to a new location, which means the cursor object might need to be changed based on its new position.

(Inherited from UIElement)
RequestBringIntoView

Occurs when BringIntoView(Rect) is called on this element.

(Inherited from FrameworkElement)
SizeChanged

Occurs when either the ActualHeight or the ActualWidth properties change value on this element.

(Inherited from FrameworkElement)
SourceUpdated

Occurs when the source value changes for any existing property binding on this element.

(Inherited from FrameworkElement)
StylusButtonDown

Occurs when the stylus button is pressed while the pointer is over this element.

(Inherited from UIElement)
StylusButtonUp

Occurs when the stylus button is released while the pointer is over this element.

(Inherited from UIElement)
StylusDown

Occurs when the stylus touches the digitizer while the stylus is over this element.

(Inherited from UIElement)
StylusEnter

Occurs when the stylus enters the bounds of this element.

(Inherited from UIElement)
StylusInAirMove

Occurs when the stylus moves over an element without actually touching the digitizer.

(Inherited from UIElement)
StylusInRange

Occurs when the stylus is close enough to the digitizer to be detected, while over this element.

(Inherited from UIElement)
StylusLeave

Occurs when the stylus leaves the bounds of the element.

(Inherited from UIElement)
StylusMove

Occurs when the stylus moves over this element. The stylus must move while on the digitizer to raise this event. Otherwise, StylusInAirMove is raised instead.

(Inherited from UIElement)
StylusOutOfRange

Occurs when the stylus is too far from the digitizer to be detected, while over this element.

(Inherited from UIElement)
StylusSystemGesture

Occurs when a user performs one of several stylus gestures.

(Inherited from UIElement)
StylusUp

Occurs when the user raises the stylus off the digitizer while it is over this element.

(Inherited from UIElement)
TargetUpdated

Occurs when the target value changes for any property binding on this element.

(Inherited from FrameworkElement)
TextInput

Occurs when this element gets text in a device-independent manner.

(Inherited from UIElement)
ToolTipClosing

Occurs just before any tooltip on the element is closed.

(Inherited from FrameworkElement)
ToolTipOpening

Occurs when any tooltip on the element is opened.

(Inherited from FrameworkElement)
TouchDown

Occurs when a finger touches the screen while the finger is over this element.

(Inherited from UIElement)
TouchEnter

Occurs when a touch moves from outside to inside the bounds of this element.

(Inherited from UIElement)
TouchLeave

Occurs when a touch moves from inside to outside the bounds of this element.

(Inherited from UIElement)
TouchMove

Occurs when a finger moves on the screen while the finger is over this element.

(Inherited from UIElement)
TouchUp

Occurs when a finger is raised off of the screen while the finger is over this element.

(Inherited from UIElement)
Unloaded

Occurs when the element is removed from within an element tree of loaded elements.

(Inherited from FrameworkElement)

在WPF 4.0中,引入了一个比较实用的库——Interactions,这个库主要是通过附加属性来对UI控件注入一些新的功能,除了内置了一系列比较好用的功能外,还提供了比较良好的扩展接口。本文这里简单的介绍一下Behavior这个扩展。

顾名思义,Behavior可以赋予控件新的行为能力,例如,我们可以通过MouseDragElementBehavior给控件附加上支持拖放的能力。使用方式如下:

  1. 添加Interactions库的引用。主要添加如下两个DLL:Microsoft.Expression.Interactions.dll和System.Windows.Interactivity.dll。

  2. 添加如下名字空间

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

  3. 在控件中添加MouseDragElementBehavior

    <Image Source="2.jpg" >
        <i:Interaction.Behaviors>
            <ei:MouseDragElementBehavior/>
        </i:Interaction.Behaviors>
    </Image>

 这三步中前面几步都是添加Interactions库的支持,对于后面介绍的Trigger和Action也是一样的,只有<ei:MouseDragElementBehavior/>一句才是和Behavior相关的。实际上,我们可以通过在Blend里直接将MouseDragElementBehavior拖放到控件上简化这一过程。加上MouseDragElementBehavior后,我们的控件就支持鼠标拖拽移动了,非常给力。

实际上,系统还提供了一系列非常好用的Behavior,后面我再单独写文章来介绍它。 
 

编写自己的Behavior

除了系统自己提供的Behavior外,我们也可以通过自己编写Behavior来实现自定义行为,一个简单的示例如下:  

    class SkewBehavior : Behavior<UIElement>
    {
        SkewTransform _transForm;

        protected override void OnAttached()
        {
            base.OnAttached();

            _transForm = new SkewTransform();

            AssociatedObject.RenderTransform = _transForm;
            AssociatedObject.RenderTransformOrigin = new Point(0.5, 0.5);
            _transForm.AngleX = 30;
        }

        protected override void OnDetaching()
        {
            _transForm.AngleX = 0;
            base.OnDetaching();
        }
    }

上面的代码同样实现了一个将控件水平方向倾斜30度的Behavior(实现得比较简单,并不完善),大体上关键的地方有如下三个:

  1. 通过AssociatedObject属性获取附加的对象。

  2. 通过重载OnAttached函数进行Behavior附加上时的初始化操作

  3. 通过重载OnDetaching函数进行移除Behavior时候的析构操作

虽然我们也可以直接通过附加属性实现这样的功能,但Interactions框架无疑规范并简化了这一行为。

最后,附上一个比较常用的鼠标拖放的Behavior,和内置的MouseDragElementBehavior不同的是,它产生鼠标事件,用于实现一些自定义的拖放操作:

    class DragDropBehavior : Behavior<UIElement>
    {
        public event EventHandler<DragDeltaEventArgs> DragDelta;
        public event EventHandler<EventArgs> Drop;

        IInputElement _parent;

        protected override void OnAttached()
        {
            base.OnAttached();

            _parent = LogicalTreeHelper.GetParent(AssociatedObject) as IInputElement;

            if (_parent == null)
                return;

            AssociatedObject.MouseLeftButtonDown += onMouseDown;
            AssociatedObject.MouseMove += onMouseMove;

            AssociatedObject.MouseLeftButtonUp += onMouseUp;
            AssociatedObject.MouseEnter += onDragEnter;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.MouseLeftButtonDown -= onMouseDown;
            AssociatedObject.MouseMove -= onMouseMove;

            AssociatedObject.MouseLeftButtonUp -= onMouseUp;
            AssociatedObject.MouseEnter -= onDragEnter;

            base.OnDetaching();
        }

        Point? start;
        private void onMouseDown(object sender, MouseButtonEventArgs e)
        {
            start = Mouse.GetPosition(_parent);
        }

        private void onMouseMove(object sender, MouseEventArgs e)
        {
            if (!start.HasValue)
                return;

            var p = Mouse.GetPosition(_parent);
            var offset = p - start.Value;

            start = p;

            DragDelta?.Invoke(AssociatedObject, new DragDeltaEventArgs(offset.X, offset.Y));
        }


        private void onMouseUp(object sender, MouseButtonEventArgs e)
        {
            tryEndDrag();
        }

        private void onDragEnter(object sender, MouseEventArgs e)
        {
            tryEndDrag();
        }

        void tryEndDrag()
        {
            if (Mouse.LeftButton != MouseButtonState.Released)
                return;

            start = null;

            Drop?.Invoke(AssociatedObject, EventArgs.Empty);
        }
    }

为了定制个性化的用户界面,我们通常会借助于WPF强大的样式(style),修改控件属性,重写控件模板(template),样式帮助我们构建一致的个性化控件。通过样式可以调整界面的显示效果,这只是界面构成的一部分,界面有很多功能是与程序功能无关的,比如停靠、拖动、缩放等,这些通用的功能要如何实现呢,所有用到的地方都单独实现肯定是不现实的,行为(behavior)这时就可以大展拳脚了。

什么是行为,行为是为控件封装好的功能。你可以为Image控件封装缩放行为,或者为所有控件(UIElement)封装拖动行为,后面有例子。

行为(Behavior)不是基础WPF的一部分,他是作为Expression Blend的设计特性而引入的,因此使用Behavior时需要手动添加reference,C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.5\Libraries\System.Windows.Interactivity.dll,安装完visual studio就会有这个文件啦。

现在开始封装一个行为,实现控件在Canvas面板上的拖拽。Behavior是一个模板类,封装控件的行为只需要继承Behavior<T>就可以了,T为控件的类型,这里要为所有控件封装拖拽行为,因此使用了UIElement。

需要重载 OnAttached和OnDetaching方法,其中AssociatedObject指的是封装了该行为的控件。

using System.Windows.Interactivity;

public class DragInCanvsBehavior : Behavior<UIElement>
    {
        private Canvas m_Canvas;
        private bool m_IsDraging;
        private Point m_PositionOffset;

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.MouseLeftButtonDown += AssociatedObjectOnMouseLeftButtonDown;
            AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
            AssociatedObject.MouseLeftButtonUp += AssociatedObjectOnMouseLeftButtonUp;
        }

        private void AssociatedObjectOnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (m_Canvas == null)
                m_Canvas = (Canvas)VisualTreeHelper.GetParent(AssociatedObject);

            m_IsDraging = true;

            m_PositionOffset = mouseButtonEventArgs.GetPosition(AssociatedObject);
            AssociatedObject.CaptureMouse();
        }

        private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            if(!m_IsDraging)
                return;

            Point mouseposition = mouseEventArgs.GetPosition(m_Canvas);
            AssociatedObject.SetValue(Canvas.LeftProperty, mouseposition.X - m_PositionOffset.X);
            AssociatedObject.SetValue(Canvas.TopProperty, mouseposition.Y - m_PositionOffset.Y);
        }

        private void AssociatedObjectOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            if (!m_IsDraging)
                return;

            m_IsDraging = false;
            AssociatedObject.ReleaseMouseCapture();
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MouseLeftButtonDown -= AssociatedObjectOnMouseLeftButtonDown;
            AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
            AssociatedObject.MouseLeftButtonUp -= AssociatedObjectOnMouseLeftButtonUp;
        }
    }

使用Behavior的方法如下,这时就会得到一个可以拖动的矩形了。

<Window x:Class="BehaviorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BehaviorDemo"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Canvas>
        <Rectangle Height="40" Width="80" Canvas.Left="100" Canvas.Top="50" Fill="Aqua">
            <i:Interaction.Behaviors>
                <local:DragInCanvsBehavior/>
            </i:Interaction.Behaviors>
        </Rectangle>

        <Ellipse Height="40" Width="80" Fill="AntiqueWhite"/>
    </Canvas>
</Window>

在i:Interaction下面除了Behavior之外,还可以定义Triggers,但是这个触发器和样式(style)中的触发器(Trigger)可不是同一个东西,这个触发器是为Silverlight准备的,因为silverlight不支持样式触发器,因此如果使用WPF,就不用关注这部分了。

定义Attached Dependency Property

public static class DigitsOnlyBehavior
    {
        public static bool GetIsDigitOnly(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsDigitOnlyProperty);
        }

        public static void SetIsDigitOnly(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDigitOnlyProperty, value);
        }

        public static readonly DependencyProperty IsDigitOnlyProperty =
          DependencyProperty.RegisterAttached("IsDigitOnly",
          typeof(bool), typeof(DigitsOnlyBehavior),
          new PropertyMetadata(false, OnIsDigitOnlyChanged));

        private static void OnIsDigitOnlyChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            // ignoring error checking
            var textBox = (TextBox)sender;
            var isDigitOnly = (bool)(e.NewValue);

            if (isDigitOnly)
                textBox.PreviewTextInput += BlockNonDigitCharacters;
            else
                textBox.PreviewTextInput -= BlockNonDigitCharacters;
        }

        private static void BlockNonDigitCharacters(object sender, TextCompositionEventArgs e)
        {
            e.Handled = e.Text.Any(ch => !Char.IsDigit(ch));
        }
    }

使用上面定义好的Attached Dependency Property

<TextBox Grid.Row="0" behaviors:DigitsOnlyBehavior.IsDigitOnly="True" Margin="6"/>

定义Behavior

public class DragBehavior : Behavior<UIElement>
{
    private Point elementStartPosition;
    private Point mouseStartPosition;
    private TranslateTransform transform = new TranslateTransform();

    protected override void OnAttached()
    {
        Window parent = Application.Current.MainWindow;
        AssociatedObject.RenderTransform = transform;

        AssociatedObject.MouseLeftButtonDown += (sender, e) => 
        {
            elementStartPosition = AssociatedObject.TranslatePoint( new Point(), parent );
            mouseStartPosition = e.GetPosition(parent);
            AssociatedObject.CaptureMouse();
        };

        AssociatedObject.MouseLeftButtonUp += (sender, e) =>
        {
            AssociatedObject.ReleaseMouseCapture();
        };

        AssociatedObject.MouseMove += (sender, e) =>
        {
            Vector diff = e.GetPosition( parent ) - mouseStartPosition;
            if (AssociatedObject.IsMouseCaptured)
            {
                transform.X = diff.X;
                transform.Y = diff.Y;
            }
        };
    }
}
<Border Background="LightBlue" >
    <e:Interaction.Behaviors>
        <b:DragBehavior/>
    </e:Interaction.Behaviors>
    <TextBlock Text="Drag me around!" />
</Border>

本文主要是以实现拖动元素作为例子。

创建Behavior:

通常这个类会继承自Behavior<T>,其中T就是此Behavior服务的对象,在此处使用的是UIElement,也就是虽有的UIElement类型的元素都可以使用。

public class DragInCanvasBehavior : Behavior<UIElement>
    {
        //元素父节点
        private Canvas canvas;
        //标识是否进入拖动
        private bool isDraging = false;
        //按下鼠标时的坐标(用于计算要移动的位置)
        private Point mouseOffset;

        /// <summary>
        /// 附加行为后
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            //添加鼠标事件(AssociatedObject也就是当前应用此Behavior的元素)
            this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
        }

        void AssociatedObject_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            //释放拖动状态
            isDraging = false;
        }

        void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            //如果进入拖动状态
            if (isDraging)
            {
                //得到新的位置
                Point newPoint = e.GetPosition(canvas);
                //旧的坐标加上新坐标和旧坐标的差
                mouseOffset.X += newPoint.X - mouseOffset.X;
                mouseOffset.Y += newPoint.Y - mouseOffset.Y;

                //设置元素的Left和Top,之所以要用X(Y)减去Width(Height),主要是为了使鼠标在元素中心
                Canvas.SetLeft(this.AssociatedObject, mouseOffset.X-(this.AssociatedObject as FrameworkElement).ActualWidth/2);
                Canvas.SetTop(this.AssociatedObject, mouseOffset.Y - (this.AssociatedObject as FrameworkElement).ActualHeight/2);
            }
        }

        void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            //将元素的父节点元素赋值为Canvas(之所以使用Canvas,是因为Canvas容易动态布局)
            if (canvas == null)
                canvas = (Canvas)VisualTreeHelper.GetParent(this.AssociatedObject);
            //进入拖动状态
            isDraging = true;
            //获得初始位置
            mouseOffset = e.GetPosition(this.AssociatedObject);
            this.AssociatedObject.CaptureMouse();
        }

        /// <summary>
        /// 分离行为
        /// </summary>
        protected override void OnDetaching()
        {
            //移除鼠标事件
            this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
        }
    }

在WPF中实现拖动,一般只需三个事件即可,MouseLeftButtonDown、MouseLeftButtonUp、MouseMove。Down事件负责进入拖动状态,并且记录初始的鼠标坐标(用于拖动中动态修改元素的位置),同时也要得到当前元素的Parent即Canvas,这样才可以在Move时候获得相对于Canvas的新坐标;Up事件负责状态变为正常,这时候在移动就没变化的;Move事件负责的事情比较重要,得到当前鼠标相对于Canvas的新坐标,然后和旧坐标进行计算,最后设置元素的新坐标。

使用Behavior:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:behavior="clr-namespace:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary"
        Title="MainWindow" Height="350" Width="525">
    <Canvas Background="LightBlue">
        <Rectangle Height="50" Width="50" Fill="Green" >
            <i:Interaction.Behaviors>
                <behavior:DragInCanvasBehavior></behavior:DragInCanvasBehavior>
            </i:Interaction.Behaviors>
        </Rectangle>
    </Canvas>
</Window>

添加对Interactivity和Behavior所属程序及的引用,页面内容很简单,一个Canvas包含一个Rectangle,在Rectangle中设置Behaviros为创建的Behavior,这样神奇的事情就发生了,运行程序,拖动Rectangle,就可以看到可以改变位置了哦。

注:由于本文的示例Behavior和WPF应用程序是分离的,所以在此需要添加对Behavior所在类库的引用,同时在Window标签中添加对Behavior所属Assembly的引用,如果你的Behavior和XAML在同一个程序集,则可以进行适当的修改。

XAML中Trigger和Action组合之后应该是等同于一个Behavior

如果只需对控件进行小幅度修饰(调整大小、位置、字体、颜色等)就用style;如果需要改变控件的外观和行为就用controlTemplate(形状、事件触发如鼠标停留效果等)。在实际项目中,经常把Template定义在Style中,通过Style 中的Property来设置控件的Template属性。

控件只是个数据和行为的载体、是个抽象的概念,至于它会长什么样子(控件的内部结构)、它的数据会长什么样子(数据显示结构)都是靠Template生成的。决定控件外观的是ControlTemplate,决定数据外观的是DataTemplate,他们正是Control类的Template和ControlTemplate两个属性的值。

WPF中的所有COntrol控件都有Template属性。下面以代码的形式,展现WPF中常用的Template。

<Window x:Class="WPFXAMLTest.WindowControlTemplate"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WindowControlTemplate" Height="300" Width="300">
    <Grid Background="Yellow">
        <Button Width="200" Height="60" Background="Cyan">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Rectangle Width="180" Height="50" Fill="{TemplateBinding Background}" RadiusX="20" RadiusY="20"/>
                        <ContentPresenter Content="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center" RecognizesAccessKey="True"/>
                    </Grid>
                </ControlTemplate>
            </Button.Template>
                <Button.Content>
                <Grid>
                    <Ellipse Fill="Red" Width="160" Height="40"/>
                    <TextBlock Text="DebugLZQ" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                </Grid>
            </Button.Content>
        </Button>
        <Button  HorizontalAlignment="Left" Margin="105,190,0,0" VerticalAlignment="Top" Width="75">
           <Button.Template>
               <ControlTemplate >
                    <TextBlock Text="DebugLZQ" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                </ControlTemplate>
           </Button.Template>
        </Button>
    </Grid>
</Window>

如果把WPF窗体看作一个舞台,那么窗体上的控件就是一个演员,它们的职责就是在用户界面上按照业务逻辑的需要扮演自己的角色。为了让同一种控件能担起不同的角色,程序员就要为它们设计多种多样的外观样式和行为动作,这就是Style。构成Style最重要的两种元素是Setter和Trigger,Setter类帮助我们设置控件的静态外观风格,Trigger则帮助我们设置控件的行为风格。

      Setter,设置器。什么的设置器呢?即属性值。我们给属性赋值的时候一般都采用“属性名=属性值”的形式。Setter类的Property属性用来指明你想为属性的哪个属性赋值;Setter类的Value属性则是你提供的属性值。

      Trigger, 触发器,即当某些条件满足的时候会触发一个行为(比如某些值的变化或动画的发生等)。触发器比较像事件。事件一般由用户操作触发的,而触发器除了有事件触发型的EventTrigger外还有数据变化触发型的Trigger/DataTrigger及多条件触发型MultiTrigger/MultiDataTrigger等。

1. 基本的Trigger

Trigger是最基本的触发器。类似于Setter,Trigger也有Property和Value两个属性,Property是Trigger关注的属性名称,Value是触发条件。Trigger还有一个Setters属性,此属性是一组Setter,一旦触发条件满足,这组Setter的“属性---值”就会被应用,触发条件不在满足后,各属性值会被还原。
下面这个例子针对的是CheckBox的Style,当CheckBox的IsCheck属性为True时前景色和字体会改变。XAML代码如下:

 

<Window x:Class="WpfApplication11.wnd11521"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd11521" Height="200" Width="300">
    <Window.Resources>
        <Style TargetType="{x:Type CheckBox}">
            <!--当单个条件满足后触发-->
            <Style.Triggers>
                <Trigger Property="IsChecked" Value="true">
                    <Setter Property="FontSize" Value="20"></Setter>
                    <Setter Property="Foreground" Value="Red"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <CheckBox Content="悄悄的我走了"></CheckBox>
        <CheckBox Content="正如我悄悄的来"></CheckBox>
        <CheckBox Content="我挥挥衣袖"></CheckBox>
    </StackPanel>
</Window>

2. MultiTrigger

MultiTrigger是一个很容易让人误解的名字,会让人以为是多个Trigger集成在一起,实际上叫MultiConditionTrigger更合适,因为必须多个条件同时成立才会被触发。MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就放在这个集合当中。让我们稍微改动一下上面的例子:

<Window x:Class="WpfApplication11.wnd11522"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd11522" Height="200" Width="300">
    <Window.Resources>
        <Style TargetType="{x:Type CheckBox}">
            <!--当多个条件满足后触发-->
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsChecked" Value="true"></Condition>
                        <Condition Property="Content" Value="正如我悄悄的来"></Condition>
                    </MultiTrigger.Conditions>
                    <Setter Property="FontSize" Value="20"></Setter>
                    <Setter Property="Foreground" Value="Red"></Setter>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <CheckBox Content="悄悄的我走了"></CheckBox>
        <CheckBox Content="正如我悄悄的来"></CheckBox>
        <CheckBox Content="我挥挥衣袖"></CheckBox>
    </StackPanel>
</Window>

3. 由数据触发DataTrigger

程序中经常会遇到基于数据执行某些判断情况,遇到这种情况我们就可以考虑使用DataTrigger。DataTrigger对象的Binding属性会把数据源源不断的送出来,一旦送出来的值与Value属性一致,DataTrigger即被触发。下面的例子中,当TextBox的Text长度小于7个字符其Border会保持红色。XAML代码如下:
<Window x:Class="WpfApplication11.wnd11523"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication11"
        Title="wnd11523" Height="200" Width="300">
    <Window.Resources>
        <local:Len2BoolConvert x:Key="l2b"></local:Len2BoolConvert>
        <!--数据满足条件后,触发-->
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text.Length, Converter={StaticResource l2b}}" 
                             Value="false">
                    <Setter Property="BorderBrush" Value="Red"></Setter>
                    <Setter Property="BorderThickness" Value="1"></Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel>
        <TextBox Margin="5" ></TextBox>
        <TextBox Margin="5" ></TextBox>
    </StackPanel>
</Window>

为了将控件自身作为数据源,我们使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将自己作为数据的来源”,这是错误的,因为不明确指出Source的值Binding就会把控件的DataContext作为自己的数据来源。Binding的Path设置为Text.Length,即我们关注的是字符串的长度。长度是一个具体的数字,如何基于这个长度值来做判断呢?这就用到了Converter。我们创建如下Converter:
/// <summary>
/// 数据长度转bool类型
/// </summary>
public class Len2BoolConvert:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int len = (int)value;
        return((len > 6)?(true):(false));
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

经过Converter转换以后,长度值就会变为bool类型值。当TextBox的文本长度小于7时DataTrigger会使用自己一组Setter把TextBox的边框设置为红色。

4. 由事件触发的EventTrigger

EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或者数据的变化来触发而是由事件触发;其次,被触发以后它并非应用一组Setter,而是执行一段动画。因此,UI的动画效果往往和EventTrigger相关联。
在下面这个例子中创建一个针对Button的Style,这个Style包含两个EventTrigger,一个由MouseEnter触发,另一个由MouseLeave触发。XAML代码如下:

<Window x:Class="WpfApplication11.wnd1524"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="wnd1524" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="{x:Type Button}">
            <!--事件触发-->
            <Style.Triggers>
                <!--鼠标进入-->
                <EventTrigger RoutedEvent="MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <!--鼠标离开-->
                <EventTrigger RoutedEvent="MouseLeave">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Canvas>
        <Button Width="50" Height="50"></Button>
    </Canvas>
</Window>

虽然在Style里面大量使用触发器,但触发器并非只能应用在Style中-----各种Template也可以拥有自己的触发器,根据需要决定触发器放在Style里面还是Template里面。

ControlTemplate 是算法的内容的表现形式,决定了控件长成什么样子,让程序员在控件原有的内部逻辑基础上扩展自己的逻辑 
DataTemplate 是数据的内容的表现形式,数据显示成什么形式,外观

DataTemplate 
常用在三处: 
ContentControl的ContentTemplate属性, 
ItemsControl的ItemTemplate属性, 
GridViewColumn的CellTemplate属性

public class AutomakerToLogoPathConverter:IValueConverter{
    public object Convert(object value,Type targetType,object parameter,CultureInfo culture){
        string uriStr = string.Format(@"/Resources/Logos/{0}.png",(stringvalue));
        return new BitmapImage(new Uri(uriStr,UriKind.Relative));
    }
    publiuc ConvertBack(object value,Type targetType,object parameter,CultureInfo culture){
    throw new NotImplementedException();
    }
}

public class NameToPhotoPathConverter:IValueConverter{
    public object Convert(object value,Type targetType,object parameter,CultureInfo culture){
        string uriStr = string.Format(@"/Resources/Images/{0}.png",(stringvalue));
        return new BitmapImage(new Uri(uriStr,UriKind.Relative));
    }
    publiuc ConvertBack(object value,Type targetType,object parameter,CultureInfo culture){
    throw new NotImplementedException();
    }
}
...
<Window
    ...>
    <Window.Resources>
        <local:AutomakerToLogoPathConverter x:Key = "a2l"/>
        <local:NameToPhotoPathConverter x:Key = "n2p"/>
        <DataTemplate x:Key = "carDetailViewTemplate"
            <Stackpanel>
                ....
                <Image Width = "400" Height = "250"
                    Source = "{Binding Name,Converter{StaticResource n2p}}"/>
                ...
            </Stackpanel>
            ...
        </DataTemplate> 
        <DataTemplate x:Key = "carListViewTemplate"
            ...
        </DataTemplate> 
    </Window.Resources>
    <StackPanel Orientation = "Horizontal" Margin = "5">
        <UserControl ContentTemplate = "{StaticResource carDetailViewTemplate}"
                     Content = "{Binding SelectedItem,ElementName = listBoxCars}"/>
        <ListBox x:Name = "listBoxCars" Width = "180" margin= “5,0”
                 ItemTemplate = "{StaticResource0 carListViewTemplate}"/>
    </StackPanel >
</Window>
    ...
private void InitialCarList(){
    List<Car> carList = new List<Car>(){
        new Car{Automaker = "tfewaf",Name = "fase",Year = "1964",TopSpeed = "131"},
        new Car{Automaker = "tfewaf",Name = "fase",Year = "1964",TopSpeed = "131"},
        new Car{Automaker = "tfewaf",Name = "fase",Year = "1964",TopSpeed = "131"},
        new Car{Automaker = "tfewaf",Name = "fase",Year = "1964",TopSpeed = "131"},
        new Car{Automaker = "tfewaf",Name = "fase",Year = "1964",TopSpeed = "131"}
    }
    this.lisrBoxCars.ItemsSource = carList;
}

ControlTemplate 
用处: 
更换ControlTemplate改变控件外观,使之具有更优的用户体验及外观。 
借助ControlTemplate让程序员和设计师并行工作。

打开Blend开始编辑 
组件上右键,编辑模板,编辑副本

ControlTemplate可以放在三个地方Application的资源字典中,界面元素的资源字典中,外部XMAL文件中。

ControlTemplate 在Blend中通过可视化界面编辑模板即可。

ControlTemplate 和DataTemplate 的区别

ControlTemplate :决定控件的外观 
DataTemplate : 决定数据的外观。与具体数据相关的控件,比如需要Binding数据的控件,数据转换Converter。 
DataTemplate 是 ControlTemplate 的一颗子树。

如何找控件树根: 
控件的TemplateParent属性的值就是应用了模板的控件。 
控件的TemplateBinding的数据源就是应用类这个模板的目标控件。

Template的应用 
1. 逐个设置控件的Template/ContentTemplate/ItemsTemplate/CellTemplate 等属性。 
2. 把Template应用在某个类型的控件或数据上。

例:

<Window.Resources>
    <DataTemplate DataType = "{x:Type local:Unit}">
        <StackPanel>
            <TextBlock Text = "{Binding Price}"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
    ...
public class Unit{
    public int Price{get;set;}
    public string Year{get;set;}
}

DataTemplate的目标数据类型和ListBox的条目类型都是Unit 
DataTemplate会自动加载到所有Unit类型对象上

<Window.Resources>
    <DataTemplate DataType = "Unit">
        <StackPanel>
            <TextBlock Text = "{Binding XPath = @Price}"/>
            <TextBlock Text = "{Binding XPath = @Year}"/>
        </StackPanel>
    </DataTemplate>
    ...
    <XmlDataProvider x:Key = "ds" XPath="Unites/Unit">
        <x:XData>
            <Units xmlns = "">
                <Unit Year = "2001" Price = "100"/>
                ...
            </Units>
        </x:XData>  
    </XmlDataProvider>
</Window.Resources>
    ...
    <ListBox ItemSource="{Binding Source = {StaticResource de}}"/>
    <ComboBox ItemSource="{Binding Source = {StaticResource de}}"/>

将XML作为数据源

<HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath = Student}">
    <TextBlock Text="{Binding XPath=@Name}"
</HierarchicalDataTemplate>

找到Template中的组件

ControlTemplate

TextBox tb = this.uc.Template.FindName("textBox1",this.uc) as Textbox;
tb.Text="Hello Wpf";
StackPanel sp = tb.Parent as StackPanel;
(sp.children[1] as TextBox).Text = "Hello Template";
(sp.children[1] as TextBox).Text = "Hello Template";

DataTemplate 
例1:

<ContentPresenter ContentTemplate="{StaticResource stuDT}"/>
...
TextBox tb = this.uc.ContentTemplate.FindName("textBox1",this.cp) as Textbox;
MessageBox.Show(tb.Text);

例2:

private void TextBoxNAme_GotFocus(object sender,RoutedEventArgs e){
    TextBox tb = e.OriginalSource as TextBox;//获取事件发起的源头
    ContentPresenter cp = tb.TemplatedParent as ContentPresenter;//获取模板目标
    Student stu = cp.Content as Student;//获取业务逻辑
    this.listViewStudent.SelectedItem = stu;//设置ListView的选中项
    //访问界面元素
    ListViewItem lvi = this.listViewStudent.
                    ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem;
    CheckBox chb = this.FindVisualChild<CheckBox>(lvi);
    MessageBox.Show(chb.Name);
}
private ChildType FindVisualChild<ChildType>(DependencyObject obj)where ChildType:DependencyObject {
    for(int i=0;i<VisualTreeHelper.GetChildrenCount(obj);i++){
        DependencyObject  child = VisualTreeHelper.Get(obj,i);
        if(child !=null && child is ChildType){
            return child as ChildType;
        }else{
            ChildType childOfChild = FindVisualChild<ChildType>(child);
            if(childOfChild !=null) return childOfChild ;
        }
    }
    return null;
}

Style样式

style包括setter和trigger,分别表示控件的静态外观风格和行为风格。

Setter 设置属性值 
例:

<Setter Property = "FontSize" Value="24"/>
<Setter Property = "FontStyle" Value="Italic"/>
<Setter Property = "Template" Value="ControlTemplate"/>

Trigger 触发器 
一般由用户操作触发。 
事件触发型EventTrigger,数据变化触发型Trigger/DataTrigger,多条件触发型MultiTrigger/MultiDataTrigger。

1.基本触发器 
Property 检测的属性,Value 触发的条件 
Setters触发后执行的动作,比如换肤 
例:

<Style Targettype = "CheckBox">
    <Style.Triggers>
        <Trigger Property = "IsChecked" Value="true">
            <Trigger.Setters>
                <Setter Property="FontSize" value="20"/>
                <Setter Property="Foreground" value="Orange"/>
            </Trigger.Setters>
        </Trigger>
    </Style.Triggers>
</Style>
    ...
<StackPanle>
    <CheckBox Content="check1"/>
    <CheckBox Content="check2"/>
    <CheckBox Content="check3"/>
    <CheckBox Content="check4"/>
</StackPanle>

2.MultiTrigger 多条件同时成立才触发,比Trigger多一个Conditions属性,需要成立的条件就存储在此集合中。
<MultiTrigger.Conditions>
    <Coadition Property="isChecked" Value="true"/>
    <Coadition Property="Content" Value="Hello"/>
</MultiTrigger.Conditions>

3.DataTrigger Binding属性的值与Value的值一样触发

<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cvt2r}}"
Value="false">
//在cvtr中进行判断操作
    <Setter.../>
    <Setter.../>
    <Setter.../>
</DataTrigger>

4.MultiDataTrigger 多数据条件同时满足时触发

...
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding Path=ID}" Value="2"/>
        <Condition Binding="{Binding Path=Name}" Value="Tom"/>
    </MultiDataTrigger.Conditions>
    <MultiDataTrigger.Setters>
    ...
    </MultiDataTrigger.Setters>
</MultiDataTrigger>

5.EventTrigger 
事件触发,比较特殊。它由事件触发,而且触发后不是应用一组setter而是执行一段动画。 
UI层的动画效果往往与EventTrigger关联。 
例如Button的MouseEnter和MouseLeave的事件触发器。

例:

<EventTrigger RoutedEvent = "MouseEnter">
    <BeginStoryboard>
        <Storyboard>
            <DoubleAnimation To="150" Duration= "0:0:0.2" Storyboard.TargetProperty="Width"/>
            <DoubleAnimation To="150" Duration= "0:0:0.2" Storyboard.TargetProperty="Width"/>
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>

不仅Style中可以使用触发器,Template中同样可以使用触发器

在asp.net世界中,我们的美工人员会为我们准备好静态页面,它注意包括三个部分:html、css和js。而在WPF世界里,也同样有着类似这三个部分的静态页面:Xaml、Style和Behaviors,当然,它们和前面三者的作用并不对等。Style几乎完成了css和js的功能,而Sliverlight 3中引入的Behaviors(封装到Expression Blend 3中和Expression Blend 3 SDK中)只是为了方便代码的复用,我们在后面详细来说。本文主要从Style样式和Behaviors行为两个方面来讲。

1.Style

 先来看看Style类的属性:

1.1Setters

Setters是Style默认的内容属性,是Setter对象或者EventSetter对象的集合,所以可以省略Style.Setters而直接使用Setter或EventSetter。

Setter是用于设置属性值的,这个属性还得是依赖属性。EventSetter是自动关联事件处理程序的。举个例子:

xaml代码:

复制代码

<Window x:Class="StyleDemo.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">
    <Window.Resources>
        <Style x:Key="labelStyle" TargetType="Label">
            <Setter Property="Foreground">
                <Setter.Value>Red</Setter.Value>
            </Setter>
            <Setter Property="Control.FontSize" Value="28" />
            <Setter Property="Slider.FontFamily" Value="宋体" />
            <EventSetter Event="MouseMove" Handler="Label_MouseMove" />
        </Style>
        <Style x:Key="buttonStyle">
            <Setter Property="Button.FontSize" Value="22" />
            <Setter Property="Button.FontFamily" Value="SimSun" />
            <Setter Property="Button.FontWeight" Value="Bold" />
            <Setter Property="Button.Background" Value="Red" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Label Content="Hi,WPF" Style="{StaticResource labelStyle}" />
            <TextBlock Text="Sliverlight 5" Style="{StaticResource buttonStyle}"/>
            <Button Content="Click">
                <Button.Style>
                    <Style>
                        <Setter Property="Control.Background">
                            <Setter.Value>
                                <LinearGradientBrush>
                                    <GradientStop Offset="0" Color="Red" />
                                    <GradientStop Offset="0.5" Color="Blue" />
                                    <GradientStop Offset="1" Color="Yellow" />
                                </LinearGradientBrush>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Button.Style>
            </Button>
            <Button Content="DoubleClick" Style="{StaticResource buttonStyle}"/>
        </StackPanel>
    </Grid>
</Window>

复制代码

 cs代码:

private void Label_MouseMove(object sender, MouseEventArgs e)
{
      MessageBox.Show("Mouse Move!!");
}

 看下效果:

需要说明一下几点:

1)以资源的形式要比内嵌的形式更具灵活性

2)在内嵌的形式中,设置属性值时,要么指定TargetType,要么使用Class.Property的形式

3)"<Setter Property="Control.Background">...</setter>"与"<Setter Property="Background">...</setter>"的区别在于,前者先去设置Control的Backgroud属性,然后应用该样式的控件继承,而后者直接去设置应用该样式的控件的属性

1.2Triggers

Triggers,即为触发器,使用它可以自动完成简单的样式改变。主要有以下几种触发器:

具个例子,一并说明下这五种触发器:

xaml代码:

<Window x:Class="StyleDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StyleDemo"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <!--Trigger属性触发器-->
        <Style x:Key="triggerKey">
            <Style.Triggers>
                <Trigger Property="Control.IsMouseOver" Value="true">
                    <Setter Property="Control.Foreground" Value="Red" />
                    <Setter Property="Control.FontSize" Value="20" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <!--MultiTrigger多条件属性触发器-->
        <Style x:Key="multiTriggerKey">
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="Control.Foreground" Value="Red"></Condition>
                        <Condition Property="Control.IsMouseOver" Value="true"></Condition>
                    </MultiTrigger.Conditions>
                    <!--<MultiTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetProperty="(FrameworkElement.Width)">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.0020000" Value="0"/>
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.3450000" Value="95"/>
                                </DoubleAnimationUsingKeyFrames>
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetProperty="(FrameworkElement.Height)">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.0020000" Value="0"/>
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.3450000" Value="54"/>
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </MultiTrigger.EnterActions>-->
                    <MultiTrigger.Setters>
                        <Setter Property="Control.ToolTip" Value="Background:Red,FontSize" />
                    </MultiTrigger.Setters>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
        <!--EventTrigger事件触发器-->
        <Style x:Key="eventTriggerKey">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="22" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
        </Style>
        <!--DataTrigger数据触发器-->
        <local:L2BConverter x:Key="l2bCvt" />
        <Style x:Key="dataTriggerKey">
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Text.Length,Converter={StaticResource l2bCvt}}" Value="false">
                    <Setter Property="Control.BorderBrush" Value="Red" />
                    <Setter Property="Control.BorderThickness" Value="1" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
        <!--MultiDataTrigger多条件数据触发器-->
        <local:L2BConverter x:Key="l2bCvt1" />
        <local:S2BConverter x:Key="s2bCvt" />
        <Style x:Key="multiDataTriggerKey">
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding RelativeSource={RelativeSource Self},Path=Text.Length,Converter={StaticResource l2bCvt1}}" Value="false" />
                        <Condition Binding="{Binding RelativeSource={RelativeSource Self},Path=Text,Converter={StaticResource s2bCvt}}" Value="false" />
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="Control.BorderBrush" Value="Red" />
                        <Setter Property="Control.BorderThickness" Value="1" />
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Label Style="{StaticResource triggerKey}" Content="Hi,WPF" />
            <TextBlock Style="{StaticResource multiTriggerKey}" Foreground="Red" FontSize="16" Text="RIA World" />
            <Button x:Name="button" Style="{StaticResource eventTriggerKey}" Content="MouseEnter" />
            <TextBox Style="{StaticResource dataTriggerKey}"/>
            <TextBox Style="{StaticResource multiDataTriggerKey}"/>
        </StackPanel>
    </Grid>
</Window>

 用到的两个Converter:

字符串长度转布尔类:

    public class L2BConverter:IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return (int)value > 6 ? true : false;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }

 字符串转布尔类

    class S2BConverter:IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Regex regex = new Regex(@"^\d*$");
            return !regex.IsMatch(value.ToString());
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

 效果大家可以copy代码运行看看。

1.3Resources

Style的Resources属性是ResourceDictionary类型,可以放一些在style中需要共享的对象资源。来看个例子:

xaml代码:

<Window x:Class="StyleDemo.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window2" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="buttonKey">
            <Style.Resources>
                <LinearGradientBrush x:Key="lgbKey">
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="0.5" Color="Green" />
                    <GradientStop Offset="1" Color="Blue" />
                </LinearGradientBrush>
            </Style.Resources>
            <Setter Property="Control.Background" Value="{StaticResource lgbKey}" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="button" Style="{StaticResource buttonKey}" />
        </StackPanel>
    </Grid>
</Window>

 效果如下:

1.4BaseOn

Style的BaseOn属性,可以实现Style的继承,从而实现多层样式。来看个简单的例子:

xaml代码:

<Window x:Class="StyleDemo.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window2" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="buttonKey">
            <Style.Resources>
                <LinearGradientBrush x:Key="lgbKey">
                    <GradientStop Offset="0" Color="Red" />
                    <GradientStop Offset="0.5" Color="Green" />
                    <GradientStop Offset="1" Color="Blue" />
                </LinearGradientBrush>
            </Style.Resources>
            <Setter Property="Control.Background" Value="{StaticResource lgbKey}" />
            <Setter Property="Control.FontFamily" Value="Times New Roman" />
            <Setter Property="Control.FontWeight" Value="Bold" />
            <Setter Property="Control.FontSize" Value="18" />
        </Style>
        <Style x:Key="buttonInheritKey" BasedOn="{StaticResource buttonKey}">
            <Setter Property="Control.Foreground" Value="DarkOrange" />
            <Setter Property="Control.FontSize" Value="22" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="button" Style="{StaticResource buttonKey}" />
            <Button Content="button1" Style="{StaticResource buttonInheritKey}" />
        </StackPanel>
    </Grid>
</Window>

 效果图如下:

1.5TargetType

这个属性,指定的是应用该Style的控件的类型。我们以一个例子为切入点来说明下:

xaml代码:

<Window x:Class="StyleDemo.Window3"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window3" Height="300" Width="300">
    <Window.Resources>
        <!--TargetType属性-->
        <Style TargetType="Button">
            <Setter Property="Foreground" Value="Yellow" />
            <Setter Property="FontSize" Value="28" />    
        </Style>
        <Style x:Key="buttonKey">
            <Setter Property="Control.FontSize" Value="12" />
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <TextBox />
            <Button Content="Click" />
            <Button Content="Click2" Style="{StaticResource buttonKey}"/>
        </StackPanel>
    </Grid>
</Window>

 效果如下:

首先,你可能会奇怪,指定了TargetType的Style这里没有x:key来标识该资源,这是因为xaml解析器会自动以其对象类型来当作它的key,类似这样:x:key="{x:Type Button}"。

另外,需要说明一下几点:

1)设置了TargetType类型的Style会应用该种类型的所有控件。

2)如果某该类型控件另外还设置了Style,会进行Merge的操作(由StaticResource或DynamicResource和TargetType确定的Style和ThemeStyle的合并)。StaticResource或DynamicResource和TargetType相同的Setter属性值,前者优先级高,不同的Setter属性值均起作用。

2.Behaviors

Style提供了重用一组属性设置的方法,为帮助构建统一良好的界面迈出了重要的一步,但是,还是有很多的限制,比如对于动画的支持不够。我们知道,通常要设计一个动画效果,需要很多的xaml代码。而这样的动画也经常会在其他的地方使用,但是我们却不得不复制那一大块代码,为了DRY,微软在Expression Blend 3推出了Behaviors行为的特性。

首先,我们需要了解Behaviors这样几个关键点:

1)Behaviors可复用代码集合(UI功能),可以被任何对象附加使用

2)设计人员和开发人员只需要将它附加到元素上,而无需写任何的逻辑代码

3)一个Behaviors可以被多个对象元素同时使用

与Behaviors相关的程序集:

System.Windows.Interactivity.dll,该链接库定义了Behaviors(行为)基础类,有了该链接库支持,即可支持Behaviors(行为);

Microsoft.Expression.Interactions.dll,该链接库提供了一些扩展行为类库,以及一些Action和Trigger类,作为演示实例;

Behaviors主要包括Trigger、Action和Behavior三个部分。需要注意的是,这里的Trigger和WPF的Trigger并不完全一样,可以同时使用它们。

IAttachObject接口的定义很简单:

// 摘要:
    //     供可以附加到另一个对象的对象使用的接口。
    public interface IAttachedObject
    {
        // 摘要:
        //     获得关联的对象。
        //
        // 备注:
        //     代表此实例附加到的对象。
        DependencyObject AssociatedObject { get; }

        // 摘要:
        //     附加到指定的对象。
        //
        // 参数:
        //   dependencyObject:
        //     要附加到的对象。
        void Attach(DependencyObject dependencyObject);
        //
        // 摘要:
        //     将此实例与其关联的对象分离。
        void Detach();
    }

 AssociatedObject是一个只读的依赖对象属性,它指定行为的应用者。

2.1自定义一个Behavior

下面我们来自己定义一个Behaviors看看,要实现的效果是在Canvas中的控件可任意拖动:

先定义一个继承自Behavior<UIElement>的DragInCanvasBehavior类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace CustomBehaviorsLib
{
    public class DragInCanvasBehavior:Behavior<UIElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            //Hook up event handlers
            this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
        }

        void AssociatedObject_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (isDragging)
            {
                AssociatedObject.ReleaseMouseCapture();
                isDragging = false;
            }
        }

        void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (isDragging)
            {
                Point point = e.GetPosition(canvas);
                AssociatedObject.SetValue(Canvas.TopProperty, point.Y-mouseOffset.Y);
                AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
            }
        }
        private Canvas canvas;
        private bool isDragging = false;
        private Point mouseOffset;
        void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (canvas == null)
                canvas = (Canvas)VisualTreeHelper.GetParent(this.AssociatedObject);
            isDragging = true;
            mouseOffset = e.GetPosition(AssociatedObject);
            AssociatedObject.CaptureMouse();
        }
        protected override void OnDetaching()
        {
            base.OnDetaching();
            //detach event handlers
            this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
        }
    }
}

然后在window4.xaml中添加对DragInCanvasBehavior类所在类库的引用,同时添加System.Window.Interactivity.dll的引用。

xaml代码如下:

<Window x:Class="StyleDemo.Window4"
        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:custom="clr-namespace:CustomBehaviorsLib;assembly=CustomBehaviorsLib"
        Title="Window4" Height="300" Width="300">
    <Grid>
        <Canvas x:Name="p_canvas">
            <Canvas x:Name="c_canvas1" Width="100" Height="100" Background="Yellow" />
            <Canvas x:Name="c_canvas2" Width="100" Height="100" Canvas.Left="110" Background="Red">
                <i:Interaction.Behaviors>
                    <custom:DragInCanvasBehavior />
                </i:Interaction.Behaviors>
            </Canvas>
            <Canvas x:Name="c_canvas3" Width="100" Height="100" Canvas.Top="110" Background="Blue" />
        </Canvas>
    </Grid>
</Window>

通过向BehaviorCollection类型的Interaction.Behaviors附加属性添加DragInCanvasBehavior实例。

效果如下:

这里只是给红色的Canvas添加了DragInCanvasBehavior,所以只有红色的Canvas可以Drag。

Expression Blend 3 以及之后的版本(4和4.5)都有Behaviors提供了很好的支持,内部提供了好多封装好的Behaviors。况且,使用blend可以很方便简单的使用它。

2.2Expression Blend中Behaviors

Expression Blend是和Visual Studio配套使用的,也就是说对应的采用的是相同的解决方案文件格式,Blend 3对应VS2008,Blend 4对应VS2010,Blend 5对应VS2012。

这里提供许多现成的行为,也包含自定义的Behaviors。用法基本类似,这里我们以MouseDragElementBehavior为例,来介绍下在Expression Blend中对Behaviors的使用。

先向界面拖放一个主Canvas,里面放三个子Canvas,背景颜色分别设为Yello、Red和Blue。然后将MouseDragElementBehavior拖放到红色的子Canvas上面,代码便自动完成了,实现了与上面相同的效果,及其方便快捷。

有关Expression Blend的使用技巧,将会单独来讲,在后面模板和动画的学习中还将会提到。

在前面一篇我们粗略说了Style和Behaviors,如果要自定义一个个性十足的控件,仅仅用Style和Behaviors是不行的,Style和Behaviors只能通过控件的既有属性来简单改变外观,还需要有ControlTemplate来彻底定制,这是改变Control的呈现,也可以通过DataTemplate来改变Data的呈现,对于ItemsControl,还可以通过ItemsPanelTemplate来改变Items容器的呈现。

1.模板

WPF模板有三种:ControlTemplate、DataTemplate和ItemsPanelTemplate,它们都继承自FrameworkTemplate抽象类。在这个抽象类中有一个FrameworkElementFactory类型的VisualTree变量,通过该变量可以设置或者获取模板的根节点,包含了你想要的外观元素树。

先来看下ControlTemplate的例子:

<Window x:Class="TemplateDemo.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">
    <Window.Resources>
        <ControlTemplate x:Key="buttonTemplate" TargetType="{x:Type Button}">
            <Grid>
                <Ellipse Width="100" Height="100">
                    <Ellipse.Fill>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                            <GradientStop Offset="0" Color="Cyan" />
                            <GradientStop Offset="1" Color="LightCyan" />
                        </LinearGradientBrush>
                    </Ellipse.Fill>
                </Ellipse>
                <Ellipse Width="80" Height="80">
                    <Ellipse.Fill>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                            <GradientStop Offset="0" Color="Yellow" />
                            <GradientStop Offset="1" Color="Transparent" />
                        </LinearGradientBrush>
                    </Ellipse.Fill>
                </Ellipse>
                <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Grid>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="Hi,WPF" Template="{StaticResource buttonTemplate}" Click="Button_Click"/>
        </StackPanel>
    </Grid>
</Window>

这里是将ControlTemplate作为资源的方式共享的,当然也可以通过Style的Setter来设置Button的Template属性来做。

效果如下:

在该ControlTemplate的VisualTree中,Button是被作为TemplatedParent的,这个属性定义在FrameworkElement和FrameworkContentElement中。关于TemplatedParent的介绍,这里可以看Mgen这篇文章。

这里完成了一个自定义风格的Button,然后在很多时候,我们只是想稍微修改下Button的外观,仍然像保留其阴影特性等功能,这时候我们就要"解剖"Button来了解其内部结构,VS2012自带的Expression Blend 5就具有这样的解剖功能。

 

生成了这样的代码:

<Style x:Key="FocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
        <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
        <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
        <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
        <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
        <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
        <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
        <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
        <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
        <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
            <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
            <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
            <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                            <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsDefaulted" Value="true">
                                <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
                                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="true">
                                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
                                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
                                <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
                                <Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

 看的出来,是由一个Border里面放了一个ContentPresenter构成的,然后是触发器定义的默认行为,相对比较简单,像ScrollBar等控件内部是很复杂的。关于ContentPresent,我们将在第二小节详细描述。

接下来,我们以Selector中的ListBox为例,来说明DataTemplate和ItemsPanelTemplate。

        <!--ItemsPanelTemplate-->
        <ItemsPanelTemplate x:Key="itemspanel">
            <StackPanel Orientation="Vertical" />
        </ItemsPanelTemplate>
        <!--DataTemplate-->
        <DataTemplate x:Key="datatemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding ID}" Width="30"/>
                <TextBlock Text="{Binding Name}" Width="60"/>
                <Image Source="{Binding imgPath}" Width="30"/>
            </StackPanel>
        </DataTemplate>    

cs代码:

        List<Student> studentList = new List<Student>()
            {
                new Student(){ID=1,Name="Rethinker",imgPath="/TemplateDemo;component/Images/1.png"},
                new Student(){ID=2,Name="Jello",imgPath="/TemplateDemo;component/Images/2.png"},
                new Student(){ID=3,Name="Taffy",imgPath="/TemplateDemo;component/Images/3.png"}
            };
            this.lbStudentList.ItemsSource = studentList;    

 注意:这里Image的Source采用的是Pack Uri,详细内容请查看WPF中的Pack Uri

 效果如下:

2.ContentPresenter

在第一节,我们发现在解剖的Button内部有个叫ContentPresenter的东东,根据名字也许你已经猜到它是干嘛的了,它就是呈现ContentControl的内容的。这里,当我们将ContentPresenter换成TextBlock好像效果也没变化。

<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

 我们知道TextBlock的Text属性是String类型,这就限制了其显示的丰富性。有人会说,既然这样那换成ContentControl算了,它可以显示更丰富的东西,类似这样:

<ContentControl Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>

 看起来也没什么问题,我们知道Button本身就是一个ContentControl,它是一个很重量级的Control,它的Content其实也是通过ContentPresenter来表现的,看起来ContentPresenter是一个更轻量级的Control。另外,ContentPresenter还有一个比较特别的地方,当你未指定它的Content时,它会默认去取该模板的使用者的Content。

在继承自ItemsControl的控件内部也有个类似的ItemsPresenter,它是来负责Item的展示。

在ItemsPresenter内部会以ItemsPanelTemplate中的容器作为自己的容器,会以ItemTemplate中的布局作为ListBoxItem的布局。当然,在具体显示内容的地方,还是要用到ContentTemplate的。归根结底,我们可以将Presenter看作是一个占位符,设置了Button的Content,它就获取,否则默认。

3.TemplatePart机制

TemplatePart机制是某些WPF控件,如ProgressBar等,通过内建的逻辑来控制控件的可是行为的方式。我们先来解剖下ProgressBar一探究竟。

我们发现里面有几个很特别的东西,一个名为PART_Track的Rectangle,一个名为PART_Indicator的Grid。这并不是偶然,实际上在ComboBox和TextBox中也有这样类似PART_×××这样命名的元素。在这些类的定义中,我们也会发现一些端倪,例如ProgressBar类的定义:

    [TemplatePart(Name = "PART_GlowRect", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "PART_Indicator", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "PART_Track", Type = typeof(FrameworkElement))]
    public class ProgressBar : RangeBase
    {
        
    }

ProgressBar用了TemplatePart这个Attribute,那它到底如何有何作用呢?实际上,如果在ControlTemplate中找到了这样的元素,就会应用一些附加的行为。

例如,在ComboBox的空间模板中有个名为PART_Popup的Popup,当它关闭时,ComboBox的DropDownClosed事件会自动触发,如果ComboBox控件模板中有名为PART_EditableTextBox的TextBox,它就会将用户的选项作为它的显示项。在后面的自定义控件这一篇中,我们也将使用它。

4.如何找Template中的控件

在Template的基类FrameworkTemplate中有FindName方法,通过它我们可以找到模板中的控件。这个方法对于ControlTemplate和ItemsPanelTemplate很直接有效,但是,在ItemsControl的DataTemplate中,因为展示的数据是集合,所以相对复杂些。在前面我们已经剖析了ListBox内部,这对于找控件是最本质的。我们将前面的DataTemplate稍微修改下,如下:

<!--DataTemplate-->
        <DataTemplate x:Key="datatemplate">
            <StackPanel x:Name="sp" Orientation="Horizontal">
                <TextBlock x:Name="tbID" Text="{Binding ID}" Width="30"/>
                <TextBlock x:Name="tbName" Text="{Binding Name}" Width="60"/>
                <Image x:Name="tbImgPath" Source="{Binding imgPath}" Width="30"/>
                <TextBlock x:Name="tbNameLen" Text="{Binding Path=Name.Length}" />
            </StackPanel>
        </DataTemplate>

要查找由某个ListBoxItem的DataTemplate生成的TextBlock元素,需要获得ListBoxItem,在该ListBoxItem内查找ContentPresenter,然后对在该 ContentPresenter 上设置的 DataTemplate 调用 FindName,在由ListBoxItem查找ContentPresenter时,需要遍历VisualTree,这里给出遍历方法:

class CommonHelper
    {
        public static T ChildOfType<T>(DependencyObject Parent) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Parent); i++)
            {
                DependencyObject obj = VisualTreeHelper.GetChild(Parent, i);
                if (obj != null && obj is T)
                {
                    return (T)obj;
                }
                else
                {
                    T child = ChildOfType<T>(obj);
                    if (child != null)
                        return child;
                }
            }
            return default(T);
        }
    }

在这里通过监听ListBox的SelectionChanged事件来展示效果,cs代码:

private void lbStudentList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //第一步:找到ListBoxItem
            ListBoxItem item = this.lbStudentList.ItemContainerGenerator.ContainerFromIndex(this.lbStudentList.SelectedIndex) as ListBoxItem;
            if (item == null) return;
            //第二步:遍历找到ContentPresenter,这里需要写个辅助方法
            ContentPresenter cp = CommonHelper.ChildOfType<ContentPresenter>(item);
            if (cp == null) return;
            //第三步:找到DataTemplate
            DataTemplate dt = cp.ContentTemplate;
            //第四步:通过DataTemplate的FindName方法
            TextBlock tb = dt.FindName("tbName", cp) as TextBlock;
            if (tb != null)
                MessageBox.Show(tb.Text);
        }

效果如下:

猜你喜欢

转载自blog.csdn.net/qq_30807313/article/details/91980368