2013-04-07 13 views
32

Le caselle di testo winforms hanno proprietà che rendono possibile un pulsante incorporato, alla fine della casella?Pulsante all'interno di una casella di testo winforms

Qualcosa come il pulsante Preferiti sulla barra degli indirizzi di Chrome:

enter image description here

Ho visto anche qualcosa di simile al seguente in alcune forme di Excel:

enter image description here


EDIT

ho seguito la risposta di Hans Passant con l'aggiunta di un gestore di eventi click e sembra funzionare bene:

protected override void OnLoad(EventArgs e) { 
     var btn = new Button(); 
     btn.Size = new Size(25, textBoxFolder.ClientSize.Height + 2); 
     btn.Location = new Point(textBoxFolder.ClientSize.Width - btn.Width, -1); 
     btn.Cursor = Cursors.Default; 
     btn.Image = Properties.Resources.arrow_diagright; 
     btn.Click += btn_Click;  
     textBoxFolder.Controls.Add(btn); 
     // Send EM_SETMARGINS to prevent text from disappearing underneath the button 
     SendMessage(textBoxFolder.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16)); 
     base.OnLoad(e); 
    } 

    [System.Runtime.InteropServices.DllImport("user32.dll")] 
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); 

    private void btn_Click(object sender, EventArgs e) { 
     MessageBox.Show("hello world"); 
    } 
+7

@Highcore, sei un pony senza troppe pretese. Aggiungi il tag [winforms] ai tag ignorati, non voglio più vedere i tuoi wpf rants. –

+0

@HighCore Ho notato a voi prima la stessa cosa che Hans sta dicendo. Consiglio vivamente di evitare semplicemente le domande di WinForms da ora in poi. –

+0

@HansPassant .... Sono appena tornato dal congedo e sto recuperando le domande - sembra che voi brave persone lo abbiate spaventato! – whytheq

risposta

44

Per ottenere il pulsante all'interno del TextBox è sufficiente aggiungerlo alla casella "Controls collection". Dovrai anche fare qualcosa di ragionevole per evitare che il testo all'interno della scatola scompaia sotto il pulsante; questo richiede un pochino di pinvoke. Come questo:

protected override void OnLoad(EventArgs e) { 
     var btn = new Button(); 
     btn.Size = new Size(25, textBox1.ClientSize.Height + 2); 
     btn.Location = new Point(textBox1.ClientSize.Width - btn.Width, -1); 
     btn.Cursor = Cursors.Default; 
     btn.Image = Properties.Resources.star; 
     textBox1.Controls.Add(btn); 
     // Send EM_SETMARGINS to prevent text from disappearing underneath the button 
     SendMessage(textBox1.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16)); 
     base.OnLoad(e); 
    } 

    [System.Runtime.InteropServices.DllImport("user32.dll")] 
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); 

si presentava così, mentre ho provato il margine destro (dovrebbe avere raccolto una bitmap più bella):

enter image description here

+0

+1 grazie Hans - Vedrò se implementa nel mio progetto; sembra semplice: trova una bitmap: importa nelle risorse del progetto; dare un nome bitmap; usa il tuo codice con la nuova bitmap con nome .... – whytheq

+0

sembra funzionare bene: ho anche aggiunto la riga 'btn.Click + = btn_Click;' in modo che faccia qualcosa. Modificherò OP con quello che ho non sicuro al 100% se ho rovinato del buon codice. – whytheq

+0

@Hans Passant: questa è una soluzione fantastica, grazie. Potresti aggiungere alcuni dettagli su come funziona la funzione SendMessage? – JWiley

0

No. Per fare una cosa del genere è necessario creare il proprio controllo utente. Può essere facilmente messo insieme da una casella di testo e un pulsante. La difficoltà è che se vuoi proprietà simili alla casella di testo, dovrai crearle tutte. Alla fine è un sacco di codice.

+0

Quella seconda schermata che ho mostrato nell'OP è da un modulo Excel, quindi deve essere là fuori da qualche parte? Posso ereditarlo da una libreria di Excel? – whytheq

+1

@whytheq - Excel scritto in C++ utilizzando l'API di Windows e MFC. È un controllo scritto personalizzato. Potresti fare qualcosa di simile in WinForms, ma dovresti creare un controllo personalizzato. Suggerirei di imparare WPF. Molto più facile da fare questo – thorkia

+0

@thorkia - sembra che l'idea di Excel fosse un po 'esagerata, ma per favore controlla la soluzione di Hans + i commenti a OP. – whytheq

3

Se siete disposti ad aggiungere un riferimento a un'altra libreria, è potrebbe prendere in considerazione l'utilizzo del Krypton Toolkit (disponibile allo https://github.com/ComponentFactory/Krypton). Il kit di strumenti di base che dovresti essere in grado di utilizzare gratuitamente (senza i nastri, il navigatore o la funzionalità dello spazio di lavoro) ti consente di aggiungere "specifiche del pulsante" a vari controlli (incluse caselle di testo) che appaiono visivamente proprio come descrivi.

7

Ecco la risposta racchiusa in una sottoclasse TextBox.

public class ButtonTextBox : TextBox { 
    private readonly Button _button; 

    public event EventHandler ButtonClick { add { _button.Click += value; } remove { _button.Click -= value; } } 

    public ButtonTextBox() { 
     _button = new Button {Cursor = Cursors.Default}; 
     _button.SizeChanged += (o, e) => OnResize(e); 
     this.Controls.Add(_button); 
    } 

    public Button Button { 
     get { 
      return _button; 
     } 
    } 

    protected override void OnResize(EventArgs e) { 
     base.OnResize(e); 
     _button.Size = new Size(_button.Width, this.ClientSize.Height + 2); 
     _button.Location = new Point(this.ClientSize.Width - _button.Width, -1); 
     // Send EM_SETMARGINS to prevent text from disappearing underneath the button 
     SendMessage(this.Handle, 0xd3, (IntPtr)2, (IntPtr)(_button.Width << 16)); 
    } 

    [System.Runtime.InteropServices.DllImport("user32.dll")] 
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); 

} 
+0

Purtroppo l'uso di questa versione ha alcuni effetti collaterali molto strani. Ad esempio, puoi impostare il campo Immagine del pulsante all'interno del Designer aprendo il membro "Button" sotto ButtonTextBox, ma una volta premuto Debug/Esegui, il pulsante "dimentica" l'immagine, e di nuovo nella finestra di progettazione, l'impostazione è scomparsa pure ... – MrCC

1

Una piccola aggiunta alla bella idea di Hans Passant - se la casella di testo è ridimensionabile, si potrebbe aggiungere un legame con il pulsante sottostante in modo che la sua posizione viene regolata sulla ClientSize cambia così:

//Adjust the Location of the button on Size Changes of the containing textBox. 
    var binding = new Binding("Location", textBox1, "ClientSize"); 
    binding.Format += (s, e) => e.Value = e.Value is Size ? new Point(((Size)e.Value).Width - btn.Width, -1) : new Point(0, 0); 
    btn.DataBindings.Add(binding); 
1

Solo una piccola espansione sulla soluzione accettata, per far sì che il pulsante appaia e agisca correttamente, sono necessari alcuni accorgimenti. Qui ci sono tweaks per una casella di ricerca:

private static readonly int SEARCH_BUTTON_WIDTH = 25; 

    private void ConfigureSearchBox() 
    { 
     var btn = new Button(); 
     btn.Size = new Size(SEARCH_BUTTON_WIDTH, searchBox.ClientSize.Height + 2); 
     btn.Dock = DockStyle.Right; 
     btn.Cursor = Cursors.Default; 
     btn.Image = Properties.Resources.Find_5650; 
     btn.FlatStyle = FlatStyle.Flat; 
     btn.ForeColor = Color.White; 
     btn.FlatAppearance.BorderSize = 0; 
     btn.Click += btn_Click; 
     searchBox.Controls.Add(btn); 
     this.AcceptButton = btn; 
     // Send EM_SETMARGINS to prevent text from disappearing underneath the button 
     SendMessage(searchBox.Handle, 0xd3, (IntPtr)2, (IntPtr)(btn.Width << 16)); 
    } 

    [System.Runtime.InteropServices.DllImport("user32.dll")] 
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); 

    private void btn_Click(object sender, EventArgs e) 
    { 
     MessageBox.Show("hello world"); 
    } 
3

ho visto in Reflector che di controllo contiene il metodo "SendMessage (int, int, int)" e ho trovato un altro modo.

using System; 
using System.Reflection; 
using System.Windows.Forms; 

static class ControlExtensions 
{ 
    static BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 
    static Type[] SendMessageSig = new Type[] { typeof(int), typeof(int), typeof(int) }; 

    internal static IntPtr SendMessage(this Control control, int msg, int wparam, int lparam) 
    { 
     MethodInfo MethodInfo = control.GetType().GetMethod("SendMessage", flags, null, SendMessageSig, null); 

     return (IntPtr)MethodInfo.Invoke(control, new object[] { msg, wparam, lparam }); 
    } 
} 

Ora sovrascrivendo WndProc in ButtonTextBox possiamo ottenere l'effetto desiderato.

public class ButtonTextBox : TextBox 
{ 
    Button button; 

    public ButtonTextBox() 
    { 
     this.button = new Button(); 
     this.button.Dock = DockStyle.Right; 
     this.button.BackColor = SystemColors.Control; 
     this.button.Width = 21; 
     this.Controls.Add(this.button); 
    } 

    protected override void WndProc(ref Message m) 
    { 
     base.WndProc(ref m); 

     switch (m.Msg) 
     { 
      case 0x30: 
       int num = this.button.Width + 3; 
       this.SendMessage(0xd3, 2, num << 16); 
       return; 
     } 
    } 
} 

E penso che questo sia un modo molto più sicuro.

+0

Ho votato la soluzione ma mi chiedo come faccia a fare quello che fa. Cosa rappresentano questi numeri? – Lara

+0

Grazie per la risposta. So che 0x0030 è il messaggio WM_SETFONT, ma perché 2 e num << 16 quando wParam dovrebbe essere un handle per il font e la parola di basso ordine di lParam un booleano da ridisegnare? Inoltre, pensi che la risposta di Hans sia la migliore? Una classe autonoma è più preferibile per me. – Lara

Problemi correlati