C#开发上位机应用的一些选择

如果你不想看介绍,可以直接跳到优雅开发示例那里。

1. WASDK(WinUI 3)

这个WASDK目前是微软主推的开源的,UI部分是结合了WinUI 3。

2. WPF

目前WPF也已经开源,而且整体上更为成熟,Visual Studio就是WPF 4.x开发的,生态也比较好。

3. WinForms

这个也是开源的,Winform算是上手即用的开发框架了,通过拖拉拽可以很轻松的创建出UI和编写对应的功能,对于UI美观程度不太重要的工业领域,这个用来做工具开发很简单,上手也容易。

4. UWP

微软对于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. 做一个上位机应用

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP
上图为应用的展示图,采用的WASDK1.1版本开发,目前已经上架了Windows商店,打包方式为MSIX,目前x64和arm64是分开的MSIX包,文档里提到可以多个MSIX包合成一个集合包,不过我采用上传多个包,让商店自动匹配。

此应用是为稚晖君的ElectronBot开发的第三方的上位机,名字就叫电子脑壳。下图是效果图展示,结合Surface平板,触摸体验良好,个人感觉很优雅。

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

B站演示视频

2. 整体的开发步骤

ElectronBot本身连接电脑采用的是libusb生成的驱动吧,这个不知道叙述的是否正确。

看下图大体能明白电脑和ElectronBot通过高速USB进行连接,当我们驱动安装成功就可以进行操作了。

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

电子脑壳应用=>ElectronBot.DotNet SDK=>LibUsbDotNet

底层调用采用的是LibUsbDotNet这个库进行底层数据传输的操作,我根据稚晖君提供的c++版本的sdk进行了封装。

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

开始创建项目前最好安装下Template Studio for WinUI

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

2.1 创建项目

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

选择模板进行创建,可以根据需要进行选择,本人选择如下。
用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

由于ElectronBot .Net SDK本身已经开源,直接以上位机主体应用做讲解。下图为应用的依赖项,主要包含SDK和OpenCV相关的nuget包。

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

应用整体不复杂,通过.Net框架自带的DI容器进行对象生命周期的管理,通过MVVM进行数据的绑定和更新。

结合Win2D和OpenCV进行图形数据处理,然后通过SDK写入到usb设备里进行控制和展示。

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

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代码如下。

用WindowsAppSDK(WASDK)优雅的开发上位机应用-LMLPHP

通过此操作,能够正常显示表盘,数据刷新也能正常使用。

当切换到时钟模式的时候,另外一个定时器会定时抓取表盘并将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

06-28 17:33