博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Designing and Implement ButtonEdit Control for Windows Forms
阅读量:4191 次
发布时间:2019-05-26

本文共 11868 字,大约阅读时间需要 39 分钟。

 
Designing and Implement ButtonEdit Control
 
 
/
黃忠成
 
 
What’s ButtonEdit Control
 
 
在撰寫商用應用程式時,我們常常會制作一種介面,利用一個
TextBox
控件及一個
Button
控件,允許使用者按下
Button
後開啟一個視窗,於該視窗中選取所要的資料,方便查詢及減少輸入錯誤的情況。由於這種介面在商用程式常常出現,許多
3rd
控件廠商都會提供整合性的控件,將
TextBox
Button
組合成為單一控件,以提供
ButtonClick
事件的方式,協助設計師設計此種介面,這種控件通常稱為
ButtonEdit
 
The Requirement
 
 
需求上,
ButtonEdit
控件是一個由
TextBox
控件及
Button
控件組合而成的複合性控件,其必須提供一個
ButtonClick
事件,允許設計師透過撰寫
ButtonClick
事件,在使用者按下按鈕後開出查詢視窗,供使用者挑選需要的資料。舉個實例來說,當使用者輸入訂單時,必須鍵入客戶編號,此時多數的商用程式都會選擇使用
ButtonEdi
t
控件,預先制作一個內含一個DataGridView控件的Form,用來顯示所有的客戶,然後於ButtonEdit控件的ButtonClick事件中開啟此Form,當使用者於DataGridView控件中選取某筆資料時,將該客戶編號回填至ButtonEdit控件,如圖1所示。
圖1
ButtonEdit
控件也可以當成一個更好的
ComboBox
控件來使用,允許使用者於按下按紐時,以下拉盒的方式,將視窗開在
ButtonEdit
控件下方,如圖
2
2
2
中,
ButtonEdit
控件所拉出的視窗中放了一個
DataGridView
控件,允許使用者選取所要的資料,這個截圖同時也帶出了此種介面的強處,由於其拉出的是一個
Form
,這意味著任何可放入
Form
的控件,都可以用這種方式呈現。
 
Designing
 
 
結構上,
ButtonEdit
控件是由
TextBox
Button
兩個控件所組成,這點可以利用
Windows Forms
所提供的
UserControl
模式來達到,不過多數的
3rd
控件廠商並不是這麼做的,他們選擇了較低階的方式,透過
Windows API
來達到,這種模式可以讓設計者得到更多的控制權,本文即是使用此種模式來開發
ButtonEdit
控件。
 
The Problem
 
 
透過
Windows API
來開發
ButtonEdit
控件時,首先必須選擇該繼承何種既有控件,這個答案很明顯,
ButtonEdit
控件是一種內含
Button
控件的
TextBox
控件,因此選擇
TextBox
類別做為繼承標的是當然的。第二個問題是
Button
控件該如何加到
TextBox
控件中?在
Windows
架構中,所有的
Window
皆可以擁有子
Window
,這意味著
TextBox
控件也可以擁有子控件,所以只要讓
Button
控件成為
TextBox
控件的子控件即可,以
Windows Forms
架構來看,只要呼叫
TextBox
控件的
Controls.Add
函式即可達到此目的。最後一個必須注意的問題是,一旦將
Button
控件變成
TextBox
的子控件後,那麼
TextBox
控件的文字輸入區域便會受到
Button
控件的覆蓋,簡略的說,原本可輸入
10
個字的
TextBo
x
控件,會因為Button控件的加入,導致6個字後的輸入皆為不可見,這點,必須透過縮減TextBox控件中的文字輸入區域來解決。
 
Implement
 
 
實作上,要解決的第一個問題是如何令
Button
控件成為
TextBox
控件的子控件,這點可透過
TextBox.Controls.Add
函式來完成,問題是,這個
Button
控件該如何選擇,內建的
Button
控件是一個可接收焦點的控件,當使用者點選時,焦點會到達此
Button
控件上,引發上一個取得焦點控件的
LostFocus
事件,將其應用於
ButtonEdit
控件上時,就會發生使用者按下內部的按鈕後,焦點由
ButtonEdit
控件中的
TextBox
區,移到了內部的
Button
控件上,連帶引發了
LostFocus
事件及
Validation
動作,這些都會造成
ButtonEdit
控件使用上的困擾。因此最好的情況是,自行開發一個不會引發焦點切離,也就是不接收焦點的
Button
控件,做為
ButtonEdit
控件所需的
Button
子控件,不過為了不增加本文的複雜度,此處仍然選擇使用內建的
Button
控件,待日後的文章中再以自定的
Button
控件來取代,另外,為了方便日後替換,這裡以内建的
Button
控件為基礎類別,設計了一個
OrpDropDownButton
控件。
程式
1
[ToolboxItem(false)]
public
class OrpDropDownButton : Button
{
        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            if (Parent != null)
                Parent.Focus();
        }
 
        public OrpDropDownButton ()
            : base()
        {
            Image = LCBResource.DROPDOWNBTN1;
            ImageAlign = ContentAlignment.MiddleCenter;
        }
}
OrpDropDownButton
ButtonEdit
控件內部的子控件,所以此處為她標上了
TooboxItem(false)
這個
Attribute
,這個動作可以讓此控件不會出現在
VS 2005
Toolbox Pattern
上。於建構子中,
OrpDropDownButton
讀入了內建的
Bitmap
檔案,也就是一個往下的箭頭,如圖
3
3
理論上,當使用者點選
OrpDropDownButton
時,她不應該獲得焦點,所以此處覆載了
OnEnter
函式,在其取得焦點後,立即將焦點還給父控件,也就是
ButtonEdit
。完成了這個簡單的
Button
後,接下來是處理
ButtonEdit
控件中的文字輸入框,這裡有一個問題必須先解決,那就是前面所提及,如何裁切可輸入的文字寬度,避免因
OrpDropDownButton
在成為
ButtonEdit
控件的子控件後,導致部份的文字輸入不可見,這點必須依賴
Windows API
SendMessage
函式,遞送一個
EM_SETRECT
訊息至
TextBox
控件,明確告知可輸入的文字區域。
程式
2
using
System;
using
System.Drawing;
using
System.ComponentModel;
using
System.Runtime.InteropServices;
using
System.Collections.Generic;
using
System.Text;
using
System.Windows.Forms;
 
namespace
LookupComboBox
{
    internal class NativeAPI
    {
        [Serializable, StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
 
            public RECT(int left_, int top_, int right_, int bottom_)
            {
                Left = left_;
                Top = top_;
                Right = right_;
                Bottom = bottom_;
            }
 
            public int Height { get { return Bottom - Top; } }
            public int Width { get { return Right - Left; } }
            public Size Size { get { return new Size(Width, Height); } }
 
            public Point Location { get { return new Point(Left, Top); } }
 
            // Handy method for converting to a System.Drawing.Rectangle
            public Rectangle ToRectangle()
            { return Rectangle.FromLTRB(Left, Top, Right, Bottom); }
 
            public static RECT FromRectangle(Rectangle rectangle)
            {
                return new RECT(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
            }
 
            public override int GetHashCode()
            {
                return Left ^ ((Top << 13) | (Top >> 0x13))
                  ^ ((Width << 0x1a) | (Width >> 6))
                  ^ ((Height << 7) | (Height >> 0x19));
            }
 
            #region
Operator overloads
 
            public static implicit operator Rectangle(RECT rect)
            {
                return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
            }
 
            public static implicit operator RECT(Rectangle rect)
            {
                return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom);
            }
 
            #endregion
        }
 
        public const uint EM_SETRECT = 0xb3;
        public const int WS_CLIPCHILDREN = 0x02000000;
        public const int WS_CLIPSIBLINGS = 0x04000000;
        public const int ES_MULTILINE = 0x0004;
 
        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, ref RECT lParam);
    }
}
當遞送
EM_SETRECT
訊息至
TextBox
控件時,必須傳入一個
RECT
的結構體,由於
.NET Framework
並未提供
RECT
結構的定義,因此此處以
P/Invoke
的規範定義此結構。另外!當使用
EM_SETRECT
訊息時,該
TextBox
控件必須標示為
MULTILINE
,這有兩種方式可以達到,一是設定
TextBox
控件的
MultiLine
屬性為
True
,二是於建立
TextBox
控件時以
ES_MULTILINE
做為
Style
參數,此處採用第二種方式,見程式
3
程式
3
[ToolboxItem(false)]
    public class OrpCustomButtonEdit:TextBox
    {
        ..................
 
        protected override void CreateHandle()
        {
            CreateParams.Style = CreateParams.Style |
                                 NativeAPI.ES_MULTILINE |
                                NativeAPI.WS_CLIPCHILDREN |
                                 NativeAPI.WS_CLIPSIBLINGS;
            base.CreateHandle();
        }
 
        ............................
    }
覆載
CreateHandle
函式可以讓我們於
Windows Forms
建立
Control
,也就是
Windows UI
物件時,修改其
Style
定義。接下來是要處理將
OrpDropDownButton
控件變成
ButtonEdit
控件的子控件後的文字輸入區裁切動作,見程式
4
程式
4
[ToolboxItem(false)]
    public class OrpCustomButtonEdit:TextBox
    {
        private OrpDropDownButton _dropBtn = null;
 
        private void AdjustTextSize()
        {
            _dropBtn.Top = 0;
            _dropBtn.Left = Width - 20;
            _dropBtn.Height = Height - 5;
            _dropBtn.Width = 16;
            Rectangle rect = new Rectangle(0, 0, _dropBtn.Left-2,
ClientRectangle.Bottom - ClientRectangle.Top);
            NativeAPI.RECT r = NativeAPI.RECT.FromRectangle(rect);
            NativeAPI.SendMessage(Handle, NativeAPI.EM_SETRECT, (IntPtr)0, ref r);
        }
 
............................
 
        protected override void OnFontChanged(EventArgs e)
        {
            base.OnFontChanged(e);
            AdjustTextSize();
        }
 
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            AdjustTextSize();
        }
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            AdjustTextSize();
        }
 
        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            AdjustTextSize();
        }
 
        protected override void InitLayout()
        {
            base.InitLayout();
            AdjustTextSize();
        }
 
        public OrpCustomButtonEdit()
            : base()
        {
.................
        }
    }
AdjustTextSize
函式負責裁切輸入區域,令其不會被內部的
OrpDropDownButton
控件所覆蓋。另外當
ButtonEdit
控件的字型、大小改變時,也意味著輸入區域必須重新計算,所以此處覆載了
FontChange
Resize
InitLayout
OnEnter
函式,確保在
ButtonEdit
控件的部份屬性變動後,能重新裁切文字輸入區域。最後一個重要的部份是
OrpCustomButtonEdit
控件如何建立
OrpDropDownButton
控件,並令其成為
OrpCustomButtonEdit
的子控件,請見程式
5
程式
5
[ToolboxItem(false)]
    public class OrpCustomButtonEdit:TextBox
    {
        private OrpDropDownButton _dropBtn = null;
 
        .......................
 
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Return || e.KeyCode == Keys.F4)
                ButtonClick(this, EventArgs.Empty);
            else
                base.OnKeyDown(e);
        }
 
        protected virtual void EmbedButtonClick(EventArgs args)
        {
        }
 
        private void ButtonClick(object sender,EventArgs args)
        {
            EmbedButtonClick(args);
        }
 
        public OrpCustomButtonEdit()
            : base()
        {
            _dropBtn = new OrpDropDownButton();
            _dropBtn.Cursor = Cursors.Hand;
            _dropBtn.CausesValidation = false;
            _dropBtn.Click += new EventHandler(ButtonClick);
            _dropBtn.TabStop = false;
            Controls.Add(_dropBtn);
        }
    }
OrpCustomButtonEdit
控件在建立
OrpDropDownButton
控件後,設定了其
Cursor
Custsors.Hand
,這使得使用者將滑鼠移到此按鈕上後,游標會顯示為
。接著將
CausesValidation
設為
False
,這關閉了當焦點移到此按鈕時,不會引發任何的
Validation
事件。然後將
TabStop
設為
False
,這避免當使用者於
OrpCustomButtonEdit
控件上按下
Tab
鍵時,焦點移到此
OrpDropDownButton
控件上,而是移到下一個可接受焦點的控件上。細心的讀者或許已經察覺,
OrpCustomButtonEdit
控件被標上了
ToolboxItem(false) Attribute
,這意味著她不會出現在
VS 2005
Toolbox Pattern
上,同時
OrpCustomButtonEdit
控件也未開放
ButtonClick
事件,而是設計了一個虛擬函式:
EmbedButtonClic
k
這個設計的目的很簡單,就是將
OrpCustomButtonEdit
控件定義成一個基底類別,留下最大的彈性給子代類別,見程式
6
程式
6
[ToolboxItem(true)]
    public class OrpButtonEdit : OrpCustomButtonEdit
    {
        private static object _onButtonClick = new object();
 
        [Category("Behavior")]
        public virtual event EventHandler ButtonClick
        {
            add
            {
                Events.AddHandler(_onButtonClick, value);
            }
            remove
            {
                Events.RemoveHandler(_onButtonClick, value);
            }
        }
 
        protected virtual void OnButtonClick(EventArgs args)
        {
            EventHandler handler = (EventHandler)Events[_onButtonClick];
            if (handler != null)
                handler(this, args);
        }
 
        protected override void EmbedButtonClick(EventArgs args)
        {
            OnButtonClick(args);
        }
    }
OrpButtonEdit
控件才是本文最後的成品,讀者們或許會有點疑惑,為何如此大費週章將一個控件拆成兩階段完成,原因是延展性及易用性,並不是每一種
ButtonEdit
控件都需要
ButtonClick
事件,如前面所提及的以下拉盒方式,用
DataGridView
控件來讓使用者選取資料的介面,就不需要設計師來撰寫
ButtonClick
事件,只需要他們設定
DataSource
及欲顯示的欄位即可。面對這種應用,如果將
OrpButtonEdit
整合到
OrpCustomButtonEdit
後,設計師將會看到
ButtonClick
事件,這很容易引發誤用,尤其是在他們沒有原始碼的情況下。
 
OrpButtonEdit
的完整程式
using
System;
using
System.Drawing;
using
System.Collections.Specialized;
using
System.ComponentModel;
using
System.ComponentModel.Design.Serialization;
using
System.Collections.Generic;
using
System.Text;
using
System.Windows.Forms;
using
System.Globalization;
using
System.Runtime.InteropServices;
using
System.Reflection;
 
namespace
LookupComboBox
{
    [ToolboxItem(false)]
    public class OrpDropDownButton : Button
    {
        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            if (Parent != null)
                Parent.Focus();
        }
 
        public OrpDropDownButton()
            : base()
        {
            Image = LCBResource.DROPDOWNBTN1;
            ImageAlign = ContentAlignment.MiddleCenter;
        }
    }
 
    [ToolboxItem(false)]
    public class OrpCustomButtonEdit:TextBox
    {
        private OrpDropDownButton _dropBtn = null;
 
        private void AdjustTextSize()
        {
            _dropBtn.Top = 0;
            _dropBtn.Left = Width - 20;
            _dropBtn.Height = Height - 5;
            _dropBtn.Width = 16;
            Rectangle rect = new Rectangle(0, 0, _dropBtn.Left-2,
ClientRectangle.Bottom - ClientRectangle.Top);
            NativeAPI.RECT r = NativeAPI.RECT.FromRectangle(rect);
            NativeAPI.SendMessage(Handle, NativeAPI.EM_SETRECT, (IntPtr)0, ref r);
        }
 
        protected override void CreateHandle()
        {
            CreateParams.Style = CreateParams.Style |
                                 NativeAPI.ES_MULTILINE |
                                 NativeAPI.WS_CLIPCHILDREN |
                                 NativeAPI.WS_CLIPSIBLINGS;
            base.CreateHandle();
        }
 
        protected override void OnFontChanged(EventArgs e)
        {
            base.OnFontChanged(e);
            AdjustTextSize();
        }
 
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            AdjustTextSize();
        }
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            AdjustTextSize();
        }
 
        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            AdjustTextSize();
        }
 
        protected override void InitLayout()
        {
            base.InitLayout();
            AdjustTextSize();
        }
 
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Return || e.KeyCode == Keys.F4)
                ButtonClick(this, EventArgs.Empty);
            else
                base.OnKeyDown(e);
        }
 
        protected virtual void EmbedButtonClick(EventArgs args)
        {
 
        }
 
        private void ButtonClick(object sender,EventArgs args)
        {
            EmbedButtonClick(args);
        }
 
        public OrpCustomButtonEdit()
            : base()
        {
            _dropBtn = new OrpDropDownButton();
            _dropBtn.Cursor = Cursors.Hand;
            _dropBtn.CausesValidation = false;
            _dropBtn.Click += new EventHandler(ButtonClick);
            _dropBtn.TabStop = false;
            Controls.Add(_dropBtn);
        }
    }
 
    [ToolboxItem(true)]
    public class OrpButtonEdit : OrpCustomButtonEdit
    {
        private static object _onButtonClick = new object();
 
        [Category("Behavior")]
        public virtual event EventHandler ButtonClick
        {
            add
            {
                Events.AddHandler(_onButtonClick, value);
            }
            remove
            {
                Events.RemoveHandler(_onButtonClick, value);
            }
        }
 
        protected virtual void OnButtonClick(EventArgs args)
        {
            EventHandler handler = (EventHandler)Events[_onButtonClick];
            if (handler != null)
                handler(this, args);
        }
 
        protected override void EmbedButtonClick(EventArgs args)
        {
            OnButtonClick(args);
        }
    }
}
 
What’s Next
 
 
在計畫中,
ButtonEdit
控件的設計會分成兩個階段,本文是第一階段,做出
ButtonEdit
的基礎及簡單應用,第二階段將引導讀者,撰寫前面所提及的以
DataGridView
來做出類似
ComboBox
控件的效果。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1138674

你可能感兴趣的文章
《给初学者的Windows Vista的补遗手册》之037
查看>>
《给初学者的Windows Vista的补遗手册》之036
查看>>
《给初学者的Windows Vista的补遗手册》之035
查看>>
Spring开发指南 0.8 发布
查看>>
微软宣布将推出XNA Game Studio
查看>>
MySQL宣布加入微软Visual Studio工业伙伴计划
查看>>
菜鸟、夫子、玫林凯与测试
查看>>
无锁编程与分布式编程那个更适合多核CPU?
查看>>
多核系统中三种典型锁竞争的加速比分析
查看>>
多核新观念-象使用内存一样使用CPU?
查看>>
OpenMP创建线程中的锁及原子操作性能比较
查看>>
多核编程中的任务随机竞争模式的概率分析
查看>>
多核编程中的任务分组竞争模式
查看>>
模块分解原理与三权分立
查看>>
模块分解原理的探索
查看>>
90%程序员写不出无BUG的二分查找程序?
查看>>
C/C++代码检视要点
查看>>
Symbian中所体现的软件编程艺术
查看>>
c/c++中指针参数如何传递内存
查看>>
Symbian程序图标问题
查看>>