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

posted @ 2020-01-21 11:43  冬天从来不怕冷  阅读(404)  评论(0编辑  收藏  举报