.NET组件控件实例编程系列——4.多列下拉框和鼠标相关组件

网页中浮动层的应用非常广泛,但Windows程序中却少有浮动层。难道Windows程序中不需要浮动层吗?根据不同的需要实现相应的功能,有人会觉得直接在界面上添加控件更简单,或者用对话窗口的方法实现是一样的,只要实现功能就可以了。当然,解决方法有很多种,这里给出采用浮动层的实现方式。比如在ComboBox控件中下拉选择项时,只能显示一列,而且项数很多的时候又不能查找,用一个带查找功能的多列下拉框会方便很多(如下图)。
多列下拉框

 有些地方需要显示多行信息,但空间却比较小,像TextBox控件的Text属性一样,采用一个下拉文本框来显示和编辑,会更加灵活。
多行编辑框
本示例包含三个组件,一个是提供下拉功能的组件DropdownComponent,一个是实现用鼠标移动控件位置的组件MovableComponent,一个是实现用鼠标调整控件大小的组件ResizableComponent。在示例中包含如何实现自定义可视化属性设计器UITypeEditor,如何将一个属性的值设置为多个枚举以及分解成多个枚举,如何实现类似ToolTip给其他控件添加属性的功能。

下面介绍各个组件的实现原理与要点:
下拉组件DropdownComponent
实现原理
1、点击ComboBox的下拉标志时,显示浮动控件
2、浮动控件失去焦点时,根据标志位判断是否隐藏控件
实现要点
1、需要将ComboBox控件的DropDownHeight属性设置为1(设置为0无效),避免出现多余的框
2、需要调整目标控件的显示位置,不能超出窗体的边框
3、可以根据需要不只针对ComboBox控件,只要在一个事件处理方法中显示浮动控件即可
该组件实现起来非常简单,主要是处理ComboBox控件的DropDown事件,以及浮动控件的Leave事件。
首先声明内部字段和属性

Code
        #region 字段和属性

        
private const string c_ControlCategory = "控制";

        
private ComboBox m_TargetComboBox = null;
        [Category(c_ControlCategory), Description(
"目标下拉控件。")]
        
public ComboBox TargetComboBox
        {
            
get { return m_TargetComboBox; }
            
set
            {
                
if (this.DesignMode)
                {
                    m_TargetComboBox 
= value;
                }
                
else
                {
                    
if (m_TargetComboBox != value)
                    {
                        
this.RemoveDropDownEventHandler();
                        m_TargetComboBox 
= value;
                        
this.AddDropDownEventHandler();
                    }
                }
            }
        }

        
private Control m_FloatControl = null;
        [Category(c_ControlCategory), Description(
"浮动控件。")]
        
public Control FloatControl
        {
            
get { return m_FloatControl; }
            
set
            {
                
if (this.DesignMode)
                {
                    m_FloatControl 
= value;
                }
                
else
                {
                    
if (m_FloatControl != value)
                    {
                        
this.RemoveLeaveEventHandler();
                        m_FloatControl 
= value;
                        
this.AddLeaveEventHanlder();
                    }
                }
            }
        }

        
private bool m_AutoHide = true;
        [Category(c_ControlCategory), Description(
"指示在浮动控件失去焦点时自动隐藏。"), DefaultValue(true)]
        
public bool AutoHide
        {
            
get { return m_AutoHide; }
            
set { m_AutoHide = value; }
        }

        
private DropDownControlWidthModeEnum m_FloatControlWidthMode = DropDownControlWidthModeEnum.UserDefine;
        [Category(c_ControlCategory),Description(
"下拉浮动控件的宽度模式。"),DefaultValue(DropDownControlWidthModeEnum.UserDefine)]
        
public DropDownControlWidthModeEnum FloatControlWidthMode
        {
            
get { return m_FloatControlWidthMode; }
            
set { m_FloatControlWidthMode = value; }
        }
        
        
#endregion 字段和属性

其中的FloatControlWidthMode属性用来指示浮动控件的宽度模式,当设置为UserDefine时,按照控件的设置尺寸显示,当设置为AutoWidth时,调整浮动控件的宽度与下拉框的宽度相等。
下面是下拉框事件处理以及相关的方法。其中将显示和隐藏方法重载了,一组是显示指定的控件,一组是用于显示外部控件,而且都设置为public。这里和之前用Label模拟网页链接的组件一样,可以根据需要选择是否公开方法,比如用其他控件控制显示浮动控件就必须用public。还有在显示浮动控件时,将浮动控件添加到窗体中,这是为了避免在其他容器中,浮动控件只显示一部分,添加到窗体后则可以完整显示,不会被其他容器遮挡。 

Code
        #region 浮动控件操作及事件处理

        
private void AddDropDownEventHandler()
        {
            
if (m_TargetComboBox != null)
            {
                TargetComboBox.DropDown 
+= new EventHandler(TargetComboBox_DropDown);
                
this.m_TargetComboBox.DropDownHeight = 1;
            }
        }

        
private void RemoveDropDownEventHandler()
        {
            
if (m_TargetComboBox != null)
            {
                TargetComboBox.DropDown 
-= new EventHandler(TargetComboBox_DropDown);
                
this.m_TargetComboBox.DropDownHeight = 106;
            }
        }

        
private void AddLeaveEventHanlder()
        {
            
if (m_FloatControl != null)
            {
                FloatControl.Leave 
+= new EventHandler(FloatControl_Leave);
                
this.m_FloatControl.Visible = false;
            }
        }

        
private void RemoveLeaveEventHandler()
        {
            
if (m_FloatControl != null)
            {
                FloatControl.Leave 
-= new EventHandler(FloatControl_Leave);
            }
        }

        
void FloatControl_Leave(object sender, EventArgs e)
        {
            
if (m_FloatControl != null && m_AutoHide)
                
this.HideFloatControl(this.m_FloatControl);
        }

        
void TargetComboBox_DropDown(object sender, EventArgs e)
        {
            
if (m_FloatControl != null)
                
this.ShowFloatControl(this.m_TargetComboBox, this.m_FloatControl);
        }

        
/// <summary>
        
/// 显示设置的浮动控件
        
/// </summary>
        public void ShowFloatControl()
        {
            
this.ShowFloatControl(this.m_TargetComboBox, this.m_FloatControl);
        }

        
/// <summary>
        
/// 隐藏设置的浮动控件
        
/// </summary>
        public void HideFloatControl()
        {
            
this.HideFloatControl(this.m_FloatControl);
        }

        
#endregion 浮动控件操作及事件处理


        
#region 浮动控件公共操作

        
/// <summary>
        
/// 显示指定的浮动控件
        
/// </summary>
        
/// <param name="DropdownControl">下拉控件,可以不是ComboBox</param>
        
/// <param name="FloatControl">要显示的控件</param>
        public void ShowFloatControl(Control DropdownControl, Control FloatControl)
        {
            
if (DropdownControl == null)
                
throw new ArgumentNullException("DropdownControl""指定的下拉控件为null。");

            
if (FloatControl == null)
                
throw new ArgumentNullException("FloatControl""指定要显示的浮动控件为null。");

            
if (this.OnFloatControlDisplayChanging(FloatControl).Cancel)
                
return;

            Form ParentForm 
= DropdownControl.FindForm();

            
//将浮动控件添加到窗体上
            if (!ParentForm.Controls.Contains(FloatControl))
                ParentForm.Controls.Add(FloatControl);

            
//调整浮动控件宽度
            if (this.m_FloatControlWidthMode == DropDownControlWidthModeEnum.AutoWidth)
                FloatControl.Width 
= DropdownControl.Width;

            
//计算坐标
            Control ParentControl = DropdownControl.Parent;
            
int intLeft = 0, intTop = 0;
            intLeft 
= DropdownControl.Left;
            intTop 
= DropdownControl.Top + DropdownControl.Height;

            
//获取相对窗体的位置
            while (ParentControl != ParentForm)
            {
                intLeft 
+= ParentControl.Left;
                intTop 
+= ParentControl.Top;
                ParentControl 
= ParentControl.Parent;
            }

            
//判断是否超出窗体范围
            if (intLeft + FloatControl.Width > ParentForm.Width)
            {
                
int intOffset = FloatControl.Width - DropdownControl.Width;
                
if (intLeft - intOffset > 0)
                {
                    intLeft 
-= intOffset;
                }
                
if (this.m_FloatControlWidthMode == DropDownControlWidthModeEnum.AutoWidth)
                    FloatControl.Width 
= FloatControl.Width - intOffset;
            }

            
if (intTop + FloatControl.Height > ParentForm.Height)
            {
                
int intOffset = FloatControl.Height + DropdownControl.Height;
                
if (intTop - intOffset > 0)
                {
                    intTop 
-= intOffset;
                }
            }

            
//设置浮动控件位置
            FloatControl.Left = intLeft;
            FloatControl.Top 
= intTop;

            
//显示浮动控件
            FloatControl.Visible = true;
            FloatControl.BringToFront();
            FloatControl.Select();

            
this.OnFloatControlDisplayChanged(FloatControl);
        }

        
/// <summary>
        
/// 隐藏指定的浮动控件
        
/// </summary>
        
/// <param name="FloatControl">要隐藏的控件</param>
        public void HideFloatControl(Control FloatControl)
        {
            
if (FloatControl == null)
                
throw new ArgumentNullException("FloatControl""指定要显示的浮动控件为null。");

            Form ParentForm 
= this.m_TargetComboBox.FindForm();
            
if (ParentForm.Controls.Contains(FloatControl))
            {
                
if (this.OnFloatControlDisplayChanging(FloatControl).Cancel)
                    
return;

                ParentForm.Controls.Remove(FloatControl);
                FloatControl.SendToBack();
                FloatControl.Visible 
= false;

                
this.OnFloatControlDisplayChanged(FloatControl);
            }
        }

        
#endregion 浮动控件公共操作
        //另外组件中还添加了几个事件,可以更灵活控制浮动控件的显示。
        
#region 内部事件及事件处理

        
/// <summary>
        
/// 浮动控件显示状态即将改变事件
        
/// </summary>
        [Description("浮动控件显示状态即将改变事件。")]
        
public event FloatControlDisplayChangingEventHandler FloatControlDisplayChanging;
        
/// <summary>
        
/// 浮动控件显示状态已改变事件
        
/// </summary>
        [Description("浮动控件显示状态已改变事件。")]
        
public event FloatControlDisplayChangdEventHandler FloatControlDisplayChanged;

        
private FloatControlDisplayChangingEventArgs OnFloatControlDisplayChanging(Control FloatControl)
        {
            FloatControlDisplayChangingEventArgs myEventArgs 
= new FloatControlDisplayChangingEventArgs(FloatControl);
            
if (this.FloatControlDisplayChanging != null)
                
this.FloatControlDisplayChanging(this, myEventArgs);
            
return myEventArgs;
        }

        
private void OnFloatControlDisplayChanged(Control FloatControl)
        {
            
if (this.FloatControlDisplayChanged != null)
                
this.FloatControlDisplayChanged(thisnew FloatControlDisplayChangedEventArgs(FloatControl));
        }

        
#endregion 内部事件及事件处理


下面介绍鼠标相关的两个组件,这两个组件有一定的关联性。
可移动组件MovableComponent
实现原理
1、鼠标移动到指定控件上,改变鼠标样式为可移动状态
2、按下鼠标时记录鼠标的当前位置
3、鼠标移动时检测是否按下左键,如果按下左键则根据当前位置和之前记录的位置计算位移
4、根据鼠标的位移设置控件的坐标
5、鼠标离开则恢复默认鼠标样式
实现要点
1、被移动控件和响应鼠标操作的控件不一定是同一个,比如示例中列标题响应操作,内容区域不响应,移动的是最外层的那个Panel。需要设置两个属性,一个响应操作,一个被移动,两者可以一致。
2、响应操作的控件内部子控件也要有不同的响应,比如示例中标题栏中的图标和标题文字响应操作,但关闭按钮不响应。这里用扩展属性实现该功能,可以给内部控件添加一个是否响应操作的属性,让设置更加灵活。

可改变大小组件ResizableComponent
实现原理
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,不限制移动方向。
该组件需要处理的鼠标事件有鼠标移入、鼠标按下、鼠标移动和鼠标离开,实现代码如下:

Code
        /// <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控件类似。
该接口的实现如下:

Code
        /// <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实现。效果如下图所示:
UITypeEditor
该组件处理目标控件的三个鼠标事件,MouseMove、MouseLeave和MouseDown。
MouseMove处理方法中,检测鼠标的坐标所处的区域,然后根据区域和允许调整大小的方向设置不同的鼠标样式。
如果鼠标左键按下,则检测鼠标的位移量,再根据所处的区域调整控件的大小和位置。
MouseDown处理方法中,记录下鼠标的位置,供调整大小时计算位移量。
MouseLeave处理方法中,恢复鼠标样式。

Code
        /// <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的,这样就可以通过位操作来解析值了。

Code
    /// <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,
    }

枚举定义好之后,在项目中添加一个自定义控件,在其中放置8个CheckBox,设置Appearance属性为Button外观。然后排布为虚拟边框的效果,如下图:
UITypeEditor
该控件主要是将ResizeHandleAreaEnum枚举值和CheckBox控件的选中状态对应起来,通过位操作来解析和设置响应操作的区域枚举,内部代码如下:

Code
        //原始响应区域
        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,这里的实现代码如下:

Code
    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编辑界面。

ResizableComponent组件也用到了扩展属性,和上面的MovableComponent组件的实现方法类似,这里不再介绍。

示例中用一个下拉框调用一个浮动层,浮动层的标题栏可以拖动,但标题栏边框不响应改变大小操作。因为将标题栏的相关控件的HandleResize属性设置为了False,否则会造成移动的同时改变大小。要实现本篇开头给出的多列下拉框的效果,可以做一个自定义控件,然后绑定数据源即可。至于数据项的绑定,会在以后的示例中介绍到。
另外这里有个小bug,当快速移动鼠标时,改变大小和移动操作都会产生滞后的效果,希望有解决方法的朋友留言。

示例代码下载: http://files.cnblogs.com/conexpress/TestMouseComponent.zip

转载于:https://www.cnblogs.com/conexpress/archive/2009/03/11/Component_Control_04.html

猜你喜欢

转载自blog.csdn.net/weixin_34092455/article/details/93352492
今日推荐