本篇介紹如何實現多列下拉框以及用鼠標移動控件和改變控件大小,示例中包含三個組件,其中介紹了如何實現自定義屬性編輯器UITypeEditor,如何模仿Anchor屬性設置一個屬性為多個枚舉值,如何模仿ToolTip控件給其他控件添加擴展屬性。
網頁中浮動層的應用非常廣泛,但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(this, new 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實現。效果如下圖所示:

該組件處理目標控件的三個鼠標事件,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(0, 0, OffsetPoint.X, 0);
break;
case ResizeHandleAreaEnum.BottomLeft:
this.SetControlBound(OffsetPoint.X, 0, -OffsetPoint.X, OffsetPoint.Y);
break;
case ResizeHandleAreaEnum.BottomCenter:
this.SetControlBound(0, 0, 0, OffsetPoint.Y);
break;
case ResizeHandleAreaEnum.BottomRight:
this.SetControlBound(0, 0, 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外觀。然后排布為虛擬邊框的效果,如下圖:

該控件主要是將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,當快速移動鼠標時,改變大小和移動操作都會產生滯后的效果,希望有解決方法的朋友留言。
示例代碼下載: https://files.cnblogs.com/conexpress/TestMouseComponent.zip
Author:Alex Leo
Email:conexpress@qq.com
Blog:http://conexpress.cnblogs.com/