操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    家好,我是沙漠盡頭的狼。

    .NET是免費(fèi),跨平臺,開源,用于構(gòu)建所有應(yīng)用的開發(fā)人員平臺。

    本文演示如何在WPF中使用Blazor開發(fā)漂亮的UI,為客戶端開發(fā)注入新活力。

    要使WPF支持Blazor,.NET版本必須是 6.0 或更高版本,本文所有示例使用的.NET 7.0,版本要求見鏈接,截圖看如下文字:

    1. WPF默認(rèn)程序

    本文從創(chuàng)建WPF Hello World開發(fā):

    使用WPF模板創(chuàng)建一個默認(rèn)程序,取名【W(wǎng)PFBlazorChat】,項(xiàng)目組織結(jié)構(gòu)如下:

    運(yùn)行項(xiàng)目,一個空白窗口:

    接著往下看,我們添加Blazor支持,本小節(jié)代碼在這WPF默認(rèn)程序源碼。

    2. 添加Blazor支持

    依然使用上面的工程,添加Blazor支持,此部分參考微軟文檔生成 Windows Presentation Foundation (WPF) Blazor 應(yīng)用,本小節(jié)快速略過。

    2.1 編輯工程文件

    雙擊工程文件WPFBlazorChat.csproj,修改處如下:

    1. 在項(xiàng)目文件的頂部,將 SDK 更改為 Microsoft.NET.Sdk.Razor
    2. 添加節(jié)點(diǎn)<RootNameSpace>WPFBlazorChat</RootNameSpace>,將項(xiàng)目命名空間 WPFBlazorChat 設(shè)置為應(yīng)用的根命名空間。
    3. 添加NugetMicrosoft.AspNetCore.Components.WebView.Wpf,版本看你選擇的.NET版本而定。

    2.2 添加_Imports.razor文件

    _Imports.razor文件類似一個Global using文件,專門給Razor組件使用,放置一些用的比較多的全局的命名空間,精簡代碼。

    內(nèi)容如下,引入了一個命名空間Microsoft.AspNetCore.Components.Web,這是Razor常用命名空間,包含用于向 Blazor 框架提供有關(guān)瀏覽器事件的信息的類型。:

    @using Microsoft.AspNetCore.Components.Web

    2.3 添加wwwroot\index.html文件

    VueReact一樣,需要一個html文件承載Razor組件,頁面內(nèi)容類似:

    <!DOCTYPE html><html lang="en"> <head>    <meta charset="utf-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>WPFBlazorChat</title>    <base href="/" />    <link href="css/app.css" rel="stylesheet" />    <link href="WpfBlazor.styles.css" rel="stylesheet" /></head> <body><div id="app">Loading...</div> <div id="blazor-error-ui">    An unhandled error has occurred.    <a href="" class="reload">Reload</a>    <a class="dismiss"></a></div><script src="_framework/blazor.webview.js"></script></body> </html>
    1. app.css文件在下面給出定義。
    2. <div id="app">Loading...</div>,這里是承載Razor組件的地方,后面所有加載的Razor組件都是在這里渲染出來的。
    3. 其他暫時(shí)不管。

    2.4 添加wwwroot\css\app.css文件

    頁面的基本樣式,通用的樣式可放在這個文件:

    html, body {    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;} h1:focus {    outline: none;} a, .btn-link {    color: #0071c1;} .btn-primary {    color: #fff;    background-color: #1b6ec2;    border-color: #1861ac;} .valid.modified:not([type=checkbox]) {    outline: 1px solid #26b050;} .invalid {    outline: 1px solid red;} .validation-message {    color: red;} #blazor-error-ui {    background: lightyellow;    bottom: 0;    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);    display: none;    left: 0;    padding: 0.6rem 1.25rem 0.7rem 1.25rem;    position: fixed;    width: 100%;    z-index: 1000;} #blazor-error-ui .dismiss {    cursor: pointer;    position: absolute;    right: 0.75rem;    top: 0.5rem;}

    2.5 添加一個Razor組件

    加一個Razor的經(jīng)典組件Counter.razorBlazorHello World程序就有這么一個組件,文件路徑:/RazorViews/Counter.razor,之所以放RazorViews目錄,是為了和WPF常用的Views目錄區(qū)分,該組件內(nèi)容如下:

    <h1>Counter</h1> <p>好開心,你點(diǎn)我了,現(xiàn)在是:<span style="color: red;">@currentCount</span></p> <button class="btn btn-primary" @onclick="IncrementCount">快快點(diǎn)我</button> @code {    private int currentCount=0;     private void IncrementCount()    {        currentCount++;    }}

    一個按鈕【快快點(diǎn)我】,點(diǎn)擊@onclick="IncrementCount"使變量currentCount自增,同時(shí)頁面顯示此變量值,相信你能看懂。

    2.6 Blazor與WPF窗體關(guān)聯(lián)

    這是兩者產(chǎn)生關(guān)系的關(guān)鍵一步,打開窗體MainWindow.xaml,修改如下:

    如上代碼,要點(diǎn)如下:

    1. 添加上面引入的NugetMicrosoft.AspNetCore.Components.WebView.Wpf的命名空間,命名為blazor,主要是要使用BlazorWebView組件;
    2. BlazorWebView組件屬性HostPage指定承載的html文件,Services指定razor組件的Ioc容器,看下面MainWindow()里標(biāo)紅的代碼;
    3. RootComponentSelector="#app"屬性指示Razor組件渲染的位置,看index.html中id為app的html元素,ComponentType指示需要在#app中渲染的Razor組件類型。

    打開MainWindow.xaml.cs,修改如下:

    在WPF里可以使用Prism等框架提供的UnityDryIocIoc容器實(shí)現(xiàn)視圖與服務(wù)的注入;Razor組件這里,默認(rèn)使用ASP.NET CoreIServiceCollection容器;如果WPF窗體與Razor組件需要共享數(shù)據(jù),可以通過后面要說的Messager發(fā)送消息,也可以通過Ioc容器注入的方式實(shí)現(xiàn),比如從WPF窗體中注入的數(shù)據(jù)(通過MainWindow構(gòu)造函數(shù)注入),通過IServiceCollection容器再注入Razor組件使用,這里后面也有提到。

    上面步驟做完后,運(yùn)行程序:

    OK,WPFBlazor集成成功,打完收工?

    等等,還沒完呢,本小節(jié)源碼在這WPF中添加Blazor,接著往下看。

    3. 自定義窗體

    看上圖,窗體邊框是WPF默認(rèn)的樣式,有時(shí)會感覺比較丑,或者不丑,設(shè)計(jì)師有其他的窗體風(fēng)格設(shè)計(jì),往往我們要自定義窗體,本節(jié)分享部分WPF與Blazor的自定義窗體實(shí)現(xiàn),更多定制化功能可能需要您自行研究。

    3.1 WPF自定義窗體

    一般實(shí)現(xiàn)是設(shè)置窗體的三個屬性WindowStyle="None" AllowsTransparency="True" Background="Transparent",即可隱藏默認(rèn)窗體的邊框,然后在內(nèi)容區(qū)自己畫標(biāo)題欄、最小化、最大化、關(guān)閉按鈕、客戶區(qū)等。

    MainWindow.xaml:隱藏WPF默認(rèn)窗體邊框

    <Window    x:Class="WPFBlazorChat.MainWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    xmlns:razorViews="clr-namespace:WPFBlazorChat.RazorViews"    Title="MainWindow"    Width="800"    Height="450"    AllowsTransparency="True"    Background="Transparent"    WindowStyle="None"    mc:Ignorable="d">    <Grid>        <blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{DynamicResource services}">            <blazor:BlazorWebView.RootComponents>                <blazor:RootComponent ComponentType="{x:Type razorViews:Counter}" Selector="#app" />            </blazor:BlazorWebView.RootComponents>    </Grid></Window>

    上面的代碼只是隱藏了WPF默認(rèn)窗體的邊框,運(yùn)行程序如下:

    看上圖,點(diǎn)擊窗體中的按鈕(其實(shí)是Razor組件的按鈕),但未執(zhí)行按鈕點(diǎn)擊事件,且窗體消失了,這是怎么回事?您可以嘗試研究下為什么,我沒有研究個所以然來,暫時(shí)加個背景處理BlazorWebView穿透的問題。

    簡單的WPF自定義窗體樣式

    我們加上自定義窗體的基本樣式看看:

    MainWindow.xaml代碼如下:

    <Window    x:Class="WPFBlazorChat.MainWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    xmlns:razorViews="clr-namespace:WPFBlazorChat.RazorViews"    Title="MainWindow"    Width="800"    Height="450"    AllowsTransparency="True" Background="Transparent" WindowStyle="None"    mc:Ignorable="d">    <Window.Resources>        <Style TargetType="{x:Type Button}">            <Setter Property="Width" Value="35" />            <Setter Property="Height" Value="25" />            <Setter Property="Margin" Value="2" />            <Setter Property="Background" Value="Transparent" />            <Setter Property="BorderThickness" Value="0" />            <Setter Property="Foreground" Value="White" />        </Style>    </Window.Resources>    <Border Background="#7160E8" CornerRadius="5">        <Grid>            <Grid.RowDefinitions>                <RowDefinition Height="35" />                <RowDefinition Height="*" />            </Grid.RowDefinitions>            <Border                Background="#7160E8" CornerRadius="5 5 0 0" MouseLeftButtonDown="MoveWindow_MouseLeftButtonDown">                <Grid>                    <TextBlock                        Margin="10,10,5,5"                        Foreground="White"                        Text="這里是窗體標(biāo)題欄,左側(cè)可放Logo、標(biāo)題,右側(cè)放窗體操作按鈕:最小化、最大化、關(guān)閉等" />                    <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">                        <Button Click="MinimizeWindow_Click" Content="―" />                        <Button Click="MaximizeWindow_Click" Content="口" />                        <Button Click="CloseWindow_Click" Content="X" />                    </StackPanel>                </Grid>            </Border>            <blazor:BlazorWebView Grid.Row="1" HostPage="wwwroot\index.html" Services="{DynamicResource services}">                <blazor:BlazorWebView.RootComponents>                    <blazor:RootComponent ComponentType="{x:Type razorViews:Counter}" Selector="#app" />                </blazor:BlazorWebView.RootComponents>            </blazor:BlazorWebView>        </Grid>    </Border></Window>

    我們給整個窗體客戶端區(qū)域加了一個背景Border(您可以去掉Border背景色,點(diǎn)擊界面按鈕試試),然后又套了一個Grid,用于放置自定義的標(biāo)題欄(標(biāo)題和窗體控制按鈕)和BlazorWebView(用于渲染Razor組件的瀏覽器組件),下面是窗體控制按鈕的響應(yīng)事件:

    using Microsoft.Extensions.DependencyInjection;using System.Windows; namespace WPFBlazorChat; public partial class MainWindow : Window{    public MainWindow()    {        InitializeComponent();        var serviceCollection=new ServiceCollection();        serviceCollection.AddWpfBlazorWebView();        Resources.Add("services", serviceCollection.BuildServiceProvider());    }     private void MoveWindow_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)    {        if (e.ClickCount==1)        {            this.DragMove();        }        else        {            MaximizeWindow_Click(null, null);        }    }     private void CloseWindow_Click(object sender, RoutedEventArgs e)    {        this.Close();    }     private void MinimizeWindow_Click(object sender, RoutedEventArgs e)    {        this.WindowState=WindowState.Minimized;    }     private void MaximizeWindow_Click(object sender, RoutedEventArgs e)    {        this.WindowState=this.WindowState==WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;    }}

    代碼簡單,處理了窗體最小化、窗體最大化(還原)、關(guān)閉、標(biāo)題欄雙擊窗體最大化(還原),上面的實(shí)現(xiàn)不是一個完美的自定義窗體實(shí)現(xiàn),至少有這兩個問題:

    • 當(dāng)您嘗試最大化后,窗體鋪滿了整個操作系統(tǒng)桌面(連任務(wù)欄區(qū)域也占用了);
    • 窗體任務(wù)欄兩個圓角未生效(紅色矩形框選的部分),即窗體下面的兩個圓角,站長未找到讓BlazorWebView出現(xiàn)圓角的屬性或其他方法;標(biāo)題欄區(qū)域(綠色矩形框選的部分)是WPF控件,所以圓角顯示正常。

    在后面的3.4小節(jié),站長使用一個第三庫實(shí)現(xiàn)了窗體圓角問題,更多比較好的WPF自定義窗體實(shí)現(xiàn)可看這篇文章:WPF三種自定義窗體的實(shí)現(xiàn),本小節(jié)中示例源碼在這WPF自定義窗體。

    3.2 WPF異形窗體

    異形窗體的需求,使用WPF實(shí)現(xiàn)是比較方便的,本來打算寫寫的,感覺偏離主題太遠(yuǎn)了,給篇文章自行看看吧:WPF異形窗體演示,文中異形窗體效果如下:

    下面介紹將窗體的標(biāo)題欄也放Razor組件中實(shí)現(xiàn)的方式。

    3.3 Blazor實(shí)現(xiàn)自定義窗體效果

    上面使用了WPF制作自定義窗體,有沒有這種需求,把菜單放置到標(biāo)題欄?這個簡單,WPF能很好實(shí)現(xiàn)。

    如果放Tab類控件呢?Tab Header是在標(biāo)題欄顯示,TabItem是在客戶端區(qū)域,Tab HeaderTabItem風(fēng)格統(tǒng)一,在一套代碼里面實(shí)現(xiàn)和維護(hù)也方便,那么在WPF+Blazor混合開發(fā)的情況怎么實(shí)現(xiàn)呢?相信通過本節(jié)Razor組件實(shí)現(xiàn)標(biāo)題欄的介紹,你能做出來。

    MainWindow.xaml恢復(fù)代碼,只設(shè)置隱藏WPF默認(rèn)窗體邊框,并給BlazorWebView套一層背景:

    后面的代碼有參考 BlazorDesktopWPF-CustomTitleBar 開源項(xiàng)目實(shí)現(xiàn)。

    我們把標(biāo)題欄做到Counter.razor組件,即標(biāo)題欄、客戶區(qū)放一個組件里,當(dāng)然你也可以分離,這里我們方便演示:

    Counter.razor

    @using WPFBlazorChat.Services <div class="titlebar" @ondblclick="WindowService.Maximize" @onmouseup="WindowService.StopMove" @onmousedown="WindowService.StartMove">    <button class="titlebar-btn" onclick="alert('js alert: navigation pressed');">        <img src="svg/navigation.svg" />    </button>    <div class="window-title">        測試窗體標(biāo)題    </div>    <div style="flex-grow:1"></div>    <button class="titlebar-btn" onclick="alert('js alert: settings pressed');">        <img src="svg/settings.svg" />    </button>    <button class="titlebar-btn" @onclick="WindowService.Minimize">        <img src="svg/minimize.svg" />    </button>    <button class="titlebar-btn" @onclick="WindowService.Maximize">        @if (WindowService.IsMaximized())        {            <img src="svg/restore.svg" />        }        else        {            <img src="svg/maximize.svg" />        }    </button>    <button class="titlebar-cbtn" @onclick="()=>WindowService.Close(false)">        <img src="svg/dismiss.svg" />    </button></div> <p>好開心,你點(diǎn)我了,現(xiàn)在是:<span style="color: red;">@currentCount</span></p> <button class="btn btn-primary" @onclick="IncrementCount">快快點(diǎn)我</button> @code {    private int currentCount=0;     protected override void OnInitialized()    {         WindowService.Init();        base.OnInitialized();    }     private void IncrementCount()    {        currentCount++;    }}

    下面給出代碼簡單說明:

    1. 第一個div充做窗體的標(biāo)題欄區(qū)域,注冊了雙擊事件調(diào)用窗體最大化(還原)方法、鼠標(biāo)按下與釋放調(diào)用窗體的移動開始與結(jié)束方法;
    2. 在第一個div里,其中有3個按鈕,即窗體的控制按鈕,調(diào)用窗體最小化、最大化(還原)、關(guān)閉方法調(diào)用;
    3. 另有兩個按鈕,演示單擊調(diào)用JavaScriptalert方法彈出消息。

    運(yùn)行效果如下:

    實(shí)現(xiàn)這個效果,還有一些代碼:

    1. 上面的代碼調(diào)用了一些方法實(shí)現(xiàn)窗體操作最小化、關(guān)閉等,代碼如下;
    2. 因?yàn)槭?/span>Razor組件,即html實(shí)現(xiàn)的界面,界面的html元素也定義了一些css樣式,代碼也一并給出。
    3. 標(biāo)題欄的按鈕使用了一些svg圖片,在倉庫里,可自行獲取。

    窗體拖動

    首先添加NugetSimplify.Windows.Forms,用于獲取鼠標(biāo)光標(biāo)的位置:

    <PackageReference Include="Simplify.Windows.Forms" Version="1.1.2" />

    添加窗體幫助類:Services\WindowService.cs

    using System;using System.Linq;using System.Windows;using System.Windows.Forms;using System.Windows.Threading;using Application=System.Windows.Application; namespace WPFBlazorChat.Services; public class WindowService{    private static bool _isMoving;    private static double _startMouseX;    private static double _startMouseY;    private static double _startWindLeft;    private static double _startWindTop;     public static void Init()    {        DispatcherTimer dispatcherTimer=new();        dispatcherTimer.Tick +=UpdateWindowPos;        dispatcherTimer.Interval=TimeSpan.FromMilliseconds(17);        dispatcherTimer.Start();    }     public static void StartMove()    {        _isMoving=true;        _startMouseX=GetX();        _startMouseY=GetY();        var window=GetActiveWindow();        if (window==null)        {            return;        }         _startWindLeft=window.Left;        _startWindTop=window.Top;    }     public static void StopMove()    {        _isMoving=false;    }     public static void Minimize()    {        var window=GetActiveWindow();        if (window !=null)        {            window.WindowState=WindowState.Minimized;        }    }     public static void Maximize()    {        var window=GetActiveWindow();        if (window !=null)        {            window.WindowState=      window.WindowState==WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;        }    }      public static bool IsMaximized()    {        var window=GetActiveWindow();        if (window !=null)        {            return window.WindowState==WindowState.Maximized;        }         return false;    }     public static void Close(bool allWindow=false)    {        if (allWindow)        {            Application.Current?.Shutdown();            return;        }         var window=GetActiveWindow();        if (window !=null)        {            window.Close();        }    }      private static void UpdateWindowPos(object? sender, EventArgs e)    {        if (!_isMoving)        {            return;        }         double moveX=GetX() - _startMouseX;        double moveY=GetY() - _startMouseY;        Window? window=GetActiveWindow();        if (window==null)        {            return;        }         window.Left=_startWindLeft + moveX;        window.Top=_startWindTop + moveY;    }     private static int GetX()    {        return Control.MousePosition.X;    }     private static int GetY()    {        return Control.MousePosition.Y;    }     private static Window? GetActiveWindow()    {        return Application.Current.Windows.Cast<Window>().FirstOrDefault(currentWindow=> currentWindow.IsActive);    }}

    上面的代碼用于窗體的最小化、最大化(還原)、關(guān)閉等實(shí)現(xiàn),需要在Razor組件里正確的調(diào)用這些方法:

    1. Counter.razor組件的OnInitialized初始化生命周期方法里調(diào)用WindowService.Init();,如上代碼,這個方法開啟定時(shí)器,定時(shí)調(diào)用UpdateWindowPos方法檢查鼠標(biāo)是否按下,如果按下,檢查間隔內(nèi)窗體的位置變化范圍,然后修改窗體位置,從而實(shí)現(xiàn)窗體位置移動(移動窗體無法使用WPF的DragMove方法,您可以嘗試使用看看它報(bào)什么錯),移動窗體有更好的方法歡迎留言。
    2. Razor組件里窗體控制按鈕的使用看上面的代碼不難理解,不過多解釋。

    上面效果的樣式文件修改如下,wwwroot\css\app.css

    /*BlazorDesktopWPF-CustomTitleBar - ? Copyright 2021 - Jam-Es.comLicensed under the MIT License (MIT). See LICENSE in the repo root for license information.*/ html, body {    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;    padding: 0;    margin: 0;} .valid.modified:not([type=checkbox]) {    outline: 1px solid #26b050;} .invalid {    outline: 1px solid red;} .validation-message {    color: red;} #blazor-error-ui {    background: lightyellow;    bottom: 0;    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);    display: none;    left: 0;    padding: 0.6rem 1.25rem 0.7rem 1.25rem;    position: fixed;    width: 100%;    z-index: 1000;} #blazor-error-ui .dismiss {    cursor: pointer;    position: absolute;    right: 0.75rem;    top: 0.5rem;} .page-container {    display: flex;    flex-direction: column;    height: 100vh;} .content-container {    padding: 0px 20px 20px 20px;    flex-grow: 1;    overflow-y: scroll;} .titlebar {    width: 100%;    height: 32px;    min-height: 32px;    background-color: #7160E8;    display: flex;    flex-direction: row;} .titlebar-btn, .titlebar-cbtn {    width: 46px;    background-color: #7160E8;    color: white;    border: none;    border-radius: 0;} .titlebar-btn:hover {    background-color: #5A5A5A;} .titlebar-btn:focus, .titlebar-cbtn:focus {    outline: 0;} .titlebar-cbtn:hover {    background-color: #E81123;} .window-title {    display: flex;    flex-direction: column;    justify-content: center;    margin-left: 5px;    color: white;}

    上面的一些代碼即實(shí)現(xiàn)了由Razor組件實(shí)現(xiàn)窗體的標(biāo)題顯示、窗體的最小化、最大化(還原)、關(guān)閉、移動等操作,然而還是會有3.1結(jié)尾出現(xiàn)的問題,即窗體圓角和窗體最大化鋪滿操作系統(tǒng)桌面任務(wù)欄的問題,下面一小節(jié)我們嘗試解決他。

    小節(jié)總結(jié):通過上面的代碼,如果放Tab控件鋪滿整個窗體,是不是有思路了?

    本小節(jié)源碼在這Razor組件實(shí)現(xiàn)窗體標(biāo)題欄功能

    3.4 Blazor與WPF比較完美的實(shí)現(xiàn)效果

    其實(shí)上面的代碼可以當(dāng)做學(xué)習(xí),即使有不小瑕疵(哈哈),本小節(jié)我們還是使用第三包解決窗體圓角和最大化問題。

    首先添加NugetModernWpfUI,該WPF控件庫本站介紹鏈接開源WPF控件庫:ModernWpf:

    <PackageReference Include="ModernWpfUI" Version="0.9.7-preview.2" />

    然后打開App.xaml,引用上面開源WPF控件的樣式:

    <Application x:Class="WPFBlazorChat.App"             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"             xmlns:ui="http://schemas.modernwpf.com/2019"             StartupUri="MainWindow.xaml">    <Application.Resources>        <ResourceDictionary>            <ResourceDictionary.MergedDictionaries>                <ui:ThemeResources />                <ui:XamlControlsResources />            </ResourceDictionary.MergedDictionaries>        </ResourceDictionary>    </Application.Resources></Application>

    最后打開MainWindow.xaml,修改如下(主要是引入的幾個屬性ui:xxxxx):

    <Window x:Class="WPFBlazorChat.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"        xmlns:ui="http://schemas.modernwpf.com/2019"        xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"        xmlns:razorViews="clr-namespace:WPFBlazorChat.RazorViews"        mc:Ignorable="d"        Title="MainWindow" Height="450" Width="800"        ui:TitleBar.ExtendViewIntoTitleBar="True"        ui:TitleBar.IsBackButtonVisible="False"        ui:TitleBar.Style="{DynamicResource AppTitleBarStyle}"        ui:WindowHelper.UseModernWindowStyle="True">    <Border Background="#7160E8" CornerRadius="5">        <blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{DynamicResource services}">            <blazor:BlazorWebView.RootComponents>                <blazor:RootComponent Selector="#app" ComponentType="{x:Type razorViews:Counter}" />            </blazor:BlazorWebView.RootComponents>        </blazor:BlazorWebView>    </Border></Window>

    就上面三處修改,我們運(yùn)行看看:

    是不是和3.3效果一樣?其實(shí)仔細(xì)看,窗體下面的圓角也有了:

    最終還是WPF解決了所有問題...

    具體怎么實(shí)現(xiàn)的窗體最大化未占操作系統(tǒng)的任務(wù)欄,以及窗體圓角問題的解決(竟然能讓BlazorWebView部分透明了)可以查看該組件相關(guān)代碼,本文不過多深究。

    另外,WPF熟手可能比較清楚,前面的代碼還不能正常的拖動改變窗體大小(不知道你發(fā)現(xiàn)沒,我當(dāng)你沒發(fā)現(xiàn)。),使用該庫后也解決了:

    本小節(jié)源碼在這解決圓角和最大化問題,下面開始本文的下半部分了,好累,終于到這了。

    4. 添加第三方Blazor組件

    工欲善其事,必先利其器!

    鑒于大部分同學(xué)前端基礎(chǔ)可能不是太好,即使使用Blazor可以少用或者不用JavaScript,但有那么一款漂亮、便捷的Blazor組件庫,這不是如虎添翼嗎?本文使用Masa Blazor做示例展示,如今Blazor組件庫眾多,選擇自己喜歡的、順手的就成:

    站長前些日子介紹過MAUI使用Masa blazor組件庫一文,本小節(jié)思路也是類似,且看我表演。

    打開Masa Blazor文檔站點(diǎn):https://blazor.masastack.com/getting-started/installation,一起來往WPF中引入這款Blazor組件庫吧。

    4.1 引入Masa.Blazor包

    打開工程文件WPFBlazorChat.csproj直接復(fù)制下面的包版本,或通過NuGet包管理器搜索Masa.Blazor安裝

    <PackageReference Include="Masa.Blazor" Version="0.6.0" />

    4.2 添加Masa.Blazor帶來的資源

    打開wwwroot\index.html,在<head></head>節(jié)點(diǎn)添加如下資源:

    <link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet" /> <link href="https://cdn.masastack.com/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet"><link href="https://cdn.masastack.com/npm/materialicons/materialicons.css" rel="stylesheet"><link href="https://cdn.masastack.com/npm/fontawesome/v5.0.13/css/all.css" rel="stylesheet"> <script src="_content/BlazorComponent/js/blazor-component.js"></script>

    完整代碼如下:

    <!DOCTYPE html><html lang="en"> <head>    <meta charset="utf-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <title>WPFBlazorChat</title>    <base href="/" />    <link href="css/app.css" rel="stylesheet" />    <link href="WpfBlazor.styles.css" rel="stylesheet" />     <link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet" />     <link href="https://cdn.masastack.com/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">    <link href="https://cdn.masastack.com/npm/materialicons/materialicons.css" rel="stylesheet">    <link href="https://cdn.masastack.com/npm/fontawesome/v5.0.13/css/all.css" rel="stylesheet">     <script src="_content/BlazorComponent/js/blazor-component.js"></script></head> <body><div id="app">Loading...</div> <div id="blazor-error-ui">    An unhandled error has occurred.    <a href="" class="reload">Reload</a>    <a class="dismiss"></a></div><script src="_framework/blazor.webview.js"></script></body> </html>

    4.3 引入Masa.Blazor命名空間

    打開_Imports.razor文件,修改如下:

    @using Microsoft.AspNetCore.Components.Web@using Masa.Blazor@using BlazorComponent

    4.4 Razor組件添加Masa.Blazor

    打開MainWindow.xaml.cs,添加一行代碼 serviceCollection.AddMasaBlazor();

    4.5 嘗試Masa.Blazor案例

    上面4步的準(zhǔn)備工作做好后,我們簡單來使用下Masa.Blazor組件。

    打開Tab組件鏈接:https://blazor.masastack.com/components/tabs,嘗試這個Demo:

    Demo的代碼我?guī)缀醪蛔兊囊耄蜷_RazorViews\Counter.razor文件,保留3.4節(jié)的標(biāo)題欄,替換了客戶區(qū)域內(nèi)容,代碼如下:

    @using WPFBlazorChat.Services <MApp>    <!--上一小節(jié)的標(biāo)題欄開始-->    <div class="titlebar" @ondblclick="WindowService.Maximize" @onmouseup="WindowService.StopMove" @onmousedown="WindowService.StartMove">        <button class="titlebar-btn" onclick="alert('js alert: navigation pressed');">            <img src="svg/navigation.svg"/>        </button>        <div class="window-title">            測試窗體標(biāo)題        </div>        <div style="flex-grow: 1"></div>        <button class="titlebar-btn" onclick="alert('js alert: settings pressed');">            <img src="svg/settings.svg"/>        </button>        <button class="titlebar-btn" @onclick="WindowService.Minimize">            <img src="svg/minimize.svg"/>        </button>        <button class="titlebar-btn" @onclick="WindowService.Maximize">            @if (WindowService.IsMaximized())            {                <img src="svg/restore.svg"/>            }            else            {                <img src="svg/maximize.svg"/>            }        </button>        <button class="titlebar-cbtn" @onclick="()=> WindowService.Close(false)">            <img src="svg/dismiss.svg"/>        </button>    </div>    <!--上一小節(jié)的標(biāo)題欄結(jié)束-->        <!--新增的Masa.Blazor Tab案例代碼開始-->    <MCard>        <MToolbar Color="cyan" Dark Flat>            <ChildContent>                <MAppBarNavIcon></MAppBarNavIcon>                 <MToolbarTitle>Your Dashboard</MToolbarTitle>                 <MSpacer></MSpacer>                 <MButton Icon>                    <MIcon>mdi-magnify</MIcon>                </MButton>                 <MButton Icon>                    <MIcon>mdi-dots-vertical</MIcon>                </MButton>            </ChildContent>             <ExtensionContent>                <MTabs @bind-Value="tab"                       AlignWithTitle                       SliderColor="yellow">                    @foreach (var item in items)                    {                        <MTab Value="item">                            @item                        </MTab>                    }                </MTabs>            </ExtensionContent>        </MToolbar>         <MTabsItems @bind-Value="tab">            @foreach (var item in items)            {                <MTabItem Value="item">                    <MCard Flat>                        <MCardText>@text</MCardText>                    </MCard>                </MTabItem>            }        </MTabsItems>    </MCard>    <!--新增的Masa.Blazor Tab案例代碼結(jié)束--></MApp> @code {     #region Masa.Blazor Tab案例C#代碼    StringNumber tab;     List<string> items=new()    {        "web", "shopping", "videos", "images", "news",    };     string text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";     #endregion            protected override void OnInitialized()    {        WindowService.Init();        base.OnInitialized();    }}

    運(yùn)行效果如下:

    是不是有那味兒了?再嘗試把Tab移到標(biāo)題欄,前面有提過的效果:

    上面的效果,代碼修改如下,刪除了原標(biāo)題欄代碼,將窗體操作按鈕放到了MToolbar里面,并使用MToolbar添加了雙擊事件、鼠標(biāo)按下、釋放事件實(shí)現(xiàn)窗體拖動:

    <MApp>     <!--新增的Masa.Blazor Tab案例代碼開始-->    <MCard>        <MToolbar Color="cyan" Dark Flat @ondblclick="WindowService.Maximize" @onmouseup="WindowService.StopMove" @onmousedown="WindowService.StartMove">            <MTabs @bind-Value="tab"                   AlignWithTitle                   SliderColor="yellow">                @foreach (var item in items)                {                    <MTab Value="item">                        @item                    </MTab>                }            </MTabs>                        <div style="flex-grow: 1"></div>            <button class="titlebar-btn" onclick="alert('js alert: settings pressed');">                <img src="svg/settings.svg"/>            </button>            <button class="titlebar-btn" @onclick="WindowService.Minimize">                <img src="svg/minimize.svg"/>            </button>            <button class="titlebar-btn" @onclick="WindowService.Maximize">                @if (WindowService.IsMaximized())                {                    <img src="svg/restore.svg"/>                }                else                {                    <img src="svg/maximize.svg"/>                }            </button>            <button class="titlebar-cbtn" @onclick="()=> WindowService.Close(false)">                <img src="svg/dismiss.svg"/>            </button>        </MToolbar>         <MTabsItems @bind-Value="tab">            @foreach (var item in items)            {                <MTabItem Value="item">                    <MCard Flat>                        <MCardText>@text</MCardText>                    </MCard>                </MTabItem>            }        </MTabsItems>    </MCard>    <!--新增的Masa.Blazor Tab案例代碼結(jié)束--></MApp>

    窗體操作按鈕的背景色也做部分修改:

    其實(shí)上面的窗體效果還是有點(diǎn)瑕疵,注意到窗體右側(cè)的豎直滾動條了嗎?在沒引入Masa.Blazor之前,右側(cè)正常顯示,引入后多了一個豎直滾動條:

    這個想去掉也簡單,在wwwroot\css\app.css追加樣式(當(dāng)時(shí)也是折騰了好一會兒,最后在Masa.Blazor群里群友給出了解決方案,十分感謝):

    問題解決css代碼:

    ::-webkit-scrollbar {    width: 0px;}

    因?yàn)?/span>Razor組件是在BlazorWebView里渲染的,即BlazorWebView就是個小型的瀏覽器呀,上面的樣式即把瀏覽器的滾動條寬度設(shè)置為0,它不就沒有了嗎?現(xiàn)在效果如下,是不是舒服了?

    添加Masa.Blazor就介紹到這里,本小節(jié)示例代碼在這里WPF中使用Masa.Blazor,下面講解WPF與Blazor混合開發(fā)后多窗體消息通知問題。

    5. 多窗體消息通知

    一般C/S窗體之間通信使用委托、事件,而在WPF開發(fā)中,可以使用一些框架提供的抽象事件訂閱\發(fā)布組件,比如Prism的事件聚集器IEventAggregator,或MvvmLightMessager。在B/S開發(fā)中,進(jìn)程內(nèi)事件通知可能就使用MediatR組件居多了,不論是在C/S還是B/S開發(fā),這些組件在一定程度上,各大程序模板可以通用的,更不用說分布式的消息隊(duì)列RabbitMQKafka是萬能的進(jìn)程間通信標(biāo)準(zhǔn)選擇了。

    上面是一些套話,站長根據(jù)Prism的事件聚集器和MvvmLight的Messager源碼閱讀,簡單封裝了一個Messager,可以適用于一般的業(yè)務(wù)需求。

    5.1 Messager封裝

    本來不想貼代碼直接給源碼鏈接的,想想代碼也不多,直接上吧。

    Message

    消息抽象類,用于定義消息類型,具體的消息需要繼承該類,比如后面的打開子窗體消息OpenSecondViewMessage

    using System; namespace WPFBlazorChat.Messages; public abstract class Message{    protected Message(object sender)    {        this.Sender=sender ?? throw new ArgumentNullException(nameof(sender));    }     public object Sender { get; }}

    IMessenger

    消息接口,只定義了三個接口:

    1. Subscribe:消息訂閱
    2. Unsubscribe:取消消息訂閱
    3. Publish:消息發(fā)送
    using System; namespace WPFBlazorChat.Messages; public interface IMessenger{    void Subscribe<TMessage>(object recipient, Action<TMessage> action,        ThreadOption threadOption=ThreadOption.PublisherThread) where TMessage : Message;     void Unsubscribe<TMessage>(object recipient, Action<TMessage>? action=null) where TMessage : Message;     void Publish<TMessage>(object sender, TMessage message) where TMessage : Message;} public enum ThreadOption{    PublisherThread,    BackgroundThread,    UiThread}

    Messenger

    消息的管理,消息中轉(zhuǎn)等實(shí)現(xiàn):

    using System;using System.Collections.Generic;using System.Linq;using System.Threading;using System.Threading.Tasks; namespace WPFBlazorChat.Messages; public class Messenger : IMessenger{    public static readonly Messenger Default=new Messenger();    private readonly object registerLock=new object();     private Dictionary<Type, List<WeakActionAndToken>>? recipientsOfSubclassesAction;     public void Subscribe<TMessage>(object recipient, Action<TMessage> action, ThreadOption threadOption)        where TMessage : Message    {        lock (this.registerLock)        {            var messageType=typeof(TMessage);             this.recipientsOfSubclassesAction ??=new Dictionary<Type, List<WeakActionAndToken>>();             List<WeakActionAndToken> list;             if (!this.recipientsOfSubclassesAction.ContainsKey(messageType))            {                list=new List<WeakActionAndToken>();                this.recipientsOfSubclassesAction.Add(messageType, list);            }            else            {                list=this.recipientsOfSubclassesAction[messageType];            }             var item=new WeakActionAndToken            { Recipient=recipient, ThreadOption=threadOption, Action=action };             list.Add(item);        }    }     public void Unsubscribe<TMessage>(object? recipient, Action<TMessage>? action) where TMessage : Message    {        var messageType=typeof(TMessage);         if (recipient==null || this.recipientsOfSubclassesAction==null ||            this.recipientsOfSubclassesAction.Count==0 || !this.recipientsOfSubclassesAction.ContainsKey(messageType))        {            return;        }         var lstActions=this.recipientsOfSubclassesAction[messageType];        for (var i=lstActions.Count - 1; i >=0; i--)        {            var item=lstActions[i];            var pastAction=item.Action;             if (pastAction !=null                && recipient==pastAction.Target                && (action==null || action.Method.Name==pastAction.Method.Name))            {                lstActions.Remove(item);            }        }    }     public void Publish<TMessage>(object sender, TMessage message) where TMessage : Message    {        var messageType=typeof(TMessage);         if (this.recipientsOfSubclassesAction !=null)        {            var listClone=this.recipientsOfSubclassesAction.Keys.Take(this.recipientsOfSubclassesAction.Count)                .ToList();             foreach (var type in listClone)            {                List<WeakActionAndToken>? list=null;                 if (messageType==type || messageType.IsSubclassOf(type) || type.IsAssignableFrom(messageType))                {                    list=this.recipientsOfSubclassesAction[type]                        .Take(this.recipientsOfSubclassesAction[type].Count)                        .ToList();                }                 if (list is { Count: > 0 })                {                    this.SendToList(message, list);                }            }        }    }     private void SendToList<TMessage>(TMessage message, IEnumerable<WeakActionAndToken> weakActionsAndTokens)        where TMessage : Message    {        var list=weakActionsAndTokens.ToList();        var listClone=list.Take(list.Count()).ToList();         foreach (var item in listClone)        {            if (item.Action is { Target: { } })            {                switch (item.ThreadOption)                {                    case ThreadOption.BackgroundThread:                        Task.Run(()=> { item.ExecuteWithObject(message); });                        break;                    case ThreadOption.UiThread:                        SynchronizationContext.Current!.Post(_=> { item.ExecuteWithObject(message); }, null);                        break;                    default:                        item.ExecuteWithObject(message);                        break;                }            }        }    }} public class WeakActionAndToken{    public object? Recipient { get; set; }     public ThreadOption ThreadOption { get; set; }     public Delegate? Action { get; set; }     public string? Tag { get; set; }     public void ExecuteWithObject<TMessage>(TMessage message) where TMessage : Message    {        if (this.Action is Action<TMessage> factAction)        {            factAction.Invoke(message);        }    }}

    有興趣的看上面的代碼,封裝代碼上面簡單全部給上,后面的消息通知都是基于上面的三個類實(shí)現(xiàn)的,比較核心。

    5.2 代碼整理

    第 5 節(jié)涉及到多窗體及多Razor組件了,需要創(chuàng)建一些目錄存放這些文件,方便分類管理。

    1. A:放Message,即一些消息通知類;
    2. B:放Razor組件,如果需要與Maui\Blazor Server(Wasm)等共享Razor組件,可以創(chuàng)建Razor類庫存儲;
    3. C:放通用服務(wù),這里只放了一個窗體管理靜態(tài)類,實(shí)際情況可以放Redis服務(wù)、RabbitMQ消息服務(wù)等;
    4. D:放WPF視圖,本示例WPF窗體只是一個殼,承載BlazorWebView使用;

    5.3 示例及代碼說明

    先看本示例效果,再給出相關(guān)代碼說明:

    圖中有三個操作:

    1. 點(diǎn)擊主窗體A的【+】按鈕,發(fā)送了OpenSecondViewMessage消息,打開子窗體B;
    2. 打開子窗體B后,再點(diǎn)擊主窗體A的【桃心】按鈕,發(fā)送了SendRandomDataMessage消息,子窗體B的第二個TabItem Header顯示了消息傳來的數(shù)字;
    3. 點(diǎn)擊子窗體B的【安卓】圖標(biāo)按鈕,給主窗體A響應(yīng)了消息ReceivedResponseMessage,主窗體收到后彈出一個對話框。

    三個消息類定義如下:

    public class OpenSecondViewMessage : Message{    public OpenSecondViewMessage(object sender) : base(sender)    {    }} public class SendRandomDataMessage : Message{    public SendRandomDataMessage(object sender, int number) : base(sender)    {        Number=number;    }     public int Number { get; set; }} public class ReceivedResponseMessage : Message{    public ReceivedResponseMessage(object sender) : base(sender)    {    }}

    除了SendRandomDataMessage傳遞了一個業(yè)務(wù)Number屬性,另兩個消息只是起到通知作用(所以沒有額外屬性定義),實(shí)際開發(fā)時(shí)可能需要傳遞業(yè)務(wù)數(shù)據(jù)。

    5.3.1 打開多窗體

    即上面的第一個操作:點(diǎn)擊主窗體A的【+】按鈕,發(fā)送了OpenSecondViewMessage消息,打開子窗體B。

    RazorViews\MainView.razor中執(zhí)行按鈕點(diǎn)擊,發(fā)送打開子窗體消息:

    ...<MCol>    <MButton class="mx-2" Fab Dark Color="indigo" OnClick="OpenNewSecondView">        <MIcon>mdi-plus</MIcon>    </MButton></MCol>... @code{...void OpenNewSecondView(){	Messenger.Default.Publish(this, new OpenSecondViewMessage(this));}...}

    App.xaml.cs里訂閱打開子窗體消息:

    public partial class App : Application{    public App()    {        // 訂閱打開子窗口消息,在主窗口點(diǎn)擊【+】按鈕        Messenger.Default.Subscribe<OpenSecondViewMessage>(this, msg=>        {            var chatWin=new SecondWindowView();            chatWin.Show();        }, ThreadOption.UiThread);    }}

    實(shí)際開發(fā)可能情況更復(fù)雜,發(fā)送的消息OpenSecondViewMessage里帶WPF窗體路由(定義的一套路徑規(guī)則尋找窗體或ViewModel),訂閱的地方也可能不在主程序,在子模塊的Module類里。

    5.3.2 發(fā)送業(yè)務(wù)數(shù)據(jù)

    即第二個操作:打開子窗體B后,再點(diǎn)擊主窗體A的【桃心】按鈕,發(fā)送了SendRandomDataMessage消息,子窗體B的第二個TabItem Header顯示了消息傳來的數(shù)字。

    1. RazorViews\MainView.razor中執(zhí)行按鈕點(diǎn)擊,發(fā)送業(yè)務(wù)消息(就當(dāng)前時(shí)間的Millisecond):
    ...<MCol>    <MButton class="mx-2" Fab Small Dark Color="pink" OnClick="SendNumber">        <MIcon>mdi-heart</MIcon>    </MButton></MCol>... @code{...void SendNumber(){	Messenger.Default.Publish(this, new SendRandomDataMessage(this, DateTime.Now.Millisecond));}...}
    1. RazorViews\SecondView.razorOnInitialized()方法里訂閱業(yè)務(wù)消息通知:
    @using WPFBlazorChat.Messages<MApp>    <MToolbar>        <MTabs BackgroundColor="primary" Grow Dark>            <MTab>                <MBadge Color="pink" Dot>                    Item One                </MBadge>            </MTab>            <MTab>                <MBadge Color="green" Content="tagCount">                    Item Two                </MBadge>            </MTab>            <MTab>                <MBadge Color="deep-purple accent-4" Icon="mi-masa">                    Item Three                </MBadge>            </MTab>        </MTabs>    </MToolbar>        <MRow>        <MButton class="mx-2" Fab Dark Large Color="purple" OnClick="ReponseMessage">            <MIcon>                mdi-android            </MIcon>        </MButton>    </MRow></MApp> @code{    private int tagCount=6;     protected override void OnInitialized()    {    	// 訂閱業(yè)務(wù)消息,在主窗口點(diǎn)擊桃心按鈕時(shí)觸發(fā)        Messenger.Default.Subscribe<SendRandomDataMessage>(this, msg=>        {            this.InvokeAsync(()=> { this.tagCount=msg.Number; });            this.StateHasChanged();        }, ThreadOption.UiThread);    }     void ReponseMessage()    {        // 通知主窗體,我已經(jīng)收到消息,請不要再發(fā)        Messenger.Default.Publish(this, new ReceivedResponseMessage(this));    }}

    注意看,上面收到消息時(shí)有兩個方法要簡單說一下,看OnInitialized()里的代碼:

    • InvokeAsync:將Number賦值給變量tagCount的代碼是在InvokeAsync方法里執(zhí)行的,這個和WPF里的Dispatcher.Invoke是一個意思,相當(dāng)于接收數(shù)據(jù)是在子線程,而賦值這個操作會即時(shí)的綁定到<MBadge Color="green" Content="tagCount">上,就需要UI線程同步。
    • StateHasChanged:相當(dāng)于WPF MVVM里的PropertyChanged事件通知,通知UI這里有值變化了,請你刷新一下,我要看看最新值。

    上面的代碼把子窗體消息回應(yīng)也貼上了,即點(diǎn)擊安卓圖標(biāo)按鈕時(shí)發(fā)送了ReceivedResponseMessage消息,在主窗體RazorViews\MainView.razor里也訂閱了這個消息,和上面的代碼類似:

    ...    <!--確認(rèn)對話框開始-->    <PConfirm Visible="_showComfirmDialog"              Title="子窗體來回應(yīng)了"              Type="AlertTypes.Warning"              OnCancel="()=> _showComfirmDialog=false"              OnOk="()=> _showComfirmDialog=false">        說你別沒事一直發(fā),它們煩!    </PConfirm>    <!--確認(rèn)對話框結(jié)束--></MApp> @code{...	// 是否顯示確認(rèn)對話框    bool _showComfirmDialog;	protected override void OnInitialized()    {        WindowService.Init();     // 訂閱子窗體響應(yīng)的消息,它已經(jīng)收到消息了,我可以休息下再發(fā)        Messenger.Default.Subscribe<ReceivedResponseMessage>(this, msg=>        {            this.InvokeAsync(()=> { _showComfirmDialog=true; });            this.StateHasChanged();        }, ThreadOption.UiThread);        base.OnInitialized();    }...}

    OnInitialized()方法里訂閱消息ReceivedResponseMessage,收到后將變量_showComfirmDialog置為true,即上面對話框的屬性Visible綁定的值,同理需要在InvokeAsync()中處理數(shù)據(jù)接收,也需要調(diào)用StateHasChanged通知UI數(shù)據(jù)變化。

    上面說了部分代碼,可能講的不太清楚,可以看本節(jié)示例源碼:多窗體消息通知。

    6. 本文示例

    本來想寫完整Demo說明的,發(fā)現(xiàn)上面把基本要點(diǎn)都拉了一遍,再粘貼一些重復(fù)代碼有點(diǎn)沒完沒了了,有興趣的拉源碼WPF與Blazor混合開發(fā)Demo查看、運(yùn)行,下面是項(xiàng)目代碼結(jié)構(gòu):

    下面是最后的示例效果圖,前面部分文章已經(jīng)發(fā)過,再發(fā)一次,哈哈:

    用戶列表窗口

    打開子窗口

    聊天窗口

    演示發(fā)送消息

    7. Click Once發(fā)布嘗試

    上一篇文章鏈接:快速創(chuàng)建軟件安裝包-ClickOnce,本文示例Click Once安裝頁面:https://dotnet9.com/WPFBlazorChat

    8. Q&A

    8.1 為啥要在WPF里使用Blazor?吃飽了撐的?

    WPF雖然相較Winform做出比較好看的UI相對容易一些,但比起Blazor,或者直接說html開發(fā)界面,還是差了一點(diǎn)點(diǎn),更何況html的資源更多一點(diǎn),嘗試一下為何不可?

    8.2 WPF + Blazor支持哪些操作系統(tǒng)

    最低支持Windows 7 SP1吧,有群友已經(jīng)嘗試在Windows 7正常運(yùn)行成功,這是本文示例Click Once安裝頁面:https://dotnet9.com/WPFBlazorChat

    8.3 Blazor 混合開發(fā)還支持哪些已有框架?

    Blazor混合開發(fā)的話,除了WPF,還有MAUI(跨平臺框架,支持平臺包括Windows\Mac\Linux\Android\iOS等)、Winform(同WPF,只能在Windows平臺運(yùn)行)等,建議閱讀微軟文檔繼續(xù)學(xué)習(xí),本文只是個引子:

    8.4 Blazor組件庫除了Masa.Blazor還有哪些?

    • 開源的Blazor組件有:Ant Design Blazor、Bootstrap Blazor、MudBlazor、Blazorise,以及微軟自家的FAST Blazor等,當(dāng)然還有不少開源的Blazor組件。
    • 收費(fèi)的Blazor組件:DevExpress、Telerik、Syncfusion等

    8.5 本文示例代碼?

    文中各小節(jié)代碼、最后的示例代碼都給出了相應(yīng)鏈接,您可返回查看。

    本號已有原創(chuàng)文章320+篇,以軟件工程為綱,DevOps為基,洞察研發(fā)效能全貌,涵蓋從需求管理、應(yīng)用/游戲開發(fā)、軟件測試、發(fā)布部署到運(yùn)營監(jiān)控的完整流程。無論您是項(xiàng)目經(jīng)理、產(chǎn)品經(jīng)理、開發(fā)人員、測試人員,還是運(yùn)維人員,在這里您都可以有所收獲,同時(shí)深入理解其他角色的工作內(nèi)容,共同助力DevOps的成功落地。歡迎關(guān)注,有任何問題可發(fā)送私信~


    在.NET的世界里,dotnet 命令是一個極其強(qiáng)大的工具,它不僅僅用于構(gòu)建、運(yùn)行和發(fā)布應(yīng)用程序,還提供了廣泛的內(nèi)置功能,幫助開發(fā)者高效地完成各種任務(wù)。然而,你可能未曾意識到,通過 dotnet tool 命令,dotnet 還相當(dāng)于一個.NET命令行工具的倉庫和包管理器,為開發(fā)者提供了強(qiáng)大的擴(kuò)展能力。這一點(diǎn),在其他編程語言如Java中,是完全沒有類似功能的。


    讓我們先來看看 dotnet tool 的基本使用。通過 dotnet tool,你可以輕松地安裝、升級、卸載各種.NET命令行工具。這些工具可能是微軟官方提供的,也可能是由社區(qū)成員開發(fā)的,它們都有助于提高開發(fā)效率、實(shí)現(xiàn)自動化工作流、簡化部署等。


    安裝一個 dotnet 命令行工具非常簡單。你可以使用以下命令來安裝一個名為 dotnetsay 的示例工具:

    dotnet tool install -g dotnetsay

    這里的 -g 參數(shù)表示全局安裝,意味著這個工具將在系統(tǒng)的任何地方都可使用。如果你只想在當(dāng)前項(xiàng)目中安裝這個工具,可以省略 -g 參數(shù)。


    安裝完成后,你就可以在命令行中直接使用這個工具了。可以通過以下方式調(diào)用它:

    dotnetsay

    要升級一個已安裝的 dotnet 命令行工具,只需運(yùn)行:

    dotnet tool update -g dotnetsay

    同樣,卸載一個工具也很直接:

    dotnet tool uninstall -g dotnetsay

    dotnet tool 命令的靈活性和強(qiáng)大之處,在于它為開發(fā)者和用戶提供了一個統(tǒng)一的入口,用于管理和使用各種命令行工具。這種管理方式使得.NET開發(fā)者能夠非常方便地集成和使用各種工具,從而提升工作效率和產(chǎn)品質(zhì)量。


    現(xiàn)在,讓我們來看看一些 dotnet tool 可安裝的常用和強(qiáng)大的命令行工具。


    1. docfx:

    - 描述:docfx 是一個由微軟開發(fā)的文檔生成工具,用于構(gòu)建 API 文檔、技術(shù)文章等。

    - 用途:為開發(fā)者提供了一種簡單的方法來從源代碼中提取注釋,并根據(jù)這些注釋生成結(jié)構(gòu)化的文檔網(wǎng)站。

    - 安裝命令:

    dotnet tool install -g docfx

    2. csys:

    - 描述:csys 是一個.NET Core命令行工具,用于系統(tǒng)監(jiān)視和診斷。它提供了如進(jìn)程查看、網(wǎng)絡(luò)監(jiān)視、系統(tǒng)資源使用統(tǒng)計(jì)等功能。

    - 用途:幫助開發(fā)者或系統(tǒng)管理員了解當(dāng)前系統(tǒng)的狀態(tài),識別潛在的性能瓶頸或問題。

    - 安裝命令:

    dotnet tool install -g csys

    3. BBDown(嗶哩嗶哩視頻下載):

    - 描述:BBDown 是一個用于下載嗶哩嗶哩(B站)視頻和音頻的命令行工具。它支持多種格式的下載,包括視頻、音頻和彈幕。

    - 用途:對于希望從嗶哩嗶哩保存內(nèi)容的用戶來說,BBDown 是一個便捷的工具。

    - 安裝命令:

    dotnet tool install -g BBDown

    以上只是 dotnet tool 可安裝工具中的一小部分,實(shí)際上還有更多的工具和庫可供使用。這些工具的存在,使得最終用戶可以根據(jù)自己的需求和喜好,靈活地選擇和使用各種工具。


    隨著.NET Core的不斷發(fā)展,.NET的跨平臺能力和生態(tài)系統(tǒng)也在逐漸強(qiáng)大。dotnet tool 作為其中的一部分,通過提供統(tǒng)一的命令行工具管理和使用機(jī)制,使得.NET開發(fā)者能夠更加方便地集成和使用各種工具和庫,進(jìn)一步促進(jìn)了.NET生態(tài)的發(fā)展。同時(shí)它也是面向最終用戶的,能夠用來安裝、使用和管理命令行工具和軟件。

網(wǎng)站首頁   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權(quán)所有