程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET組件控件實例編程系列——4.多列下拉框和鼠標相關組件(二)

.NET組件控件實例編程系列——4.多列下拉框和鼠標相關組件(二)

編輯:關於.NET

可改變大小組件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,不限制移動方向。

該組件需要處理的鼠標事件有鼠標移入、鼠標按下、鼠標移動和鼠標離開,實現代碼如下:

/// <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控件類似。

該接口的實現如下:

/// <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處理方法中,恢復鼠標樣式。

/// <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的,這樣就可以通過位操作來解析值了。

/// <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控件的選中狀態對應起來,通過位操作來解 析和設置響應操作的區域枚舉,內部代碼如下:

//原始響應區域
        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,這裡的實現代碼如下:

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,當快速移動鼠標時,改變大小和移動操作都會產生滯後的效果,希望有解決方法 的朋友留言。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved