Designing and Implement ButtonEdit Control for Windows Forms

 
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 控件的效果。
### 回答1: 设计线性和开关电源的控制回路是一项重要的工作,可以保证电源的性能和稳定性。线性和开关电源是常见的电源类型,其工作原理和性能要求略有不同。 线性电源的控制回路主要包括电压反馈回路和当前反馈回路。电压反馈回路通过测量输出电压,并经过稳压器和放大器进行反馈控制。当前反馈回路则测量输出电流,并通过电流传感器和比较器对电流进行反馈控制。这些回路可以调整电源的输出电压和电流,以维持所需的稳定状态。 开关电源的控制回路则更加复杂。开关电源通过开关器件的周期性开关操作来控制输出电压和电流。其控制回路一般包括脉冲宽度调制器(PWM)和反馈控制回路。PWM负责将输入信号转换为开关器件的开关信号,以控制输出电压和电流的波形。反馈控制回路通过测量输出电压和电流,并将其与参考信号进行比较,通过调整PWM的占空比来实现输出的稳定性。 在设计控制回路时,需要考虑电源的负载变化、输出纹波、响应时间等因素。同时,选择合适的控制器、传感器和滤波器等元件也是很重要的。通过模拟和仿真等手段,可以优化和调整控制回路的参数,以满足特定的设计要求。 总之,设计线性和开关电源的控制回路需要综合考虑电源的特性和要求,并选择合适的控制方法和元件,以实现稳定、高效的电源输出。 ### 回答2: 设计线性和开关电源的控制环路涉及电源系统的稳定性和反馈机制。电源控制环路的设计目标是在负载变化时保持输出电压的稳定性和精确性。 线性电源的控制环路通常包括一个稳压器,在输入电压和负载变化时调整输出电压以保持在设定范围内。其中最常见的稳压器是线性稳压器和开环控制的电源。 开关电源的控制环路更复杂。开关电源通常通过高频开关和磁性元件(例如变压器和电感器)来转换输入电压。控制环路中的关键组件包括比较器、误差放大器、PWM(脉宽调制)控制器和反馈网络。通过比较输出电压与设定值,误差放大器将误差信号放大并传递给PWM控制器。PWM控制器以一定的频率开关开关管,调整开关管的开关周期和占空比,以使输出电压保持在设定范围内。 在设计控制环路时,需要考虑几个因素。首先,必须选择恰当的控制架构,包括类型和配置。其次,需要确定合适的反馈机制,以确保输出电压的准确性和稳定性。并且,必须采取适当的补偿措施,以保证系统的稳态和动态响应。最后,需要进行模拟和实验验证,以确保控制环路的性能和可靠性。 综上所述,设计线性和开关电源的控制环路是一项复杂的任务,涉及到稳定性、反馈机制和精确性。正确设计和实施控制环路可以确保电源系统的性能和稳定性。 ### 回答3: 线性电源和开关电源是常用的电源类型,用于为电子设备提供稳定的电压和电流。设计控制回路对于这两种电源都非常重要。 对于线性电源,控制回路的设计旨在实现稳定的输出电压。线性电源基于传统的电压调节器原理,输入电压经过变压器降压后,通过功率晶体管进行调整,以实现所需的输出电压。控制回路可以监测输出电压,并在需要时调整功率晶体管的导通时间或频率,以保持输出电压的稳定性。这可以通过使用反馈电路来实现,其中输出电压与参考电压进行比较,并根据差异来调整控制元件。设计控制回路还需要考虑输入电压变化、负载变化等因素对输出电压的影响。 对于开关电源,控制回路的设计旨在实现高效率和稳定的输出。开关电源通过周期性地开关功率开关,将输入电压转换为高频脉冲信号,再经过滤波器和转换器,最后输出所需的稳定电压。控制回路可以监测输出电压和电流,并相应地调整开关时间和频率,以保持输出的稳定性和效率。这可以通过使用PWM(脉宽调制)技术来实现,其中输出电压与参考电压进行比较,并根据差异来调整开关元件的控制信号。设计控制回路还需要考虑输入电压范围、负载变化、开关频率等因素对输出稳定性和效率的影响。 总结起来,设计控制回路对于线性电源和开关电源都是至关重要的。通过使用适当的反馈电路和PWM技术,可以确保输出电压稳定、效率高,并满足电子设备的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值