用WindowsAppSDK(WASDK)優雅的開發上位機應用

C#開發上位機應用的一些選擇

如果你不想看介紹,可以直接跳到優雅開發示例那裡。

1. WASDK(WinUI 3)

Windows 應用 SDK 是一組新的開發人員組件和工具,它們代表著 Windows 應用開發平台的下一步發展。 Windows 應用 SDK 提供一組統一的 API 和工具,可供從 Windows 11 到 Windows 10 版本 1809 上的任何桌面應用以一致的方式使用。

Windows 應用 SDK 不會用 C++ 替換 Windows SDK 或現有桌面 Windows 應用類型,例如 .NET(包括 Windows 窗體和 WPF)和桌面 Win32。 相反,Windows 應用 SDK 使用一組通用 API 來補充這些現有工具和應用類型,開發人員可以在這些平台上依賴這些 API 來執行操作。 有關更多詳細資訊,請參閱Windows 應用 SDK 的優勢。

這個WASDK目前是微軟主推的開源的,UI部分是結合了WinUI 3。

2. WPF

歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢量的呈現引擎,構建用於利用現代圖形硬體。 WPF 提供一套完善的應用程式開發功能,這些功能包括 Extensible Application Markup Language (XAML)、控制項、數據綁定、布局、二維和三維圖形、動畫、樣式、模板、文檔、媒體、文本和版式。 WPF 屬於 .NET,因此可以生成整合 .NET API 其他元素的應用程式。

目前WPF也已經開源,而且整體上更為成熟,Visual Studio就是WPF 4.x開發的,生態也比較好。

3. WinForms

歡迎使用 Windows 窗體的桌面指南,Windows 窗體是一個可創建適用於 Windows 的豐富桌面客戶端應用的 UI 框架。 Windows 窗體開發平台支援廣泛的應用開發功能,包括控制項、圖形、數據綁定和用戶輸入。 Windows 窗體採用 Visual Studio 中的拖放式可視化設計器,可輕鬆創建 Windows 窗體應用。

這個也是開源的,Winform算是上手即用的開發框架了,通過拖拉拽可以很輕鬆的創建出UI和編寫對應的功能,對於UI美觀程度不太重要的工業領域,這個用來做工具開發很簡單,上手也容易。

4. UWP

UWP 是創建適用於 Windows 的客戶端應用程式的眾多方法之一。 UWP 應用使用 WinRT API 來提供強大的 UI 和高級非同步功能,這些功能非常適用於 Internet 連接的設備。

微軟對於UWP,只能說曾經愛過,當初UWP可是當紅炸子雞,號稱跨windows全平台,不過現在也是跨windows全平台,可惜沒搞好,不過雖然不夠受重視,但是一時半會還是死不掉,畢竟WASDK還不夠成熟。

為什麼選擇WASDK

通過上面的介紹,大家對於windows下的原生UI開發框架應該有了一些了解,如果拋開語言限制的話還有更多的選擇,比如QT,各種前端的跨平台,像微軟自己家的MAUI什麼的,我之前還寫了一篇WinUI遷移到即將”過時”的.NET MAUI個人體驗

最近的微軟Windows App SDK 1.1版本發布了,意味著BUG應該少了很多,也可以正式的在一些項目中使用了。通過官方的WinUI庫,我們可以輕鬆的構建符合Win11設計規範的UI,由於UWP的種種問題,WPF和WinForms又是只開源,應該不會有大的新特性了,外加本人以前也經常玩玩UWP,通過前景和自己的喜好,肯定是選擇WASDK了。

優雅開發示例

1. 做一個上位機應用

上位機示例圖
上圖為應用的展示圖,採用的WASDK1.1版本開發,目前已經上架了Windows商店,打包方式為MSIX,目前x64和arm64是分開的MSIX包,文檔里提到可以多個MSIX包合成一個集合包,不過我採用上傳多個包,讓商店自動匹配。

此應用是為稚暉君的ElectronBot開發的第三方的上位機,名字就叫電子腦殼。下圖是效果圖展示,結合Surface平板,觸摸體驗良好,個人感覺很優雅。

實物控制

B站演示影片

2. 整體的開發步驟

ElectronBot本身連接電腦採用的是libusb生成的驅動吧,這個不知道敘述的是否正確。

看下圖大體能明白電腦和ElectronBot通過高速USB進行連接,當我們驅動安裝成功就可以進行操作了。

img

電子腦殼應用=>ElectronBot.DotNet SDK=>LibUsbDotNet

底層調用採用的是LibUsbDotNet這個庫進行底層數據傳輸的操作,我根據稚暉君提供的c++版本的sdk進行了封裝。

目前c#版本的SDKElectronBot.DotNet是開源的,demo示例也是windowsAppSDK的,大家感興趣的可以star一下。

開始創建項目前最好安裝下Template Studio for WinUI

img

2.1 創建項目

img

選擇模板進行創建,可以根據需要進行選擇,本人選擇如下。
img

由於ElectronBot .Net SDK本身已經開源,直接以上位機主體應用做講解。下圖為應用的依賴項,主要包含SDK和OpenCV相關的nuget包。

應用依賴項

應用整體不複雜,通過.Net框架自帶的DI容器進行對象生命周期的管理,通過MVVM進行數據的綁定和更新。

結合Win2D和OpenCV進行圖形數據處理,然後通過SDK寫入到usb設備里進行控制和展示。

軟體整體的實現邏輯

2.2 關鍵點程式碼講解

下面的程式碼是通過切換Combox事件,動態創建不同的錶盤並綁定到MainWindows的控制項上。

private ICommand _clockChangedCommand;
public ICommand ClockChangedCommand => 
    _clockChangedCommand ?? (_clockChangedCommand = new RelayCommand(ClockChanged));

private async void ClockChanged()
{
    var clockName = _clockComboxSelect.DataKey;

    if (!string.IsNullOrWhiteSpace(clockName))
    {
        var viewProvider = _viewProviderFactory.CreateClockViewProvider(clockName);

        Element = viewProvider.CreateClockView(clockName);
    }

    await Task.CompletedTask;
}

public UIElement Element
{
    get => _element;
    set => SetProperty(ref _element, value);
}

xaml程式碼如下。

img

通過此操作,能夠正常顯示錶盤,數據刷新也能正常使用。

當切換到時鐘模式的時候,另外一個定時器會定時抓取錶盤並將xaml轉化成圖片進行傳輸,主要涉及到Win2D庫的使用,程式碼如下。

if (_electron.Connect())
{
    var bitmap = new RenderTargetBitmap();
    await bitmap.RenderAsync(Element);
    var pixels = await bitmap.GetPixelsAsync();

    // Transfer the pixel data from XAML to Win2D for further processing.
    using CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    using CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromBytes(
        canvasDevice, pixels.ToArray(), bitmap.PixelWidth, bitmap.PixelHeight, 
        Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized);

    using IRandomAccessStream stream = new InMemoryRandomAccessStream();

    await canvasBitmap.SaveAsync(stream, CanvasBitmapFileFormat.Png);

    Bitmap image = new Bitmap(stream.AsStream());

    var mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(image);

    var mat1 = mat.Resize(new OpenCvSharp.Size(240, 240), 0, 0, OpenCvSharp.InterpolationFlags.Area);

    var mat2 = mat1.CvtColor(OpenCvSharp.ColorConversionCodes.RGBA2BGR);

    var dataMeta = mat2.Data;

    var data = new byte[240 * 240 * 3];

    Marshal.Copy(dataMeta, data, 0, 240 * 240 * 3);

    await Task.Run(() =>
    {
        if (_electron.Connect())
        {
            _electron.SetImageSrc(data);

            _electron.Sync();
        }
    });


}

上面程式碼通過RenderTargetBitmap和Win2D將Xaml元素轉化成CanvasBitmap,然後再通過OpenCV將canvasBitmap轉化成下位機可識別的位元組數組,通過SDK進行傳輸到下位機。

整體的開發過程和UWP很相似,UI部分用到的很多API都是UWP的改名版本,上位機目前沒有開源,所以只能截取部分程式碼進行講解了,如果想交流大家可以評論區見。

3. 遇到的一些問題

目前Windows App SDK有一些BUG,在我使用的過程中主要發現使用WinRT的串口監聽事件失效,已在github提了bug,回頭應該能夠修復,還有WinRT里的一些API只認UWP UI Api windows.UI開頭的一些對象,還需要大家多使用多回饋,這樣WASDK開發才能良性循環。

public async Task InitAsync()
{
    // Target all Serial Devices present on the system
    var deviceSelector = SerialDevice.GetDeviceSelector();

    var myDevices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(deviceSelector);

    deviceWatcher = DeviceInformation.CreateWatcher(deviceSelector);

    deviceWatcher.Added += new TypedEventHandler<DeviceWatcher, DeviceInformation>(this.OnDeviceAdded);
    deviceWatcher.Removed += new TypedEventHandler<DeviceWatcher, DeviceInformationUpdate>(this.OnDeviceRemoved);

}

上面程式碼註冊的事件,在目前1.1版本的WASDK不生效,官方已經標註為BUG,當然在UWP里就正常,UWP在有些時候還是挺靠譜的嘛。

個人總結感悟

通過這個上位機應用的開發,也是對WASDK和UWP相關技術的使用能熟練一些了,從WPF到UWP再到WASDK和MAUI,XAML相關的開發都是可繼承的,開發方式很相似,對於技術的遷移來說也算是沒什麼障礙吧,經常會聽到很多人說微軟出了這麼多技術,都學不動了什麼的,其實大家掌握內涵,對於新技術的接受還是很快的。

特別鳴謝以及參考推薦文檔

感謝dino.c大佬的一個番茄鍾,因為我的錶盤其實就是抄他番茄鐘的程式碼。

感謝h哥火火給的一些思路。

當然還要感謝超超,畢竟有些程式碼還是抄他的。

參考推薦文檔如下

一個番茄鍾

Win2D samples

opencvsharp

WindowsAppSDK

WindowsCommunityToolkit

ElectronBot

ElectronBot.DotNet

LibUsbDotNet

Tags: