2
votes

For Windows 10 UWP app I have such XAML structure:

<ScrollViewer>
    <StackPanel>
        <Slider />
        <Slider />
        ...
    </StackPanel>
</ScrollViewer>

I would like to create such user experience:

  1. When the user begins horizontal swipe gesture, the slider under the touch should receive the input and start changing its value, while vertical scrolling is completely disabled (even when the user continue draw circle motions)

  2. When the user begins vertical swipe gesture, the scrollviewer should receive the input and start scrolling vertically, while sliders under the touch should stay intact (even when the user continue draw circle motions).

Is it possible to implement this behavior in pure XAML? I think I have tried all possible combinations of properties related to scroll... No luck. Any idea anybody?

2
Interesting, but why would user be making circular draw motions when the only interactions available are X slide(rs) and Y scroll? - Chris W.
No reason for user to make circular motions, it is important that once initial interaction has started (vertical scrolling or slider changing) there should be no switches to different behavior no matter what user draw with fingers. - lenin
I would watch the focus of the Thumb in the slider to disable scrolling, but I have no immediate answer for you without doing some tinkering too. Sorry amigo, hopefully someone who gets to play in UWP more than I get to comes along in the meantime. +1 either way. - Chris W.

2 Answers

0
votes

After testing on mobile emulator with OS version 10586, I found that when the ScrollViewer is vertically scrolled, it won't effect the Slider inside even if I draw circle, and when the Slider is swiped horizontally, only when its value reaches the max value, the vertical scrolling of the ScrollViewer will be effected if I draw circle.

Is it possible to implement this behavior in pure XAML?

Yes, it is possible.

When the user begins horizontal swipe gesture, the slider under the touch should receive the input and start changing its value, while vertical scrolling is completely disabled (even when the user continue draw circle motions)

You can install the Microsoft.Xaml.Behaviors.Uwp.Managed package in your project, then use its DataTriggerBehavior for example like this:

<ScrollViewer x:Name="scrollViewer" HorizontalScrollMode="Disabled" VerticalScrollMode="Auto">
    <Interactivity:Interaction.Behaviors>
        <Core:DataTriggerBehavior Binding="{Binding ElementName=slider1,Path=FocusState}" ComparisonCondition="NotEqual" Value="Unfocused">
            <Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Disabled" />
        </Core:DataTriggerBehavior>
        <Core:DataTriggerBehavior Binding="{Binding ElementName=slider1,Path=FocusState}" ComparisonCondition="Equal"  Value="Unfocused">
            <Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Auto" />
        </Core:DataTriggerBehavior>
        <Core:DataTriggerBehavior Binding="{Binding ElementName=slider2,Path=FocusState}" ComparisonCondition="NotEqual" Value="Unfocused">
            <Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Disabled" />
        </Core:DataTriggerBehavior>
        <Core:DataTriggerBehavior Binding="{Binding ElementName=slider2,Path=FocusState}" ComparisonCondition="Equal"  Value="Unfocused">
            <Core:ChangePropertyAction PropertyName="VerticalScrollMode" Value="Auto" />
        </Core:DataTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
    <StackPanel Height="1300">
        <Slider Margin="0,200" x:Name="slider1" />
        <Slider x:Name="slider2" />
    </StackPanel>
</ScrollViewer>

When using this package in xaml, you will need to declare it in the header for example like this:

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"

As you can see in my code, I compared FocusState property of the Slider, if its value is Unfocused, then the vertical scroll mode of the ScrollViewer is enabled. So when user interact with this layout, after swiping on the Slider, he will need to click on the blank part to make the Slider lost focus first, then the vertical scroll mode can be enabled.

When the user begins vertical swipe gesture, the scrollviewer should receive the input and start scrolling vertically, while sliders under the touch should stay intact (even when the user continue draw circle motions).

Base on my test, I think this gesture is ensured by default, so I didn't code for this. If it is not by your side, please leave a comment and provide your device type and OS version so can I have a test.

0
votes

I have a very similar issue and was able to resolve it with the following custom control. This is a CommandSlider that also allows you to send commands like a button after the sliding is complete (not what you need) but the ScrollViewer manipulation code is in there also. See if this helps your situation. It allows you to do all the work in complete XAML like you've requested.

NOTE: The slider must have ManipulationMode="TranslateX" or "TranslateY"depending on the orientation for this to work also.

public sealed class CommandSlider : Slider
{
    public CommandSlider()
    {
        IsThumbToolTipEnabled = false;
        PointerCaptureLost += (s, e) => (Command?.CanExecute(CommandParameter)).GetValueOrDefault().Switch(() => Command?.Execute(CommandParameter));
        Loaded += (s, e) => ParentScrollViewer = this.GetParent<ScrollViewer>();
    }

    private ScrollViewer ParentScrollViewer { get; set; }

    protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
    {
        base.OnManipulationDelta(e);

        if (ParentScrollViewer != null)
        {                
            var scrollX = Orientation == Orientation.Vertical
                        ? e.Position.X * -1 + ParentScrollViewer.HorizontalOffset
                        : ParentScrollViewer.HorizontalOffset;

            var scrollY = Orientation == Orientation.Horizontal
                        ? e.Position.Y * -1 + ParentScrollViewer.VerticalOffset
                        : ParentScrollViewer.VerticalOffset;

            var zoom = ParentScrollViewer.ZoomFactor;

            ParentScrollViewer.ChangeView(scrollX, scrollY, zoom);
        }
    }

    public object CommandParameter
    {
        get => GetValue(CommandParameterProperty);
        set => SetValue(CommandParameterProperty, value);
    }

    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(CommandSlider), new PropertyMetadata(null));

    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(CommandSlider), new PropertyMetadata(null));

    public double SourceValue
    {
        get => (double)GetValue(SourceValueProperty);
        set => SetValue(SourceValueProperty, value);
    }

    public static readonly DependencyProperty SourceValueProperty =
        DependencyProperty.Register(nameof(SourceValue), typeof(double), typeof(CommandSlider), new PropertyMetadata(0d,
            (s, e) => (s as CommandSlider).Value = (double)e.NewValue));
}

Custom Extension Method

public static class XAMLExtensions
{
    public static T GetParent<T>(this DependencyObject dependencyObject) where T : DependencyObject
    {
        var parentDependencyObject = VisualTreeHelper.GetParent(dependencyObject);

        switch (parentDependencyObject)
        {
            case null:
                return null;
            case T parent:
                return parent;
            default:
                return GetParent<T>(parentDependencyObject);
        }        
    }
}