MAUI 初体验 联合 WinForm 让家里废弃的手机当做电脑副品用起来
软件效果图
软件架构草图
效果解释:运行 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软件主程序窗口页面内容
`<?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;
11 一篇文章不表述不了多少东西,也不能让许多同学尝鲜,这里我放出开源地址供同学们把玩
注意:开源代码中的IP地址和有道的ID秘钥都是入门级配置和有免费额度限制的,所以同学们把玩的时候不用恶搞哈,尽量让更多的同学能下载F5尝鲜
MAUI 初体验 联合 WinForm 让家里废弃的手机当做电脑副品用起来的更多相关文章
- Knockout.js初体验
前不久在网上看到一个轻量级MVVM js类库叫Knockout.js,觉得很好奇,搜了一下Knockout.js相关资料,也初体验了一下,顿时感觉这个类库的设计很有意思.接下来就搞清楚什么是Knock ...
- ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)
早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把.同时记录体验的过程.如果你也想按照本文的步骤体验的话,那你得先安 ...
- 215.Spring Boot+Spring Security:初体验
[视频&交流平台] SpringBoot视频:http://t.cn/R3QepWG Spring Cloud视频:http://t.cn/R3QeRZc SpringBoot Shiro视频 ...
- 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)
数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...
- Spring boot集成Rabbit MQ使用初体验
Spring boot集成Rabbit MQ使用初体验 1.rabbit mq基本特性 首先介绍一下rabbitMQ的几个特性 Asynchronous Messaging Supports mult ...
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
- Spring之初体验
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...
- Xamarin.iOS开发初体验
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0
随机推荐
- App切换到后台后如何保持持续定位?
为了保护用户隐私,大多数应用只会在前台运行时获取用户位置,当应用在后台运行时,定位功能会被禁止.这就导致APP在后台或者锁屏时无法正常记录GPS轨迹,这对打车.共享出行.跑步等需要实时记录用户轨迹的应 ...
- HCIA-Datacom 3.3 实验三:以太网链路聚合实验
实验介绍 随着网络规模不断扩大,用户对骨干链路的带宽和可靠性提出越来越高的要求.在传统技术中,常用更换高速率的接口板或更换支持高速率接口板的设备的方式来增加带宽,但这种方案需要付出高额的费用,而且不够 ...
- [CF1539F] Strange Array (线段树)
题面 有一个长度为 n \tt n n 的序列 a \tt a a ,对于每一个位置 i ∈ [ 1 , n ] \tt i\in[1,n] i∈[1,n]: 选择一个区间 [ l , r ] \tt ...
- [BZOJ3625][CF438E]小朋友和二叉树 (多项式开根,求逆)
题面 题解 设多项式的第a项为权值和为a的二叉树个数,多项式的第a项表示是否为真,即 则,所以F是三个多项式的卷积,其中包括自己: ,1是F的常数项,即. 我们发现这是一个一元二次方程,可以求出,因为 ...
- KingbaseES V8R3 shared_buffer占用过多导致实例崩溃
背景 有这样一个案例.客户备库意外宕机,从集群日志只看出发生了主备切换,备库一直持续恢复备库没有成功,从数据库日志看到如下报错: terminating connection because of c ...
- spark 读取Geomesa(Hbase)数据
package com.grady.geomesa import org.apache.hadoop.conf.Configuration import org.apache.spark.SparkC ...
- Macos下用pycharm运行django项目死活安装不上mysqlclient怎么办!!??
花了我三天时间,佛了 我刚从win过渡到mac,想着把代码迁移一下. 然后看到依赖里面有一个mysqlclient,然后pip3 install死活装不上 解决方案: 在这里写上这个 然后就好,死了
- 开源即时通讯GGTalk 8.0发布,增加Linux客户端,支持在统信UOS、银河麒麟上运行!
GGTalk在2021年推出7.0后,经过一年多时间的开发,终于推出8.0版本,实现了Linux客户端. 这几年,信创国产化的势头越来越猛,政府事企业单位都在逐步转向使用国产OS.国产CPU.国产数据 ...
- ProxySQL(6):管理后端节点
文章转载自:https://www.cnblogs.com/f-ck-need-u/p/9286922.html 配置后端节点前的说明 为了让ProxySQL能够找到后端的MySQL节点,需要将后端的 ...
- Traefik知识点
Traefik 的各种 Providers Traefik 中的配置发现是通过下面的一些 providers 来实现的. providers 是现有的一些基础架构组件,可以是编排工具,容器引擎,云提供 ...