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

綠蔭阿廣發表於2022-06-28

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

相關文章