软件效果图

软件架构草图

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

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

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

3:创建后的项目图

4:添加项目引用

CommunityToolkit.Mvvm MAUI 的官方 MVVM 库,可以很方便的让C#像VUE那样简单的使用双向绑定

MQTTnet 这里我们 MAUI 客户端和服务端之间使用 MQTT 协议来通信

Newtonsoft.Json 这个不解释,不引用也可以直接使用官方的 System.Text.Json

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

  1. `<?xml version="1.0" encoding="utf-8" ?>
  2. <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  4. x:Class="MauiAppClient.MainPage"
  5. xmlns:viewmodel="clr-namespace:MauiAppClient.ViewModel"
  6. x:DataType="viewmodel:MainPageModel"
  7. BackgroundColor="Black"
  8. >
  9. <ScrollView>
  10. <StackLayout Margin="20,35,20,25">
  11. <Label
  12. x:Name="LabelTips"
  13. Text="{Binding StrTips}"
  14. FontSize="12"
  15. TextColor="Red"
  16. Margin="15"
  17. />
  18. <Label
  19. x:Name="LabelKey"
  20. Text="{Binding StrKey}"
  21. FontSize="32"
  22. TextColor="GreenYellow"
  23. Margin="15"
  24. />
  25. <Label
  26. x:Name="LabelValue1"
  27. Text="{Binding StrValue1}"
  28. FontSize="26"
  29. TextColor="#08e589"
  30. Margin="15"
  31. />
  32. <Label
  33. x:Name="LabelValue2"
  34. Text="{Binding StrValue2}"
  35. FontSize="26"
  36. TextColor="#08e589"
  37. Margin="15"
  38. />
  39. </StackLayout>
  40. </ScrollView>
  41. </ContentPage>
  42. `

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

  1. public partial class MainPageModel : ObservableObject
  2. {
  3. [ObservableProperty]
  4. public string strTips;
  5. [ObservableProperty]
  6. public string strKey;
  7. [ObservableProperty]
  8. public string strValue1;
  9. [ObservableProperty]
  10. public string strValue2;
  11. }

7:MAUI 程序主代码

  1. public static IMqttClient _mqttClient;
  2. public MainPageModel _vm;
  3. public MainPage(MainPageModel vm)
  4. {
  5. InitializeComponent();
  6. _vm = vm;
  7. BindingContext = _vm;
  8. MqttInit();
  9. }
  10. /// <summary>
  11. /// 初始化MQTT
  12. /// </summary>
  13. public void MqttInit()
  14. {
  15. string clientId = Guid.NewGuid().ToString();
  16. var optionsBuilder = new MqttClientOptionsBuilder()
  17. .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号
  18. //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码
  19. .WithClientId(clientId) // 设置客户端id
  20. .WithCleanSession()
  21. .WithTls(new MqttClientOptionsBuilderTlsParameters
  22. {
  23. UseTls = false // 是否使用 tls加密
  24. });
  25. var clientOptions = optionsBuilder.Build();
  26. _mqttClient = new MqttFactory().CreateMqttClient();
  27. _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件
  28. _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件
  29. _mqttClient.ApplicationMessageReceivedAsync += _mqttClient_ApplicationMessageReceivedAsync; // 收到消息事件
  30. _mqttClient.ConnectAsync(clientOptions);
  31. }
  32. /// <summary>
  33. /// 客户端连接关闭事件
  34. /// </summary>
  35. /// <param name="arg"></param>
  36. /// <returns></returns>
  37. private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
  38. {
  39. _vm.StrTips = "已断开与服务端的连接";
  40. int i = 0;
  41. Task.Factory.StartNew(() =>
  42. {
  43. while (_mqttClient == null || _mqttClient.IsConnected == false)
  44. {
  45. i++;
  46. Thread.Sleep(5 * 1000);
  47. MqttInit();
  48. _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);
  49. _vm.StrTips = "尝试重新连..." + i;
  50. }
  51. }).ConfigureAwait(false);
  52. return Task.CompletedTask;
  53. }
  54. /// <summary>
  55. /// 客户端连接成功事件
  56. /// </summary>
  57. /// <param name="arg"></param>
  58. /// <returns></returns>
  59. private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
  60. {
  61. _vm.StrTips = "已连接服务端";
  62. _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);
  63. return Task.CompletedTask;
  64. }
  65. /// <summary>
  66. /// 收到消息事件
  67. /// </summary>
  68. /// <param name="arg"></param>
  69. /// <returns></returns>
  70. private Task _mqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
  71. {
  72. string msg = Encoding.UTF8.GetString(arg.ApplicationMessage.Payload);
  73. var model = Newtonsoft.Json.JsonConvert.DeserializeObject<MsgModel>(msg);
  74. _vm.StrKey = "词语:" + model.query?.ToString();
  75. string value1 = "翻译:";
  76. if (model.translation != null)
  77. {
  78. foreach (var item in model.translation)
  79. {
  80. value1 += item;
  81. }
  82. }
  83. _vm.StrValue1 = value1;
  84. string value2 = "解释:";
  85. if (model.basic != null && model.basic.explains != null)
  86. {
  87. foreach (var item in model.basic.explains)
  88. {
  89. value2 += item;
  90. }
  91. }
  92. _vm.StrValue2 = value2;
  93. return Task.CompletedTask;
  94. }
  95. public void Publish(string data)
  96. {
  97. var message = new MqttApplicationMessage
  98. {
  99. Topic = "pc-helper",
  100. Payload = Encoding.Default.GetBytes(data),
  101. QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
  102. Retain = true // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。
  103. };
  104. _mqttClient.PublishAsync(message);
  105. }

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

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

项目文件中增加如下配置

  1. <PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">
  2. <AndroidKeyStore>True</AndroidKeyStore>
  3. <AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>
  4. <AndroidSigningKeyAlias>key</AndroidSigningKeyAlias>
  5. <AndroidSigningKeyPass></AndroidSigningKeyPass>
  6. <AndroidSigningStorePass></AndroidSigningStorePass>
  7. </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 主程序代码

  1. public partial class MainForm : Form
  2. {
  3. int crtlSpace;//定义快捷键
  4. public static IMqttClient _mqttClient;
  5. public MainForm()
  6. {
  7. InitializeComponent();
  8. crtlSpace = "CtrlSpace".GetHashCode();
  9. //组合键模式 None = 0,Alt = 1,Ctrl = 2,Shift = 4,WindowsKey = 8
  10. Win32Api.RegisterHotKey(this.Handle, crtlSpace, 2, (int)Keys.Space);
  11. }
  12. /// <summary>
  13. /// 热键
  14. /// </summary>
  15. /// <param name="m"></param>
  16. protected override void WndProc(ref Message m)
  17. {
  18. const int WM_HOTKEY = 0x0312;
  19. int wParam = (int)m.WParam;
  20. switch (m.Msg)
  21. {
  22. case WM_HOTKEY:
  23. if (wParam == crtlSpace)
  24. {
  25. var text = Clipboard.GetText();
  26. string result= YouDao.GetFanYiResult(text);
  27. Publish(result);
  28. AppentTextLog("触发:【" + text + "】->" + System.DateTime.Now);
  29. }
  30. break;
  31. }
  32. base.WndProc(ref m);
  33. }
  34. /// <summary>
  35. /// 隐藏窗体
  36. /// </summary>
  37. /// <param name="sender"></param>
  38. /// <param name="e"></param>
  39. private void button_closed_Click(object sender, EventArgs e)
  40. {
  41. this.Visible = false;
  42. }
  43. private void MainForm_Load(object sender, EventArgs e)
  44. {
  45. MqttInit();
  46. }
  47. /// <summary>
  48. /// 窗口拖拽
  49. /// </summary>
  50. /// <param name="sender"></param>
  51. /// <param name="e"></param>
  52. private void MainForm_MouseDown(object sender, MouseEventArgs e)
  53. {
  54. Win32Api.ReleaseCapture();
  55. Win32Api.SendMessage(this.Handle, Win32Api.WM_SYSCOMMAND, Win32Api.SC_MOVE + Win32Api.HTCAPTION, 0);
  56. }
  57. /// <summary>
  58. /// 双击右下角图标显示隐藏
  59. /// </summary>
  60. /// <param name="sender"></param>
  61. /// <param name="e"></param>
  62. private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
  63. {
  64. if (e.Button == MouseButtons.Left)
  65. {
  66. this.Visible = !this.Visible;
  67. }
  68. }
  69. /// <summary>
  70. /// 右键右下角图标退出程序
  71. /// </summary>
  72. /// <param name="sender"></param>
  73. /// <param name="e"></param>
  74. private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
  75. {
  76. //卸载注册热键
  77. Win32Api.UnregisterHotKey(this.Handle, this.crtlSpace);
  78. //关闭窗口
  79. this.Close();
  80. }
  81. /// <summary>
  82. /// 初始化MQTT
  83. /// </summary>
  84. public void MqttInit()
  85. {
  86. string clientId=Guid.NewGuid().ToString();
  87. var optionsBuilder = new MqttClientOptionsBuilder()
  88. .WithTcpServer("xxx.xxx.xxx.xxx", 1883) // 要访问的mqtt服务端的 ip 和 端口号
  89. //.WithCredentials("admin", "123456") // 要访问的mqtt服务端的用户名和密码
  90. .WithClientId(clientId) // 设置客户端id
  91. .WithCleanSession()
  92. .WithTls(new MqttClientOptionsBuilderTlsParameters
  93. {
  94. UseTls = false // 是否使用 tls加密
  95. });
  96. var clientOptions = optionsBuilder.Build();
  97. _mqttClient = new MqttFactory().CreateMqttClient();
  98. _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; // 客户端连接成功事件
  99. _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync; // 客户端连接关闭事件
  100. _mqttClient.ConnectAsync(clientOptions);
  101. }
  102. /// <summary>
  103. /// 客户端连接关闭事件
  104. /// </summary>
  105. /// <param name="arg"></param>
  106. /// <returns></returns>
  107. private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
  108. {
  109. AppentTextLog($"MQTT服务已关闭");
  110. return Task.CompletedTask;
  111. }
  112. /// <summary>
  113. /// 客户端连接成功事件
  114. /// </summary>
  115. /// <param name="arg"></param>
  116. /// <returns></returns>
  117. private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
  118. {
  119. AppentTextLog($"MQTT服务已连接^v^");
  120. _mqttClient.SubscribeAsync("pc-helper", MqttQualityOfServiceLevel.AtLeastOnce);
  121. return Task.CompletedTask;
  122. }
  123. /// <summary>
  124. /// 发送MQTT消息
  125. /// </summary>
  126. /// <param name="data"></param>
  127. public void Publish(string data)
  128. {
  129. var message = new MqttApplicationMessage
  130. {
  131. Topic = "pc-helper",
  132. Payload = Encoding.UTF8.GetBytes(data),
  133. QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
  134. Retain = true // 服务端是否保留消息。true为保留,如果有新的订阅者连接,就会立马收到该消息。
  135. };
  136. _mqttClient.PublishAsync(message);
  137. }
  138. /// <summary>
  139. /// 更新窗体文本
  140. /// </summary>
  141. /// <param name="text"></param>
  142. void AppentTextLog(string text)
  143. {
  144. Action act = delegate ()
  145. {
  146. textBox_log.AppendText(Environment.NewLine + text + Environment.NewLine);
  147. textBox_log.ScrollToCaret();
  148. if (textBox_log.Text.Count() > 100000)
  149. {
  150. textBox_log.Clear();
  151. }
  152. };
  153. this.Invoke(act);
  154. }
  155. }

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

  1. public static string GetFanYiResult(string text)
  2. {
  3. Dictionary<String, String> dic = new Dictionary<String, String>();
  4. string url = "https://openapi.youdao.com/api";
  5. string appKey = "xxx";
  6. string appSecret = "xxx";
  7. string salt = Guid.NewGuid().ToString();
  8. dic.Add("from", "auto");
  9. dic.Add("to", "zh-CHS");
  10. dic.Add("signType", "v3");
  11. TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
  12. long millis = (long)ts.TotalMilliseconds;
  13. string curtime = Convert.ToString(millis / 1000);
  14. dic.Add("curtime", curtime);
  15. string signStr = appKey + Truncate(text) + salt + curtime + appSecret;
  16. string sign = ComputeHash(signStr, new SHA256CryptoServiceProvider());
  17. dic.Add("q", text);
  18. dic.Add("appKey", appKey);
  19. dic.Add("salt", salt);
  20. dic.Add("sign", sign);
  21. return Post(url, dic);
  22. }

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

  1. 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;

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

https://gitee.com/diystring/pchelper

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

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

  1. Knockout.js初体验

    前不久在网上看到一个轻量级MVVM js类库叫Knockout.js,觉得很好奇,搜了一下Knockout.js相关资料,也初体验了一下,顿时感觉这个类库的设计很有意思.接下来就搞清楚什么是Knock ...

  2. ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)

    早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把.同时记录体验的过程.如果你也想按照本文的步骤体验的话,那你得先安 ...

  3. 215.Spring Boot+Spring Security:初体验

    [视频&交流平台] SpringBoot视频:http://t.cn/R3QepWG Spring Cloud视频:http://t.cn/R3QeRZc SpringBoot Shiro视频 ...

  4. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  5. Spring boot集成Rabbit MQ使用初体验

    Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...

  6. .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验

    不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...

  7. Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验

    Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...

  8. Spring之初体验

                                     Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...

  9. Xamarin.iOS开发初体验

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0

随机推荐

  1. Iterator与Generator

    Iterator Iterator 概念 Iterator 提供了一种统一的接口机制,为各种不同数据结构提供统一的访问机制.定义 Iterator 就是提供一个具有 next() 方法的对象,每次调用 ...

  2. iommu系列之---概念解释篇

    本文会对iommu中的一些容易引起疑惑的概念进行阐述,内核版本为4.19. 先上简写: DMAR - DMA remapping DRHD - DMA Remapping Hardware Unit ...

  3. kubernetes之镜像拉取策略ImagePullSecrets;

    1.容器镜像是什么? 1.容器镜像(Container Image)是最终运行的软件: 2.容器镜像(最初为Docker镜像,现在叫OCI镜像更合适)是将软件打包的形式.但是容器镜像还可以携带额外的设 ...

  4. ServletFileUpload 文件上传

    import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.FileUploadExcepti ...

  5. Spring 源码学习笔记11——Spring事务

    Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...

  6. 写给前端的 react-native 入门指南

    前言 本文主要介绍 react-native(下称 RN) 的入门, 和前端的异同点 文章不涉及功能的具体实现 选择优势 我们先说说, 为什么很多人会选择使用 RN .他对应的特性和普通 Web 的区 ...

  7. liunx系统docker部署.net core3.1

    此篇文章演示基本的基于docker部署.netcore服务,liunx系统腾讯云ubuntu,.net core版本3.1. 1.安装docker apt install docker.io 2.拉取 ...

  8. 第九十三篇:ESLint:可组装的javaScript和JSX检查工具

    好家伙, 1.什么是ESLint? 代码检查工具,用来检查你的代码是否符合指定的规范 2.ESLint有什么用? 统一JavaScript代码风格的工具 在合作开发的时候, 每个成员的代码风格都有可能 ...

  9. 第四十八篇:webpack的基本使用(二) --安装和配置webpack-dev-server插件

    好家伙, 1.webpack中的默认约定 默认的打包入口文件为src  -->index.js 默认的输出文件路径为dist -->main.js 既然有默认,那么就说明肯定能改 2.en ...

  10. 跟我学Python图像处理丨何为图像的灰度非线性变换

    摘要:本文主要讲解灰度线性变换,基础性知识希望对您有所帮助. 本文分享自华为云社区<[Python图像处理] 十六.图像的灰度非线性变换之对数变换.伽马变换>,作者:eastmount . ...