当前位置:   article > 正文

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

moveable.js

示例代码下载: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>         /// 鼠标离开事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         void HandleControl_MouseLeave(object sender, EventArgs e)        {            if (this.m_Enable)                this.HandleControl.Cursor = Cursors.Default;        }        /// <summary>         /// 鼠标进入事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         void HandleControl_MouseEnter(object sender, EventArgs e)        {            if (this.m_Enable)            {                switch (this.m_MovableDirection)                {                    case DirectionEnum.All:                        this.HandleControl.Cursor = Cursors.SizeAll;                        break;                    case DirectionEnum.Horizontal:                        this.HandleControl.Cursor = Cursors.SizeWE;                        break;                    case DirectionEnum.Vertical:                        this.HandleControl.Cursor = Cursors.SizeNS;                        break;                    default:                        break;                }            }        }        /// <summary>         /// 之前的鼠标位置         /// </summary>         private Point m_PreviousLocation;        /// <summary>         /// 鼠标按下事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         void HandleControl_MouseDown(object sender, MouseEventArgs e)        {            if (this.m_Enable)                m_PreviousLocation = Control.MousePosition;        }        /// <summary>         /// 鼠标移动事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         void HandleControl_MouseMove(object sender, MouseEventArgs e)        {            if (this.m_Enable && e.Button == MouseButtons.Left && this.m_MovableControl != null)            {                Point PositionOffset = Control.MousePosition;                PositionOffset.Offset(-this.m_PreviousLocation.X, -this.m_PreviousLocation.Y);                int intNewX = this.m_MovableControl.Location.X + PositionOffset.X;                int intNewY = this.m_MovableControl.Location.Y + PositionOffset.Y;                switch (this.m_MovableDirection)                {                    case DirectionEnum.All:                        this.m_MovableControl.Location = new Point(intNewX, intNewY);                        break;                    case DirectionEnum.Horizontal:                        this.m_MovableControl.Location = new Point(intNewX, this.m_MovableControl.Location.Y);                        break;                    case DirectionEnum.Vertical:                        this.m_MovableControl.Location = new Point(this.m_MovableControl.Location.X, intNewY);                        break;                    default:                        break;                }                m_PreviousLocation = Control.MousePosition;            }        }

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

该接口的实现如下:

  1.         /// <summary>         /// 不响应操作的控件的列表         /// </summary>         private List<Control> m_NoHandleControls = new List<Control>();        /// <summary>         /// IExtenderProvider成员方法-是否可扩展         /// </summary>         public bool CanExtend(object extendee)        {            if (m_HandleControl != null && IsContainSubControl(m_HandleControl, extendee as Control))                return true;            else                return false;        }        /// <summary>         /// 是否包含下级控件         /// </summary>         /// <param name="Parent">上级控件</param>         /// <param name="Child">下级控件</param>         /// <returns></returns>         private bool IsContainSubControl(Control Parent, Control Child)        {            bool blnResult = false;            if (Parent == null || Child == null)                blnResult = false;            else            {                if (Parent.Controls.Contains(Child))                    blnResult = true;                else                {                    foreach (Control item in Parent.Controls)                    {                        if (IsContainSubControl(item, Child))                        {                            blnResult = true;                            break;                        }                    }                }            }            return blnResult;        }        /// <summary>         /// IExtenderProvider成员方法-设置响应移动属性         /// </summary>         public void SetHandleMove(Control control, bool value)        {            if (value)            {                if (m_NoHandleControls.Contains(control))                    m_NoHandleControls.Remove(control);            }            else            {                if (!m_NoHandleControls.Contains(control))                    m_NoHandleControls.Add(control);            }        }        /// <summary>         /// 成员方法-获取响应移动属性         /// </summary>         [DefaultValue(true)]        [Description("指示控件是否响应改变位置操作。")]        public bool GetHandleMove(Control control)        {            if (m_HandleControl != null && (control == this.m_HandleControl || IsContainSubControl(m_HandleControl, control)))            {                if (this.m_NoHandleControls.Contains(control))                    return false;                else                    return true;            }            else                return false;        }

 

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

 

 

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

该组件的类图如下:

 

ResizableComponent组件的属性有:

Enable:指示组件是否可用

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

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

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

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

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

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

 

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

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

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

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

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

  1.         /// <summary>         /// 鼠标按下事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void SizableControl_MouseDown(object sender, MouseEventArgs e)        {            if (!m_Enable)                return;            m_ResizeOriginalPoint = Control.MousePosition;        }        /// <summary>         /// 鼠标移动事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void SizableControl_MouseMove(object sender, MouseEventArgs e)        {            if (!m_Enable)                return;            if (e.Button == MouseButtons.None)            {                this.CheckMousePoint(sender as Control, e.Location);                return;            }            if (e.Button != MouseButtons.Left)                return;            Point OffsetPoint = Control.MousePosition;            OffsetPoint.Offset(-m_ResizeOriginalPoint.X, -m_ResizeOriginalPoint.Y);            switch (m_HandleArea)            {                case ResizeHandleAreaEnum.TopLeft:                    this.SetControlBound(OffsetPoint.X, OffsetPoint.Y, -OffsetPoint.X, -OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.TopCenter:                    this.SetControlBound(0, OffsetPoint.Y, 0, -OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.TopRight:                    this.SetControlBound(0, OffsetPoint.Y, OffsetPoint.X, -OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.CenterLeft:                    this.SetControlBound(OffsetPoint.X, 0, -OffsetPoint.X, 0);                    break;                case ResizeHandleAreaEnum.CenterRight:                    this.SetControlBound(00, OffsetPoint.X, 0);                    break;                case ResizeHandleAreaEnum.BottomLeft:                    this.SetControlBound(OffsetPoint.X, 0, -OffsetPoint.X, OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.BottomCenter:                    this.SetControlBound(000, OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.BottomRight:                    this.SetControlBound(00, OffsetPoint.X, OffsetPoint.Y);                    break;                case ResizeHandleAreaEnum.Center:                default:                    break;            }            this.m_ResizeOriginalPoint = Control.MousePosition;        }        /// <summary>         /// 鼠标离开事件         /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void SizableControl_MouseLeave(object sender, EventArgs e)        {            if (!m_Enable)                return;            (sender as Control).Cursor = Cursors.Default;            this.m_ResizableControl.Cursor = Cursors.Default;        }

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

  1.     /// <summary>     /// 改变大小控制区域枚举     /// </summary>     [Flags]    [Serializable]    [Editor(typeof(ResizeHandleAreaUITypeEditor), typeof(System.Drawing.Design.UITypeEditor))]    public enum ResizeHandleAreaEnum    {        /// <summary>         /// 中央区域,不响应操作         /// </summary>         Center = 0,        /// <summary>         /// 顶端靠左         /// </summary>         TopLeft = 1,        /// <summary>         /// 顶端居中         /// </summary>         TopCenter = 2,        /// <summary>         /// 顶端靠右         /// </summary>         TopRight = 4,        /// <summary>         /// 中间靠左         /// </summary>         CenterLeft = 8,        /// <summary>         /// 中间靠右         /// </summary>         CenterRight = 16,        /// <summary>         /// 底部靠左         /// </summary>         BottomLeft = 32,        /// <summary>         /// 底部居中         /// </summary>         BottomCenter = 64,        /// <summary>         /// 底部靠右         /// </summary>         BottomRight = 128,    }

 

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

 

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

  1.         //原始响应区域         private ResizeHandleAreaEnum m_OldAears;        /// <summary>         /// 改变大小的响应区域枚举         /// </summary>         public ResizeHandleAreaEnum ResizeHandleAreas        {            get            {                ResizeHandleAreaEnum Areas = ResizeHandleAreaEnum.Center;                if (chkTopLeft.Checked)                    Areas |= ResizeHandleAreaEnum.TopLeft;                if (chkTopCenter.Checked)                    Areas |= ResizeHandleAreaEnum.TopCenter;                if (chkTopRight.Checked)                    Areas |= ResizeHandleAreaEnum.TopRight;                if (chkCenterLeft.Checked)                    Areas |= ResizeHandleAreaEnum.CenterLeft;                if (chkCenterRight.Checked)                    Areas |= ResizeHandleAreaEnum.CenterRight;                if (chkBottomLeft.Checked)                    Areas |= ResizeHandleAreaEnum.BottomLeft;                if (chkBottomCenter.Checked)                    Areas |= ResizeHandleAreaEnum.BottomCenter;                if (chkBottomRight.Checked)                    Areas |= ResizeHandleAreaEnum.BottomRight;                if (Areas == ResizeHandleAreaEnum.Center)                    return m_OldAears;                else                    return Areas;            }        }        /// <summary>         /// 设置响应改变大小的区域         /// </summary>         /// <param name="ResizeHandleArea"></param>         public void SetValue(ResizeHandleAreaEnum ResizeHandleArea)        {            m_OldAears = ResizeHandleArea;            chkTopLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopLeft) != 0);            chkTopCenter.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopCenter) != 0);            chkTopRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.TopRight) != 0);            chkCenterLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.CenterLeft) != 0);            chkCenterRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.CenterRight) != 0);            chkBottomLeft.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomLeft) != 0);            chkBottomCenter.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomCenter) != 0);            chkBottomRight.Checked = ((m_OldAears & ResizeHandleAreaEnum.BottomRight) != 0);        }

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

  1.     internal class ResizeHandleAreaUITypeEditor : UITypeEditor    {        private ResizeHandleAreaEditorControl m_EditorControl = null;        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)        {            return UITypeEditorEditStyle.DropDown;        }        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)        {            if (m_EditorControl == null)                m_EditorControl = new ResizeHandleAreaEditorControl();            m_EditorControl.SetValue((ResizeHandleAreaEnum)value);            IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));            edSvc.DropDownControl(m_EditorControl);            return m_EditorControl.ResizeHandleAreas;        }    }

 

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

 

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

 

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

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/1015110
推荐阅读
相关标签
  

闽ICP备14008679号