#759 – Device-Independent Units (DIPs)

(Also known as WPF Units)

In WPF, when you’re specifying size or position for user interface elements, the values are always by default in Device-Indepdent Units (DIPs).

1 DIP = 1/96 inch.  This means at 96 dpi, 1 DIP = 1 pixel.

At 120 dpi, 1 DIP = 1.25 pixels.  (120/96)

The purpose of using DIPs when indicating desired size or position is that controls will still have the same physical size on your screen, regardless of the DPI setting.  A button that is specified as being 96 DIPs wide will be 1″ wide, whether your display is set to 96 DPI, 120 DPI, or 150 DPI.

To convert from DIPs to pixels, you need to know your screen DPI setting (e.g. 96, 120):

# pixels = (DIPs) * (DPI / 96)

So 96 DIPs = 96 pixels at 96 DPI, and 96 DIPs = 120 pixels at 120 DPI.

#758 – Access Actual Height and Width from within LayoutUpdated Event Handler

The ActualHeight and ActualWidth properties of a FrameworkElement can be read to get the values for the final height and width that the layout system assigned to the element.

The LayoutUpdated event of a UIElement (parent of FrameworkElement) will fire whenever the layout changes.  You can handle this event and read the values of ActualHeight and ActualWidth from within the handler.

You should typically only read these properties from within the LayoutUpdated handler.  If you try reading them at some other point, there’s no guarantee that they have arrived at their final values as a result of the layout changing.

#757 – RenderSize, ActualHeight and ActualWidth

The ActualHeight and ActualWidth properties of an element that inherits from FrameworkElement indicate the final height and width of the element, after layout has taken place.

This same information is also available via the UIElement.RenderSize property (UIElement is the parent class of FrameworkElement).  RenderSize is of type Size, which is a struct containing the width and the height.

#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