摘要:将ScrollToVerticalOffset更改为WD中WDScrollViewer的AnimateScroll。将TranslatePoint更改为TransformToAncestor来计算相对于_scrollViewer的位置。
增强 WPF 实现描点导航
控件名:NavScrollPanel
作 者:WPFDevelopersOrg - 驚鏵
原文链接[1]
:https://github.com/WPFDevelopersOrg/WPFDevelopers码云链接[2]
:https://gitee.com/WPFDevelopersOrg/WPFDevelopers框架支持.NET4 至 .NET8;
Visual Studio 2022;
基于上一篇新增平滑滚动动画。
将 ScrollToVerticalOffset更改为WD中WDScrollViewer的AnimateScroll。
将 TranslatePoint更改为TransformToAncestor来计算相对于_scrollViewer的位置。
public classNavScrollPanel:Control{
staticNavScrollPanel
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavScrollPanel),
new FrameworkPropertyMetadata(typeof(NavScrollPanel)));
}
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
publicstaticreadonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(NavScrollPanel), new PropertyMetadata(, OnItemsSourceChanged));
public DataTemplate ItemTemplate
{
get => (DataTemplate)GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
publicstaticreadonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(NavScrollPanel), new PropertyMetadata);
publicint SelectedIndex
{
get => (int)GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
publicstaticreadonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register(nameof(SelectedIndex), typeof(int), typeof(NavScrollPanel), new PropertyMetadata(-1, OnSelectedIndexChanged));
private ListBox _navListBox;
private WDScrollViewer _scrollViewer;
private StackPanel _contentPanel;
public override voidOnApplyTemplate
{
base.OnApplyTemplate;
_navListBox = GetTemplateChild("PART_ListBox") as ListBox;
_scrollViewer = GetTemplateChild("PART_ScrollViewer") as WDScrollViewer;
_contentPanel = GetTemplateChild("PART_ContentPanel") as StackPanel;
if (_navListBox != )
{
_navListBox.DisplayMemberPath = "Title";
_navListBox.SelectionChanged -= NavListBox_SelectionChanged;
_navListBox.SelectionChanged += NavListBox_SelectionChanged;
}
if (_scrollViewer != )
{
_scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
}
RenderContent;
}
private voidNavListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedIndex = _navListBox.SelectedIndex;
}
private voidScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
double currentOffset = _scrollViewer.VerticalOffset;
double viewportHeight = _scrollViewer.ViewportHeight;
for (int i = 0; i < _contentPanel.Children.Count; i++)
{
var element = _contentPanel.Children[i] as FrameworkElement;
if (element == ) continue;
Point relativePoint = element.TransformToAncestor(_scrollViewer).Transform(new Point(0, 0));
if (relativePoint.Y >= 0 && relativePoint.Y < viewportHeight)
{
if (_navListBox.SelectedIndex != i)
{
_navListBox.SelectedIndex = i;
}
break;
}
}
}
private static voidOnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NavScrollPanel control)
{
control.RenderContent;
}
}
private static voidOnSelectedIndexChanged
{
{
int index = (int)e.NewValue;
if (control._contentPanel != &&
index >= 0 && index < control._contentPanel.Children.Count)
{
var target = control._contentPanel.Children[index] as FrameworkElement;
if (target != )
{
var virtualPoint = target.TranslatePoint(new Point(0, 0), control._contentPanel);
control._scrollViewer.AnimateScroll(virtualPoint.Y);
}
}
}
}
private voidRenderContent
{
if (_contentPanel == || ItemsSource == || ItemTemplate == )
return;
_contentPanel.Children.Clear;
foreach (var item in ItemsSource)
{
var content = new ContentControl
{
Content = item,
ContentTemplate = ItemTemplate,
Margin = new Thickness(10)
};
_contentPanel.Children.Add(content);
}
}
}
控件模板通过 ListBox和ScrollViewer实现。
Grid.Column="1"
IsScrollAnimation="True"
VerticalScrollBarVisibility="Auto">
IsScrollAnimation是否启用滚动动画。
IsScrollAnimation设置为true时,注册ScrollChanged事件。捕捉滚动的位置,进行平滑滚动。
OnMouseWheel鼠标滚轮事件,启用了IsScrollAnimation属性时,平滑动画来完成滚动。
AnimateScroll方法实现平滑滚动的。通过DoubleAnimation来控制滚动的位置变化。
public classWDScrollViewer:ScrollViewer{
privatestaticdouble _lastLocation;
publicstaticreadonly DependencyProperty IsScrollAnimationProperty = DependencyProperty.Register("IsScrollAnimation", typeof(bool), typeof(WDScrollViewer), new PropertyMetadata(false, OnIsScrollAnimationChanged));
publicbool IsScrollAnimation
{
get
{
return (bool)GetValue(IsScrollAnimationProperty);
}
set
{
SetValue(IsScrollAnimationProperty, value);
}
}
private static voidOnIsScrollAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WDScrollViewer wDScrollViewer)
{
if (wDScrollViewer.IsScrollAnimation)
{
wDScrollViewer.ScrollChanged -= OnScrollChanged;
wDScrollViewer.ScrollChanged += OnScrollChanged;
}
else
{
}
}
}
private static voidOnScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (sender is WDScrollViewer wDScrollViewer && e.VerticalChange != 0.0)
{
_lastLocation = wDScrollViewer.VerticalOffset;
}
}
protected override voidOnMouseWheel(MouseWheelEventArgs e)
{
if (!IsScrollAnimation)
{
base.OnMouseWheel(e);
return;
}
int delta = e.Delta;
double num = _lastLocation - (double)(delta * 2);
ScrollToVerticalOffset(_lastLocation);
if (num < 0.0)
{
num = 0.0;
}
if (num > base.ScrollableHeight)
{
num = base.ScrollableHeight;
}
AnimateScroll(num);
_lastLocation = num;
e.Handled =true;
}
public voidAnimateScroll(double toValue)
{
BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, );
DoubleAnimation doubleAnimation = new DoubleAnimation
{
EasingFunction = new CubicEase
{
EasingMode = EasingMode.EaseOut
},
From = base.VerticalOffset,
To = toValue,
Duration = TimeSpan.FromMilliseconds(800.0)
};
Timeline.SetDesiredFrameRate(doubleAnimation, 40);
BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, doubleAnimation);
}
}
1. 定义数据结构public class SectionItem{
public string Title { get; set; }
public object Content { get; set; }
}
2. 初始化数据并绑定Sections = new ObservableCollection
{
new SectionItem{ Title = "播放相关", Content = new PlaybackSettings},
new SectionItem{ Title = "桌面歌词", Content = new DesktopLyrics},
new SectionItem{ Title = "快捷键", Content = new ShortcutKeys},
new SectionItem{ Title = "隐私设置", Content = new PrivacySettings},
new SectionItem{ Title = "关于我们", Content = new About},
};
DataContext = this;
3. 模板定义
4. 使用控件
ItemsSource="{Binding Sections}" />
5. 新增NavScrollPanelExample.xaml
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfNavPanel"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
Title="NavScrollPanel - 锚点导航"
Width="800"
Height="450"
mc:Ignorable="d">
FontSize="20"
Text="{Binding Title}" />
Background="#F0F0F0"
CornerRadius="10">
GitHub 源码地址[3]
Gitee 源码地址[4]
[1]
https://github.com/WPFDevelopersOrg/WPFDevelopers[2]
https://gitee.com/WPFDevelopersOrg/WPFDevelopers[3]
GitHub 源码地址:
[4]
Gitee 源码地址:
来源:opendotnet