使用 ElementUI 组件构建无边框 Window 桌面应用(WinForm/WPF)

生活不可能像你想象得那么好,但也不会像你想象得那么糟。 我觉得人的脆弱和坚强都超乎自己的想象。 有时,我可能脆弱得一句话就泪流满面;有时,也发现自己咬着牙走了很长的路。

——莫泊桑 《一生》

一、技术栈

Vite + Vue3 + TS + ElementUI(plus) + .NET Framework 4.7.2,开发环境为 Win10,VS2019,VS Code。 

二、实现原理

1、WinForm 窗口无边框

设置属性 FormBorderStyle 为 None ,

FormBorderStyle = FormBorderStyle.None;

2、WPF 窗口无边框

设置属性 WindowStyle ="None" ,

WindowStyle = WindowStyle.None;
<Window x:Class="SerialDevTool.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"  Title="MainWindow" WindowStyle ="None"AllowsTransparency="True"WindowState="Normal"WindowStartupLocation="CenterScreen"><Grid></Grid>
</Window> 

3、user32.dll 库

该库包含了一些与用户界面交互相关的函数,其中,ReleaseCapture 函数用于释放鼠标捕获,SendMessage 函数用于向指定的窗口发送消息。

// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-releasecapture?redirectedfrom=MSDN
// 从当前线程中的窗口释放鼠标捕获,并还原正常鼠标输入处理。 捕获鼠标的窗口接收所有鼠标输入,而不考虑光标的位置,但当光标热点位于另一个线程的窗口中时单击鼠标按钮除外。
BOOL ReleaseCapture();// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-sendmessage
// 将指定的消息发送到一个或多个窗口。 SendMessage 函数调用指定窗口的窗口过程,在窗口过程处理消息之前不会返回。
LRESULT SendMessage([in] HWND   hWnd,[in] UINT   Msg,[in] WPARAM wParam,[in] LPARAM lParam
);

4、自定义窗口拖拽实现

引入 user32.dll 库,监听界面上某区域的鼠标事件,触发鼠标事件后,通过 ReleaseCapture 函数释放当前鼠标捕获并还原正常鼠标输入处理,由 SendMessage 函数实现当前窗口的移动过程。

5、Chromium Embedded Framework

通过 CefSharp 库内嵌一个浏览器控件到 DotNet 窗口应用中。

6、接收 Javascript 信息

ChromiumWebBrowser 类提供了 JavascriptMessageReceived 方法,

//
// 摘要:
//     Event handler that will get called when the message that originates from CefSharp.PostMessage
public event EventHandler<JavascriptMessageReceivedEventArgs> JavascriptMessageReceived;

三、TitleBar 样式设计与实现

1、布局

左边三个按钮分别触发最小化、最大/正常化、关闭窗口,标题居中,

2、代码实现

// app\src\components\TitleBarSimple.vue
<template><div class="common grid-content"><div class="common my-button"><el-button id="minimizedButton" @click="minimizedWindow" type="danger" circle /><el-button id="normalizedButton" @click="normalizedWindow" type="primary" circle /><el-button id="closeButton" @click="closeWindow" type="default" circle /></div><div @mousedown="handleMouseDown" class="common my-title-bar" id="my-title"><div> <el-text tag="b">{{mytitle}}</el-text> </div></div></div>
</template><script lang="ts" setup>const mytitle:string = 'Awesome Application 版本 1.0.0.0(开发版本) (64 位)'/**最小化窗口 */
const minimizedWindow = () => {const ret = { type: 'minimized' };CefSharp.PostMessage(ret);
}/**关闭窗口 */
const closeWindow = () => {const ret = { type: 'close' };CefSharp.PostMessage(ret);
}/**最大/正常窗口 */
const normalizedWindow = () => {const ret = { type: 'normalized' };CefSharp.PostMessage(ret);
}/**鼠标左键点击事件 */
const handleMouseDown = (event: any) => {// 检查是否是鼠标左键点击事件if (event.button === 0) {const ret = { type: 'mousedown' };CefSharp.PostMessage(ret);}
}</script><style lang="scss">
/* cnpm install -D sass */.el-row {margin-bottom: 20px;
}.el-row:last-child {margin-bottom: 0;
}.el-col {border-radius: 4px;
}.el-button.is-circle {width: 10px;height: 10px;border-radius: 50%;padding: 8px;
}.common {display: flex;/* 水平居中 */justify-content: center; /* 垂直居中 */align-items: center; 
}.grid-content {min-height: 30px;margin-bottom: 5px;background: #FAFAFA;
}.my-button {padding-left: 5px;width: 105px;
}.my-title-bar {width: calc(100% - 105px);
}</style>

四、WinForm 无边框实现

1、新建一个窗口项目

将默认的 Form1 重命名为 MainForm,安装 CefSharp 库 ,这里使用的版本是 119.1.20,

CefSharp.WinForms

2、初始化窗口配置

/// <summary>/// 设置基础样式/// </summary>private void InitWinFormStyle(){// 无边框FormBorderStyle = FormBorderStyle.None;// 窗口大小Size = new Size(1280, 720);// 启动位置StartPosition = FormStartPosition.CenterScreen;}

3、在 UI 线程上异步执行 Action

using System;
using System.Windows.Forms;namespace MyWinFormApp.Controls
{public static class ControlExtensions{/// <summary>/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread./// </summary>/// <param name="control">the control for which the update is required</param>/// <param name="action">action to be performed on the control</param>public static void InvokeOnUiThreadIfRequired(this Control control, Action action){//If you are planning on using a similar function in your own code then please be sure to//have a quick read over https://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed//No actionif (control.Disposing || control.IsDisposed || !control.IsHandleCreated){return;}if (control.InvokeRequired){control.BeginInvoke(action);}else{action.Invoke();}}}
}

4、窗口拖拽实现

        [DllImport("user32.dll")]public static extern bool ReleaseCapture();[DllImport("user32.dll")]public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int IParam);/// <summary>/// 系统命令/// </summary>public const int WM_SYSCOMMAND = 0x0112;/// <summary>/// 移动窗口的系统命令/// </summary>public const int SC_MOVE = 0xF010;/// <summary>/// 鼠标位于窗口的标题栏上/// </summary>public const int HTCAPTION = 0x0002;/// <summary>/// 无边框窗口拖拽/// SC_MOVE + HTCAPTION 是将移动窗口的命令与标题栏的点击组合起来,以便在拖动标题栏时移动窗口/// 当用户在当前窗口按住鼠标左键并拖动时,鼠标位置会被识别为位于标题栏上,从而触发移动窗口的操作/// </summary>private void DragNoneBorderWindows(){this.InvokeOnUiThreadIfRequired(() => {ReleaseCapture();SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);});}

5、接收并处理 Javascript 信息

        private void MouseDownJavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e){if (e.Message != null){dynamic ret = e.Message;switch (ret.type){case "mousedown":{DragNoneBorderWindows();break;}case "minimized":{ChangeWindowState("minimized");break;}case "close":{ChangeWindowState("close");break;}case "normalized":{ChangeWindowState("normalized");break;}default: break;}};}/// <summary>/// 处理窗口状态:最大化/正常化/最小化/关闭/// </summary>/// <param name="type"></param>private void ChangeWindowState(string type){this.InvokeOnUiThreadIfRequired(() => {if (type.Equals("minimized")){this.WindowState = FormWindowState.Minimized;return;}if (type.Equals("maximized")){this.WindowState = FormWindowState.Maximized;return;}if (type.Equals("normalized")){if(this.WindowState == FormWindowState.Normal){this.WindowState = FormWindowState.Maximized;return;}this.WindowState = FormWindowState.Normal;return;}if (type.Equals("close")){this.DoCloseWindows();return;}});}/// <summary>/// 关闭窗口/// </summary>public void DoCloseWindows(){this.InvokeOnUiThreadIfRequired(() =>{browser.Dispose();Cef.Shutdown();Close();});}

6、添加浏览器控件

        /// <summary>/// Create a new instance in code or add via the designer/// </summary>private void AddChromiumWebBrowser(){browser = new ChromiumWebBrowser("http://localhost:5173/");// 消息接收事件browser.JavascriptMessageReceived += MouseDownJavascriptMessageReceived;this.Controls.Add(browser);}

7、配置 CEF

using CefSharp;
using CefSharp.WinForms;
using System;
using System.IO;
using System.Windows.Forms;namespace MyWinFormApp
{static class Program{/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(){InitCefSettings();Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new MainForm());}private static void InitCefSettings(){#if ANYCPUCefRuntime.SubscribeAnyCpuAssemblyResolver();
#endif// Pseudo code; you probably need more in your CefSettings also.var settings = new CefSettings(){//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist dataCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")};//Example of setting a command line argument//Enables WebRTC// - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access// - CEF Doesn't currently support displaying a UI for media access permissions////NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restartsettings.CefCommandLineArgs.Add("enable-media-stream");//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-streamsettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");//For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180)settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");//Perform dependency check to make sure all relevant resources are in our output directory.Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);}}
}

8、运行效果

五、WPF 无边框实现

1、新建一个窗口项目

安装 CefSharp 库 ,这里使用的版本是 119.1.20,

2、初始化窗口配置

<Window x:Class="MyWpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:MyWpfApp"WindowStyle ="None"Width="960"Height="540"WindowStartupLocation="CenterScreen"mc:Ignorable="d"Title="MainWindow"><Grid><Grid x:Name="ContentGrid"></Grid></Grid>
</Window>
        /// <summary>/// 设置基础样式/// </summary>private void InitWindowsStyle(){// 无边框WindowStyle = WindowStyle.None;// 窗口大小Width = 960;Height = 540;// 启动位置WindowStartupLocation = WindowStartupLocation.CenterScreen;}

3、在 UI 线程上异步执行 Action

using System;
using System.Windows.Threading;namespace MyWpfApp.Dispatchers
{public static class DispatcherExtensions{/// <summary>/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread./// </summary>/// <param name="dispatcher">the dispatcher for which the update is required</param>/// <param name="action">action to be performed on the dispatcher</param>public static void InvokeOnUiThreadIfRequired(this Dispatcher dispatcher, Action action){if (dispatcher.CheckAccess()){action.Invoke();}else{dispatcher.BeginInvoke(action);}}}
}

4、窗口拖拽实现

        [DllImport("user32.dll")]public static extern bool ReleaseCapture();[DllImport("user32.dll")]public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int IParam);/// <summary>/// 系统指令/// </summary>public const int WM_SYSCOMMAND = 0x0112;/// <summary>/// 鼠标移动/// </summary>public const int SC_MOVE = 0xF010;public const int HTCAPTION = 0x0002;/// <summary>/// 无边框窗口拖拽/// </summary>private void DragNoneBorderWindows(){Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() => {ReleaseCapture();SendMessage(new WindowInteropHelper(this).Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);});}

5、接收并处理 Javascript 信息

private void MouseDownJavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e){if (e.Message != null){dynamic ret = e.Message;switch (ret.type){case "mousedown":{DragNoneBorderWindows();break;}case "minimized":{ChangeWindowState("minimized");break;}case "close":{ChangeWindowState("close");break;}case "normalized":{ChangeWindowState("normalized");break;}default: break;}};}/// <summary>/// 处理窗口状态:最大化/正常化/最小化/关闭/// </summary>/// <param name="type"></param>private void ChangeWindowState(string type){Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() => {if (type.Equals("minimized")){this.WindowState = WindowState.Minimized;return;}if (type.Equals("maximized")){this.WindowState = WindowState.Maximized;return;}if (type.Equals("normalized")){if (this.WindowState == WindowState.Normal){this.WindowState = WindowState.Maximized;return;}this.WindowState = WindowState.Normal;return;}if (type.Equals("close")){this.DoCloseWindows();return;}});}/// <summary>/// 关闭窗口/// </summary>public void DoCloseWindows(){Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() =>{browser.Dispose();Close();});}

6、添加浏览器控件

        /// <summary>/// Create a new instance in code or add via the designer/// </summary>private void AddChromiumWebBrowser(){// browser = new ChromiumWebBrowser("http://localhost:5173/");browser = new ChromiumWebBrowser("http://http://mywpf.test");// 消息接收事件browser.JavascriptMessageReceived += MouseDownJavascriptMessageReceived;this.ContentGrid.Children.Add(browser);}

7、打包前端

npm run build

将打包后的 dist 文件夹下所有文件复制到 Fontend 文件夹,

8、配置 CEF

using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.Wpf;
using System;
using System.IO;
using System.Windows;namespace MyWpfApp
{/// <summary>/// App.xaml 的交互逻辑/// </summary>public partial class App : Application{/// <summary>/// 重写退出方法/// </summary>/// <param name="e"></param>protected override void OnExit(ExitEventArgs e){base.OnExit(e);// 关闭 CefSharpCef.Shutdown();}/// <summary>/// 重写启动方法/// </summary>/// <param name="e"></param>protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);// 初始化 CEFInitCefSettings();}/// <summary>/// 初始化 CEF 配置/// </summary>private static void InitCefSettings(){#if ANYCPUCefRuntime.SubscribeAnyCpuAssemblyResolver();
#endif// Pseudo code; you probably need more in your CefSettings also.var settings = new CefSettings(){//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist dataCachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")};//Example of setting a command line argument//Enables WebRTC// - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access// - CEF Doesn't currently support displaying a UI for media access permissions////NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restartsettings.CefCommandLineArgs.Add("enable-media-stream");//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-streamsettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");//For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180)settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");// 本地代理域settings.RegisterScheme(new CefCustomScheme{SchemeName = "http",DomainName = "mywpf.test",SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\MyWpfApp\Frontend",hostName: "mywpf.test", //Optional param no hostname/domain checking if nulldefaultPage: "index.html") //Optional param will default to index.html});//Perform dependency check to make sure all relevant resources are in our output directory.Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);}}
}

9、运行效果

参考资料

基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读493次。基于 .Net CEF 库,能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.csdn.net/weixin_47560078/article/details/133974513一个 Vue 3 UI 框架 | Element PlusA Vue 3 based component library for designers and developersicon-default.png?t=N7T8https://element-plus.gitee.io/zh-CN/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/291724.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python字符串处理全攻略(一):常用内置方法轻松掌握

文章目录 引言Python字符串常用内置方法str.capitalize()语法示例运行结果注意事项 str.upper()语法示例注意事项 str.lower()语法示例注意事项 str.center()语法示例注意事项 str.count()语法示例注意事项 str.endswith()语法示例注意事项 str.find()语法示例注意事项 结束语 …

单片机原理及应用:Keil μVision4和Proteus 8的配置介绍

笔者所在的专业最近开设了单片机课程&#xff0c;对笔者而言&#xff0c;虽然之前有一定的代码基础。但还是第一次面对既要求代码架构又要求电路仿真的领域。为了巩固知识和增强记忆&#xff0c;特此创建了这个专栏&#xff0c;谨以一名非电专业初学者的身份记录和分享知识。 …

[node]Node.js 中REPL简单介绍

[node]Node.js 中REPL简单介绍 什么是REPL为什么使用REPL如何使用REPL 命令REPL模式node的全局内容展示node全局所有模块查看全局模块具体内容其它命令 实践 什么是REPL Node.js REPL(Read Eval Print Loop:交互式解释器) 表示电脑的环境&#xff0c;类似 Windows 系统的终端或…

BDD - Python Behave 入门

BDD - Python Behave 入门 Behave 是什么Behave 的主要特点和组成部分Behave 实践安装 BehaveBehave 项目目录结构创建项目创建 Feature 文件创建步骤定义文件 执行用例执行全部用例执行部分用例 生成报告生成 Json report生成 HTML 报告生成 Junit report生成 Cucumber report…

大数据技术基础-读书笔记

大数据技术基础-读书笔记 一、大数据概述 大数据是指在一定时间内无法用常规软件工具对其内容进行抓取、处理、分析和管理的数据集合。 大数据一般会涉及两种以上的数据形式&#xff0c;数据量通常是100TB以上的高速、实时数据流&#xff0c;或者从每年增长速度快的小数据开…

【Docker-5】镜像编排

Dockerfile语法 制作apache镜像 httpd.service 文件路径&#xff1a;/lib/systemd/system/httpd.service [rootdocker-0002 ~]# mkdir apache [rootdocker-0002 ~]# cd apache拷贝动态页面到docker-0002的/root/apache/ [rootecs-proxy ~]# scp /root/5/public/info.php 192.…

js中的Array.from()和Array.of()方法的用法详情

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

Spring Boot学习随笔- 拦截器实现和配置(HandlerInterceptor、addInterceptors)、jar包部署和war包部署

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第十三章、拦截器 拦截器 &#xff1a;Interceptor 拦截 中断 类似于javaweb中的Filter&#xff0c;不过没有Filter那么强大 作用 Spring MVC的拦截器是一种用于在请求处理过程中进行预处理和后处理的机制。拦…

C# SQLite基础工具类

目录 1、安装System.Data.SQLite工具包 2、创建数据库 3、数据库的连接与断开 4、执行一条SQL语句 5、批量执行sql语句 6、返回首行首列值 7、执行sql语句返回datatable 1、安装System.Data.SQLite工具包 2、创建数据库 /// <summary> /// 数据库路径 …

量化交易学习笔记:XGBoost 在量化选股中的应用

一、引言 本篇文章通过借鉴传统机器学习算法——XGBoost——对相同的量价因子进行实验&#xff0c;方便与深度学习模型进行对比实践。 二、算法介绍 XGBoost 是在 Gradient Boosting&#xff08;梯度提升&#xff09;框架下实现的机器学习算法&#xff0c;全称为“极限梯度提…

文献速递:生成对抗网络医学影像中的应用—— CG-3DSRGAN:用于从低剂量PET图像恢复图像质量的分类指导的3D生成对抗网络

文献速递&#xff1a;生成对抗网络医学影像中的应用—— CG-3DSRGAN&#xff1a;用于从低剂量PET图像恢复图像质量的分类指导的3D生成对抗网络 本周给大家分享文献的主题是生成对抗网络&#xff08;Generative adversarial networks, GANs&#xff09;在医学影像中的应用。文献…

Sentinel 流量治理组件教程

前言 官网首页&#xff1a;home | Sentinel (sentinelguard.io) 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形…