3

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

 1 year ago
source link: https://www.cnblogs.com/rtwo/p/16848374.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

软件效果图

image

软件架构草图

image

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

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

image

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

image

3:创建后的项目图

image

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;
image

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

https://gitee.com/diystring/pchelper

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK