1. 程式人生 > 其它 >WPF實現新手提示功能

WPF實現新手提示功能

一、概要

本篇文章分享一個新手介面提示的案例,我們經常會在各種app中會遇到不斷讓你點下一步引導你使用客戶端的提示,根據不同的引數配置顯示不同提示氣泡的樣式。這裡就分享一下在WPF中如何去實現,我們先看下面的效果。

文章中只出現了部分關鍵程式碼全部程式碼在,原始碼地址在Github上。

二、思路

通過上圖顯示的內容我做了以下分析:

開始之前我們先明白幾個概念,不然思路不清楚後面的事情很難做。

  • 目標控制元件- 指的是我們需要解釋提示的控制元件
  • 氣泡- 具體的提示內容,同時支援下一步
  • 線- 將氣泡和目標控制元件連線起來,達到視覺輔助
  • 位置、樣式- 通過簡單演算法計算出目標控制元件和提示氣泡的位置並用線連線起來,且初始化線和氣泡的樣式。

1.半透明灰的遮罩層

<Window
     x:Name="gw"
     Title="GuideWin"
     AllowsTransparency="True"
     Background="#01FFFFFF"
     ShowInTaskbar="False"
     WindowStyle="None"
     mc:Ignorable="d">
    <Grid>
        <Border
        x:Name="border"
        BorderBrush="White"
        BorderThickness="2"
        CornerRadius="5"
        Opacity="0.8">
        <Border.Effect>
            <DropShadowEffect
                BlurRadius="8"
                ShadowDepth="0"
                Color="#FF414141" />
        </Border.Effect>
        <Border
            Margin="0"
            Background="Black"
            CornerRadius="5"
            Opacity="0.5" />
        </Border>
        <Canvas x:Name="canvas" />
    </Grid>
 </Window>

這裡會定義一個window將它的樣式設定透明等樣式便於覆蓋到其他窗體上,遮罩層使用的是border控制元件,為什麼選擇它因為有兩個特性。

  • 第一個是因為它支援圓角和方角可以適應不同的窗體樣式。
  • 第二個它支援陰影效果。

2.提示氣泡

<UserControl
        d:DesignHeight="450"
        d:DesignWidth="800"
        Background="Transparent"
        mc:Ignorable="d">
    <Viewbox   MouseLeftButtonDown="Viewbox_MouseLeftButtonDown">
    <Grid>
        <Border
            x:Name="RootBrd"
            Background="Transparent"
            BorderThickness="1">
            <TextBlock
                x:Name="ContentTxb"
                Margin="3"
                Background="Transparent" />
        </Border>
     </Grid>
     </Viewbox>
</UserControl>

這裡會定義一個UserControl,這個UserControl是用來當做氣泡顯示具體內容的,也方便"嵌入"到窗體中使用。後面如果需要一整套提示流程時建立一個集合裝好這些一步步初始化好的提示氣泡的樣式和內容即可。

3.線和氣泡出現的位置和樣式

public void StartGuide(GuideModel guide)
    {
        var focusElemt = guide.UserControl;
        var point = focusElemt.TransformToAncestor(Window.GetWindow(focusElemt)).Transform(new Point(0, 0));
        var rectangleGeometry = new RectangleGeometry();
        rectangleGeometry.Rect = new Rect(0, 0, this.Width, this.Height);
        borderGeometry = Geometry.Combine(borderGeometry, rectangleGeometry, GeometryCombineMode.Union, null);
        border.Clip = borderGeometry;
        var rectangleGeometry1 = new RectangleGeometry();
        rectangleGeometry1.Rect = new Rect(point.X - 5, point.Y - 5, focusElemt.ActualWidth + 10, focusElemt.ActualHeight + 10);
        borderGeometry = Geometry.Combine(borderGeometry, rectangleGeometry1, GeometryCombineMode.Exclude, null);
        border.Clip = borderGeometry;
        InitHintControl(guide, point, focusElemt.ActualWidth, focusElemt.ActualHeight, guide.IsNear);
        currentStep++;
    }

畫線的思路:線有兩個端點,我在這裡稱為線的起始點和線的終點,線的起始點出現的位置通常是“目標控制元件”的高的二分之一處,所以通過TransformToAncestor方法拿到控制元件座標之後從左上角的0,0點的位置在帶入控制元件的寬高計算出線起始點的座標。

/// <summary>
    /// 計算相對位置
    /// </summary>
    /// <param name="startPoint">已知起始點座標</param>
    /// <param name="angle">相對位置角度</param>
    /// <param name="bevel">相對距離</param>
    /// <returns></returns>
    private Point CalculateRelativePoint(Point startPoint, int angle, double bevel)
    {
        var radian = angle * Math.PI / 180;
        var xMargin = Math.Cos(radian) * bevel;
        var yMargin = Math.Sin(radian) * bevel;
        return new Point(startPoint.X + xMargin, startPoint.Y + yMargin);
    }

氣泡出現的位置實現思路:1.知道起始位置,2.知道相對位置的角度,3.知道相對距離,4.計算出相對位置

公式為:

  • 相對半徑 =(角度 * π / 180);
  • 相對位置X軸座標 = Cos(相對半徑)* 相對距離;
  • 相對位置Y軸座標 = Sin(相對半徑)* 相對距離;

最終得出X和Y的值,從而確認到氣泡的初始位置和線的結束位置。

/// <summary>
    /// 初始化氣泡內容及位置
    /// </summary>
    /// <param name="guide"></param>
    /// <param name="point"></param>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <param name="isNear"></param>
    private void InitHintControl(GuideModel guide, Point point, double width, double height, bool isNear)
    {
        hintWin = new Hint(guide);
        hintWin.NextHintEvent -= OnNextHintEvent;
        hintWin.NextHintEvent += OnNextHintEvent;
        canvas.Children.Add(hintWin);
        var startPoint = new Point { X = point.X + width + 5, Y = point.Y + height / 2 };
        var relativePoint = CalculateRelativePoint(startPoint, 45 + guide.Angle, 100);
        if (isNear)
        {
            var nearRelativePoint = CalculateRelativePoint(point, 45 + guide.Angle, 1);
            Canvas.SetLeft(hintWin, nearRelativePoint.X);
            Canvas.SetTop(hintWin, nearRelativePoint.Y);
        }
        else
        {
            Canvas.SetLeft(hintWin, relativePoint.X - 5);
            Canvas.SetTop(hintWin, relativePoint.Y - hintWin.Height / 2);
        }

        if (guide.Direct == Direct.Visible)
        {
            InitPath(startPoint, relativePoint, 3, guide.PathColor, guide.BulgeStyle);
        }
    }

    /// <summary>
    /// 初始化連線氣泡的線樣式及位置
    /// </summary>
    /// <param name="startPoint"></param>
    /// <param name="relativePoint"></param>
    /// <param name="pathThickness"></param>
    /// <param name="pathColor"></param>
    /// <param name="bulgeStyle"></param>
    private void InitPath(Point startPoint, Point relativePoint,
        int pathThickness = 1, string pathColor = "#000000", BulgeStyle bulgeStyle = BulgeStyle.Dotted)
    {
        var path = new Path();
        var pathGeometry = new PathGeometry();
        var pathFigure = new PathFigure();
        pathFigure.StartPoint = startPoint;
        var segmentCollection = new PathSegmentCollection();
        segmentCollection.Add(new LineSegment() { Point = relativePoint });
        pathFigure.Segments = segmentCollection;
        pathGeometry.Figures = new PathFigureCollection() { pathFigure };
        path.Data = pathGeometry;
        path.Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString(pathColor));
        if (bulgeStyle == BulgeStyle.Dotted)
        {
            path.StrokeDashArray = new DoubleCollection { 2, 4 };
        }
        path.StrokeThickness = pathThickness;
        canvas.Children.Add(path);
    }

4.下一步功能

下一步這個功能大致的思路就是,不斷的從“步驟集合”中取出下一個元素。根據對應的model初始化好下一步提示氣泡裡的內容即可,當走到最後一個元素時通常會是最後一步那麼直接關閉掉“遮罩層窗體”即可。

/// <summary>
    /// 下一步具體實現
    /// </summary>
    private void OnNextHintEvent()
    {
        if (_guideModels.Count == 0) return;
        var beforGuide = _guideModels[currentStep - 1];
        if (_guideModels.Count == currentStep)
        {
            if (beforGuide.Callback != null)
            {
                beforGuide.Callback.Invoke();
            }
            else
            {
                EndStep();
            }
            return;
        }
        var currentGuide = _guideModels[currentStep];
        if (beforGuide != null)
        {
            canvas.Children.Clear();
            if (beforGuide.Callback != null)
            {
                beforGuide.Callback.Invoke();
            }
        }
        if (currentGuide != null)
        {
            StartGuide(currentGuide);
        }
    }
釋出於剛剛