Designing and Implement ButtonEdit Control for Windows Forms

news/2025/3/14 18:59:28
Designing and Implement ButtonEdit Control
/黃忠成
 
What’s ButtonEdit Control
 
 在撰寫商用應用程式時,我們常常會制作一種介面,利用一個TextBox控件及一個Button控件,允許使用者按下Button後開啟一個視窗,於該視窗中選取所要的資料,方便查詢及減少輸入錯誤的情況。由於這種介面在商用程式常常出現,許多3rd控件廠商都會提供整合性的控件,將TextBoxButton組合成為單一控件,以提供ButtonClick事件的方式,協助設計師設計此種介面,這種控件通常稱為ButtonEdit
 
The Requirement
 
 需求上,ButtonEdit控件是一個由TextBox控件及Button控件組合而成的複合性控件,其必須提供一個ButtonClick事件,允許設計師透過撰寫ButtonClick事件,在使用者按下按鈕後開出查詢視窗,供使用者挑選需要的資料。舉個實例來說,當使用者輸入訂單時,必須鍵入客戶編號,此時多數的商用程式都會選擇使用ButtonEdit控件,預先制作一個內含一個DataGridView控件的Form,用來顯示所有的客戶,然後於ButtonEdit控件的ButtonClick事件中開啟此Form,當使用者於DataGridView控件中選取某筆資料時,將該客戶編號回填至ButtonEdit控件,如圖1所示。
圖1
ButtonEdit控件也可以當成一個更好的ComboBox控件來使用,允許使用者於按下按紐時,以下拉盒的方式,將視窗開在ButtonEdit控件下方,如圖2
2
2中,ButtonEdit控件所拉出的視窗中放了一個DataGridView控件,允許使用者選取所要的資料,這個截圖同時也帶出了此種介面的強處,由於其拉出的是一個Form,這意味著任何可放入Form的控件,都可以用這種方式呈現。
 
Designing
 
 結構上,ButtonEdit控件是由TextBoxButton兩個控件所組成,這點可以利用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個字的TextBox控件,會因為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;
        }
}
OrpDropDownButtonButtonEdit控件內部的子控件,所以此處為她標上了TooboxItem(false)這個Attribute,這個動作可以讓此控件不會出現在VS 2005Toolbox Pattern上。於建構子中,OrpDropDownButton讀入了內建的Bitmap檔案,也就是一個往下的箭頭,如圖3
3
理論上,當使用者點選OrpDropDownButton時,她不應該獲得焦點,所以此處覆載了OnEnter函式,在其取得焦點後,立即將焦點還給父控件,也就是ButtonEdit。完成了這個簡單的Button後,接下來是處理ButtonEdit控件中的文字輸入框,這裡有一個問題必須先解決,那就是前面所提及,如何裁切可輸入的文字寬度,避免因OrpDropDownButton在成為ButtonEdit控件的子控件後,導致部份的文字輸入不可見,這點必須依賴Windows APISendMessage函式,遞送一個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控件的字型、大小改變時,也意味著輸入區域必須重新計算,所以此處覆載了FontChangeResizeInitLayoutOnEnter函式,確保在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控件後,設定了其CursorCustsors.Hand,這使得使用者將滑鼠移到此按鈕上後,游標會顯示為。接著將CausesValidation設為False,這關閉了當焦點移到此按鈕時,不會引發任何的Validation事件。然後將TabStop設為False,這避免當使用者於OrpCustomButtonEdit控件上按下Tab鍵時,焦點移到此OrpDropDownButton控件上,而是移到下一個可接受焦點的控件上。細心的讀者或許已經察覺,OrpCustomButtonEdit控件被標上了ToolboxItem(false) Attribute,這意味著她不會出現在VS 2005Toolbox Pattern上,同時OrpCustomButtonEdit控件也未開放ButtonClick事件,而是設計了一個虛擬函式:EmbedButtonClick這個設計的目的很簡單,就是將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控件的效果。



文章来源:https://blog.csdn.net/zhaowei001/article/details/2271662
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-3785913.html

相关文章

如果你是程序员你或许不须以撰写组件维生,但我确信你必定得依赖组件维生!

程序员总是处于一个多变的年代中&#xff0c;不管你现在拥有多少技术&#xff0c;明天你都有可能又得从头学习&#xff0c;此言或有悚听之意&#xff0c;但事实离此不远。回想一下&#xff0c;当年你所学会的程序语言或开发工具&#xff0c;今天有多少还是你手中的赚钱工具&…

封面 雷军个性内敛和刘强东霸气外露,哪个更有潜力成为千亿大佬?

今天有网友问一个问题&#xff1a;雷军的小米和刘强东的京东&#xff0c;哪个更有潜力&#xff1f;我的答案是&#xff0c;各有千秋。 小米和京东的商业模式不同 一个是在构建全球最大的物联网平台&#xff0c;一个是电商平台&#xff0c;未来的发展发向也不一样。所以很难说谁…

MySQL8的新特性ROLE

【MySQL的ROLE解决了什么问题】 假设你是一个职业素养良好的DBA比较同时又比较注重权限管理的话&#xff1b;可能遇到过这样的问题&#xff0c;数据库中有多个开发人员的账号&#xff1b;有一天要建 一个新的schema&#xff0c;如果你希望之前所有的账号都能操作这个schema下的…

Inside ASP.NET 2.0-即时编译系统

Inside ASP.NET 2.0-即时编译系统文/ 黄忠成(原文刊登于Run! PC) 从ASP.NET 1.1 到2.0&#xff0c; 编译系统的进化 在笔者撰写『深入剖析ASP.NET 元件设计』一书时&#xff0c;曾相当深入的探讨ASP.NET 1.1 的即时编译模型&#xff0c; 该章节以图1 为开端&#xff0c; 一步步…

阿里巴巴在创新榜上把硅谷巨头按在地上“摩擦”,他到底强在哪?

无创意&#xff0c;不创新&#xff0c;毋宁死。已经成为企业生存的不二法门。 创新对于企业而言&#xff0c;是关乎企业生死存亡的生命线。不创新&#xff0c;不紧握时代脉搏&#xff0c;再高大的巨人&#xff0c;也会迅速倒下。也真是因此&#xff0c;全球四大四大会计师事务所…

像Component的Control

像Component的Control當我第一次接觸到ToolStrip控件時&#xff0c;我很好奇&#xff0c;為何此控件的行為就像是Component一樣&#xff0c;意思是當你將其拖到Form上後&#xff0c;除了在Form上看到她外&#xff0c;你還可以在Component Tray上找到她&#xff0c;如下圖&#…

共同肩负脱贫致富的重担, 袁隆平与阿里巴巴究竟能碰撞出怎样的火花呢?

“世界杂交水稻之父”袁隆平虽已88岁高龄&#xff0c;但他和他的团队从未停歇过&#xff0c;前脚宣布在沙漠成功种植水稻&#xff0c;最高亩产超过500公斤&#xff0c;后脚就领衔青岛“海水稻”研发团队加入农村淘宝发起的亩产一千美金计划&#xff0c;双方将在电商脱贫领域展开…

修復VS.NET 2005

最近常聽到VS.NET 2005的奇怪問題,例如DataSource Wizard在選取Database做為來源時就當住,或是無來由的,Properties窗再也打不開等等,即使重新安裝Visual Studio 2005也無法解決.在多方嘗試後,我發現這些問題的來源是因為Visual Studio 2005會儲存用戶的個人設定,而有時這些設定…

spring data jpa 分页查询

https://www.cnblogs.com/hdwang/p/7843405.html spring data jpa 分页查询 法一&#xff08;本地sql查询,注意表名啥的都用数据库中的名称&#xff0c;适用于特定数据库的查询&#xff09; public interface UserRepository extends JpaRepository<User, Long> {Query(v…

上海一个宣布,外国商家直呼:亚马逊在中国新零售面前啥也不是!

刚刚&#xff0c;上海市商务委宣布正式启动打造全球新品首发地。 在上海市领导的见证下&#xff0c;天猫与登上上海2018全球新品首发地人气榜单的Kerr&Kroes等品牌签署了战略合作协议。 这些品牌中还包括星巴克甄选咖啡烘培工坊、静安大悦城、维多利亚的秘密大秀、上海时装…