winform中在业务进程外嵌套一个封皮进程,实现无边框窗体移动、自适应大小
随着时代以及技术的不断发展,仅仅满足业务功能已经不能适应市场客户,因此当前开发越来越注重UI层面的美观与展示,前段时间新来的需求,在业务进程外增加一个进程,当做封皮来进行产品功能信息的展示以及简单操作业务进程。
简介:无边框窗体,实现了窗体移动、自适应大小,使用封皮进程直接开启业务进程并调用业务进程中开启任务的函数,实现了可以在封皮进程中直接新建或打开业务进程中的任务。
主要功能: 点击新建或打开,可以直接打开业务进程的exe,最多可以打开8个业务进程,支持关闭业务进程,如果直接点击关闭按钮关闭封皮进程,那么打开的所有业务进程也会关闭。
代码中主要实现思路是,使用TabControl控件来展示业务进程,上图中的任务1、任务2、任务3就是TabControl控件中的TabPage,通过这种方式来管理多个业务进程。点击新建任务或打开任务,会在click事件中设置好业务进程exe的路径,使用Process.Start()函数启动进程,之后调用WindowsAPI设置业务进程显示的位置以及修改业务进程的父窗体为封皮进程。
private void newBtn_Click(object sender, EventArgs e) { _index++; //最多支持8个任务 if (_index >= 7) { newBtn.Enabled = false; openBtn.Enabled = false; newTask.Enabled = false; openTask.Enabled = false; } #region 增加标签 TabPage tp = new TabPage(); tp.Text = $@"任务{_index + 1}"; tp.Name = $@"btn{_index}"; exeTabControl.TabPages.Add(tp); #endregion //调用业务进程exe string filePath = System.IO.Directory.GetCurrentDirectory() + @"\XXX.exe"; appIdleEvent = new EventHandler(Application_Idle); parentCon = tp; strGUID = "任务" + (_index + 1); _argument = 1; Start(filePath); _clickIndex = _index; //设置选项卡中选中最新创建的选项 exeTabControl.SelectedIndex = _index; //设置关闭按钮有效性 deleteBtn.Enabled = true; //设置封面不可见 this.backgroundImagePanel.Visible = false; }
首先判断了业务进程开启的数量是否为最大值 - 1(要求最多为8个),超出时,设置新建和打开按钮无效;未超出时,向TabControl控件新增加TabPage用来表示新打开的任务,filePath表示的是业务进程exe的存储路径,根据各自的路径修改,开启业务进程的代码主要实在Start()函数中,appIdleEvent是自定义的一个事件,用来将新开启的业务进程显示在封皮进程中,并且设置封皮进程为业务进程的父窗体,代码如下:
public IntPtr Start(string filePath) { try { //实例化开启进程的类ProcessStartInfo 传入的参数是业务进程exe的路径 ProcessStartInfo info = new ProcessStartInfo(filePath); info.UseShellExecute = true; info.CreateNoWindow = false; //传入主线程Main函数中的参数,参数含义是业务进程的宽和高 (自适应大小) info.Arguments = $"{this.exeTabControl.Width - 5},{this.exeTabControl.Height - 5}"; info.WindowStyle = ProcessWindowStyle.Minimized; //全局变量记录开启的进程 m_AppProcess.Add(Process.Start(info)); m_AppProcess[_index].WaitForInputIdle(); //触发事件 Application.Idle += appIdleEvent; } catch (Exception) { if (m_AppProcess.Count > _index && m_AppProcess?[_index] != null) { if (!m_AppProcess[_index].HasExited) m_AppProcess[_index].Kill(); m_AppProcess = null; } } return m_AppProcess[_index].Handle; }
info.Arguments 这段代码是在开启业务进程中,传入参数,在业务进程中的Main函数中使用Main函数的参数string[] args接收即可,winform中的Main函数一般是在Program.cs文件中。这段代码就是开启了业务进程 Process.Start(info) ,并且将开启的业务进程记录到全局变量m_AppProcess中,记录到全局变量主要是为了关闭进程使用。最后触发了appIdleEvent事件来设置业务进程显示的位置以及父窗体,代码如下:
//确保应用程序嵌入到容器中 private void Application_Idle(object sender, EventArgs e) { if (this.m_AppProcess?[_index] == null || this.m_AppProcess[_index].HasExited) { this.m_AppProcess[_index] = null; Application.Idle -= appIdleEvent; return; } while (m_AppProcess[_index].MainWindowHandle == IntPtr.Zero) { System.Threading.Thread.Sleep(100); m_AppProcess[_index].Refresh(); } Application.Idle -= appIdleEvent; EmbedProcess(m_AppProcess[_index], parentCon, 0); }
//将指定的程序嵌入指定的控件 private void EmbedProcess(Process app, Control con, int flag) { if (app == null || app.MainWindowHandle == IntPtr.Zero || con == null) return; //设置窗体展示方式 CurGlobals.ShowWindow(app.MainWindowHandle, (short)5); //设置父窗体 CurGlobals.SetParent(app.MainWindowHandle, con.Handle); SetWindowLong(new HandleRef(this, app.MainWindowHandle), GWL_STYLE, WS_VISIBLE); CurGlobals.SendMessage(app.MainWindowHandle, WM_SETTEXT, IntPtr.Zero, strGUID); CurGlobals.MoveWindow(app.MainWindowHandle, 0, 0, con.Width, con.Height, true); //向业务进程发消息,通知业务进程开启或打开任务 if (flag != -1) { CurGlobals.SENDMESSAGETRANSFERSTRUCT temp = new CurGlobals.SENDMESSAGETRANSFERSTRUCT(); temp.cbData = _argument; temp.dwData = Handle; CurGlobals.SendMessage(app.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref temp); } }
EmbedProcess函数中使用的函数基本上都是WindowsAPI,封装在了CurGlobal.cs文件中
无边框窗体移动代码如下:
#region 窗体移动 private void titlePanel_MouseDown(object sender, MouseEventArgs e) { _mousePos = Cursor.Position; _isMouseDown = true; } private void titlePanel_MouseUp(object sender, MouseEventArgs e) { _isMouseDown = false; this.Focus(); } private void titlePanel_MouseMove(object sender, MouseEventArgs e) { if (!_isMouseDown) return; Point tempPos = Cursor.Position; this.Location = new Point(this.Location.X + (tempPos.X - _mousePos.X), this.Location.Y + (tempPos.Y - _mousePos.Y)); _mousePos = Cursor.Position; } #endregion
在封皮窗体上方,添加一个panel控件,Name为titlePanel,只可以通过titlePanel区域进行窗体的移动。
无边框窗体自适应大小代码如下:
private enum MouseDirection { Horizontal, //水平方向,只改变宽度 Vertical, //垂直方向,只改变高度 Declining, //倾斜方向,同时改变宽和高 None, //未改变 } //是否改变了窗体大小 private bool _isChangeSize; //表示拖动的方向 private MouseDirection _direction = MouseDirection.None; //鼠标移动位置变量 private Point _mouseOff; private void panel1_MouseDown(object sender, MouseEventArgs e) { //记录鼠标位置 _mouseOff = new Point(-e.X, -e.Y); #region 当鼠标的位置处于边缘时,允许改变大小 if (e.Location.X >= this.Width - 10 && e.Location.Y >= this.Height - 10) { _isChangeSize = true; } else if (e.Location.Y >= this.Height - 6) { _isChangeSize = true; } //改变宽度 else if (e.Location.X >= this.Width - 6) { _isChangeSize = true; } else { this.Cursor = Cursors.Arrow; _isChangeSize = false; } #endregion } private void panel1_MouseUp(object sender, MouseEventArgs e) { _isChangeSize = false; _direction = MouseDirection.None; } private void panel1_MouseMove(object sender, MouseEventArgs e) { if (e.Location.X >= this.Width - 10 && e.Location.Y >= this.Height - 10) { this.Cursor = Cursors.SizeNWSE; _direction = MouseDirection.Declining; } //只改变宽度 else if (e.Location.X >= this.Width - 6) { this.Cursor = Cursors.SizeWE; _direction = _direction == MouseDirection.Declining ? MouseDirection.Declining : MouseDirection.Horizontal; } else if (e.Location.Y >= this.Height - 6) { this.Cursor = Cursors.SizeNS; _direction = _direction == MouseDirection.Declining ? MouseDirection.Declining : MouseDirection.Vertical; } else { this.Cursor = Cursors.Arrow; } //改变窗体大小 ResizeWindow(); } private void ResizeWindow() { if (!_isChangeSize) return; //外皮窗体最小为初始大小,为了适应主控主窗体中ReoGrid表格 if (_direction == MouseDirection.Horizontal) { this.Width = MousePosition.X - this.Left >= _formWidth ? MousePosition.X - this.Left : _formWidth; } else if (_direction == MouseDirection.Vertical) { this.Height = MousePosition.Y - this.Top >= _formHeight ? MousePosition.Y - this.Top : _formHeight; } else if (_direction == MouseDirection.Declining) { this.Width = MousePosition.X - this.Left >= _formWidth ? MousePosition.X - this.Left : _formWidth; this.Height = MousePosition.Y - this.Top >= _formHeight ? MousePosition.Y - this.Top : _formHeight; } }
因为业务要求窗体必须由边框,所以无法直接使用截获Windows消息的方式来改变窗体的大小,只能在主窗体上放置一个panel控件,Name为backgroundImagePanel,向该panel控件添加事件 MouseDown、MouseUp、MouseMove,通过这三个事件来判断用户是否改变了窗体,在ResizeWindow函数中进行窗体大小的对应调整,并且在主窗体的Resize事件中刷新窗体,防止改变大小时背景图看起来非常闪烁。
经验尚浅,如有不足之处欢迎指教。
完整项目地址:https://github.com/hhhhhaleyLi/winform-/tree/master
如您感觉不错欢迎Star,谢谢