2016-07-19 38 views
2

对于Windows 10 UWP应用程序,我有这样的XAML结构:XAML滑块内部的ScrollViewer

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

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

  1. 当用户开始横向轻扫手势时,触摸滑块下应接收输入,并开始改变其值,而垂直滚动完全禁用(即使当用户继续绘制圆运动)

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

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

+0

有趣,但为什么用户做圆形运动平局时唯一可用的相互作用X滑阀(RS)和Y滚动? –

+0

用户没有理由做出圆形运动,重要的是一旦初始交互已经开始(垂直滚动或滑块改变),不应该切换到不同的行为,而不管用户用手指画什么。 – lenin

+0

我会在滑块中观看Thumb的焦点以禁用滚动,但我没有立即回答您,也没有做一些修补。对不起,我希望有人能在UWP比赛中获得比我更多的机会。 +1方式。 –

回答

0

与操作系统版本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失去焦点,然后可以启用垂直滚动模式。

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

基于我的测试,我认为这个手势是默认保证的,所以我没有为此编码。如果不在您身边,请留下评论并提供您的设备类型和操作系统版本,以便我可以进行测试。

+0

谢谢Grace,我已经在Lumia1520 10.0.10586.494上试过了你的代码,但是它没有解决问题:如果我从滑块开始垂直滚动,我没有运气 - 滑块抓住我的所有手势,并且没有发生垂直滚动。在你的示例代码中,你有''这样可以在两个滑块周围留出很多空间来与scrollviewer直接交互。在实际应用中根本没有这样的空白空间。所有区域都充满滑块,这就是为什么需要scrollviewer。目标是以某种方式猜测初始滑动方向并相应采取行动。 – lenin

+0

我已经有一个相当丑陋的实现:它'预览'滑动的初始20px,然后决定它是水平还是垂直。如果是垂直的 - 连续恢复初始滑块值并用scrollviewer手动垂直滚动。如果水平 - 当滑块完全控制时不做任何事情。如果存在一些纯粹的XAML解决方案,那将是非常好的... – lenin

+0

@lenin,在我的代码中,我确保只有在滑块失去焦点时,scrollviewer才能垂直滚动。因此,如果scrollviewer中没有空白区域使滑块失去焦点,我不知道滚动查看器如何获取操作手势,除非您禁用某些滑块,否则所有手势都将由滑块处理。有趣的是,我不知道。 –

0

我有一个非常类似的问题,并能够解决它与下面的自定义控件。这是一个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); 
     }   
    } 
}