C# winform MDI窗口的自动吸附贴边

一开始接触C# 项目就打算做这个功能,但是当时水平不够,研究了几天没研究出来,就放弃了。几个月后,写C#代码写的多了,也就得心应手,无师自通了,偶然间又想起这个功能,便小小研究了一下,没想到如同水到渠成一样, 很快就搞出来了。这也给我一些启发,其实能力达到了,很多东西自然而然的就懂了,就像学游泳,小学时候怎么都学不会,高中一下水即使没人教也立刻就会了。

先说一下原理,MDI窗口分为主子窗口,主窗口保存所有子窗口的位置,子窗口移动位置时更新主窗口的记录。子窗口响应鼠标在标题栏的点击事件,更新位置,并遍历主窗口保存的位置表,如果该位置有窗口或者在边缘,就贴上。

 

主窗口:


        //每个窗口的四条边,对应四个MAP。移动窗口时找范围,在每条边的附近就吸附上
        public object m_LocationLock = new object();
        public Dictionary<Guid, int> m_X_LeftLocation = new Dictionary<Guid, int>();
        public Dictionary<Guid, int> m_X_RightLocation = new Dictionary<Guid, int>();
        public Dictionary<Guid, int> m_Y_TopLocation = new Dictionary<Guid, int>();
        public Dictionary<Guid, int> m_Y_DownLocation = new Dictionary<Guid, int>();

子窗口:


        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);//先执行系统的WndProc再说
            Mourse_LocationChanged(ref m);
        }
        private void Form_TotalQuote_SizeChanged(object sender, EventArgs e)
        {

            Form_LocationChanged();
        }

        private void Form_TotalQuote_LocationChanged(object sender, EventArgs e)
        {
            Form_LocationChanged();
        }

 

 /// <summary>
        /// 位置自动变化后 记录位置
        /// </summary>
        public void Form_LocationChanged()
        {
            //有些窗口(如输入交易)打开后大小会变动。因此需要手动调一下此函数
            //拖动窗口大小导致的location变化 也要记录
            //贴边其他窗口
            m_LocatRight = this.Location.X + this.Width;
            m_LocatDown = this.Location.Y + this.Height;
            //四象更新赋值
            lock (m_ThisMainForm.m_LocationLock)
            {
                this.m_ThisMainForm.m_X_LeftLocation[this.m_GUID] = this.Location.X;
                this.m_ThisMainForm.m_X_RightLocation[this.m_GUID] = m_LocatRight;
                this.m_ThisMainForm.m_Y_TopLocation[this.m_GUID] = this.Location.Y;
                this.m_ThisMainForm.m_Y_DownLocation[this.m_GUID] = m_LocatDown;
            }
        }
        /// <summary>
        /// 鼠标拖动导致位置移动,自动吸附
        /// </summary>
        /// <param name="m"></param>
        protected void Mourse_LocationChanged(ref Message m)
        {
            if (m.Msg >= 0x00A1 && m.Msg <= 0x00A9)
            {
                Mourse_LocationChanged();
            }
        }
        //从最小化恢复 会改变位置 导致恢复失败
        public void Mourse_LocationChanged()
        {
            //贴边左上角
            if (this.WindowState != FormWindowState.Normal)
                return;
            m_Park = false;
            int X = this.Location.X;
            //int Right = this.Location.X + this.Size.Width;
            int Y = this.Location.Y;
            //int Down = this.Location.Y + this.Size.Height;
            if (X < 15 && -15 < X)
            { X = 1; m_Park = true; }
            //由于有滚动条的存在,右下的磁性没有意义
            // else if (Right > this.MdiParent.Width - 50 && Right < this.MdiParent.Width + 10)
            //{ X = this.MdiParent.Width - this.Size.Width - 20; m_Park = true; }
            if (Y < 15 && -15 < Y)
            { Y = 0; m_Park = true; }
            // else if (Down > this.MdiParent.Height - 96 && Down < this.MdiParent.Height - 36)//最大化之后任务栏也算高度了
            // { Y = this.MdiParent.Height - this.Size.Height - 66; m_Park = true; }

            //if (m_Park)
            //    m_tmpLocation = new Point(X, Y);
            //停靠左上

            //四象更新赋值
            // Point m_tmpLocation = new Point();
            lock (m_ThisMainForm.m_LocationLock)
            {
                if (!this.m_ThisMainForm.m_X_LeftLocation.ContainsKey(this.m_GUID))
                    return;
                if (m_Park)
                {
                    this.Location = new Point(X, Y);
                    //return;
                }
                //贴边其他窗口
                m_LocatRight = this.Location.X + this.Width;
                m_LocatDown = this.Location.Y + this.Height;

                this.m_ThisMainForm.m_X_LeftLocation[this.m_GUID] = this.Location.X;
                this.m_ThisMainForm.m_X_RightLocation[this.m_GUID] = m_LocatRight;
                this.m_ThisMainForm.m_Y_TopLocation[this.m_GUID] = this.Location.Y;
                this.m_ThisMainForm.m_Y_DownLocation[this.m_GUID] = m_LocatDown;

                //鼠标左键松开之后,m_AutoPark置为false
                //if (m_AutoPark)//自动的改变位置 不再循环触发改变位置 。不是手动的
                //   return;
                //右侧吸附|||||
                foreach (var v in this.m_ThisMainForm.m_X_LeftLocation)
                {
                    if (v.Key == m_GUID)
                        continue;
                    if (m_LocatRight > v.Value - 20 && m_LocatRight < v.Value + 20)
                    {
                        //确保二者是有接触的
                        int ttop = m_ThisMainForm.m_Y_TopLocation[v.Key];
                        int tdown = m_ThisMainForm.m_Y_DownLocation[v.Key];
                        //右侧窗口顶部低于左侧顶部,则右侧窗口的顶部需要高于左侧底部
                        if (this.Location.Y >= ttop)
                        {
                            if (this.Location.Y <= tdown)
                            {
                                this.Location = new Point(v.Value - this.Width, Location.Y);
                                break;
                            }
                        }
                        //右侧窗口顶部高于左侧顶部,则右侧窗口的底部需要低于左侧顶部
                        else
                        {
                            if (m_LocatDown >= ttop)
                            {
                                this.Location = new Point(v.Value - this.Width, Location.Y);
                                break;
                            }
                        }
                    }
                }
                //左侧吸附|||||
                foreach (var v in this.m_ThisMainForm.m_X_RightLocation)
                {
                    if (v.Key == m_GUID)
                        continue;
                    if (Location.X > v.Value - 20 && Location.X < v.Value + 20)
                    {
                        int ttop = m_ThisMainForm.m_Y_TopLocation[v.Key];
                        int tdown = m_ThisMainForm.m_Y_DownLocation[v.Key];
                        if (this.Location.Y >= ttop)
                        {
                            if (this.Location.Y <= tdown)
                            {
                                this.Location = new Point(v.Value, Location.Y);
                                break;
                            }
                        }
                        else
                        {
                            if (m_LocatDown >= ttop)
                            {
                                this.Location = new Point(v.Value, Location.Y);
                                break;
                            }
                        }
                    }
                }
                //上侧吸附===
                foreach (var v in this.m_ThisMainForm.m_Y_TopLocation)
                {
                    if (v.Key == m_GUID)
                        continue;
                    if (m_LocatDown > v.Value - 20 && m_LocatDown < v.Value + 20)
                    {
                        int tleft = m_ThisMainForm.m_X_LeftLocation[v.Key];
                        int tright = m_ThisMainForm.m_X_RightLocation[v.Key];
                        //上侧窗口左部大于下侧左部,则下侧窗口的左部需要大于上侧左部
                        if (this.Location.X >= tleft)
                        {
                            if (this.Location.X <= tright)
                            {
                                this.Location = new Point(Location.X, v.Value - this.Height);
                                break;
                            }
                        }
                        else
                        {
                            if (m_LocatRight >= tleft)
                            {
                                this.Location = new Point(Location.X, v.Value - this.Height);
                                break;
                            }
                        }
                    }
                }
                //下侧吸附===
                foreach (var v in this.m_ThisMainForm.m_Y_DownLocation)
                {
                    if (v.Key == m_GUID)
                        continue;
                    if (Location.Y > v.Value - 20 && Location.Y < v.Value + 20)
                    {
                        int tleft = m_ThisMainForm.m_X_LeftLocation[v.Key];
                        int tright = m_ThisMainForm.m_X_RightLocation[v.Key];
                        //上侧窗口左部大于下侧左部,则下侧窗口的左部需要大于上侧左部
                        if (this.Location.X >= tleft)
                        {
                            if (this.Location.X <= tright)
                            {
                                this.Location = new Point(Location.X, v.Value);
                                break;
                            }
                        }
                        else
                        {
                            if (m_LocatRight >= tleft)
                            {
                                this.Location = new Point(Location.X, v.Value);
                                break;
                            }
                        }
                    }
                }
                //问题1  没挨着 光上下位置一样 不应贴住
                //问题2  两个窗口同时符合贴住原则,导致死循环。
                //均解决
            }

        }
  /// <summary>
        /// 关闭后删除定位
        /// </summary>
        public void Form_LocationClosed()
        {
            lock (m_ThisMainForm.m_LocationLock)
            {
                this.m_ThisMainForm.m_X_LeftLocation.Remove(this.m_GUID);
                this.m_ThisMainForm.m_X_RightLocation.Remove(this.m_GUID);
                this.m_ThisMainForm.m_Y_TopLocation.Remove(this.m_GUID);
                this.m_ThisMainForm.m_Y_DownLocation.Remove(this.m_GUID);
            }
        }

要点就是需要重载WndProc,捕捉鼠标在非客户区点击事件。至于效率,由于改变位置时只是遍历了一下dictionnary,效率很快,并不会有卡顿、占用cpu现象。

刚工作的时候经常做知识分享,工作后忙起来就很久没空写博客了。写的这个是我自认为比较有一点技术含量的,就分享上来,感觉很coooool

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值