鼠标组件——可移动和可改变大小组件

来源:互联网 发布:js的call和apply 编辑:程序博客网 时间:2024/05/18 01:45

示例代码下载:http://download.csdn.net/source/950979

 本示例演示如何实现用鼠标移动控件位置,如何用鼠标改变控件的大小,这两种功能分别由两个组件实现,ResizableComponentMovableComponent

 

本示例演示效果如下,在窗体中用Panel控件模拟一个浮动窗口,移动到边缘区域时鼠标样式会改变为调整大小状态,按下鼠标进行拖动即可改变大小。移动到标题栏鼠标样式会改变为可移动状态,按下鼠标进行拖动即可移动控件位置。

 

 

可移动组件实现原理:

1、鼠标移动到指定控件上,改变鼠标样式为可移动状态

2、按下鼠标时记录鼠标的当前位置

3、鼠标移动时检测是否按下左键,如果按下左键则根据当前位置和之前记录的位置计算位移

4、根据鼠标的位移设置控件的坐标

5、鼠标离开则恢复默认鼠标样式

 

可移动组件实现要点:

1、被移动控件和响应鼠标操作的控件不一定是同一个,比如示例中列标题响应操作,内容区域不响应,移动的是最外层的那个Panel。需要设置两个属性,一个响应操作,一个被移动,两者可以一致。

2、响应操作的控件内部子控件也要有不同的响应,比如示例中标题栏中的图标和标题文字响应操作,但关闭按钮不响应。这里用扩展属性实现该功能,可以给内部控件添加一个是否响应操作的属性,让设置更加灵活。

 

可改变大小组件实现原理:

1、这里将控件分成9个区域,上左、上中、上右、中左、中央、中右、下左、下中、下右。中央区域被其他8个区域包围形成一个虚拟的边框。边框的宽度可以自定义,中央区域不响应操作,其他8个区域可以选择性响应操作。

2、鼠标移动过程中检测鼠标坐标。如果处在边缘处,则根据不同的位置设置不同的改变大小的鼠标样式。

3、在鼠标按下事件中记录下当前鼠标坐标

4、鼠标移动过程中,如果鼠标左键按下,则根据当前位置和之前记录的位置计算位移

5、根据鼠标位移和鼠标所处的区域,调整控件的大小和位置

6、鼠标移开时恢复默认鼠标样式

 

可改变大小组件实现要点:

1、内部控件可能覆盖边缘,内部控件也需要处理鼠标事件。和可移动组件一样通过扩展属性指示内部控件是否允许响应操作。

2、可响应改变大小的位置可以自定义,实现自定义UITypeEditor,可视化设置。

3、向上或向右改变大小需要同时改变控件的位置,非对角线方向改变大小时要忽略垂直方向上的位移。

 

下面介绍详细的实现过程。

枚举:

DirectionEnum:方向枚举,All-所有方向,Horizontal-水平方向,Vertical-垂直方向。该枚举在移动操作和改变大小操作中都可以用到。

ResizeHandleAreaEnum:改变大小可处理区域枚举,把需要处理改变大小的控件分成3*3的区域,除了Center区域,其他区域都允许响应鼠标操作。该枚举变量用自定义UITypeEditor进行编辑,后面再详细介绍。

 

MovableComponent组件的类图和类详细信息

 

 

 

MovableComponent组件包含5个属性:

Enable:指示组件是否可用

EnableInnerControl:指示是否允许HandleControl控件的内部控件响应鼠标操作。

HandleControl:响应鼠标操作的控件,可以和被移动的控件不一致,一般是被移动控件内部的控件。

MovableControl:被移动的控件。

MoveableDirection:控件可以被移动的方向,默认为All,不限制移动方向。

 

该组件需要处理的鼠标事件有鼠标移入、鼠标按下、鼠标移动和鼠标离开,实现代码如下:

  1.         /// <summary>
  2.         /// 鼠标离开事件
  3.         /// </summary>
  4.         /// <param name="sender"></param>
  5.         /// <param name="e"></param>
  6.         void HandleControl_MouseLeave(object sender, EventArgs e)
  7.         {
  8.             if (this.m_Enable)
  9.                 this.HandleControl.Cursor = Cursors.Default;
  10.         }
  11.         /// <summary>
  12.         /// 鼠标进入事件
  13.         /// </summary>
  14.         /// <param name="sender"></param>
  15.         /// <param name="e"></param>
  16.         void HandleControl_MouseEnter(object sender, EventArgs e)
  17.         {
  18.             if (this.m_Enable)
  19.             {
  20.                 switch (this.m_MovableDirection)
  21.                 {
  22.                     case DirectionEnum.All:
  23.                         this.HandleControl.Cursor = Cursors.SizeAll;
  24.                         break;
  25.                     case DirectionEnum.Horizontal:
  26.                         this.HandleControl.Cursor = Cursors.SizeWE;
  27.                         break;
  28.                     case DirectionEnum.Vertical:
  29.                         this.HandleControl.Cursor = Cursors.SizeNS;
  30.                         break;
  31.                     default:
  32.                         break;
  33.                 }
  34.             }
  35.         }
  36.         /// <summary>
  37.         /// 之前的鼠标位置
  38.         /// </summary>
  39.         private Point m_PreviousLocation;
  40.         /// <summary>
  41.         /// 鼠标按下事件
  42.         /// </summary>
  43.         /// <param name="sender"></param>
  44.         /// <param name="e"></param>
  45.         void HandleControl_MouseDown(object sender, MouseEventArgs e)
  46.         {
  47.             if (this.m_Enable)
  48.                 m_PreviousLocation = Control.MousePosition;
  49.         }
  50.         /// <summary>
  51.         /// 鼠标移动事件
  52.         /// </summary>
  53.         /// <param name="sender"></param>
  54.         /// <param name="e"></param>
  55.         void HandleControl_MouseMove(object sender, MouseEventArgs e)
  56.         {
  57.             if (this.m_Enable && e.Button == MouseButtons.Left && this.m_MovableControl != null)
  58.             {
  59.                 Point PositionOffset = Control.MousePosition;
  60.                 PositionOffset.Offset(-this.m_PreviousLocation.X, -this.m_PreviousLocation.Y);
  61.                 int intNewX = this.m_MovableControl.Location.X + PositionOffset.X;
  62.                 int intNewY = this.m_MovableControl.Location.Y + PositionOffset.Y;
  63.                 switch (this.m_MovableDirection)
  64.                 {
  65.                     case DirectionEnum.All:
  66.                         this.m_MovableControl.Location = new Point(intNewX, intNewY);
  67.                         break;
  68.                     case DirectionEnum.Horizontal:
  69.                         this.m_MovableControl.Location = new Point(intNewX, this.m_MovableControl.Location.Y);
  70.                         break;
  71.                     case DirectionEnum.Vertical:
  72.                         this.m_MovableControl.Location = new Point(this.m_MovableControl.Location.X, intNewY);
  73.                         break;
  74.                     default:
  75.                         break;
  76.                 }
  77.                 m_PreviousLocation = Control.MousePosition;
  78.             }
  79.         }

另外为了实现扩展属性,必须实现IExtenderProvider接口,关于IExtenderProvider接口的详细介绍请参考MSDN。这里默认允许内部控件响应鼠标操作,只记录不响应操作的内部控件。实现该接口后还要在组件上添加特性,格式为[ProvideProperty("HandleMove", typeof(Control))]。将组件放到窗体上,设置好HandleControl之后,就可以看到HandleControl的内部控件都会增加一个movableComponent1 上的 HandleMove属性,和ToolTip控件类似。

该接口的实现如下:

  1.         /// <summary>
  2.         /// 不响应操作的控件的列表
  3.         /// </summary>
  4.         private List<Control> m_NoHandleControls = new List<Control>();
  5.         /// <summary>
  6.         /// IExtenderProvider成员方法-是否可扩展
  7.         /// </summary>
  8.         public bool CanExtend(object extendee)
  9.         {
  10.             if (m_HandleControl != null && IsContainSubControl(m_HandleControl, extendee as Control))
  11.                 return true;
  12.             else
  13.                 return false;
  14.         }
  15.         /// <summary>
  16.         /// 是否包含下级控件
  17.         /// </summary>
  18.         /// <param name="Parent">上级控件</param>
  19.         /// <param name="Child">下级控件</param>
  20.         /// <returns></returns>
  21.         private bool IsContainSubControl(Control Parent, Control Child)
  22.         {
  23.             bool blnResult = false;
  24.             if (Parent == null || Child == null)
  25.                 blnResult = false;
  26.             else
  27.             {
  28.                 if (Parent.Controls.Contains(Child))
  29.                     blnResult = true;
  30.                 else
  31.                 {
  32.                     foreach (Control item in Parent.Controls)
  33.                     {
  34.                         if (IsContainSubControl(item, Child))
  35.                         {
  36.                             blnResult = true;
  37.                             break;
  38.                         }
  39.                     }
  40.                 }
  41.             }
  42.             return blnResult;
  43.         }
  44.         /// <summary>
  45.         /// IExtenderProvider成员方法-设置响应移动属性
  46.         /// </summary>
  47.         public void SetHandleMove(Control control, bool value)
  48.         {
  49.             if (value)
  50.             {
  51.                 if (m_NoHandleControls.Contains(control))
  52.                     m_NoHandleControls.Remove(control);
  53.             }
  54.             else
  55.             {
  56.                 if (!m_NoHandleControls.Contains(control))
  57.                     m_NoHandleControls.Add(control);
  58.             }
  59.         }
  60.         /// <summary>
  61.         /// 成员方法-获取响应移动属性
  62.         /// </summary>
  63.         [DefaultValue(true)]
  64.         [Description("指示控件是否响应改变位置操作。")]
  65.         public bool GetHandleMove(Control control)
  66.         {
  67.             if (m_HandleControl != null && (control == this.m_HandleControl || IsContainSubControl(m_HandleControl, control)))
  68.             {
  69.                 if (this.m_NoHandleControls.Contains(control))
  70.                     return false;
  71.                 else
  72.                     return true;
  73.             }
  74.             else
  75.                 return false;
  76.         }

 

实现IExtenderProvider接口后,将组件拖放到窗体上,设置相关HandleControl之后,则会为其内部控件增加HandleMove属性,效果如下图:

 

 

下面介绍ResizableComponent可改变大小组件的实现。

该组件的类图如下:

 

ResizableComponent组件的属性有:

Enable:指示组件是否可用

EnableInnerControl:当内部控件覆盖目标可缩放控件的边缘时,是否允许内部控件响应鼠标改变大小操作

MinSize:可缩放控件可以调整的最小尺寸

ResizableControl:目标可改变大小的控件

ResizeBorderWidth:响应改变大小操作的边框宽度,对应可缩放控件的内部虚拟边框,当鼠标移动到这一个虚拟边框中会改变样式

ResizeDirection:可改变大小的方向,水平、垂直和不限制

ResizeHandleAreas:响应改变大小操作的控制区域,用自定义UITypeEditor实现。效果如下图所示:

 

该组件处理目标控件的三个鼠标事件,MouseMoveMouseLeaveMouseDown

MouseMove处理方法中,检测鼠标的坐标所处的区域,然后根据区域和允许调整大小的方向设置不同的鼠标样式。

如果鼠标左键按下,则检测鼠标的位移量,再根据所处的区域调整控件的大小和位置。

MouseDown处理方法中,记录下鼠标的位置,供调整大小时计算位移量。

MouseLeave处理方法中,恢复鼠标样式。

  1.         /// <summary>
  2.         /// 鼠标按下事件
  3.         /// </summary>
  4.         /// <param name="sender"></param>
  5.         /// <param name="e"></param>
  6.         private void SizableControl_MouseDown(object sender, MouseEventArgs e)
  7.         {
  8.             if (!m_Enable)
  9.                 return;
  10.             m_ResizeOriginalPoint = Control.MousePosition;
  11.         }
  12.         /// <summary>
  13.         /// 鼠标移动事件
  14.         /// </summary>
  15.         /// <param name="sender"></param>
  16.         /// <param name="e"></param>
  17.         private void SizableControl_MouseMove(object sender, MouseEventArgs e)
  18.         {
  19.             if (!m_Enable)
  20.                 return;
  21.             if (e.Button == MouseButtons.None)
  22.             {
  23.                 this.CheckMousePoint(sender as Control, e.Location);
  24.                 return;
  25.             }
  26.             if (e.Button != MouseButtons.Left)
  27.                 return;
  28.             Point OffsetPoint = Control.MousePosition;
  29.             OffsetPoint.Offset(-m_ResizeOriginalPoint.X, -m_ResizeOriginalPoint.Y);
  30.             switch (m_HandleArea)
  31.             {
  32.                 case ResizeHandleAreaEnum.TopLeft:
  33.                     this.SetControlBound(OffsetPoint.X, OffsetPoint.Y, -OffsetPoint.X, -OffsetPoint.Y);
  34.                     break;
  35.                 case ResizeHandleAreaEnum.TopCenter:
  36.                     this.SetControlBound(0, OffsetPoint.Y, 0, -OffsetPoint.Y);
  37.                     break;
  38.                 case ResizeHandleAreaEnum.TopRight:
  39.                     this.SetControlBound(0, OffsetPoint.Y, OffsetPoint.X, -OffsetPoint.Y);
  40.                     break;
  41.                 case ResizeHandleAreaEnum.CenterLeft:
  42.                     this.SetControlBound(OffsetPoint.X, 0, -OffsetPoint.X, 0);
  43.                     break;
  44.                 case ResizeHandleAreaEnum.CenterRight:
  45.                     this.SetControlBound(0, 0, OffsetPoint.X, 0);
  46.                     break;
  47.                 case ResizeHandleAreaEnum.BottomLeft:
  48.                     this.SetControlBound(OffsetPoint.X, 0, -OffsetPoint.X, OffsetPoint.Y);
  49.                     break;
  50.                 case ResizeHandleAreaEnum.BottomCenter:
  51.                     this.SetControlBound(0, 0, 0, OffsetPoint.Y);
  52.                     break;
  53.                 case ResizeHandleAreaEnum.BottomRight:
  54.                     this.SetControlBound(0, 0, OffsetPoint.X, OffsetPoint.Y);
  55.                     break;
  56.                 case ResizeHandleAreaEnum.Center:
  57.                 default:
  58.                     break;
  59.             }
  60.             this.m_ResizeOriginalPoint = Control.MousePosition;
  61.         }
  62.         /// <summary>
  63.         /// 鼠标离开事件
  64.         /// </summary>
  65.         /// <param name="sender"></param>
  66.         /// <param name="e"></param>
  67.         private void SizableControl_MouseLeave(object sender, EventArgs e)
  68.         {
  69.             if (!m_Enable)
  70.                 return;
  71.             (sender as Control).Cursor = Cursors.Default;
  72.             this.m_ResizableControl.Cursor = Cursors.Default;
  73.         }

其他方法都是辅助检测和调整坐标用的。下面介绍如何实现自定义的UITypeEditor。这里定义了一个枚举ResizeHandleAreaEnum,用来标识调整大小的区域。因为设置的响应操作的区域允许有多个,所以这些枚举值必须都是2的次方数,在二进制中表示则都只有一位是1的,这样就可以通过位操作来解析值了。

  1.     /// <summary>
  2.     /// 改变大小控制区域枚举
  3.     /// </summary>
  4.     [Flags]
  5.     [Serializable]
  6.     [Editor(typeof(ResizeHandleAreaUITypeEditor), typeof(System.Drawing.Design.UITypeEditor))]
  7.     public enum ResizeHandleAreaEnum
  8.     {
  9.         /// <summary>
  10.         /// 中央区域,不响应操作
  11.         /// </summary>
  12.         Center = 0,
  13.         /// <summary>
  14.         /// 顶端靠左
  15.         /// </summary>
  16.         TopLeft = 1,
  17.         /// <summary>
  18.         /// 顶端居中
  19.         /// </summary>
  20.         TopCenter = 2,
  21.         /// <summary>
  22.         /// 顶端靠右
  23.         /// </summary>
  24.         TopRight = 4,
  25.         /// <summary>
  26.         /// 中间靠左
  27.         /// </summary>
  28.         CenterLeft = 8,
  29.         /// <summary>
  30.         /// 中间靠右
  31.         /// </summary>
  32.         CenterRight = 16,
  33.         /// <summary>
  34.         /// 底部靠左
  35.         /// </summary>
  36.         BottomLeft = 32,
  37.         /// <summary>
  38.         /// 底部居中
  39.         /// </summary>
  40.         BottomCenter = 64,
  41.         /// <summary>
  42.         /// 底部靠右
  43.         /// </summary>
  44.         BottomRight = 128,
  45.     }

 

枚举定义好之后,在项目中添加一个自定义控件,在其中放置8CheckBox,设置Appearance属性为Button外观。然后排布为虚拟边框的效果,如下图:

 

该控件主要是将ResizeHandleAreaEnum枚举值和CheckBox控件的选中状态对应起来,通过位操作来解析和设置响应操作的区域枚举,内部代码如下:

  1.         //原始响应区域
  2.         private ResizeHandleAreaEnum m_OldAears;
  3.         /// <summary>
  4.         /// 改变大小的响应区域枚举
  5.         /// </summary>
  6.         public ResizeHandleAreaEnum ResizeHandleAreas
  7.         {
  8.             get
  9.             {
  10.                 ResizeHandleAreaEnum Areas = ResizeHandleAreaEnum.Center;
  11.                 if (chkTopLeft.Checked)
  12.                     Areas |= ResizeHandleAreaEnum.TopLeft;
  13.                 if (chkTopCenter.Checked)
  14.                     Areas |= ResizeHandleAreaEnum.TopCenter;
  15.                 if (chkTopRight.Checked)
  16.                     Areas |= ResizeHandleAreaEnum.TopRight;
  17.                 if (chkCenterLeft.Checked)
  18.                     Areas |= ResizeHandleAreaEnum.CenterLeft;
  19.                 if (chkCenterRight.Checked)
  20.                     Areas |= ResizeHandleAreaEnum.CenterRight;
  21.                 if (chkBottomLeft.Checked)
  22.                     Areas |= ResizeHandleAreaEnum.BottomLeft;
  23.                 if (chkBottomCenter.Checked)
  24.                     Areas |= ResizeHandleAreaEnum.BottomCenter;
  25.                 if (chkBottomRight.Checked)
  26.                     Areas |= ResizeHandleAreaEnum.BottomRight;
  27.                 if (Areas == ResizeHandleAreaEnum.Center)
  28.                     return m_OldAears;
  29.                 else
  30.                     return Areas;
  31.             }
  32.         }
  33.         /// <summary>
  34.         /// 设置响应改变大小的区域
  35.         /// </summary>
  36.         /// <param name="ResizeHandleArea"></param>
  37.         public void SetValue(ResizeHandleAreaEnum ResizeHandleArea)
  38.         {
  39.             m_OldAears = ResizeHandleArea;
  40.             chkTopLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopLeft) != 0);
  41.             chkTopCenter.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopCenter) != 0);
  42.             chkTopRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopRight) != 0);
  43.             chkCenterLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.CenterLeft) != 0);
  44.             chkCenterRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.CenterRight) != 0);
  45.             chkBottomLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomLeft) != 0);
  46.             chkBottomCenter.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomCenter) != 0);
  47.             chkBottomRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomRight) != 0);
  48.         }

为了让该枚举值在PropertyGrid中编辑时显示自定义的UI界面,需要继承UITypeEditor类,关于UITypeEditor的具体介绍请参考MSDN,这里的实现代码如下:

  1.     internal class ResizeHandleAreaUITypeEditor : UITypeEditor
  2.     {
  3.         private ResizeHandleAreaEditorControl m_EditorControl = null;
  4.         public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
  5.         {
  6.             return UITypeEditorEditStyle.DropDown;
  7.         }
  8.         public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
  9.         {
  10.             if (m_EditorControl == null)
  11.                 m_EditorControl = new ResizeHandleAreaEditorControl();
  12.             m_EditorControl.SetValue((ResizeHandleAreaEnum)value);
  13.             IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
  14.             edSvc.DropDownControl(m_EditorControl);
  15.             return m_EditorControl.ResizeHandleAreas;
  16.         }
  17.     }

 

在该枚举上添加Editor特性[Editor(typeof(ResizeHandleAreaUITypeEditor), typeof(System.Drawing.Design.UITypeEditor))],之后只要使用到该属性,在PropertyGrid中显示的就是UI编辑界面。

 

另外本组件也用到了扩展属性,和之前的MovableComponent的实现方法类似,这里不再介绍。

 

示例代码下载:http://download.csdn.net/source/950979