简体   繁体   English

WPF Scrollviewer:如何允许通过触摸/鼠标滚动并允许内部子项(按钮)触发样式更新

[英]WPF Scrollviewer: How to allow scrolling via touch/mouse and allow inner children (buttons) to trigger style updates

I have a scrollviewer with an itemscontrol, and inside of the itemscontrol are buttons.我有一个带有项目控件的滚动查看器,项目控件内部是按钮。 Here is the xaml:这是 xaml:

` `

    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
        <ItemsControl Style="{DynamicResource FileItemsControlStyle}" ItemsSource="{Binding Files.Files}"
                Padding="4" ManipulationBoundaryFeedback="FileListBox_ManipulationBoundaryFeedback">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Style="{StaticResource ImportsFileBorder}" Margin="0, 2" Height="75" HorizontalAlignment="Stretch">
                    <b:Interaction.Triggers>
                        <b:EventTrigger EventName="PreviewMouseDown">
                            <b:InvokeCommandAction Command="{Binding Path=DataContext.RenderSelectedFileCommand,
                                        RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}"/>
                        </b:EventTrigger>
                    </b:Interaction.Triggers>
                    <DockPanel LastChildFill="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                        <ToggleButton IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" 
                                      PreviewMouseDown="ToggleSliderButton_PreviewMouseDown" Style="{DynamicResource ToggleSliderStyle}"
                                      DockPanel.Dock="Left" Margin="10,15,10,0" />

                        <TextBlock Text="{Binding DisplayFileName}" Width="Auto" VerticalAlignment="Center" FontWeight="Bold" TextTrimming="CharacterEllipsis"
                                   FontSize="{DynamicResource LargeFontSize}" Foreground="Lime"/>
                    </DockPanel>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>

` `

My issue is that if I have PanningMode = "Both" for the scrollviewer I can scroll via my mouse and touch device perfectly fine.我的问题是,如果我为滚动查看器设置 PanningMode = "Both",我可以通过我的鼠标和触摸设备完美滚动。 And if I click a button with my mouse, the style triggers and it highlights.如果我用鼠标单击一个按钮,样式就会触发并突出显示。 However with touch, the buttons are not highlighting (but the buttons commands do fire).但是,通过触摸,按钮不会突出显示(但按钮命令会触发)。 If I turn PanningMode too "None" then the styles trigger on the buttons fine (but I can't scroll obviously).如果我将 PanningMode 设置为太“无”,则按钮上的 styles 触发器正常(但我无法明显滚动)。 I need to be able to scroll through the list of buttons even if my touch event starts on a button but somehow determine if I'm actually just clicking the button or if I'm scrolling.我需要能够滚动按钮列表,即使我的触摸事件是在按钮上开始的,但不知何故确定我是否真的只是点击按钮或者我是否正在滚动。 Ideally if I'm scrolling, the button I started on to begin the scrolling wouldn't highlight.理想情况下,如果我正在滚动,我开始滚动的按钮不会突出显示。

I found another Stackoverflow article here: WPF, ScrollViewer consuming touch before longpress我在这里找到了另一篇 Stackoverflow 文章: WPF,ScrollViewer consuming touch before longpress

and tried implementing the answer from there:并尝试从那里实施答案:

` `

public class ScrollViewerWithTouch : ScrollViewer   
{
      /// <summary>
      /// Original panning mode.
      /// </summary>
      private PanningMode panningMode;

  /// <summary>
  /// Set panning mode only once.
  /// </summary>
  private bool panningModeSet;

  /// <summary>
  /// Initializes static members of the <see cref="ScrollViewerWithTouch"/> class.
  /// </summary>
  static ScrollViewerWithTouch()
  {
     DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollViewerWithTouch), new FrameworkPropertyMetadata(typeof(ScrollViewerWithTouch)));
  }

  protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
  {
     base.OnManipulationCompleted(e);

     // set it back
     this.PanningMode = this.panningMode;
  }

  protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
  {
     // figure out what has the user touched
     var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
     if (result != null && result.VisualHit != null)
     {
        var hasButtonParent = this.HasButtonParent(result.VisualHit);

        // if user touched a button then turn off panning mode, let style bubble down, in other case let it scroll
        this.PanningMode = hasButtonParent ? PanningMode.None : this.panningMode;
     }

     base.OnManipulationStarted(e);
  }

  protected override void OnTouchDown(TouchEventArgs e)
  {
     // store panning mode or set it back to it's original state. OnManipulationCompleted does not do it every time, so we need to set it once more.
     if (this.panningModeSet == false)
     {
        this.panningMode = this.PanningMode;
        this.panningModeSet = true;
     }
     else
     {
        this.PanningMode = this.panningMode;
     }

     base.OnTouchDown(e);         
  }

  private bool HasButtonParent(DependencyObject obj)
  {
     var parent = VisualTreeHelper.GetParent(obj);

     if ((parent != null) && (parent is ButtonBase) == false)
     {
        return HasButtonParent(parent);
     }

     return parent != null;
  }
}

` `

However, this doesn't allow me to start scrolling if my touch starts on the buttons.但是,如果我在按钮上开始触摸,这将不允许我开始滚动。 Is there some way I could modify this too allow me to scroll when needed but then turn panning mode off if I'm just touching/clicking a button and allow the style too trigger?有什么方法可以修改它,允许我在需要时滚动,但如果我只是触摸/单击按钮并允许样式触发,则关闭平移模式?

I ended up modifying the custom scrollviewer and creating a custom button class that inherits from ButtonBase that exposes a SetIsPressable() function to allow me to set the IsPressable on the button that is readonly since my style triggers off of that property.我最终修改了自定义滚动查看器并创建了一个继承自 ButtonBase 的自定义按钮 class,它公开了一个 SetIsPressable() function 以允许我在只读按钮上设置 IsPressable,因为我的样式会触发该属性。

Here is how I modified the scrollviewer:这是我修改滚动查看器的方式:

namespace NoNeedForYouToKnow.UI.Module.Control.TouchScrollViewer
{
    /// <summary>
    /// This scroll viewer is meant to be able to handle touch/mouse scrolling as well as notifying child components that implement
    /// IPressable that their IsPressed property has changed. If you aren't wrapping anything implementing IPressable, then you
    /// probably just need to use a regular scrollviewer.
    /// </summary>
    public class PressableScrollViewer : ScrollViewer
    {
        private IPressable _button;

        /// <summary>
        /// Initializes static members of the <see cref="PressableScrollViewer"/> class.
        /// </summary>
        static PressableScrollViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PressableScrollViewer), new FrameworkPropertyMetadata(typeof(PressableScrollViewer)));
        }

        protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
        {
            _button?.SetIsPressed(false);
            base.OnManipulationCompleted(e);
        }

        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
            var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
            if (result != null && result.VisualHit != null)
            {
                var isOverButton = this.HasParent(result.VisualHit, _button);
                if(!isOverButton)
                    _button.SetIsPressed(false);
                base.OnManipulationDelta(e);
            }
        }

        protected override void OnScrollChanged(ScrollChangedEventArgs e)
        {
            _button?.SetIsPressed(false);
            base.OnScrollChanged(e);
        }

        protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
        {
            // figure out what has the user touched
            var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
            if (result != null && result.VisualHit != null)
            {
                var button = this.HasButtonParent(result.VisualHit);
                if (button != null)
                {
                    _button = button;
                    button.SetIsPressed(true);
                }
            }

            base.OnManipulationStarted(e);
        }

        private PressableButton HasButtonParent(DependencyObject obj)
        {
            var parent = VisualTreeHelper.GetParent(obj);

            if ((parent != null) && (parent is PressableButton) == false)
            {
                return HasButtonParent(parent);
            }

            return (PressableButton) parent;
        }

        private bool HasParent(DependencyObject obj, IPressable pressableObject)
        {
            if(pressableObject == null) return false;
            var parent = VisualTreeHelper.GetParent(obj);

            if (parent == pressableObject || obj == pressableObject) return true;
            else if (parent == null)
                return false;
            else
                return HasParent(parent, pressableObject);
        }
    }
}

It handles toggling the IsPressable property when you start your touch on the button and handles turning to false at the proper times.当您开始触摸按钮时,它会处理切换 IsPressable 属性,并在适当的时候处理变为 false。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM