以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 创建具有吸附效果的窗口 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=51012) |
-- 作者:卷积内核 -- 发布时间:8/6/2007 3:32:00 PM -- 创建具有吸附效果的窗口 在许多程序中,窗口可以被拖放到另一个窗口中,并溶合为一体,例如c++ builder中的Class Explorer与其它窗口就是典型一例,在c++ builder中,这种特性被称为"窗口吸附"。 在bcb中的许多组件都具有与"吸附"功能相关的属性,例如:DockSite属性、UseDockManage属性,以及DragKing属性,它的属性可以设置为dkDock。如果将这些属性值简单设置一下,窗口将自动具有吸附功能,不过这样的吸附功能非常死板,没有实用价值。下面笔者就给大家介绍一下如何在c++ builder中实现真正的窗口吸附效果。 一、设计一个主窗口 新建一工程,主窗体命名为MainForm,单元文件命名为Main。添加一个TMainMenu命名为MainMenu1,双击该主菜单添加两个子菜单分别为"红色窗口"和"蓝色窗口",这两个子菜单分别用来显示不同颜色的窗口。接着在窗口的最右端添加一个宽度为0,高度为整个窗口高的Panel组件并命名为DockPanel,建立Panel组件是因为被"吸附"的窗口必须要有一个"吸附区",这种吸附区可以是窗口,也可以是窗口化组件,如Panel。再在Panel右边添加一个Splliter组件,命名为VSplliter宽度设为4,高度为窗体的高度。建立Splliter组件是为了有效地建立被吸附窗口的间隔区,并且可以方便用户调整被吸附窗口所占据的宽度。(如图1所示) 二、设计"被吸附"窗口 作为主窗口的设计窗体,除了一般的属性设置外,有关Dock的属性均无需修改,只要将"被吸附"窗体的属性进行修改就可以了。 选择"File | New Form"命令,建立一个新窗体,将新窗体命令为DockWindow和吸附功能相关的属性修改如下: 属性 值 DockSite True DockKind dkDock DockMode dmAutomatic 在窗体中放置一个Memo组件,并将其Align属性修改为alClient,使其充满整个窗口区域。放置Memo组件的目的是使窗口在被吸附时有明显的边界特征,并且可以赋予窗口不同颜色。 三、设计实现"吸附"功能 实际上,产生吸附作用的并不是主窗体,而是主窗体中的Panel组件,所以Panel组件中与吸附功能有关的组件也必须和吸附窗口一样进行修改,修改值如上表所示。接着为Panel添加如下事件:OnDockOver、OnGetSiteinfo、OnDockDrop、OnUndock代码与解释如下: //-------------------------------------------------------------------------------------- void __fastcall TMainWin::DockPanelDockOver(TObject *Sender,TDragDockObject *Source, int X, int Y, TDragState State,bool &Accept) { Accept = (dynamic_cast<TDockableForm*>(Source->Control) != NULL); // (1) if (Accept) // (2) { Windows::TPoint TopLeft = DockPanel->ClientToScreen(Point(0, 0)); // (3) Windows::TPoint BottomRight = DockPanel->ClientToScreen(Point(this->ClientWidth / 3, DockPanel->Height)); // (4) Source->DockRect = Windows::TRect(TopLeft, BottomRight); (5) } } //-------------------------------------------------------------------------------------- 解释:OnDockOver事件是在被吸附窗口拖动经过吸附面板时产生的,也就是说,当用户拖动被吸附窗口经过DockPanel组件时,就会产生这个事件。第(1)句作用是首先将句柄接受的参数Source->Control(代表吸附窗口)强制转换为TDockableForm类型(可吸附窗体);然后判断强制转换是否成功或者Source中是否包含可吸附对象;最后将判断结果保存到Accept变量中。第(2)句的作用是如果上述转换成功后,才可设置吸附区域的虚框。第(3)~(4)句用来设置要显示的吸附区域,其中使用了Panel组件的Client To Screen方法,该方法可将面板从标系下的点转换为目前屏幕坐标系中的点。(5)句是将上面设置的区域指定给被吸附的对象。 //-------------------------------------------------------------------------------------- void __fastcall TMainWin::DockPanelGetSiteInfo(TObject *Sender,TControl *DockClient, TRect &InfluenceRect, TPoint &MousePos,bool &CanDock) { CanDock = (dynamic_cast<TDockableForm*>(DockClient) != NULL); } //--------------------------------------------------------------------------- 解释:当一个吸附组件的DockSite属性为True时,OnGetSiteInfo事件将在OnDockDrop事件之前产生,其中的代码可以用被吸附对象进行一些初始化。 //-------------------------------------------------------------------------------------- void __fastcall TMainWin::DockPanelDockDrop(TObject *Sender,TDragDockObject *Source, int X, int Y) { TPanel* SenderPanel = dynamic_cast<TPanel*>(Sender);//将调用该方法的对象强制转换为TPanel类型组件 if (SenderPanel == NULL) //判断上句的转换是否成功,若不成功则给出一个提示 throw EInvalidCast(""); if (SenderPanel->DockClientCount == 1)//判断是否包含被吸附窗口,如果存在,那么调用ShowPanel重新显示Panel组件 ShowPanel(SenderPanel, true, NULL); SenderPanel->DockManager->ResetBounds(true);//重新画被吸附窗口 } //--------------------------------------------------------------------------- 解释:OnDockDrop事件是在被吸附窗口吸附到面板上之后产生的事件,吸附之后,程序应该调用后面定义的ShowPanel函数,使Panel组件按照新的大小显示出来(包含被吸附窗口),然后利用它的DockManager重画被吸附的窗口。 //-------------------------------------------------------------------------------------- void __fastcall TMainWin::DockPanelUnDock(TObject *Sender,TControl *Client, TWinControl *NewTarget, bool &Allow) { TPanel* SenderPanel = dynamic_cast<TPanel*>(Sender); if (SenderPanel == NULL) throw EInvalidCast(""); if (SenderPanel->DockClientCount == 1) // 确保当前吸附面板中包含了被吸附对象,然后调用ShowPanel方法"解放"它 ShowPanel(SenderPanel, false, NULL); } //--------------------------------------------------------------------------- OnUndock事件是在吸附对象被"解放"之前被调用的,所以这里可以为用户提供一个不"解放"吸附对象的机会。 接下来自定义一个函数ShowPanel()用来控制吸附对象 其中:各参数说明:APanel--吸附面板对象,MakeVisible--面板可见标志,Client--重新显示面板时要显示的吸附对象。 //--------------------------------------------------------------------------- void TMainWin::ShowPanel(TPanel* APanel, bool MakeVisible, TControl* Client) { if (!MakeVisible && (APanel->VisibleDockClientCount > 1))//判断如果面板不可见,并且其中包含一个以上的被吸附对象,则直接返回 return; if (APanel == DockPanel)//判断如果调用函数时得到的Panel对象是吸附面板,则将Splliter组件的可见性与Panel对象保持一致 VSplitter->Visible = MakeVisible; if (MakeVisible)//在吸附面板可见的情况下,将面板的宽度设为窗口客户区的1/3,并同时将Splliter移动到面板的右边 { APanel->Width = ClientWidth / 3; VSplitter->Left = APanel->Width + VSplitter->Width; } else APanel->Width = 0;//面板不可见时,维护面板宽度为0 if (MakeVisible && (Client != NULL)) Client->Show();//显示被吸附对象,并且要确保面板可见 } //--------------------------------------------------------------------------------------------- 下面的工作就是实现菜单命令的功能,"红色窗口"命令生成红色窗口,"兰色窗口"命令生成兰色窗口,"关闭"命令关闭整个程序。下面只给出动态生成红色窗口的事件句柄如下:(兰色窗口跟这个代码类似,这里省略) //--------------------------------------------------------------------------------------------- //"红色窗口"命令的OnClick事件句柄: void __fastcall TMainWin::CMredwindowClick(TObject *Sender) { TDockableForm *redform=new TDockableForm(this); redform->Memo1->Color=clRed; //要实现兰色窗口的动态生成,将上句改为 redform->Memo1->Color=clBlue; redform->Show(); } 接下来在被吸附窗口DockWindow的OnClose事件添加如下代码: //--------------------------------------------------------------------------- void __fastcall TDockableForm::FormClose(TObject *Sender, TCloseAction &Action) { if (dynamic_cast<TPanel*>(HostDockSite) != NULL) MainWin->ShowPanel(static_cast<TPanel*>(HostDockSite), false, NULL); Action = caHide; Accept = (dynamic_cast<TDockableForm*>(Source->Control) != NULL); // Draw dock preview depending on where the cursor is relative to our client area
|
-- 作者:卷积内核 -- 发布时间:8/6/2007 3:32:00 PM -- //--------------------------------------------------------------------------- 接下来在被吸附窗口DockWindow的OnDockAlign事件中添加如下代码: TAlign TDockableForm::ComputerDockAlign(TRect & DockRect, const TPoint & MousePos) { Windows::TRect DockTopRect, DockLeftRect, DockBottomRect, DockRightRect, DockCenterRect; Windows::TPoint TopLeft, BottomRight; TAlign Result = alNone; // Divide form up into docking "Zones" TopLeft = Windows::TPoint(0, 0); BottomRight = Windows::TPoint(ClientWidth / 5, ClientHeight); DockLeftRect = Windows::TRect(TopLeft, BottomRight); TopLeft = Windows::TPoint(ClientWidth / 5, 0); BottomRight = Windows::TPoint(ClientWidth / 5 * 4, ClientHeight / 5); DockTopRect = Windows::TRect(TopLeft, BottomRight); TopLeft = Windows::TPoint(ClientWidth / 5 * 4, 0); BottomRight = Windows::TPoint(ClientWidth, ClientHeight); DockRightRect = Windows::TRect(TopLeft, BottomRight); TopLeft = Windows::TPoint(ClientWidth / 5, ClientHeight / 5 * 4); BottomRight = Windows::TPoint(ClientWidth / 5 * 4, ClientHeight); DockBottomRect = Windows::TRect(TopLeft, BottomRight); TopLeft = Windows::TPoint(ClientWidth / 5, ClientHeight / 5); BottomRight = Windows::TPoint(ClientWidth / 5 * 4, ClientHeight / 5 * 4); DockCenterRect = Windows::TRect(TopLeft, BottomRight); // Find out where the mouse cursor is, // to decide where to draw dock preview. if (PtInRect(&DockLeftRect, MousePos)) { Result = alLeft; DockRect = DockLeftRect; DockRect.Right = ClientWidth / 2; } else if (PtInRect(&DockTopRect, MousePos)) { Result = alTop; DockRect = DockTopRect; DockRect.Left = 0; DockRect.Right = ClientWidth; DockRect.Bottom = ClientHeight / 2; } else if (PtInRect(&DockRightRect, MousePos)) { Result = alRight; DockRect = DockRightRect; DockRect.Left = ClientWidth / 2; } else if (PtInRect(&DockBottomRect, MousePos)) { Result = alBottom; DockRect = DockBottomRect; DockRect.Left = 0; DockRect.Right = ClientWidth; DockRect.Top = ClientHeight / 2; } else if (PtInRect(&DockCenterRect, MousePos)) { Result = alClient; DockRect = DockCenterRect; } if (Result != alNone) DockRect = TRect(TopLeft, BottomRight);
|
-- 作者:卷积内核 -- 发布时间:8/6/2007 3:38:00 PM -- vs.net解决方案 三个页面,一个主监控页面 一个主页面,一个跟随页面 代码如下,代码没有怎么整理,肯定写的不台好看,目的只是证明方法是否可行. 代码一 主监控窗口fMain.cs using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace WT { /// <summary> /// Form1 的摘要说明。 /// </summary> public class fMain : System.Windows.Forms.Form { private static bool islian=false;//是否被吸引住 private static int liantype;//吸引方向 private static int cxmax=20;//磁性距离最大距离正值 private static int cxmin=-20;//磁性距离最大距离负值 /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null; private static Form f1=new F1(); private System.Windows.Forms.Label label1; private static Form f2=new F2(); public fMain() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.SuspendLayout(); // // label1 // this.label1.Location = new System.Drawing.Point(8, 8); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(280, 48); this.label1.TabIndex = 1; this.label1.Text = "监控窗口,本窗口内处理吸引事件."; // // fMain // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(292, 29); this.Controls.Add(this.label1); this.Name = "fMain"; this.Text = "Main"; this.Load += new System.EventHandler(this.fMain_Load); this.ResumeLayout(false); } #endregion /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.Run(new fMain()); } private void button1_Click(object sender, System.EventArgs e) { } private void fMain_Load(object sender, System.EventArgs e) { f1.Show(); f2.Show(); } //主窗口计算悬浮 public static void sum(){ if (islian) { switch(liantype) { case 1: f2.SetDesktopLocation(f1.DesktopLocation.X,f1.DesktopLocation.Y+f1.Height); break; case 2: f2.SetDesktopLocation(f1.DesktopLocation.X,f1.DesktopLocation.Y-f2.Height); break; case 3: f2.SetDesktopLocation(f1.DesktopLocation.X-f2.Width,f1.DesktopLocation.Y); break; case 4: f2.SetDesktopLocation(f1.DesktopLocation.X+f2.Width,f1.DesktopLocation.Y); break; } } else { xuafu(); } } //悬浮识别处理 public static void xuafu(){ int x; int y; //处理下边磁性 x=f1.DesktopLocation.X; y=f1.DesktopLocation.Y+f1.Height; if ((x-f2.DesktopLocation.X<cxmax&&x-f2.DesktopLocation.X>cxmin)&&(y-f2.DesktopLocation.Y<cxmax&&y-f2.DesktopLocation.Y>cxmin)) { f2.SetDesktopLocation(x,y); islian=true; liantype=1; } else{islian=false;} //处理上边磁性 x=f1.DesktopLocation.X; y=f1.DesktopLocation.Y; if((x-f2.DesktopLocation.X<cxmax&&x-f2.DesktopLocation.X>cxmin)&&(y-f2.DesktopLocation.Y-f2.Height<cxmax&&y-f2.DesktopLocation.Y-f2.Height>cxmin)) { f2.SetDesktopLocation(x,y-f2.Height); islian=true; liantype=2; } //处理左边磁性 x=f1.DesktopLocation.X; y=f1.DesktopLocation.Y; if((x-f2.DesktopLocation.X-f2.Width<cxmax&&x-f2.DesktopLocation.X-f2.Width>cxmin)&&(y-f2.DesktopLocation.Y<cxmax&&y-f2.DesktopLocation.Y>cxmin)) { f2.SetDesktopLocation(x-f2.Width,y); islian=true; liantype=3; } //处理右边磁性 x=f1.DesktopLocation.X+f1.Width; y=f1.DesktopLocation.Y; if((x-f2.DesktopLocation.X<cxmax&&x-f2.DesktopLocation.X>cxmin)&&(y-f2.DesktopLocation.Y<cxmax&&y-f2.DesktopLocation.Y>cxmin)) { f2.SetDesktopLocation(x,y); islian=true; liantype=4; } } //分窗口计算悬浮 public static void sum2() { xuafu(); } } } |
-- 作者:卷积内核 -- 发布时间:8/6/2007 3:39:00 PM -- 代码二(主窗口,就是用来悬浮其它窗口的窗口)F1.cs using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; namespace WT { /// <summary> /// F1 的摘要说明。 /// </summary> public class F1 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null; public F1() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.SuspendLayout(); // // label1 // this.label1.Location = new System.Drawing.Point(8, 0); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(216, 136); this.label1.TabIndex = 0; this.label1.Text = "主窗体,分窗口吸引后自动跟随拖动"; // // F1 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(208, 101); this.Controls.Add(this.label1); this.Name = "F1"; this.Text = "F1"; this.Load += new System.EventHandler(this.F1_Load); this.Move += new System.EventHandler(this.F1_Move); this.ResumeLayout(false); } #endregion private void F1_Load(object sender, System.EventArgs e) { } private void F1_Move(object sender, System.EventArgs e) { fMain.sum(); } } } 分窗口F2.cs using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; namespace WT { /// <summary> /// F2 的摘要说明。 /// </summary> public class F2 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.Container components = null; public F2() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// <summary> /// 清理所有正在使用的资源。 /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// </summary> private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.SuspendLayout(); // // label1 // this.label1.Location = new System.Drawing.Point(8, 8); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(216, 160); this.label1.TabIndex = 0; this.label1.Text = "分窗体,被主窗口吸引后,自动跟随"; // // F2 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(208, 165); this.Controls.Add(this.label1); this.Name = "F2"; this.Text = "F2"; this.Move += new System.EventHandler(this.F2_Move); this.ResumeLayout(false); } #endregion private void F2_Move(object sender, System.EventArgs e) { fMain.sum2(); } } } 全部玩了,只完成了2个窗口间的磁性关系,3个4个都一会是,只是增加代码长度,编码除了敲代码水平外 |
-- 作者:卷积内核 -- 发布时间:8/6/2007 3:41:00 PM -- Vc 下的实现: void CBmpDlgDlg::OnLButtonDown(UINT nFlags, CPoint point) { SetCapture(); GetCursorPos(&m_Prevpoint);//m_Prevpoint是CBmpDlgDlg类中的一个CPoint成员变量。 CDialog::OnLButtonDown(nFlags, point); } void CBmpDlgDlg::OnMouseMove(UINT nFlags, CPoint point) { CPoint delpt,pt; CRect rc; if(GetCapture() == this) { GetCursorPos(&pt); delpt = pt - m_Prevpoint; GetWindowRect(&rc); rc += delpt; if(rc.top < 10) rc.top = 0; SetWindowPos(&wndTop,rc.left,rc.top,0,0, SWP_NOSIZE); m_Prevpoint = pt; } CDialog::OnMouseMove(nFlags, point); } void CBmpDlgDlg::OnLButtonUp(UINT nFlags, CPoint point) { if(GetCapture() == this) ReleaseCapture(); CDialog::OnLButtonUp(nFlags, point); } |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
3,380.859ms |