#1,074 – Attached Event Syntax

Attached events allow attaching a handler for an event that is defined in an element other than the one adding the handler.  For example, a StackPanel might define a handler for the Click event that is defined in ButtonBase.

When defining handlers in XAML, the event name is used by itself if that event is defined for the element raising the event.

        <Button Content="Click Me" Click="Button_Click"/>

When defining a handler on an element that does not define the handler’s event, you prefix the event name with the name of the class that defines the event.

    <StackPanel Button.Click="StackPanel_Button_Click">
Advertisement

#1,073 – Attached Events

When events are routed in WPF, elements up (bubbling) or down (tunneling) the logical tree are given the chance to add a handler for a particular event.

For example, if you have a Button within a StackPanel and the user clicks on the Button, the Button will raise a Click event, but also bubble the event up to the StackPanel so that it can also raise the Click event.

Click is defined as a RoutedEventHandler in the ButtonBase class.  There is also a public static ClickEvent object of type RoutedEvent defined in ButtonBase.  So we think of ButtonBase as “owning” the Click event.  This makes sense, since buttons are generally the elements that will raise Click events.

The StackPanel does not define a Click event, but can define a handler for a routed event object that it does not own.  This is known as an attached event.

#1,066 – Elements Must Be Visible and Enabled to Fire Events

Events fired from user interface elements in WPF are typically routed events, firing events from elements up or down the logical tree after the source element has fired the event.

A user interface element must be both visible (Visibility = Visible) and enabled (IsEnabled = true) in order to fire an event.  If an element is not visible or is disabled, the topmost element beneath the element may fire the event instead.

Below, if we set IsEnabled on the Label to false, its MouseDown event will not fire and the StackPanel will become the source of the event instead.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Events" Width="208" Height="183"
        MouseDown="Window_MouseDown">

    <StackPanel MouseDown="StackPanel_MouseDown"
                Background="AliceBlue" Margin="10">
        <Label Content="Label" Background="Orange" Margin="10"
               MouseDown="Label_MouseDown"
               IsEnabled="False"/>
    </StackPanel>
</Window>

When we click on the Label, the StackPanel fires the MouseDown event, followed by the parent Window.

1066-002

1066-001

#756 – Making Element Stop at Edge of Window When Using Inertia

We saw previously how to stop an object at the edge of a window when using touch manipulation.  You likely also want the object to stop moving when it hits the edge of its container when moving as a result of inertia (i.e. you’ve already lifted your finger from the screen).

In the following code fragment, we’ve enabled positional inertia.  We then check to see if the object is past the container boundary.  If it is and if it’s moving as a result of inertia, we stop the inertial event by calling Complete and we move the object back within the window.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            ImageTransform = new MatrixTransform();
        }

        private MatrixTransform imageTransform;
        public MatrixTransform ImageTransform
        {
            get { return imageTransform; }
            set
            {
                if (value != imageTransform)
                {
                    imageTransform = value;
                    RaisePropertyChanged("ImageTransform");
                }
            }
        }

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Ask for manipulations to be reported relative to the canvas
            e.ManipulationContainer = canvMain;

            // Support translation and scaling
            e.Mode = ManipulationModes.Translate;
        }

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // Translation inertia - 10 in/sec^2 deceleration
            e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0);
        }

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            Matrix m = imageTransform.Matrix;

            // If element beyond edge, report back to WPF
            Vector pastEdgeVector;
            if (ElementPastBoundary(e.Source as FrameworkElement, out pastEdgeVector) &&
                e.IsInertial)
            {
                m.Translate(-1.0 * pastEdgeVector.X, -1.0 * pastEdgeVector.Y);
                imageTransform.Matrix = m;

                e.Complete();
                e.Handled = true;
                return;
            }

            // Find center of element and then transform to get current location of center
            FrameworkElement fe = e.Source as FrameworkElement;
            Point center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
            center = m.Transform(center);

            // Update matrix to reflect translation and rotation
            ManipulationDelta md = e.DeltaManipulation;
            m.Translate(md.Translation.X, md.Translation.Y);

            imageTransform.Matrix = m;
            RaisePropertyChanged("ImageTransform");

            e.Handled = true;
        }

        private bool ElementPastBoundary(FrameworkElement fe, out Vector pastEdgeVector)
        {
            bool pastEdge = false;

            pastEdgeVector = new Vector();

            FrameworkElement feParent = fe.Parent as FrameworkElement;
            if (feParent != null)
            {
                Rect feRect = fe.TransformToAncestor(feParent).TransformBounds(
                    new Rect(0.0, 0.0, fe.ActualWidth, fe.ActualHeight));

                if (feRect.Right > feParent.ActualWidth)
                    pastEdgeVector.X = feRect.Right - feParent.ActualWidth;

                if (feRect.Left < 0)
                    pastEdgeVector.X = feRect.Left;

                if (feRect.Bottom > feParent.ActualHeight)
                    pastEdgeVector.Y = feRect.Bottom - feParent.ActualHeight;

                if (feRect.Top < 0)
                    pastEdgeVector.Y = feRect.Top;

                if ((pastEdgeVector.X != 0) || (pastEdgeVector.Y != 0))
                    pastEdge = true;
            }

            return pastEdge;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

#755 – Implementing Rotational Inertia during Touch Manipulation

In the same way that you can support inertia as a result of touch manipulation during translation and expansion, you can also set up rotational inertia.  When the user rotates an element using touch, the element has some initial rotational velocity (in deg/ms) when they lift their fingers off the screen.  You can then specify a desired value for a rotational deceleration (deg/ms^2).

As with translation and expansion, you specify the desired rotational deceleration in a handler for the ManipulationInertiaStarting event.  In the example below, we display the initial rotational velocity to the console and then specify a deceleration of 100 degrees/sec^2.  (Reduce velocity by 100 deg/sec each second).

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // Rotational inertia - 100 deg/sec^2 deceleration
            Console.WriteLine(string.Format("Initial rotational velocity = {0} deg/sec",
                e.RotationBehavior.InitialVelocity * 1000.0));
            e.RotationBehavior.DesiredDeceleration = 100.0 / (1000.0 * 1000.0);
        }

755-001

#754 – Implementing Inertia for Expansion during Touch Manipulation

You can set the TranslationBehavior.DesiredDeceleration in the ManipulationInertiaStarting event to allow inertia when translating using touch manipulation.  This allows an element to continue moving a little bit after you lift your finger off the element while doing translation manipulation.

You can also enable inertia for expansion (i.e. scaling) during touch manipulation.  An element will then continue expanding or contracting when you lift your fingers from the screen while doing expansion using touch.  You do this by setting the ExpansionBehavior.DesiredDeceleration property.

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // 10 in/sec^2 deceleration
            e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0);

            // 960 DIPS/sec^2 deceleration
            Console.WriteLine(string.Format("Init Expansion Velocity = {0}", e.ExpansionBehavior.InitialVelocity));
            e.ExpansionBehavior.DesiredDeceleration = 960.0 / (1000.0 * 1000.0);
        }

Doing this is perhaps a little less useful than specifying inertia during translation.  Inertia as part of expansion is a little less intuitive.

#753 – Scale vs. Expansion in ManipulationDelta Events

When handling a ManipulationDelta event during touch manipulation, you often care about the ManipulationDelta.Scale property, which indicates the updated scale of an element, relative to its previous size (e.g. 0.5 = 1/2 size).

You can also access a ManipulationDelta.Expansion property, which tells you the actual number of device independent units (1/96th in) that the element is changing, relative to its last known size.

The example below dumps out both scale and expansion values as we scale with touch.

        private Vector totalScale = new Vector(1.0, 1.0);
        private Vector totalExpansion = new Vector(0.0, 0.0);

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            ManipulationDelta md = e.DeltaManipulation;

            totalScale.X *= md.Scale.X;
            totalScale.Y *= md.Scale.Y;

            totalExpansion.X += md.Expansion.X;
            totalExpansion.Y += md.Expansion.Y;

            Console.WriteLine(string.Format(
                "Scale: {0},{1}.  Expansion: {2},{3}",
                md.Scale.X, md.Scale.Y, md.Expansion.X, md.Expansion.Y));
            Console.WriteLine(string.Format(
                "  Total Scale: {0},{1}.  Total Expansion: {2},{3}",
                totalScale.X, totalScale.Y, totalExpansion.X, totalExpansion.Y));
        }

753-001

#752 – Tracking Total Scale when Scaling by Touch Manipulation

When you use touch manipulation events to scale an element, you typically read the Scale property of the ManipulationDelta object passed in to the ManipulationDelta event handler.   This property reports a delta scaling value to apply to the element, derived from the user’s touch manipulation (e.g. pinch/expand).

For example, a scale value of 1.05 says “scale the object 5% larger than it was the last time that this event was fired”.

In the code example below, we also track total scale, relative to the original size of the element.  (Note that we don’t actually scale the element here).

        private Vector totalScale = new Vector(1.0, 1.0);

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            ManipulationDelta md = e.DeltaManipulation;
            Vector scale = md.Scale;

            totalScale.X *= scale.X;
            totalScale.Y *= scale.Y;

            Console.WriteLine(string.Format(
                "Scale: X={0}, Y={1}, TotalScale: X={2}, Y={3}",
                scale.X, scale.Y, totalScale.X, totalScale.Y));

            e.Handled = true;
        }

752-001

#751 – Indicating which Touch Manipulation Modes You Support

When you support touch manipulation events, you can choose which types of touch manipulation to support for an element.  The modes include:

  • Translation (in X, Y, or both)
  • Rotation
  • Scaling

You indicate which modes you want to support within the ManipulationStarting event handler, setting the ManipulationStartingEventArgs.Mode property.  You can set this property to some combination of :

  • ManipulationModes.None
  • ManipulationModes.TranslateX
  • ManipulationModes.TranslateY
  • ManipulationModes.Translate
  • ManipulationModes.Rotate
  • ManipulationModes.Scale
  • ManipulationModes.All

When you enable a mode, the ManipulationDelta event will fire when a user manipulates the element using touch, and will include data for that style of manipulation.  When a manipulation mode isn’t supported, the ManipulationDelta event will not fire for that mode.  It may fire for other manipulation actions, but will not include data for modes that are disabled.

The mode values listed above can be combined using the OR (|) operator.

            // Support translation and scaling
            e.Mode = ManipulationModes.Translate | ManipulationModes.Scale;

#750 – Using Touch Manipulation to Translate in Just One Dimension

You can set the IsManipulationEnabled property and handle the ManipulationDelta event for an element, to support translating (moving) the element using touch.

By default, when you read the ManipulationDelta.Translation property, it will contain translation values for both X and Y.  If you want to allow moving the element only horizontally or vertically, you can handle the ManipulationStarting event and set the ManipulationStartingEventArgs.Mode property to either TranslateX or TranslateY.

The example below shows how we could limit translation to be only horizontal.  You could do the same thing by ignoring the Y component of the Translation property in the ManipulationDelta event handler.

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Ask for manipulations to be reported relative to the canvas
            e.ManipulationContainer = canvMain;

            // Allow only horizontal translation
            e.Mode = ManipulationModes.TranslateX;
        }