• 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,谢谢

  • 相关阅读:
    java回调函数这样说,应该明确了吧!
    Aamazon Web Service EC2 Ubuntu 新建用户而且用ssh连接host
    Html animation by css(Sequence Js Tutorial)
    shell脚本一键安装mysql5.7.x
    HDU 4544 湫湫系列故事――消灭兔子
    简明python教程 --C++程序员的视角(八):标准库
    简明python教程 --C++程序员的视角(七):异常
    简明python教程 --C++程序员的视角(六):输入输出IO
    简明python教程 --C++程序员的视角(五):面向对象的编程
    简明python教程 --C++程序员的视角(四):容器类型(字符串、元组、列表、字典)和参考
  • 原文地址:https://www.cnblogs.com/haley24/p/12221227.html
Copyright © 2020-2023  润新知