UWP ListView 滚动条在触摸模式下不可选
UWP ListView scroll bar not selectable in touch mode
我在通用 Windows 应用程序中有一个简单的 ListView
。它在支持触摸的设备和我的本地机器上都可以很好地滚动。在触摸设备上,我希望能够点击并拖动滚动条拇指以便快速滚动列表(与使用鼠标单击并拖动滚动拇指相同)。但是,当我尝试 select 滚动条拇指时,它不起作用;拇指不能select。
起初我以为滚动条太小了,所以我摆弄了ScrollBar
的默认样式来增加宽度。然后我尝试调整默认 ScrollViewer
样式中的其他值,例如 ScrollingIndicatorMode
和 ScrollingIndicatorStates
,以便它们始终使用 MouseIndicator
,并确保所有 IsHitTestVisible
是 True
。无果。
似乎默认样式中一定有某些允许这样做的东西,但我无法通过反复试验找到它,而且 MSDN 文档中似乎没有任何地方可以解决这种样式。
这在触摸模式下可行吗?
Is this doable in touch mode?
据我所知,目前这是不可能的。 ScrollBar
的滚动事件只能通过滚动内容或通过鼠标输入移动Thumb
来触发。
这是设计使然,但在触摸设备上如果我们想快速滚动列表,我们只需快速滑动一个项目一小段距离,ListView
会先加速滚动然后放慢速度,最后停下来。其他包含ScrollViewer
的控件表现相同,在视口(ScrollViewer
的内容)在触摸设备中,移动拇指不起作用,为了加快滚动速度,你只能快速滑动视口上的手势。
我们的建议是您可以提交请求以通过 Windows 反馈工具添加此新功能进行开发。
为了 post 诚实的缘故,我想 post 我的工作实际上并没有我想象的那么困难,现在看起来真的很好。
基本思路是使用一个垂直方向的 Slider
控件(有一个 Thumb
可以 select 可以拖动,可以沿着轨道移动)并将 Slider
的 Value
属性(拖动时会发生变化)同步到 ListView
的 ScrollIntoView
方法,这样您就可以滚动 ListView
因为 Slider
Thumb
被拖动。反之亦然(当 ListView
正常滚动时移动 Slider
Thumb
)也是干净、无缝的体验所必需的。我在下面有一些代码示例可以帮助您入门(并非所有内容都可以立即使用)。此外,编辑 Slider
的默认 Style
模板,使其看起来完全像一个滚动条(甚至添加工具提示以在拖动时显示当前值)。
注意:我在代码隐藏中使用 WinRTXamlToolkit 轻松遍历 VisualTree
。
后面的代码:
public sealed partial class MainPage : Page
{
private bool _isScroll = false;
private bool _isSlide = false;
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.ValueChanged += LetterSliderValueChanged;
DataContext = vm;
}
/// <summary>
/// Bring list items into view on the screen based on the value (letter) of the slider
/// </summary>
/// <param name="sender">The view model to bring into view</param>
private void LetterSliderValueChanged(object sender, RoutedEventArgs e)
{
if (_isScroll) return;
if (sender == null) return;
_isSlide = true;
ListView?.ScrollIntoView(sender, ScrollIntoViewAlignment.Leading);
}
/// <summary>
/// Update the position of the slider when the ListView is scrolling from a normal touch
/// </summary>
private void ScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_isSlide)
{
_isSlide = false;
return;
}
_isScroll = true;
var scrollViewer = sender as ScrollViewer;
var scrollBars = scrollViewer.GetDescendantsOfType<ScrollBar>();
var verticalBar = scrollBars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
// Normalize the scales to move the slider thumb in sync with scrolling
var sliderTotal = LetterSlider.Maximum - LetterSlider.Minimum;
var barTotal = verticalBar.Maximum - verticalBar.Minimum;
var barPercent = verticalBar.Value / barTotal;
LetterSlider.Value = (barPercent * sliderTotal) + LetterSlider.Minimum;
_isScroll = false;
}
/// <summary>
/// Add the slider method to the ListView's ScrollViewer Viewchanged event
/// </summary>
private void ListViewLoaded(object sender, RoutedEventArgs e)
{
var listview = sender as ListView;
if (listview == null) return;
var scrollViewer = listview.GetFirstDescendantOfType<ScrollViewer>();
scrollViewer.ViewChanged -= ScrollViewerViewChanged;
scrollViewer.ViewChanged += ScrollViewerViewChanged;
}
}
XAML:
<Slider x:Name="LetterSlider" Orientation="Vertical"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="{Binding MaxSliderLetter, Mode=OneTime}"
Minimum="{Binding MinSliderLetter, Mode=OneTime}"
RenderTransformOrigin="0.5,0.5">
<Slider.RenderTransform>
<RotateTransform Angle="180"/>
</Slider.RenderTransform>
</Slider>
查看模型:
//Method to update the ListView to show items based on the letter of the slider
public RoutedEventHandler ValueChanged;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
NotifyPropertyChanged("SliderValue");
char letter = (char)_sliderValue;
var items = ItemsGroup.FirstOrDefault(i => (char)i.Key == letter);
if (items == null) return;
ValueChanged(items.FirstOrDefault(), new RoutedEventArgs());
}
}
我在通用 Windows 应用程序中有一个简单的 ListView
。它在支持触摸的设备和我的本地机器上都可以很好地滚动。在触摸设备上,我希望能够点击并拖动滚动条拇指以便快速滚动列表(与使用鼠标单击并拖动滚动拇指相同)。但是,当我尝试 select 滚动条拇指时,它不起作用;拇指不能select。
起初我以为滚动条太小了,所以我摆弄了ScrollBar
的默认样式来增加宽度。然后我尝试调整默认 ScrollViewer
样式中的其他值,例如 ScrollingIndicatorMode
和 ScrollingIndicatorStates
,以便它们始终使用 MouseIndicator
,并确保所有 IsHitTestVisible
是 True
。无果。
似乎默认样式中一定有某些允许这样做的东西,但我无法通过反复试验找到它,而且 MSDN 文档中似乎没有任何地方可以解决这种样式。
这在触摸模式下可行吗?
Is this doable in touch mode?
据我所知,目前这是不可能的。 ScrollBar
的滚动事件只能通过滚动内容或通过鼠标输入移动Thumb
来触发。
这是设计使然,但在触摸设备上如果我们想快速滚动列表,我们只需快速滑动一个项目一小段距离,ListView
会先加速滚动然后放慢速度,最后停下来。其他包含ScrollViewer
的控件表现相同,在视口(ScrollViewer
的内容)在触摸设备中,移动拇指不起作用,为了加快滚动速度,你只能快速滑动视口上的手势。
我们的建议是您可以提交请求以通过 Windows 反馈工具添加此新功能进行开发。
为了 post 诚实的缘故,我想 post 我的工作实际上并没有我想象的那么困难,现在看起来真的很好。
基本思路是使用一个垂直方向的 Slider
控件(有一个 Thumb
可以 select 可以拖动,可以沿着轨道移动)并将 Slider
的 Value
属性(拖动时会发生变化)同步到 ListView
的 ScrollIntoView
方法,这样您就可以滚动 ListView
因为 Slider
Thumb
被拖动。反之亦然(当 ListView
正常滚动时移动 Slider
Thumb
)也是干净、无缝的体验所必需的。我在下面有一些代码示例可以帮助您入门(并非所有内容都可以立即使用)。此外,编辑 Slider
的默认 Style
模板,使其看起来完全像一个滚动条(甚至添加工具提示以在拖动时显示当前值)。
注意:我在代码隐藏中使用 WinRTXamlToolkit 轻松遍历 VisualTree
。
后面的代码:
public sealed partial class MainPage : Page
{
private bool _isScroll = false;
private bool _isSlide = false;
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.ValueChanged += LetterSliderValueChanged;
DataContext = vm;
}
/// <summary>
/// Bring list items into view on the screen based on the value (letter) of the slider
/// </summary>
/// <param name="sender">The view model to bring into view</param>
private void LetterSliderValueChanged(object sender, RoutedEventArgs e)
{
if (_isScroll) return;
if (sender == null) return;
_isSlide = true;
ListView?.ScrollIntoView(sender, ScrollIntoViewAlignment.Leading);
}
/// <summary>
/// Update the position of the slider when the ListView is scrolling from a normal touch
/// </summary>
private void ScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_isSlide)
{
_isSlide = false;
return;
}
_isScroll = true;
var scrollViewer = sender as ScrollViewer;
var scrollBars = scrollViewer.GetDescendantsOfType<ScrollBar>();
var verticalBar = scrollBars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
// Normalize the scales to move the slider thumb in sync with scrolling
var sliderTotal = LetterSlider.Maximum - LetterSlider.Minimum;
var barTotal = verticalBar.Maximum - verticalBar.Minimum;
var barPercent = verticalBar.Value / barTotal;
LetterSlider.Value = (barPercent * sliderTotal) + LetterSlider.Minimum;
_isScroll = false;
}
/// <summary>
/// Add the slider method to the ListView's ScrollViewer Viewchanged event
/// </summary>
private void ListViewLoaded(object sender, RoutedEventArgs e)
{
var listview = sender as ListView;
if (listview == null) return;
var scrollViewer = listview.GetFirstDescendantOfType<ScrollViewer>();
scrollViewer.ViewChanged -= ScrollViewerViewChanged;
scrollViewer.ViewChanged += ScrollViewerViewChanged;
}
}
XAML:
<Slider x:Name="LetterSlider" Orientation="Vertical"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="{Binding MaxSliderLetter, Mode=OneTime}"
Minimum="{Binding MinSliderLetter, Mode=OneTime}"
RenderTransformOrigin="0.5,0.5">
<Slider.RenderTransform>
<RotateTransform Angle="180"/>
</Slider.RenderTransform>
</Slider>
查看模型:
//Method to update the ListView to show items based on the letter of the slider
public RoutedEventHandler ValueChanged;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
NotifyPropertyChanged("SliderValue");
char letter = (char)_sliderValue;
var items = ItemsGroup.FirstOrDefault(i => (char)i.Key == letter);
if (items == null) return;
ValueChanged(items.FirstOrDefault(), new RoutedEventArgs());
}
}