调整代码
This commit is contained in:
147
Melskin/Controls/TutorialPrompt.xaml.cs
Normal file
147
Melskin/Controls/TutorialPrompt.xaml.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Melskin.Controls
|
||||
{
|
||||
public class TutorialGuide : Control
|
||||
{
|
||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(TutorialGuide), new PropertyMetadata("Tutorial"));
|
||||
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(TutorialGuide), new PropertyMetadata(string.Empty));
|
||||
public string Description { get => (string)GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty StepTextProperty = DependencyProperty.Register(nameof(StepText), typeof(string), typeof(TutorialGuide), new PropertyMetadata("1 / 1"));
|
||||
public string StepText { get => (string)GetValue(StepTextProperty); set => SetValue(StepTextProperty, value); }
|
||||
|
||||
// 路由事件
|
||||
public static readonly RoutedEvent NextEvent = EventManager.RegisterRoutedEvent(nameof(Next), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TutorialGuide));
|
||||
public event RoutedEventHandler Next { add => AddHandler(NextEvent, value); remove => RemoveHandler(NextEvent, value); }
|
||||
|
||||
public static readonly RoutedEvent BackEvent = EventManager.RegisterRoutedEvent(nameof(Back), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TutorialGuide));
|
||||
public event RoutedEventHandler Back { add => AddHandler(BackEvent, value); remove => RemoveHandler(BackEvent, value); }
|
||||
|
||||
public static readonly RoutedEvent CloseEvent = EventManager.RegisterRoutedEvent(nameof(Close), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TutorialGuide));
|
||||
public event RoutedEventHandler Close { add => AddHandler(CloseEvent, value); remove => RemoveHandler(CloseEvent, value); }
|
||||
public static readonly DependencyProperty PlacementProperty =
|
||||
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(TutorialGuide), new PropertyMetadata(PlacementMode.Top));
|
||||
|
||||
public PlacementMode Placement
|
||||
{
|
||||
get => (PlacementMode)GetValue(PlacementProperty);
|
||||
set => SetValue(PlacementProperty, value);
|
||||
}
|
||||
static TutorialGuide()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TutorialGuide), new FrameworkPropertyMetadata(typeof(TutorialGuide)));
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
if (GetTemplateChild("PART_NextBtn") is Button next) next.Click += (s, e) => RaiseEvent(new RoutedEventArgs(NextEvent));
|
||||
if (GetTemplateChild("PART_BackBtn") is Button back) back.Click += (s, e) => RaiseEvent(new RoutedEventArgs(BackEvent));
|
||||
if (GetTemplateChild("PART_CloseBtn") is Button close) close.Click += (s, e) => RaiseEvent(new RoutedEventArgs(CloseEvent));
|
||||
}
|
||||
}
|
||||
public class TutorialHighlighter : Control
|
||||
{
|
||||
static TutorialHighlighter()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TutorialHighlighter), new FrameworkPropertyMetadata(typeof(TutorialHighlighter)));
|
||||
}
|
||||
|
||||
// 目标控件属性
|
||||
public static readonly DependencyProperty TargetElementProperty =
|
||||
DependencyProperty.Register("TargetElement", typeof(FrameworkElement), typeof(TutorialHighlighter), new PropertyMetadata(null, OnTargetChanged));
|
||||
|
||||
public FrameworkElement TargetElement
|
||||
{
|
||||
get => (FrameworkElement)GetValue(TargetElementProperty);
|
||||
set => SetValue(TargetElementProperty, value);
|
||||
}
|
||||
|
||||
private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is TutorialHighlighter helper) helper.UpdatePosition();
|
||||
}
|
||||
|
||||
public void UpdatePosition()
|
||||
{
|
||||
if (TargetElement == null) return;
|
||||
|
||||
// 获取 TutorialLayer (也就是当前的父容器 Canvas)
|
||||
var parentCanvas = VisualTreeHelper.GetParent(this) as UIElement;
|
||||
if (parentCanvas == null) return;
|
||||
|
||||
// 关键修改:相对于 parentCanvas 计算坐标,而不是相对于 Window
|
||||
Point location = TargetElement.TranslatePoint(new Point(0, 0), parentCanvas);
|
||||
|
||||
// 加上偏移量(让边框比目标稍微大一点,包围住目标)
|
||||
double offset = 4;
|
||||
Canvas.SetLeft(this, location.X - offset);
|
||||
Canvas.SetTop(this, location.Y - offset);
|
||||
|
||||
this.Width = TargetElement.ActualWidth + (offset * 2);
|
||||
this.Height = TargetElement.ActualHeight + (offset * 2);
|
||||
}
|
||||
}
|
||||
public class TutorialLayer : Canvas
|
||||
{
|
||||
private TutorialHighlighter _highlighter;
|
||||
private TutorialGuide _guide;
|
||||
|
||||
public TutorialLayer()
|
||||
{
|
||||
// 初始化两个组件
|
||||
_highlighter = new TutorialHighlighter();
|
||||
_guide = new TutorialGuide();
|
||||
|
||||
this.Children.Add(_highlighter);
|
||||
this.Children.Add(_guide);
|
||||
|
||||
// 初始隐藏
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public void ShowStep(FrameworkElement target, string title, string content, string step)
|
||||
{
|
||||
this.Visibility = Visibility.Visible;
|
||||
|
||||
// 1. 更新文字
|
||||
_guide.Title = title;
|
||||
_guide.Description = content;
|
||||
_guide.StepText = step;
|
||||
|
||||
// 2. 强制刷新布局(确保目标控件的 ActualWidth/Height 已计算)
|
||||
target.UpdateLayout();
|
||||
|
||||
// 3. 定位高亮框 (Highlighter)
|
||||
_highlighter.TargetElement = target;
|
||||
_highlighter.UpdatePosition();
|
||||
|
||||
// 4. 定位提示框 (Guide)
|
||||
// 获取高亮框在当前 Canvas 里的位置
|
||||
double hLeft = Canvas.GetLeft(_highlighter);
|
||||
double hTop = Canvas.GetTop(_highlighter);
|
||||
double hWidth = _highlighter.Width;
|
||||
double hHeight = _highlighter.Height;
|
||||
|
||||
// 默认逻辑:放在目标右侧。如果右侧放不下,放在左侧。
|
||||
double guideLeft = hLeft + hWidth + 10;
|
||||
double guideTop = hTop;
|
||||
|
||||
if (guideLeft + 350 > this.ActualWidth) // 350 是 TutorialGuide 的宽度
|
||||
{
|
||||
guideLeft = hLeft - 350 - 10; // 放左边
|
||||
}
|
||||
|
||||
Canvas.SetLeft(_guide, guideLeft);
|
||||
Canvas.SetTop(_guide, guideTop);
|
||||
}
|
||||
public void Close() => this.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user