【炫麗】從0開始做一個(gè)WPF+Blazor對(duì)話小程序
從一個(gè)WPF Hello World程序開始,逐漸引入Blazor,做個(gè)免費(fèi)能看的對(duì)話小程序耍耍。
大家好,我是沙漠盡頭的狼。
.NET是免費(fèi),跨平臺(tái),開源,用于構(gòu)建所有應(yīng)用的開發(fā)人員平臺(tái)。
本文演示如何在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)建一個(gè)默認(rèn)程序,取名【W(wǎng)PFBlazorChat】,項(xiàng)目組織結(jié)構(gòu)如下:

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

接著往下看,我們添加Blazor支持,本小節(jié)代碼在這WPF默認(rèn)程序源碼。
2. 添加Blazor支持
依然使用上面的工程,添加Blazor支持,此部分參考微軟文檔生成 Windows Presentation Foundation (WPF) Blazor 應(yīng)用,本小節(jié)快速略過。
2.1 編輯工程文件
雙擊工程文件WPFBlazorChat.csproj
,修改處如下:

在項(xiàng)目文件的頂部,將 SDK 更改為?
Microsoft.NET.Sdk.Razor
。添加節(jié)點(diǎn)
<RootNameSpace>WPFBlazorChat</RootNameSpace>
,將項(xiàng)目命名空間?WPFBlazorChat
?設(shè)置為應(yīng)用的根命名空間。添加
Nuget
包Microsoft.AspNetCore.Components.WebView.Wpf
,版本看你選擇的.NET
版本而定。
2.2 添加_Imports.razor
文件
_Imports.razor
文件類似一個(gè)Global
?using文件,專門給Razor
組件使用,放置一些用的比較多的全局的命名空間,精簡(jiǎn)代碼。
內(nèi)容如下,引入了一個(gè)命名空間Microsoft.AspNetCore.Components.Web
,這是Razor
常用命名空間,包含用于向?Blazor
?框架提供有關(guān)瀏覽器事件的信息的類型。:
@using Microsoft.AspNetCore.Components.Web
HTML
Copy
2.3 添加wwwroot\index.html
文件
和Vue
、React
一樣,需要一個(gè)html
文件承載Razor
組件,頁(yè)面內(nèi)容類似:
app.css
文件在下面給出定義。看
<div id="app">Loading...</div>
,這里是承載Razor
組件的地方,后面所有加載的Razor
組件都是在這里渲染出來的。其他暫時(shí)不管。
2.4 添加wwwroot\css\app.css
文件
頁(yè)面的基本樣式,通用的樣式可放在這個(gè)文件:
2.5 添加一個(gè)Razor組件
加一個(gè)Razor的經(jīng)典組件Counter.razor
,Blazor
的Hello World
程序就有這么一個(gè)組件,文件路徑:/RazorViews/Counter.razor
,之所以放RazorViews
目錄,是為了和WPF常用的Views
目錄區(qū)分,該組件內(nèi)容如下:
一個(gè)按鈕【快快點(diǎn)我】,點(diǎn)擊@onclick="IncrementCount"
使變量currentCount
自增,同時(shí)頁(yè)面顯示此變量值,相信你能看懂。
2.6 Blazor與WPF窗體關(guān)聯(lián)
這是兩者產(chǎn)生關(guān)系的關(guān)鍵一步,打開窗體MainWindow.xaml
,修改如下:

如上代碼,要點(diǎn)如下:
添加上面引入的
Nuget
包Microsoft.AspNetCore.Components.WebView.Wpf
的命名空間,命名為blazor
,主要是要使用BlazorWebView
組件;BlazorWebView
組件屬性HostPage
指定承載的html文件,Services
指定razor組件的Ioc
容器,看下面MainWindow()
里標(biāo)紅的代碼;RootComponent
的Selector="#app"
屬性指示Razor
組件渲染的位置,看index.html
中id為app
的html元素,ComponentType
指示需要在#app
中渲染的Razor
組件類型。
打開MainWindow.xaml.cs
,修改如下:
在WPF里可以使用Prism等框架提供的Unity
、DryIoc
等Ioc
容器實(shí)現(xiàn)視圖與服務(wù)的注入;Razor
組件這里,默認(rèn)使用ASP.NET Core
的IServiceCollection
容器;如果WPF窗體與Razor組件需要共享數(shù)據(jù),可以通過后面要說的Messager
發(fā)送消息,也可以通過Ioc
容器注入的方式實(shí)現(xiàn),比如從WPF窗體中注入的數(shù)據(jù)(通過MainWindow
構(gòu)造函數(shù)注入),通過IServiceCollection
容器再注入Razor
組件使用,這里后面也有提到。

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

OK,WPF
與Blazor
集成成功,打完收工?
等等,還沒完呢,本小節(jié)源碼在這WPF中添加Blazor,接著往下看。
3. 自定義窗體

看上圖,窗體邊框是WPF默認(rèn)的樣式,有時(shí)會(huì)感覺比較丑,或者不丑,設(shè)計(jì)師有其他的窗體風(fēng)格設(shè)計(jì),往往我們要自定義窗體,本節(jié)分享部分WPF與Blazor的自定義窗體實(shí)現(xiàn),更多定制化功能可能需要您自行研究。
3.1 WPF自定義窗體
一般實(shí)現(xiàn)是設(shè)置窗體的三個(gè)屬性WindowStyle="None" AllowsTransparency="True" Background="Transparent"
,即可隱藏默認(rèn)窗體的邊框,然后在內(nèi)容區(qū)自己畫標(biāo)題欄、最小化、最大化、關(guān)閉按鈕、客戶區(qū)等。
MainWindow.xaml:隱藏WPF默認(rèn)窗體邊框
上面的代碼只是隱藏了WPF默認(rèn)窗體的邊框,運(yùn)行程序如下:

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

MainWindow.xaml
代碼如下:
我們給整個(gè)窗體客戶端區(qū)域加了一個(gè)背景Border
(您可以去掉Border背景色,點(diǎn)擊界面按鈕試試),然后又套了一個(gè)Grid,用于放置自定義的標(biāo)題欄(標(biāo)題和窗體控制按鈕)和BlazorWebView
(用于渲染Razor組件的瀏覽器組件),下面是窗體控制按鈕的響應(yīng)事件:
代碼簡(jiǎn)單,處理了窗體最小化、窗體最大化(還原)、關(guān)閉、標(biāo)題欄雙擊窗體最大化(還原),上面的實(shí)現(xiàn)不是一個(gè)完美的自定義窗體實(shí)現(xiàn),至少有這兩個(gè)問題:
當(dāng)您嘗試最大化后,窗體鋪滿了整個(gè)操作系統(tǒng)桌面(也霸占了任務(wù)欄區(qū)域);
窗體任務(wù)欄兩個(gè)圓角未生效(標(biāo)題欄區(qū)域是WPF控件,所以正常),即窗體下面的兩個(gè)圓角,站長(zhǎng)未找到讓
BlazorWebView
出現(xiàn)圓角的屬性或其他方法。

在后面的3.4
小節(jié),站長(zhǎng)使用一個(gè)第三庫(kù)實(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)題欄?這個(gè)簡(jiǎn)單,WPF能很好實(shí)現(xiàn)。
如果放Tab類控件呢?Tab Header是在標(biāo)題欄顯示,TabItem是在客戶端區(qū)域,Tab Header與TabItem風(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](https://github.com/James231/BlazorDesktopWPF-CustomTitleBar)實(shí)現(xiàn)。
我們把標(biāo)題欄做到Counter.razor
組件,即標(biāo)題欄、客戶區(qū)放一個(gè)組件里,當(dāng)然你也可以分離,這里我們方便演示:
Counter.razor
下面給出代碼簡(jiǎn)單說明:
第一個(gè)
div
充做窗體的標(biāo)題欄區(qū)域,注冊(cè)了雙擊事件調(diào)用窗體最大化(還原)方法、鼠標(biāo)按下與釋放調(diào)用窗體的移動(dòng)開始與結(jié)束方法;在第一個(gè)
div
里,其中有3個(gè)按鈕,即窗體的控制按鈕,調(diào)用窗體最小化、最大化(還原)、關(guān)閉方法調(diào)用;另有兩個(gè)按鈕,演示單擊調(diào)用
JavaScript
的alert
方法彈出消息。

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

實(shí)現(xiàn)這個(gè)效果,還有一些代碼:
上面的代碼調(diào)用了一些方法實(shí)現(xiàn)窗體操作最小化、關(guān)閉等,代碼如下;
因?yàn)槭?code>Razor組件,即
html
實(shí)現(xiàn)的界面,界面的html
元素也定義了一些css
樣式,代碼也一并給出。標(biāo)題欄的按鈕使用了一些
svg
圖片,在倉(cāng)庫(kù)里,可自行獲取。
窗體拖動(dòng)
首先添加Nuget
包Simplify.Windows.Forms
,用于獲取鼠標(biāo)光標(biāo)的位置:
<PackageReference Include="Simplify.Windows.Forms" Version="1.1.2" />
添加窗體幫助類:Services\WindowService.cs
上面的代碼用于窗體的最小化、最大化(還原)、關(guān)閉等實(shí)現(xiàn),需要在Razor
組件里正確的調(diào)用這些方法:
Counter.razor
組件的OnInitialized
初始化生命周期方法里調(diào)用WindowService.Init();
,如上代碼,這個(gè)方法開啟定時(shí)器,定時(shí)調(diào)用UpdateWindowPos
方法檢查鼠標(biāo)是否按下,如果按下,檢查間隔內(nèi)窗體的位置變化,然后修改窗體位置,從而實(shí)現(xiàn)窗體位置移動(dòng)(移動(dòng)窗體無法使用WPF的DragMove
方法,您可以嘗試使用看看它報(bào)什么錯(cuò))。Razor
組件里窗體控制按鈕的使用看上面的代碼不難理解,不過多解釋。
上面效果的樣式文件修改如下,wwwroot\css\app.css
:
上面的一些代碼即實(shí)現(xiàn)了由Razor
組件實(shí)現(xiàn)窗體的標(biāo)題顯示、窗體的最小化、最大化(還原)、關(guān)閉、移動(dòng)等操作,然而還是會(huì)有3.1
結(jié)尾出現(xiàn)的問題,即窗體圓角和窗體最大化鋪滿操作系統(tǒng)桌面任務(wù)欄的問題,下面一小節(jié)我們嘗試解決他。
小節(jié)總結(jié):通過上面的代碼,如果放Tab控件鋪滿整個(gè)窗體,是不是有思路了?
本小節(jié)源碼在這Razor組件實(shí)現(xiàn)窗體標(biāo)題欄功能
3.4 Blazor與WPF比較完美的實(shí)現(xiàn)效果
其實(shí)上面的代碼可以當(dāng)做學(xué)習(xí),即使有不小瑕疵(哈哈),本小節(jié)我們還是使用第三包解決窗體圓角和最大化問題。
首先添加Nuget
包ModernWpfUI
,該WPF控件庫(kù)本站介紹鏈接開源WPF控件庫(kù):ModernWpf:
<PackageReference Include="ModernWpfUI" Version="0.9.7-preview.2" />
然后打開App.xaml
,引用上面開源WPF控件的樣式:
最后打開MainWindow.xaml
,修改如下:
就上面三處修改,我們運(yùn)行看看:

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

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

具體怎么實(shí)現(xiàn)的窗體最大化未占操作系統(tǒng)的任務(wù)欄,以及窗體圓角問題的解決(竟然能讓BlazorWebView
部分透明了)可以查看該組件相關(guān)代碼,本文不過多深究。
另外,WPF熟手可能比較清楚,前面的代碼不能正常的拖動(dòng)改變窗體大?。ú恢滥惆l(fā)現(xiàn)沒,我當(dāng)你沒發(fā)現(xiàn)。),使用該庫(kù)后也解決了:

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

4. 添加第三方Blazor組件
工欲善其事,必先利其器!
鑒于大部分同學(xué)前端基礎(chǔ)可能不是太好,即使使用Blazor可以少用或者不用JavaScript,那么有那么一款漂亮、便捷的Blazor
組件庫(kù),這不是如虎添翼嗎?本文使用Masa Blazor做示例顯示,如今Blazor組件庫(kù)眾多,選擇自己喜歡的、順手的就成:

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

打開Masa Blazor文檔站點(diǎn):https://blazor.masastack.com/getting-started/installation,一起來往WPF中引入這款Blazor組件庫(kù)吧。
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)添加如下資源:
完整代碼如下:
4.3 引入Masa.Blazor命名空間
打開_Imports.razor
文件,修改如下:
4.4 Razor組件添加Masa.Blazor
打開MainWindow.xaml.cs
,添加一行代碼?serviceCollection.AddMasaBlazor();

4.5 嘗試Masa.Blazor案例
上面4步的準(zhǔn)備工作做好后,我們簡(jiǎn)單來使用下Masa.Blazor
組件。
打開Tab組件鏈接:https://blazor.masastack.com/components/tabs,嘗試這個(gè)Demo:

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

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

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

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

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

問題解決`css`代碼:
因?yàn)?code>Razor組件是在BlazorWebView
里渲染的,即BlazorWebView
就是個(gè)小型的瀏覽器呀,上面的樣式即把瀏覽器的滾動(dòng)條寬度設(shè)置為0,它不就沒有了嗎?現(xiàn)在效果如下,是不是舒服了?

添加Masa.Blazor就介紹到這里,本小節(jié)示例代碼在這里WPF中使用Masa.Blazor,下面講解WPF與Blazor混合開發(fā)后多窗體消息通知問題。
5. 多窗體消息通知
一般C/S
窗體之間通信使用委托、事件,而在WPF
開發(fā)中,可以使用一些框架提供的抽象事件訂閱\發(fā)布
組件,比如Prism
的事件聚集器IEventAggregator
,或MvvmLight
的Messager
。在B/S
開發(fā)中,進(jìn)程內(nèi)事件通知可能就使用MediatR
組件居多了,不論是在C/S
還是B/S
開發(fā),這些組件在一定程度上,各大程序模板可以通用的,更不用說分布式的消息隊(duì)列RabbitMQ
?和?Kafka
是萬能的進(jìn)程間通信標(biāo)準(zhǔn)選擇了。
上面是一些套話,站長(zhǎng)根據(jù)Prism
的事件聚集器和MvvmLight
的Messager源碼閱讀,簡(jiǎn)單封裝了一個(gè)Messager
,可以適用于一般的業(yè)務(wù)需求。
5.1 Messager封裝
本來不想貼代碼直接給源碼鏈接的,想想代碼也不多,直接上吧。
Message
消息抽象類,用于定義消息類型,具體的消息需要繼承該類,比如后面的打開子窗體消息OpenSecondViewMessage
。
IMessenger
消息接口,只定義了三個(gè)接口:
Subscribe:消息訂閱
Unsubscribe:取消消息訂閱
Publish:消息發(fā)送
Messenger
消息的管理,消息中轉(zhuǎn)等實(shí)現(xiàn):
有興趣的看上面的代碼,封裝代碼上面簡(jiǎn)單全部給上。
5.2 代碼整理
第 5 節(jié)涉及到多窗體及多Razor
組件了,需要?jiǎng)?chuàng)建一些目錄存放這些文件,方便分類管理。

A:放Message,即一些消息通知類;
B:放Razor組件,如果需要與Maui\Blazor Server(Wasm)等共享Razor組件,可以創(chuàng)建Razor類庫(kù)存儲(chǔ);
C:放通用服務(wù),這里只放了一個(gè)窗體管理靜態(tài)類,實(shí)際情況可以放Redis服務(wù)、RabbitMQ消息服務(wù)等;
D:放WPF視圖,本示例WPF窗體只是一個(gè)殼,承載BlazorWebView使用;
5.3 示例及代碼說明
先看本示例效果,再給出相關(guān)代碼說明:

圖中有三個(gè)操作:
點(diǎn)擊主窗體A的【+】按鈕,發(fā)送了
OpenSecondViewMessage
消息,打開子窗體B;打開子窗體B后,再點(diǎn)擊主窗體A的【桃心】按鈕,發(fā)送了
SendRandomDataMessage
消息,子窗體B的第二個(gè)TabItem Header顯示了消息傳來的數(shù)字;點(diǎn)擊子窗體B的【安卓】圖標(biāo)按鈕,給主窗體A響應(yīng)了消息
ReceivedResponseMessage
,主窗體收到后彈出一個(gè)對(duì)話框。
三個(gè)消息類定義如下:
除了SendRandomDataMessage
傳遞了一個(gè)業(yè)務(wù)Number
屬性,另兩個(gè)消息只是起到通知作用,實(shí)際開發(fā)時(shí)可能需要傳遞業(yè)務(wù)數(shù)據(jù)。
打開多窗體
即上面的第一個(gè)操作:點(diǎn)擊主窗體A的【+】按鈕,發(fā)送了OpenSecondViewMessage
消息,打開子窗體B。
在RazorViews\MainView.razor
中執(zhí)行按鈕點(diǎn)擊,發(fā)送打開子窗體消息:
在App.xaml.cs
里訂閱打開子窗體消息:
實(shí)際開發(fā)可能情況更復(fù)雜,發(fā)送的消息OpenSecondViewMessage
里帶WPF窗體路由(定義的一套路徑規(guī)則尋找窗體或ViewModel
),訂閱的地方也可能不在主程序,在子模塊的Module
類里。
發(fā)送業(yè)務(wù)數(shù)據(jù)
即第二個(gè)操作:打開子窗體B后,再點(diǎn)擊主窗體A的【桃心】按鈕,發(fā)送了SendRandomDataMessage
消息,子窗體B的第二個(gè)TabItem Header顯示了消息傳來的數(shù)字。
在
RazorViews\MainView.razor
中執(zhí)行按鈕點(diǎn)擊,發(fā)送業(yè)務(wù)消息(就當(dāng)前時(shí)間的Millisecond):
在
RazorViews\SecondView.razor
的OnInitialized()
方法里訂閱業(yè)務(wù)消息通知:
注意看,上面收到消息時(shí)有兩個(gè)方法要簡(jiǎn)單說一下,看OnInitialized()
里的代碼:
InvokeAsync:將Number賦值給變量
tagCount
的代碼是在InvokeAsync
方法里執(zhí)行的,這個(gè)和WPF里的Dispatcher.Invoke
是一個(gè)意思,相當(dāng)于接收數(shù)據(jù)是在子線程,而賦值這個(gè)操作會(huì)即時(shí)的綁定到<MBadge Color="green" Content="tagCount">
上,也需要UI線程同步。StateHasChanged:相當(dāng)于MVVM里的
PropertyChanged
事件通知,通知UI這里有值變化了,請(qǐng)你刷新一下,我要看看最新值。
上面的代碼把子窗體消息回應(yīng)也貼上了,即點(diǎn)擊安卓圖標(biāo)按鈕時(shí)發(fā)送了ReceivedResponseMessage
消息,在主窗體RazorViews\MainView.razor
里也訂閱了這個(gè)消息,和上面的代碼類似:
在OnInitialized()
方法里訂閱消息ReceivedResponseMessage
,收到后將變化_showComfirmDialog
置為true
,即上面對(duì)話框的屬性Visible
綁定的值,同理需要在InvokeAsync()
中處理數(shù)據(jù)接收,也需要調(diào)用StateHasChanged
通知UI數(shù)據(jù)變化。
上面說了部分代碼,可能講的不太清楚,可以看示例源碼:多窗體消息通知。
6. 本文示例
本來想寫完整Demo的,發(fā)現(xiàn)上面把基本要點(diǎn)都拉了一遍,再粘貼一些重復(fù)代碼有點(diǎn)沒完沒了了,有興趣的拉源碼WPF與Blazor混合開發(fā)Demo,下面是項(xiàng)目代碼結(jié)構(gòu)大概:

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

打開子窗口

聊天窗口

演示發(fā)送消息
?

7. Click Once發(fā)布嘗試
上一篇文章鏈接:快速創(chuàng)建軟件安裝包-ClickOnce
8. Q&A
8.1 為啥要在WPF里使用Blazor?吃飽了撐的?
WPF雖然相較Winform做出比較好看的UI相對(duì)容易一些,但比起B(yǎng)lazor,或者直接說html開發(fā)界面,還是差了一點(diǎn)點(diǎn),更何況html的資源更多一點(diǎn),嘗試一下為何不可?
8.2 WPF + Blazor支持哪些操作系統(tǒng)
最低支持Windows 7 SP1吧,有群友已經(jīng)嘗試正常運(yùn)行成功,這是本文示例Click Once安裝頁(yè)面:https://dotnet9.com/WPFBlazorChat
8.3 Blazor 混合開發(fā)還支持哪些已有框架?
Blazor混合開發(fā)的話,除了WPF,還有MAUI(跨平臺(tái)框架,支持平臺(tái)包括Windows\Mac\Linux\Android\iOS等)、Winform(同WPF,只能在Windows平臺(tái)運(yùn)行)等,建議閱讀微軟文檔學(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)鏈接。