MAUI 初体验 联合 WinForm 让家里废弃的手机当做电脑副品用起来

软件效果图

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

软件架构草图

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

效果解释:运行 winform 端后 使用 ctrl+c 先复制任何词语,然后ctrl+空格 就可以将翻译结果显示在 安卓,IOS,windows 甚至 mac 任意客户端

1:使用 VS2022 + net6 创建 MAUI 项目,创建不了的先安装

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

2:创建时使用 .NET MAUI 应用,应用名字自己定义,我这里用 MauiAppClient

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

3:创建后的项目图

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

4:添加项目引用

CommunityToolkit.Mvvm MAUI 的官方 MVVM 库,可以很方便的让C#像VUE那样简单的使用双向绑定
MQTTnet 这里我们 MAUI 客户端和服务端之间使用 MQTT 协议来通信
Newtonsoft.Json 这个不解释,不引用也可以直接使用官方的 System.Text.Json

5:打开 MainPage.xaml 将文件修改为一下内容,MainPage.xaml内容为APP软件主程序窗口页面内容

`<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"              x:Class="MauiAppClient.MainPage"              xmlns:viewmodel="clr-namespace:MauiAppClient.ViewModel"              x:DataType="viewmodel:MainPageModel"              BackgroundColor="Black"              >      <ScrollView>         <StackLayout Margin="20,35,20,25">              <Label                     x:Name="LabelTips"                     Text="{Binding StrTips}"                     FontSize="12"                     TextColor="Red"                     Margin="15"                     />              <Label                     x:Name="LabelKey"                     Text="{Binding StrKey}"                     FontSize="32"                     TextColor="GreenYellow"                     Margin="15"                     />              <Label                     x:Name="LabelValue1"                     Text="{Binding StrValue1}"                     FontSize="26"                     TextColor="#08e589"                     Margin="15"                     />              <Label                     x:Name="LabelValue2"                     Text="{Binding StrValue2}"                     FontSize="26"                     TextColor="#08e589"                     Margin="15"                     />         </StackLayout>     </ScrollView>  </ContentPage> ` 

6:创建页面绑定视图模型 有不熟悉 CommunityToolkit.Mvvm 组件的同学建议先查阅下此组件的使用方式。加好模型后记得注入一下 builder.Services.AddSingleton();

    public partial class MainPageModel : ObservableObject     {         [ObservableProperty]         public string strTips;          [ObservableProperty]         public string strKey;          [ObservableProperty]         public string strValue1;          [ObservableProperty]         public string strValue2;     } 

7:MAUI 程序主代码

        public static IMqttClient _mqttClient;         public MainPageModel _vm;         public MainPage(MainPageModel vm)         {             InitializeComponent();             _vm = vm;             BindingContext = _vm;             MqttInit();         }          /// <summary>         /// 初始化MQTT         /// </summary>         public void MqttInit()         {             string clientId = Guid.NewGuid().ToString();             var optionsBuilder = new MqttClientOptionsBuilder()                 .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号                                                         //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码                 .WithClientId(clientId) // 设置客户端id                 .WithCleanSession()                 .WithTls(new MqttClientOptionsBuilderTlsParameters                 {                     UseTls = false  // 是否使用 tls加密                 });              var clientOptions = optionsBuilder.Build();             _mqttClient = new MqttFactory().CreateMqttClient();             _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件             _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件             _mqttClient.ApplicationMessageReceivedAsync += _mqttClient_ApplicationMessageReceivedAsync; // 收到消息事件             _mqttClient.ConnectAsync(clientOptions);         }          /// <summary>         /// 客户端连接关闭事件         /// </summary>         /// <param name="arg"></param>         /// <returns></returns>         private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)         {             _vm.StrTips = "已断开与服务端的连接";             int i = 0;             Task.Factory.StartNew(() =>             {                 while (_mqttClient == null || _mqttClient.IsConnected == false)                 {                     i++;                     Thread.Sleep(5 * 1000);                     MqttInit();                     _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);                     _vm.StrTips = "尝试重新连..." + i;                 }             }).ConfigureAwait(false);              return Task.CompletedTask;         }          /// <summary>         /// 客户端连接成功事件         /// </summary>         /// <param name="arg"></param>         /// <returns></returns>         private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)         {             _vm.StrTips = "已连接服务端";             _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);              return Task.CompletedTask;         }         /// <summary>         /// 收到消息事件         /// </summary>         /// <param name="arg"></param>         /// <returns></returns>         private Task _mqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)         {             string msg = Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);             var model = Newtonsoft.Json.JsonConvert.DeserializeObject<MsgModel>(msg);             _vm.StrKey = "词语:" + model.query?.ToString();              string value1 = "翻译:";             if (model.translation != null)             {                 foreach (var item in model.translation)                 {                     value1 += item;                 }             }             _vm.StrValue1 = value1;              string value2 = "解释:";             if (model.basic != null && model.basic.explains != null)             {                 foreach (var item in model.basic.explains)                 {                     value2 += item;                 }             }              _vm.StrValue2 = value2;             return Task.CompletedTask;         }          public void Publish(string data)         {             var message = new MqttApplicationMessage             {                 Topic = "pc-helper",                 Payload = Encoding.Default.GetBytes(data),                 QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,                 Retain = true  // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。             };             _mqttClient.PublishAsync(message);         } 

MAUI 发布 APK 供手机下载安装使用 右键 MAUI 项目>使用终端 输入如下命令

keytool -genkey -v -keystore myapp.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000

项目文件中增加如下配置

<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">     <AndroidKeyStore>True</AndroidKeyStore>     <AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>     <AndroidSigningKeyAlias>key</AndroidSigningKeyAlias>     <AndroidSigningKeyPass></AndroidSigningKeyPass>     <AndroidSigningStorePass></AndroidSigningStorePass> </PropertyGroup> 

发布

dotnet publish -f:net6.0-android -c:Release /p:AndroidSigningKeyPass=youpwd /p:AndroidSigningStorePass=youpwd 替换命令中的 youpwd

参考官方 发布 文章 https://learn.microsoft.com/zh-cn/dotnet/maui/android/deployment/publish-cli

8:创建 winform 程序 使用 .net framework 4.7 主要使用全局快捷方式 需要用到 user32.dll 主程序代码

 public partial class MainForm : Form     {         int crtlSpace;//定义快捷键         public static IMqttClient _mqttClient;         public MainForm()         {             InitializeComponent();             crtlSpace = "CtrlSpace".GetHashCode();              //组合键模式 None = 0,Alt = 1,Ctrl = 2,Shift = 4,WindowsKey = 8             Win32Api.RegisterHotKey(this.Handle, crtlSpace, 2, (int)Keys.Space);         }          /// <summary>         /// 热键         /// </summary>         /// <param name="m"></param>         protected override void WndProc(ref Message m)         {             const int WM_HOTKEY = 0x0312;             int wParam = (int)m.WParam;             switch (m.Msg)             {                 case WM_HOTKEY:                     if (wParam == crtlSpace)                     {                         var text = Clipboard.GetText();                         string result= YouDao.GetFanYiResult(text);                         Publish(result);                         AppentTextLog("触发:【" + text + "】->" + System.DateTime.Now);                     }                     break;             }             base.WndProc(ref m);         }          /// <summary>         /// 隐藏窗体         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void button_closed_Click(object sender, EventArgs e)         {             this.Visible = false;         }          private void MainForm_Load(object sender, EventArgs e)         {             MqttInit();         }          /// <summary>         /// 窗口拖拽         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void MainForm_MouseDown(object sender, MouseEventArgs e)         {             Win32Api.ReleaseCapture();             Win32Api.SendMessage(this.Handle, Win32Api.WM_SYSCOMMAND, Win32Api.SC_MOVE + Win32Api.HTCAPTION, 0);         }          /// <summary>         /// 双击右下角图标显示隐藏         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)         {             if (e.Button == MouseButtons.Left)             {                 this.Visible = !this.Visible;             }         }          /// <summary>         /// 右键右下角图标退出程序         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)         {             //卸载注册热键             Win32Api.UnregisterHotKey(this.Handle, this.crtlSpace);             //关闭窗口             this.Close();         }          /// <summary>         /// 初始化MQTT         /// </summary>         public void MqttInit()         {             string clientId=Guid.NewGuid().ToString();             var optionsBuilder = new MqttClientOptionsBuilder()                 .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号                                                         //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码                 .WithClientId(clientId) // 设置客户端id                 .WithCleanSession()                 .WithTls(new MqttClientOptionsBuilderTlsParameters                 {                     UseTls = false  // 是否使用 tls加密                 });              var clientOptions = optionsBuilder.Build();             _mqttClient = new MqttFactory().CreateMqttClient();             _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件             _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件             _mqttClient.ConnectAsync(clientOptions);         }          /// <summary>         /// 客户端连接关闭事件         /// </summary>         /// <param name="arg"></param>         /// <returns></returns>         private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)         {             AppentTextLog($"MQTT服务已关闭");             return Task.CompletedTask;         }          /// <summary>         /// 客户端连接成功事件         /// </summary>         /// <param name="arg"></param>         /// <returns></returns>         private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)         {             AppentTextLog($"MQTT服务已连接^v^");              _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);              return Task.CompletedTask;         }          /// <summary>         /// 发送MQTT消息         /// </summary>         /// <param name="data"></param>         public void Publish(string data)         {             var message = new MqttApplicationMessage             {                 Topic = "pc-helper",                 Payload = Encoding.UTF8.GetBytes(data),                 QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,                 Retain = true  // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。             };             _mqttClient.PublishAsync(message);         }          /// <summary>         /// 更新窗体文本         /// </summary>         /// <param name="text"></param>         void AppentTextLog(string text)         {             Action act = delegate ()             {                 textBox_log.AppendText(Environment.NewLine + text + Environment.NewLine);                 textBox_log.ScrollToCaret();                  if (textBox_log.Text.Count() > 100000)                 {                     textBox_log.Clear();                 }             };             this.Invoke(act);         }     } 

9:翻译API这里使用了“有道”

        public static string GetFanYiResult(string text)         {             Dictionary<String, String> dic = new Dictionary<String, String>();             string url = "https://openapi.youdao.com/api";             string appKey = "xxx";             string appSecret = "xxx";             string salt = Guid.NewGuid().ToString();             dic.Add("from", "auto");             dic.Add("to", "zh-CHS");             dic.Add("signType", "v3");             TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));             long millis = (long)ts.TotalMilliseconds;             string curtime = Convert.ToString(millis / 1000);             dic.Add("curtime", curtime);             string signStr = appKey + Truncate(text) + salt + curtime + appSecret;             string sign = ComputeHash(signStr, new SHA256CryptoServiceProvider());             dic.Add("q", text);             dic.Add("appKey", appKey);             dic.Add("salt", salt);             dic.Add("sign", sign);             return Post(url, dic);         } 

10: 到这里两个端都有了,就差最关键的 MQTT 服务了。MQTTnet 组件本身是可以起身起一个服务供软件使用的(有兴趣的同学可以自己扩展),这里我们直接使用 docker 线上部署一个 mqtt 服务 (emqx,感兴趣的同学可以查阅此工具,自带web管理)

docker run -dit --restart=always -d --name emqx -e EMQX_HOST="127.0.0.1" -e EMQX_NAME="emqx"  -p 4369:4369 -p 4370:4370 -p 5369:5369 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 0.0.0.0:1883:1883 -p 0.0.0.0:18083:18083 -p 0.0.0.0:9981:8081 emqx/emqx:latest; 

MAUI 初体验 联合 WinForm  让家里废弃的手机当做电脑副品用起来

11 一篇文章不表述不了多少东西,也不能让许多同学尝鲜,这里我放出开源地址供同学们把玩

https://gitee.com/diystring/pchelper

注意:开源代码中的IP地址和有道的ID秘钥都是入门级配置和有免费额度限制的,所以同学们把玩的时候不用恶搞哈,尽量让更多的同学能下载F5尝鲜

发表评论

评论已关闭。

相关文章