【问题标题】:XAML Slider inside ScrollViewerScrollViewer 中的 XAML 滑块
【发布时间】:2016-11-22 21:50:58
【问题描述】:

对于 Windows 10 UWP 应用,我有这样的 XAML 结构:

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

我想创造这样的用户体验:

  1. 当用户开始水平滑动手势时,触摸下的滑块应接收输入并开始更改其值,而垂直滚动完全禁用(即使用户继续画圈动作)

  2. 当用户开始垂直滑动手势时,scrollviewer 应接收输入并开始垂直滚动,而触摸下的滑块应保持不变(即使用户继续绘制圆圈动作)。

    李>

是否可以在纯 XAML 中实现此行为?我想我已经尝试了与滚动相关的所有可能的属性组合......没有运气。有人知道吗?

【问题讨论】:

  • 很有趣,但是当唯一可用的交互是 X 滑动(rs)和 Y 滚动时,为什么用户要进行圆形绘制动作?
  • 用户没有理由做圆周运动,重要的是一旦初始交互开始(垂直滚动或滑块更改),无论用户用手指画什么,都不应切换到不同的行为。
  • 我会在滑块中查看 Thumb 的焦点以禁用滚动,但如果不做一些修补,我无法立即为您提供答案。抱歉,朋友,希望在此期间能比我更多地在 UWP 中玩的人出现。 +1 无论哪种方式。

标签: xaml slider uwp scrollviewer


【解决方案1】:

在10586版本的手机模拟器上测试,发现ScrollViewer垂直滚动时,即使画圈也不会影响里面的Slider,而Slider水平滑动时,只有当它的值达到最大值时,如果我画圆圈,ScrollViewer 的垂直滚动才会生效。

是否可以在纯 XAML 中实现此行为?

是的,有可能。

当用户开始水平滑动手势时,触摸下的滑块应接收输入并开始更改其值,而垂直滚动完全禁用(即使用户继续画圈动作)

您可以在您的项目中安装Microsoft.Xaml.Behaviors.Uwp.Managed 包,然后使用它的DataTriggerBehavior,例如:

<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>

在 xaml 中使用此包时,您需要在标头中声明它,例如:

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

正如您在我的代码中看到的,我比较了SliderFocusState 属性,如果其值为Unfocused,则启用ScrollViewer 的垂直滚动模式。所以当用户与这个布局交互时,在Slider上滑动后,需要先点击空白部分使Slider失去焦点,然后才能启用垂直滚动模式。

当用户开始垂直滑动手势时,scrollviewer 应接收输入并开始垂直滚动,而触摸下的滑块应保持不变(即使用户继续绘制圆圈运动)。

根据我的测试,我认为默认情况下可以确保此手势,因此我没有为此编写代码。如果不在您身边,请发表评论并提供您的设备类型和操作系统版本,以便我进行测试。

【讨论】:

  • 谢谢 Grace,我已经在 Lumia1520 10.0.10586.494 上尝试过你的代码,但是它并没有解决问题:如果我从滑块开始垂直滚动我没有运气 - 滑块抓住了我所有的手势并且不会发生垂直滚动。在您的示例代码中,您有 &lt;StackPanel Height="1300"&gt;,它在 2 个滑块周围提供了大量空白空间,可以直接与滚动查看器交互。在实际应用中根本没有这样的空白。所有区域都充满了滑块,这就是需要滚动查看器的原因。目标是以某种方式猜测初始滑动方向并采取相应行动。
  • 我已经有了一个相当丑陋的实现:它“预览”滑动的初始 20 像素,然后决定它是水平的还是垂直的。如果垂直 - 连续恢复初始滑块值并使用 scrollviewer 手动垂直滚动。如果水平 - 当滑块获得完全控制时,什么也不做。如果存在一些纯 XAML 解决方案,那就太好了……
  • @lenin,在我的代码中,我确保只有当滑块失去焦点时,滚动查看器才能垂直滚动。因此,如果滚动查看器内没有空白空间使滑块失去焦点,我不知道滚动查看器如何获得操作手势,除非您禁用某些滑块,所有手势都将由滑块处理。有趣,我不知道。
【解决方案2】:

我有一个非常相似的问题,并且能够使用以下自定义控件解决它。这是一个 CommandSlider,它还允许您在滑动完成后发送按钮之类的命令(不是您需要的),但 ScrollViewer 操作代码也在那里。看看这是否对您的情况有所帮助。它允许您按照您的要求在完整的 XAML 中完成所有工作。

注意:滑块必须有ManipulationMode="TranslateX" or "TranslateY",这也取决于方向。

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));
}

自定义扩展方法

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);
        }        
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-13
    • 2017-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多