I have a scrollviewer with an itemscontrol, and inside of the itemscontrol are buttons. Here is the 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. 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). 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
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?
CodePudding user response:
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.
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.