WPF|快速添加新手引導(dǎo)功能(支持MVVM)

閱讀導(dǎo)航

  1. 前言
  • 案例一
  • 案例二
  • 案例三(本文介紹的方式)
  1. 如何使用王滤?
  2. 控件如何開發(fā)的逻杖?
  3. 總結(jié)

1. 前言

案例一

站長(zhǎng)分享過 眾尋 大佬的一篇 WPF 簡(jiǎn)易新手引導(dǎo) 一文竭鞍,新手引導(dǎo)的效果挺不錯(cuò)的编振,如下圖:

5001.gif

該文給出的代碼未使用 MVVM 的開發(fā)方式宜狐,提示框使用的用戶控件肛搬、蒙版窗體樣式與后臺(tái)代碼未分離饭豹,但給大家分享了開發(fā)新手引導(dǎo)功能的一個(gè)參考鸵赖。

案例二

開源項(xiàng)目 AIStudio.Wpf.Controls,它的新手引導(dǎo)效果如下:

5202.gif

此開源項(xiàng)目也有參考上文(WPF 簡(jiǎn)易新手引導(dǎo))拄衰,并且重構(gòu)為 MVVM 版本它褪,方便綁定使用。

并且提示框顯示的位置還跟隨目標(biāo)控件在主窗體中的位置靈活變換翘悉,不至于顯示在蒙版窗體之外茫打,如下圖所示:

當(dāng)目標(biāo)控件右側(cè)空間足夠顯示引導(dǎo)提示框時(shí),引導(dǎo)提示框就顯示在目標(biāo)控件右側(cè)妖混;在右側(cè)空間不足時(shí)老赤,則將引導(dǎo)提示框顯示在目標(biāo)控件左側(cè):

5203.png

案例三(本文介紹的方式)

站長(zhǎng)根據(jù)上面的開源項(xiàng)目 AIStudio.Wpf.Controls 做了一個(gè)自己的版本 Dotnet9WPFControls,去掉了上一步按鈕制市、增加標(biāo)題綁定抬旺、下一步按鈕內(nèi)容綁定、提示框樣式修改等祥楣,效果如下:

5201.gif

后面段落就介紹 怎么使用 Dotnet9WPFControls 添加新手引導(dǎo)功能开财,并簡(jiǎn)單提及這個(gè)自定義控件的開發(fā)細(xì)節(jié),主要原理還是看上文 WPF 簡(jiǎn)易新手引導(dǎo) 哈荣堰。

希望對(duì)有需要給自己的項(xiàng)目添加新手引導(dǎo)功能的朋友有一定幫助床未,通過此文你也能修改出滿足自己需求的效果。

2. 如何使用振坚?

2.1 創(chuàng)建一個(gè)WPF項(xiàng)目

使用 .NET 6|7 創(chuàng)建一個(gè)名為 "NewbieGuideDemo" 的 WPF 解決方案:

5204.png

2.2 引入nuget包

  • 添加Nuget包1: Dotnet9WPFControls

該包提供引導(dǎo)控件及其樣式薇搁,記得勾選“包括預(yù)發(fā)行版”,然后點(diǎn)擊安裝渡八。

5205.png
  • 添加Nuget包2:Prism.DryIoc

使用該包啃洋,主要是使用 Prism 封裝的一些 MVVMIOC 功能屎鳍,方便協(xié)助開發(fā)宏娄。

添加上述兩個(gè)Nuget包后,項(xiàng)目工程文件定義如下:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Dotnet9WPFControls" Version="0.1.0-preview.2" />
    <PackageReference Include="Prism.DryIoc" Version="8.1.97" />
  </ItemGroup>

</Project>

2.3 添加樣式文件

打開 App.xaml 文件逮壁,引入 Dotnet9WPFControls 默認(rèn)主題文件:

<prism:PrismApplication
    x:Class="NewbieGuideDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="http://prismlibrary.com/">
    <prism:PrismApplication.Resources>

        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/Dotnet9WPFControls;component/Themes/Dotnet9WPFControls.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </prism:PrismApplication.Resources>
</prism:PrismApplication>

注意上面的根節(jié)點(diǎn) <prism:PrismApplication />孵坚,同時(shí)修改App.xaml.cs文件,這里不做過多說明,具體使用請(qǐng)參考 Prism

using Prism.DryIoc;
using Prism.Ioc;
using System.Windows;

namespace NewbieGuideDemo
{
    public partial class App : PrismApplication
    {
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }

        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
    }
}

2.4 定義引導(dǎo)信息

給主窗體 MainWindow 添加一個(gè) ViewModel 類:MainWindowViewModel.cs

using Dotnet9WPFControls.Controls;
using Prism.Mvvm;
using System.Collections.Generic;

namespace NewbieGuideDemo
{
    public class MainWindowViewModel : BindableBase
    {
        private GuideInfo? _guide;

        public GuideInfo Guide =>
            _guide ??= new GuideInfo("快速添加新手引導(dǎo)", "這樣添加新手引導(dǎo)卖宠,或許比較優(yōu)雅");

        public List<GuideInfo> Guides => new() {Guide};
    }
}

在上面的 ViewModel 中巍杈,定義了一個(gè)引導(dǎo)屬性 Guide,這個(gè)屬性是與提示框綁定展示:

5206.png
  • 第一個(gè)參數(shù)定義了引導(dǎo)提示框的標(biāo)題 “快速添加新手引導(dǎo)”
  • 第二個(gè)參數(shù)定義了引導(dǎo)提示框的提示內(nèi)容 “這樣添加新手引導(dǎo)扛伍,或許比較優(yōu)雅”

第二個(gè)屬性 Guides, 是一個(gè)引導(dǎo)信息列表筷畦,可綁定多個(gè)引導(dǎo)信息,點(diǎn)擊按鈕即會(huì)查看下一個(gè)引導(dǎo)刺洒,本示例為了演示鳖宾,只寫了一個(gè)引導(dǎo)。

2.5 界面綁定引導(dǎo)信息

先貼上 MainWindow.xaml 所有代碼:

<Window
    x:Class="NewbieGuideDemo.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:prism="http://prismlibrary.com/"
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:dotnet9="https://dotnet9.com"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="Dotnet9 WPF新手引導(dǎo)功能" Width="800" Height="450"
    prism:ViewModelLocator.AutoWireViewModel="True"
    AllowsTransparency="True" Background="Transparent" WindowStyle="None" 
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Window.Resources>
        <dotnet9:BindControlToGuideConverter x:Key="BindControlToGuideConverter" />
    </Window.Resources>
    <Border
        Background="White" BorderBrush="#ccc" BorderThickness="1" MouseLeftButtonDown="Border_MouseDown">
        <Grid>
            <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="點(diǎn)擊測(cè)試新手引導(dǎo)">
                <dotnet9:GuideHelper.GuideInfo>
                    <MultiBinding Converter="{StaticResource BindControlToGuideConverter}">
                        <Binding RelativeSource="{RelativeSource Self}" />
                        <Binding Path="Guide" />
                    </MultiBinding>
                </dotnet9:GuideHelper.GuideInfo>
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <i:ChangePropertyAction PropertyName="Display" TargetName="GuideControl" Value="True" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

            <dotnet9:GuideControl x:Name="GuideControl" Guides="{Binding Guides}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Loaded">
                        <i:ChangePropertyAction PropertyName="Display" Value="True" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </dotnet9:GuideControl>
        </Grid>
    </Border>
</Window>

下面快速過一遍逆航。

2.5.1 引入的命名空間說明

看上面的代碼鼎文,引入了 dotnet9prismi三個(gè)命名空間:

  • dotnet9 命名空間

引入引導(dǎo)控件 GuideControl 及 轉(zhuǎn)換器 BindControlToGuideConverter纸泡。

  • prism 命名空間

主要用途在 prism:ViewModelLocator.AutoWireViewModel="True" 這句代碼漂问,將視圖 MainWindow.xamlMainWindowViewModel.cs進(jìn)行綁定,有興趣可以看 Prism 源碼女揭,了解視圖是如何發(fā)現(xiàn)ViewModel的約定規(guī)則。

  • i 命名空間

主要用此命名空間下的觸發(fā)器栏饮,事件觸發(fā)屬性更改吧兔。

2.5.2 幾處關(guān)鍵代碼簡(jiǎn)單說明

上面代碼貼的是引導(dǎo)控件(自定義控件)的使用方式(站長(zhǎng)注Dotnet9WPFControls 中還有引導(dǎo)窗體的方式,本文不做說明袍嬉,要不然太占篇幅了境蔼,請(qǐng)查看控件Demo GuideWindowView)。

a: 將引導(dǎo)控件加到容器最上層

先關(guān)注后面的幾行代碼:

<Grid>
    <!--這里省略業(yè)務(wù)控件布局-->
    <dotnet9:GuideControl x:Name="GuideControl" Guides="{Binding Guides}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Loaded">
                <i:ChangePropertyAction PropertyName="Display" Value="True" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </dotnet9:GuideControl>
</Grid>
  • 將引導(dǎo)控件加到 Grid 容器最后伺通,意圖是讓引導(dǎo)控件顯示在所有控件的最上層(同一層級(jí)添加了多個(gè)控件箍土,如果位置重疊,那么后加入的控件會(huì)顯示在先添加的控件上方罐监,呈現(xiàn)遮擋效果)吴藻;
  • 綁定了前面 MainWindowViewModel 中定義的引導(dǎo)信息列表 Guides,點(diǎn)擊下一步按鈕(本文顯示為我知道了)時(shí)弓柱,會(huì)按列表添加順序切換引導(dǎo)信息沟堡;
  • 使用 i:Interaction.Triggers實(shí)現(xiàn)控件加載完成時(shí),自動(dòng)顯示引導(dǎo)提示信息矢空,見上面的 示例三效果航罗;

b:綁定目標(biāo)控件與引導(dǎo)屬性

目標(biāo)控件的引導(dǎo)屬性與目標(biāo)控件引用綁定,引導(dǎo)界面顯示時(shí)通過目標(biāo)控件計(jì)算出目標(biāo)控件的位置和大小屁药,準(zhǔn)確將目標(biāo)控件標(biāo)識(shí)出來粥血,引導(dǎo)提示框定位也才能正確設(shè)置:

<dotnet9:BindControlToGuideConverter x:Key="BindControlToGuideConverter" />
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="點(diǎn)擊測(cè)試新手引導(dǎo)">
    <dotnet9:GuideHelper.GuideInfo>
        <MultiBinding Converter="{StaticResource BindControlToGuideConverter}">
            <Binding RelativeSource="{RelativeSource Self}" />
            <Binding Path="Guide" />
        </MultiBinding>
    </dotnet9:GuideHelper.GuideInfo>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <i:ChangePropertyAction PropertyName="Display" TargetName="GuideControl" Value="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

如上代碼引入 BindControlToGuideConverter 轉(zhuǎn)換器, 該轉(zhuǎn)換器是個(gè)黏合類,將目標(biāo)控件的引用添加到引導(dǎo)對(duì)象上,轉(zhuǎn)換器具體定義如下:

public class BindControlToGuideConverter : IMultiValueConverter
{
    public object? Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length < 2)
        {
            return null;
        }

        var element = values[0] as FrameworkElement;
        var guide = values[1] as GuideInfo;
        if (guide != null)
        {
            guide.TargetControl = element;
        }

        return guide;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

目標(biāo)控件的引用賦值給引導(dǎo)對(duì)象的 TargetControl 屬性复亏。

Demo代碼完畢绢彤,直接運(yùn)行項(xiàng)目,效果如下蜓耻,源碼在這 NewbieGuideDemo

5207.gif

3. 控件如何開發(fā)的茫舶?

關(guān)于原理,WPF 簡(jiǎn)易新手引導(dǎo) 這篇介紹的不錯(cuò)刹淌,可以先看看饶氏。

關(guān)于本示例的實(shí)現(xiàn)方式,暫時(shí)不做太多說明有勾,詳細(xì)請(qǐng)直接查看源碼 Dotnet9WPFControls疹启,本文后半截大概提一下。

代碼組織結(jié)構(gòu)如下:

5208.png
  • GuideInfo:定義引導(dǎo)信息類蔼卡,如標(biāo)題喊崖、內(nèi)容、下一步按鈕顯示內(nèi)容雇逞。
  • GuideHintControl:引導(dǎo)提示框控件荤懂,顯示引導(dǎo)標(biāo)題、引導(dǎo)內(nèi)容塘砸、下一步按鈕节仿,即 GuideInfo 綁定的控件。
  • GuideControl:引導(dǎo)控件掉蔬,用于目標(biāo)控件無法獲取到自己的窗體這種(即無法獲取在窗體中的位置)廊宪,比如您開發(fā)的程序?yàn)榈谌匠绦虿寮@種,上面的代碼即是使用此引導(dǎo)控件實(shí)現(xiàn)的效果女轿。
  • GuideWindow:引導(dǎo)窗體箭启,GuideControl 引導(dǎo)控件的相互補(bǔ)充。
  • GuideControlBase:引導(dǎo)控件輔助類
  • BindControlToGuideConverter:引導(dǎo)信息與引導(dǎo)的目標(biāo)控件綁定轉(zhuǎn)換器
  • GuideHelper:引導(dǎo)幫助類蛉迹,綁定目標(biāo)控件的引導(dǎo)信息使用傅寡,外加一個(gè)顯示 引導(dǎo)窗體 的靜態(tài)命令。
  • Guide.xaml:定義引導(dǎo)遮罩層(GuideControlGuideWindow)婿禽、引導(dǎo)提示框(GuideHintControl)樣式的資源文件赏僧,定義外觀請(qǐng)改這個(gè)文件

重點(diǎn):

a) GuideControlBase

GuideControlBaseGuideControlGuideWindow 的輔助類,因?yàn)檫@兩個(gè)類實(shí)現(xiàn)的功能是類似的扭倾,所以封裝大部分功能在 GuideControlBase 中淀零,比如將目標(biāo)控件區(qū)域從遮罩層 Clip 出來,并將 GuideHintControl 提示框控件添加到遮罩層之上膛壹,顯示出新手引導(dǎo)的效果驾中。

b) GuideControl 和 GuideWindow

GuideControl 是用于顯示在包含目標(biāo)控件的容器內(nèi)使用的唉堪,GuideControl放置的容器不一定是目標(biāo)控件的直接容器,可以有嵌套肩民,比如目標(biāo)控件在ListBox子項(xiàng)ListBoxItem內(nèi)唠亚,而引導(dǎo)控件GuideControl可以在ListBox的外層容器之上;

GuideWindow 用于貼在目標(biāo)控件所在的窗體上持痰,GuideWindow 作為目標(biāo)控件窗體的子窗體灶搜,Show()在目標(biāo)控件窗體上,不能使用ShowDialog()的方式(為啥工窍?ShowDialog()會(huì)使除引導(dǎo)窗體之外的窗體處于無效狀態(tài)(disable))割卖。

這兩種方式(GuideControl 和 GuideWindow)總體呈現(xiàn)效果是一樣的,目標(biāo)控件所在的窗體是自定義窗體患雏,Demo能正常顯示下面的效果鹏溯,普通窗體需要對(duì)目標(biāo)控件 Clip 的位置和提示框的位置進(jìn)行偏移處理,修改位置見 GuideControlGuideWindow的方法 ShowGuide(FrameworkElement? targetControl, GuideInfo guide)淹仑。

控件帶的兩個(gè)新手引導(dǎo)Demo如下:

新手引導(dǎo)Demo一

GuideControl方式丙挽,站長(zhǎng)推薦,即以控件的方式顯示新手引導(dǎo)匀借,點(diǎn)擊看代碼

5209.gif

新手引導(dǎo)Demo二

GuideWindow方式颜阐,即以子窗體的方式顯示新手引導(dǎo),點(diǎn)擊看代碼

5210.gif

詳細(xì)開發(fā)不展開說了怀吻,一切都在代碼中瞬浓。

4. 總結(jié)

前面寫了不少,其實(shí)不多蓬坡,謝謝開源帶來的力量。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兆龙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敲董,更是在濱河造成了極大的恐慌紫皇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腋寨,死亡現(xiàn)場(chǎng)離奇詭異聪铺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萄窜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門铃剔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撒桨,“玉大人,你說我怎么就攤上這事键兜》锢啵” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵普气,是天一觀的道長(zhǎng)谜疤。 經(jīng)常有香客問我,道長(zhǎng)现诀,這世上最難降的妖魔是什么夷磕? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮赶盔,結(jié)果婚禮上企锌,老公的妹妹穿的比我還像新娘。我一直安慰自己于未,他們只是感情好撕攒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烘浦,像睡著了一般抖坪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闷叉,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天擦俐,我揣著相機(jī)與錄音,去河邊找鬼握侧。 笑死蚯瞧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的品擎。 我是一名探鬼主播埋合,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萄传!你這毒婦竟也來了甚颂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤秀菱,失蹤者是張志新(化名)和其女友劉穎振诬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衍菱,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赶么,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梦碗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禽绪。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓖救,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出印屁,到底是詐尸還是另有隱情循捺,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布雄人,位于F島的核電站从橘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏础钠。R本人自食惡果不足惜恰力,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旗吁。 院中可真熱鬧踩萎,春花似錦、人聲如沸很钓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽码倦。三九已至企孩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袁稽,已是汗流浹背勿璃。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留推汽,地道東北人补疑。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像歹撒,于是被迫代替她去往敵國和親癣丧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容