File: System\Windows\Controls\Primitives\Thumb.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Threading;
using System.Runtime.InteropServices;
 
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Input;
using System.Windows.Media;
 
using MS.Utility;
using MS.Win32;
using MS.Internal;
using MS.Internal.PresentationFramework;                   // SafeSecurityHelper
 
namespace System.Windows.Controls.Primitives
{
    /// <summary>
    ///     The thumb control enables basic drag-movement functionality for scrollbars and window resizing widgets.
    /// </summary>
    /// <remarks>
    ///     The thumb can receive mouse focus but it cannot receive keyboard focus.
    /// As well, there is no threshhold at which the control stops firing its DragDeltaEvent.
    /// Once in mouse capture, the DragDeltaEvent fires until the mouse button is released.
    /// </remarks>
    [DefaultEvent("DragDelta")]
    [Localizability(LocalizationCategory.NeverLocalize)]
    public class Thumb : Control
    {
        #region Constructors
 
        /// <summary>
        ///     Default Thumb constructor
        /// </summary>
        /// <remarks>
        ///     Automatic determination of current Dispatcher. Use alternative constructor
        ///     that accepts a Dispatcher for best performance.
        /// </remarks>
        public Thumb() : base()
        {
        }
 
        static Thumb()
        {
            // Register metadata for dependency properties
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Thumb), new FrameworkPropertyMetadata(typeof(Thumb)));
            _dType = DependencyObjectType.FromSystemTypeInternal(typeof(Thumb));
            FocusableProperty.OverrideMetadata(typeof(Thumb), new FrameworkPropertyMetadata(MS.Internal.KnownBoxes.BooleanBoxes.FalseBox));
 
            EventManager.RegisterClassHandler(typeof(Thumb), Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCapture));
 
            IsEnabledProperty.OverrideMetadata(typeof(Thumb), new UIPropertyMetadata(new PropertyChangedCallback(OnVisualStatePropertyChanged)));
            IsMouseOverPropertyKey.OverrideMetadata(typeof(Thumb), new UIPropertyMetadata(new PropertyChangedCallback(OnVisualStatePropertyChanged)));
        }
 
        #endregion
 
        #region Properties and Events
 
        /// <summary>
        ///     Event fires when user press mouse's left button on the thumb.
        /// </summary>
        public static readonly RoutedEvent DragStartedEvent = EventManager.RegisterRoutedEvent("DragStarted", RoutingStrategy.Bubble, typeof(DragStartedEventHandler), typeof(Thumb));
 
        /// <summary>
        ///     Event fires when the thumb is in a mouse capture state and the user moves the mouse around.
        /// </summary>
        public static readonly RoutedEvent DragDeltaEvent = EventManager.RegisterRoutedEvent("DragDelta", RoutingStrategy.Bubble, typeof(DragDeltaEventHandler), typeof(Thumb));
 
        /// <summary>
        ///     Event fires when user released mouse's left button or when CancelDrag method is called.
        /// </summary>
        public static readonly RoutedEvent DragCompletedEvent = EventManager.RegisterRoutedEvent("DragCompleted", RoutingStrategy.Bubble, typeof(DragCompletedEventHandler), typeof(Thumb));
 
        /// <summary>
        /// Add / Remove DragStartedEvent handler
        /// </summary>
        [Category("Behavior")]
        public event DragStartedEventHandler DragStarted { add { AddHandler(DragStartedEvent, value); } remove { RemoveHandler(DragStartedEvent, value); } }
 
        /// <summary>
        /// Add / Remove DragDeltaEvent handler
        /// </summary>
        [Category("Behavior")]
        public event DragDeltaEventHandler DragDelta { add { AddHandler(DragDeltaEvent, value); } remove { RemoveHandler(DragDeltaEvent, value); } }
 
        /// <summary>
        /// Add / Remove DragCompletedEvent handler
        /// </summary>
        [Category("Behavior")]
        public event DragCompletedEventHandler DragCompleted { add { AddHandler(DragCompletedEvent, value); } remove { RemoveHandler(DragCompletedEvent, value); } }
 
 
        private static readonly DependencyPropertyKey IsDraggingPropertyKey = 
                DependencyProperty.RegisterReadOnly(
                        "IsDragging", 
                        typeof(bool), 
                        typeof(Thumb),
                        new FrameworkPropertyMetadata(
                                MS.Internal.KnownBoxes.BooleanBoxes.FalseBox,
                                new PropertyChangedCallback(OnIsDraggingPropertyChanged)));
 
        /// <summary>
        ///     DependencyProperty for the IsDragging property.
        ///     Flags:              None
        ///     Default Value:      false
        /// </summary>
        public static readonly DependencyProperty IsDraggingProperty = IsDraggingPropertyKey.DependencyProperty;
 
        /// <summary>
        ///     IsDragging indicates that left mouse button is pressed over the thumb.
        /// </summary>
        [Bindable(true), Browsable(false), Category("Appearance")]
        public bool IsDragging
        {
            get { return (bool) GetValue(IsDraggingProperty); }
            protected set { SetValue(IsDraggingPropertyKey, MS.Internal.KnownBoxes.BooleanBoxes.Box(value)); }
        }
 
        #endregion Properties and Events
 
        /// <summary>
        ///     Called when IsDraggingProperty is changed on "d."
        /// </summary>
        /// <param name="d">The object on which the property was changed.</param>
        /// <param name="e">EventArgs that contains the old and new values for this property</param>
        private static void OnIsDraggingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var thumb = (Thumb)d;
            thumb.OnDraggingChanged(e);
            thumb.UpdateVisualState();
        }
 
        #region Public methods
 
        /// <summary>
        ///     This method cancels the dragging operation.
        /// </summary>
        public void CancelDrag()
        {
            if (IsDragging)
            {
                if (IsMouseCaptured)
                {
                    ReleaseMouseCapture();
                }
                ClearValue(IsDraggingPropertyKey);
                RaiseEvent(new DragCompletedEventArgs(_previousScreenCoordPosition.X - _originScreenCoordPosition.X, _previousScreenCoordPosition.Y - _originScreenCoordPosition.Y, true));
            }
        }
 
        #endregion Public methods
 
 
        #region Virtual methods
 
        /// <summary>
        ///     This method is invoked when the IsDragging property changes.
        /// </summary>
        /// <param name="e">DependencyPropertyChangedEventArgs for IsDragging property.</param>
        protected virtual void OnDraggingChanged(DependencyPropertyChangedEventArgs e)
        {
        }
 
        #endregion Virtual methods
 
        #region Override methods
 
        /// <summary>
        ///     Change to the correct visual state for the ButtonBase.
        /// </summary>
        /// <param name="useTransitions">
        ///     true to use transitions when updating the visual state, false to
        ///     snap directly to the new visual state.
        /// </param>
        internal override void ChangeVisualState(bool useTransitions)
        {
            // See ButtonBase.ChangeVisualState.
            // This method should be exactly like it, except we use IsDragging instead of IsPressed for the pressed state
            if (!IsEnabled)
            {
                VisualStateManager.GoToState(this, VisualStates.StateDisabled, useTransitions);
            }
            else if (IsDragging)
            {
                VisualStateManager.GoToState(this, VisualStates.StatePressed, useTransitions);
            }
            else if (IsMouseOver)
            {
                VisualStateManager.GoToState(this, VisualStates.StateMouseOver, useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, VisualStates.StateNormal, useTransitions);
            }
 
            if (IsKeyboardFocused)
            {
                VisualStateManager.GoToState(this, VisualStates.StateFocused, useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, VisualStates.StateUnfocused, useTransitions);
            }
 
            base.ChangeVisualState(useTransitions);
        }
 
        /// <summary>
        /// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer() 
        {
            return new ThumbAutomationPeer(this);
        }
 
        /// <summary>
        /// This is the method that responds to the MouseButtonEvent event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (!IsDragging)
            {
                e.Handled = true;
                Focus();
                CaptureMouse();
                SetValue(IsDraggingPropertyKey, true);
                _originThumbPoint = e.GetPosition(this);
                _previousScreenCoordPosition = _originScreenCoordPosition = SafeSecurityHelper.ClientToScreen(this,_originThumbPoint);
                bool exceptionThrown = true;
                try
                {
                    RaiseEvent(new DragStartedEventArgs(_originThumbPoint.X, _originThumbPoint.Y));
                    exceptionThrown = false;
                }
                finally
                {
                    if (exceptionThrown)
                    {
                        CancelDrag();
                    }
                }
            }
            else
            {
                // This is weird, Thumb shouldn't get MouseLeftButtonDown event while dragging.
                // This may be the case that something ate MouseLeftButtonUp event, so Thumb never had a chance to
                // reset IsDragging property
                Debug.Assert(false,"Got MouseLeftButtonDown event while dragging!");
            }
            base.OnMouseLeftButtonDown(e);
        }
 
 
        /// <summary>
        /// This is the method that responds to the MouseButtonEvent event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (IsMouseCaptured && IsDragging)
            {
                e.Handled = true;
                ClearValue(IsDraggingPropertyKey);
                ReleaseMouseCapture();
                Point pt = SafeSecurityHelper.ClientToScreen(this, e.MouseDevice.GetPosition(this));
                RaiseEvent(new DragCompletedEventArgs(pt.X - _originScreenCoordPosition.X, pt.Y - _originScreenCoordPosition.Y, false));
            }
            base.OnMouseLeftButtonUp(e);
        }
 
        // Cancel Drag if we lost capture
        private static void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            Thumb thumb = (Thumb)sender;
 
            if (Mouse.Captured != thumb)
            {
                thumb.CancelDrag();
            }
        }
 
        /// <summary>
        /// This is the method that responds to the MouseEvent event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
 
            if (IsDragging)
            {
                if (e.MouseDevice.LeftButton == MouseButtonState.Pressed)
                {
                    Point thumbCoordPosition = e.GetPosition(this);
                    // Get client point then convert to screen point
                    Point screenCoordPosition = SafeSecurityHelper.ClientToScreen(this, thumbCoordPosition);
 
                    // We will fire DragDelta event only when the mouse is really moved
                    if (screenCoordPosition != _previousScreenCoordPosition)
                    {
                        _previousScreenCoordPosition = screenCoordPosition;
                        e.Handled = true;
                        RaiseEvent(new DragDeltaEventArgs(thumbCoordPosition.X - _originThumbPoint.X,
                                                          thumbCoordPosition.Y - _originThumbPoint.Y));
                    }
                }
                else
                {
                    if (e.MouseDevice.Captured == this)
                        ReleaseMouseCapture();
                    ClearValue(IsDraggingPropertyKey);
                    _originThumbPoint.X = 0;
                    _originThumbPoint.Y = 0;
                }
            }
        }
        
        //
        //  This property
        //  1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
        //  2. This is a performance optimization
        //
        internal override int EffectiveValuesInitialSize
        {
            get { return 19; }
        }
 
        #endregion
 
        #region Data
 
        /// <summary>
        /// The point where the mouse was clicked down (Thumb's co-ordinate).
        /// </summary>
        private Point _originThumbPoint; //
 
        /// <summary>
        /// The position of the mouse (screen co-ordinate) where the mouse was clicked down.
        /// </summary>
        private Point _originScreenCoordPosition;
 
        /// <summary>
        /// The position of the mouse (screen co-ordinate) when the previous DragDelta event was fired
        /// </summary>
        private Point _previousScreenCoordPosition;
 
        #endregion
 
        #region DTypeThemeStyleKey
 
        // Returns the DependencyObjectType for the registered ThemeStyleKey's default
        // value. Controls will override this method to return approriate types.
        internal override DependencyObjectType DTypeThemeStyleKey
        {
            get { return _dType; }
        }
 
        private static DependencyObjectType _dType;
 
        #endregion DTypeThemeStyleKey
    }
}