none
making the slider slide with one click anywhere on the slider?

    Question

  • Hey guys,

    I am trying to get the Slider to behave like this:
    1. If the user clicks (mouse down) on the slider somewhere the thumb is moved to the location of the click immediately
    2. when the user clicked on the slider and he moves the mouse, the thumb is sliding with the cursor...
    1.
    Apparently there is a property called "IsMoveToPointEnabled" that if set to True causes the RepeatButton behaviour of the Slider to disappear and the thumb immediately moves to the point where clicked.

    2.
    This is the default behaviour of the slider if you click on the thumb anyway but unfortunately if you click not on the thumb (thus using the IsMoveToPointEnabled) you cannot move the thumb after the first click without clicking on the thumb again.

    Any ideas on how to overcome this? Any hints appreciated.

    cheers,
    Patrick
    Tuesday, October 09, 2007 1:17 AM

Answers

  • To make this work, you need to dig into the template of the slider and get a reference to it's thumb.  Once you have that, you can set a MouseEnter handler on the thumb.  Anytime the thumb is moved in response to a click on the track, it will be moved under the mouse.  This will conveniently raise the MouseEnter event while the left mouse button is already down. 

     

    In your MouseEnter handler, you just need to check whether the left mouse button is down.  If so, is the mouse already captured?  If so, then the thumb already has capture and you're golden.  If not, then the thumb was moved under the mouse so you need to raise a new MouseLeftButtonDown event on the thumb so that it captures the mouse.

     

    Assume that you have a Slider declared in Window1.xaml:

     

    Code Block

     

    <Window x:Class="WindowsApplication1.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="WindowsApplication1" Height="300" Width="300">

      <Grid>

        <Slider Name="slider" VerticalAlignment="Top"

            IsMoveToPointEnabled="True" Width="100"

            Maximum="10" Minimum="1" />

      </Grid>

    </Window>

     

     

     

    In your code behind, you just need to add the following code:

     

    Code Block

     

    public Window1()

    {

        InitializeComponent();

        slider.ApplyTemplate();

        Thumb thumb = (slider.Template.FindName(

            "PART_Track", slider) as Track).Thumb;

        thumb.MouseEnter

            += new MouseEventHandler(thumb_MouseEnter);

    }

     

    private void thumb_MouseEnter(object sender,

        MouseEventArgs e)

    {

        if (e.LeftButton == MouseButtonState.Pressed

            && e.MouseDevice.Captured == null)

        {

            // the left button is pressed on mouse enter

            // but the mouse isn't captured, so the thumb

            // must have been moved under the mouse in response

            // to a click on the track.

            // Generate a MouseLeftButtonDown event.

            MouseButtonEventArgs args = new MouseButtonEventArgs(

                e.MouseDevice, e.Timestamp, MouseButton.Left);

            args.RoutedEvent = MouseLeftButtonDownEvent;

            (sender as Thumb).RaiseEvent(args);

        }

    }

     

     

    Wednesday, October 10, 2007 1:20 AM
  • > I used it in a subclass of slider so I added the part in the constructor into the OnInitialized(...) method

     

    Even better!  A portable solution.  Smile

     

    One suggestion... since you're now within the control, I would move the code from OnInitialized to OnApplyTemplate.  Then you don't need to call ApplyTemplate directly.  OnApplyTemplate will be called when the control template is applied and anytime it gets re-applied (if the template changes).

     

    Last edit... I promise.

     

    Code Block

     

    public class MySlider : Slider

    {

        private Thumb _thumb = null;

     

        public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

            if (_thumb != null)

            {

                _thumb.MouseEnter -= thumb_MouseEnter;

            }

            _thumb = (GetTemplateChild("PART_Track") as Track).Thumb;

            if (_thumb != null)

            {

                _thumb.MouseEnter += thumb_MouseEnter;

            }

        }

     

        private void thumb_MouseEnter(object sender, MouseEventArgs e)

        {

            if (e.LeftButton == MouseButtonState.Pressed)

            {

                // the left button is pressed on mouse enter

                // so the thumb must have been moved under the mouse

                // in response to a click on the track.

                // Generate a MouseLeftButtonDown event.

                MouseButtonEventArgs args = new MouseButtonEventArgs(

                    e.MouseDevice, e.Timestamp, MouseButton.Left);

                args.RoutedEvent = MouseLeftButtonDownEvent;

                (sender as Thumb).RaiseEvent(args);

            }

        }

    }

     

     

     

    Thinking about it more, the check for Capture is probably superfluous.  If the mouse is already captured, it's not possible for the MouseEnter to fire.  So really you'd just need to check the state of the left mouse button.

     

    Wednesday, October 10, 2007 1:47 AM

All replies

  •  

    Maybe you could make the slider lose focus and then gain it again programmatically...

     

    thus making it the first click evertime?

     

    HTH.

     

    Si.

    Wednesday, October 10, 2007 12:05 AM
  • To make this work, you need to dig into the template of the slider and get a reference to it's thumb.  Once you have that, you can set a MouseEnter handler on the thumb.  Anytime the thumb is moved in response to a click on the track, it will be moved under the mouse.  This will conveniently raise the MouseEnter event while the left mouse button is already down. 

     

    In your MouseEnter handler, you just need to check whether the left mouse button is down.  If so, is the mouse already captured?  If so, then the thumb already has capture and you're golden.  If not, then the thumb was moved under the mouse so you need to raise a new MouseLeftButtonDown event on the thumb so that it captures the mouse.

     

    Assume that you have a Slider declared in Window1.xaml:

     

    Code Block

     

    <Window x:Class="WindowsApplication1.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="WindowsApplication1" Height="300" Width="300">

      <Grid>

        <Slider Name="slider" VerticalAlignment="Top"

            IsMoveToPointEnabled="True" Width="100"

            Maximum="10" Minimum="1" />

      </Grid>

    </Window>

     

     

     

    In your code behind, you just need to add the following code:

     

    Code Block

     

    public Window1()

    {

        InitializeComponent();

        slider.ApplyTemplate();

        Thumb thumb = (slider.Template.FindName(

            "PART_Track", slider) as Track).Thumb;

        thumb.MouseEnter

            += new MouseEventHandler(thumb_MouseEnter);

    }

     

    private void thumb_MouseEnter(object sender,

        MouseEventArgs e)

    {

        if (e.LeftButton == MouseButtonState.Pressed

            && e.MouseDevice.Captured == null)

        {

            // the left button is pressed on mouse enter

            // but the mouse isn't captured, so the thumb

            // must have been moved under the mouse in response

            // to a click on the track.

            // Generate a MouseLeftButtonDown event.

            MouseButtonEventArgs args = new MouseButtonEventArgs(

                e.MouseDevice, e.Timestamp, MouseButton.Left);

            args.RoutedEvent = MouseLeftButtonDownEvent;

            (sender as Thumb).RaiseEvent(args);

        }

    }

     

     

    Wednesday, October 10, 2007 1:20 AM
  • Thank you for the detailed and excellent reply. Worked like a charm.
    I used it in a subclass of slider so I added the part in the constructor into the OnInitialized(...) method...

    Thanks Doc!
    Wednesday, October 10, 2007 1:44 AM
  • > I used it in a subclass of slider so I added the part in the constructor into the OnInitialized(...) method

     

    Even better!  A portable solution.  Smile

     

    One suggestion... since you're now within the control, I would move the code from OnInitialized to OnApplyTemplate.  Then you don't need to call ApplyTemplate directly.  OnApplyTemplate will be called when the control template is applied and anytime it gets re-applied (if the template changes).

     

    Last edit... I promise.

     

    Code Block

     

    public class MySlider : Slider

    {

        private Thumb _thumb = null;

     

        public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

            if (_thumb != null)

            {

                _thumb.MouseEnter -= thumb_MouseEnter;

            }

            _thumb = (GetTemplateChild("PART_Track") as Track).Thumb;

            if (_thumb != null)

            {

                _thumb.MouseEnter += thumb_MouseEnter;

            }

        }

     

        private void thumb_MouseEnter(object sender, MouseEventArgs e)

        {

            if (e.LeftButton == MouseButtonState.Pressed)

            {

                // the left button is pressed on mouse enter

                // so the thumb must have been moved under the mouse

                // in response to a click on the track.

                // Generate a MouseLeftButtonDown event.

                MouseButtonEventArgs args = new MouseButtonEventArgs(

                    e.MouseDevice, e.Timestamp, MouseButton.Left);

                args.RoutedEvent = MouseLeftButtonDownEvent;

                (sender as Thumb).RaiseEvent(args);

            }

        }

    }

     

     

     

    Thinking about it more, the check for Capture is probably superfluous.  If the mouse is already captured, it's not possible for the MouseEnter to fire.  So really you'd just need to check the state of the left mouse button.

     

    Wednesday, October 10, 2007 1:47 AM
  •  Dr. WPF wrote:
     

    Thinking about it more, the check for Capture is probably superfluous.  If the mouse is already captured, it's not possible for the MouseEnter to fire.  So really you'd just need to check the state of the left mouse button.

     



    indeed

     Dr. WPF wrote:

    One suggestion... since you're now within the control, I would move the code from OnInitialized to OnApplyTemplate.  Then you don't need to call ApplyTemplate directly.  OnApplyTemplate will be called when the control template is applied and anytime it gets re-applied (if the template changes).


    Excellent suggestion! Thanks

    cheers
    Patrick
    Wednesday, October 10, 2007 2:01 AM
  • Hello,

    How could this work without using a mouse. With a custom touch screen I have a point on the screen, I have access to the slider element and now I want to move the slider to the point on the screen. The problem seems to be the MouseDevice. I cannot create my custom mousedevice.

    Any suggestions?

    Arnoud
    Sunday, February 10, 2008 9:59 PM
  • Hi all,

    Thanks for this thread... This slider does almost exactly what I want.

    But with this implementation, cliking outside, maintain pressed and move over the thumb will also be detected as "thumb need to follow the mouse move".

    To avoid this side effect, I just write the code above...

    public partial class AudioSlider : System.Windows.Controls.Slider 
      { 
        public AudioSlider() 
        { 
          InitializeComponent(); 
        } 
        #region override Member 
     
        public override void OnApplyTemplate() 
        { 
          base.OnApplyTemplate(); 
     
          if (m_thumb != null
          { 
            m_thumb.MouseEnter -= thumb_MouseEnter; 
            m_thumb.LostMouseCapture -= thumb_LostMouseCapture; 
          } 
     
          m_thumb = (GetTemplateChild("PART_Track"as Track).Thumb; 
     
          if (m_thumb != null
          { 
            m_thumb.MouseEnter += thumb_MouseEnter; 
            m_thumb.LostMouseCapture += thumb_LostMouseCapture; 
          } 
     
        } 
     
        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) 
        { 
          base.OnPreviewMouseLeftButtonDown(e); 
          m_isDownOnSlider = true
        } 
        #endregion 
        #region Private handler 
     
        private void thumb_MouseEnter(object sender, MouseEventArgs e) 
        { 
          if (e.LeftButton == MouseButtonState.Pressed && m_isDownOnSlider) 
          { 
            // the left button is pressed on mouse enter 
            // so the thumb must have been moved under the mouse 
            // in response to a click on the track. 
            // Generate a MouseLeftButtonDown event. 
            MouseButtonEventArgs args = new MouseButtonEventArgs( 
                e.MouseDevice, e.Timestamp, MouseButton.Left); 
            args.RoutedEvent = MouseLeftButtonDownEvent; 
            (sender as Thumb).RaiseEvent(args); 
          } 
        } 
        private void thumb_LostMouseCapture(object sender, EventArgs e) 
        { 
          m_isDownOnSlider = false
        } 
        #endregion 
        
        #region Private Members 
     
        private Thumb m_thumb = null
        private bool m_isDownOnSlider = false
        #endregion 


    Hope this can help somebody...

    Thanks again for all stuff.

    Regards

    Florian



    • Edited by Florian Brulhart Wednesday, September 17, 2008 8:04 AM I forgot to override the OnPreviewMouseLeftButtonDown for setting the isdownonslider member
    • Proposed as answer by Florian Brulhart Wednesday, December 10, 2008 2:24 PM
    Friday, September 12, 2008 8:44 AM
  • I also ran into this problem when developing a touch screen application.  Dr. WPF's solution doesn't work well if you have a very skinny Thumb and have snap to ticks enabled with relatively sparse ticks.  For example, say you have a horizontal Slider with Ticks placed 100 pixels apart and a Thumb with a 1 pixel width.  If you click a few pixels to the right of a Tick and drag right, the thumb will not follow the mouse.

    To make it work in that case, I just do this:

     public class TouchSlider : Slider
     {
      public TouchSlider()
      {
       MouseMove += OnMouseMove;
      }
    
      private void OnMouseMove(object sender, MouseEventArgs e)
      {
       if (e.LeftButton != MouseButtonState.Pressed) return;
    
       var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left) { RoutedEvent = MouseLeftButtonDownEvent };
       base.OnPreviewMouseLeftButtonDown(args);
      }
    }

    This isn't as elegant as Dr. WPF's solution because it banks on the fact that the Slider class internally does the SnapToTick call in its PreviewMouseLeftButtonDown handler, which I discovered via .NET Reflector.

    I really wish WPF had a built-in suite of Controls designed entirely for touch screens and the imprecision/parallax issues that entails, especially on big touch screens.  I feel like I have to re-invent the wheel with this stuff.

    • Proposed as answer by Ben Schoepke Monday, January 10, 2011 6:02 PM
    Monday, January 10, 2011 4:34 PM