增强 WPF 实现描点导航

360影视 欧美动漫 2025-04-26 08:42 2

摘要:将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实现。











x:Name="PART_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. 使用控件ItemTemplate="{StaticResource SectionTemplate}"
ItemsSource="{Binding Sections}" />

5. 新增NavScrollPanelExample.xamlx:Class="WpfNavPanel.MainWindow"
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">



Margin="0,10"
FontSize="20"
Text="{Binding Title}" />
Padding="20"
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

相关推荐