Slider \ ScrollViewer in a touch interface not working properly
Asked Answered
E

4

9

In WPF I've got the following XAML:

<ScrollViewer Canvas.Left="2266" Canvas.Top="428" Height="378" Name="scrollViewer1" Width="728" PanningMode="VerticalOnly" PanningRatio="2">
    <Canvas Height="1732.593" Width="507.667">
        <Slider Height="40.668" x:Name="slider1" Width="507.667" Style="{DynamicResource SliderStyle1}" Canvas.Left="15" Canvas.Top="150" />
        </Slider>
    </Canvas>
</ScrollViewer>

It's a ScrollViewer containing a Slider. I'm using the following on a touch-screen, and I'm using the panning even to scroll the ScrollViewer vertically. When PanningMode="VerticalOnly" is set, the slider stops working!

I'm assuming the ScollViewer is consuming the touch\slide event and handling it before the slider does (but I think I'm wrong on this front).

Is there any workaround for this?

Erased answered 3/12, 2011 at 16:22 Comment(0)
U
14

I just solved this issue in our app.

What is happening is that the ScrollViewer captures the TouchDevice in its PreviewTouchMove handler, which "steals" the TouchDevice from other controls and prevents them from receiving any PreviewTouchMove or TouchMove events.

In order to work around this, you need to implement a custom Thumb control that captures the TouchDevice in the PreviewTouchDown event and stores a reference to it until the PreviewTouchUp event occurs. Then the control can "steal" the capture back in its LostTouchCapture handler, when appropriate. Here is some brief code:

public class CustomThumb : Thumb
{
    private TouchDevice currentDevice = null;

    protected override void OnPreviewTouchDown(TouchEventArgs e)
    {
        // Release any previous capture
        ReleaseCurrentDevice();
        // Capture the new touch
        CaptureCurrentDevice(e);
    }

    protected override void OnPreviewTouchUp(TouchEventArgs e)
    {
        ReleaseCurrentDevice();
    }

    protected override void OnLostTouchCapture(TouchEventArgs e)
    {
        // Only re-capture if the reference is not null
        // This way we avoid re-capturing after calling ReleaseCurrentDevice()
        if (currentDevice != null)
        {
            CaptureCurrentDevice(e);
        }
    }

    private void ReleaseCurrentDevice()
    {
        if (currentDevice != null)
        {
            // Set the reference to null so that we don't re-capture in the OnLostTouchCapture() method
            var temp = currentDevice;
            currentDevice = null;
            ReleaseTouchCapture(temp);
        }
    }

    private void CaptureCurrentDevice(TouchEventArgs e)
    {
        bool gotTouch = CaptureTouch(e.TouchDevice);
        if (gotTouch)
        {
            currentDevice = e.TouchDevice;
        }
    }
}

Then you will need to re-template the Slider to use the CustomThumb instead of the default Thumb control.

Unchurch answered 20/1, 2012 at 19:11 Comment(3)
I haven't tested this approach yet, but it looks valid enough. I will choose it as the answer. The way I solved all my issues was by using the Microsoft Surface 2.0 SDK microsoft.com/download/en/details.aspx?id=26716 and used the ScrollViewer from their library (which handles all the problems above).Erased
This solution worked for me. It was very convenient as I had already re-templated the Slider to use a custom Thumb with a shape/size more suitable to touch.Elodea
The provided approach works like a charm! I faced the problem while removing the (for me deprecated) Microsoft Surface 2.0 SDK.Tilda
G
2

i strugled with a similar issue. the workaround was this one (none of the others worked for me): i created a custom thumb, and then i used it inside a scrollbar style in xaml as the PART_Track's thumb.

public class DragableThumb : Thumb
{
    double m_originalOffset;
    double m_originalDistance;
    int m_touchID;

    /// <summary>
    /// Get the parent scrollviewer, if any
    /// </summary>
    /// <returns>Scroll viewer or null</returns>
    ScrollViewer GetScrollViewer()
    {
        if (TemplatedParent is ScrollBar && ((ScrollBar)TemplatedParent).TemplatedParent is ScrollViewer)
        {
            return ((ScrollViewer)((ScrollBar)TemplatedParent).TemplatedParent);
        }

        return null;
    }

    /// <summary>
    /// Begin thumb drag
    /// </summary>
    /// <param name="e">Event arguments</param>
    protected override void OnTouchDown(TouchEventArgs e)
    {
        ScrollViewer scrollViewer;

        base.OnTouchDown(e);

        m_touchID = e.TouchDevice.Id;

        if ((scrollViewer = GetScrollViewer()) != null)
        {
            m_originalOffset = scrollViewer.HorizontalOffset;
            m_originalDistance = e.GetTouchPoint(scrollViewer).Position.X;
        }
    }

    /// <summary>
    /// Handle thumb delta
    /// </summary>
    /// <param name="e">Event arguments</param>
    protected override void OnTouchMove(TouchEventArgs e)
    {
        ScrollViewer scrollViewer;
        double actualDistance;

        base.OnTouchMove(e);

        if ((scrollViewer = GetScrollViewer()) != null && m_touchID == e.TouchDevice.Id)
        {
            actualDistance = e.GetTouchPoint(scrollViewer).Position.X;
            scrollViewer.ScrollToHorizontalOffset(m_originalOffset + (actualDistance - m_originalDistance) * scrollViewer.ExtentWidth / scrollViewer.ActualWidth);
        }
    }
}
Gruff answered 7/8, 2012 at 14:12 Comment(0)
D
1

The following worked for me. I searched around for a long time for something that would work. I adapted this for touch from How to make WPF Slider Thumb follow cursor from any point. This is a much simpler fix and allows you to avoid creating a custom slider/thumb control.

 <Slider TouchMove="OnTouchMove" IsMoveToPointEnabled="True"/>

IsMoveToPointEnable must be set to true for this to work.

 private void Slider_OnTouchMove(object sender, TouchEventArgs e)
 {
    Slider slider = (Slider)sender;        
    TouchPoint point = e.GetTouchPoint (slider );
    double d = 1.0 / slider.ActualWidth * point.Position.X;
    int p = int(slider.Maximum * d);
    slider.Value = p;
 }
Dowd answered 4/12, 2014 at 16:59 Comment(0)
E
0

This is nice and simple and worked for me - although it's worth wrapping up in a generic function and extending to handle the slider minimum value also as it may not be zero. What a pain to have to do though. There are many thing about WPF that are cool, but so many simple things require extra steps it really can be detrimental to productivity.

Esemplastic answered 17/2, 2015 at 12:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.