一开始接触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