2009-03-22 12 views
16

saluto tutti,.NET --- controllo TextBox - attendere fino all'utente avviene digitando

C'è una costruito nel modo di sapere quando un utente è fatto a digitare in una casella di testo? (Prima di toccare la scheda, o spostare il mouse) Ho una query di database che si verifica sull'evento textchanged e tutto funziona perfettamente. Tuttavia, ho notato che c'è un po 'di ritardo perché, se un utente digita velocemente nella casella di testo, il programma è impegnato a eseguire una query per ciascun carattere. Quindi quello che speravo era un modo per vedere se l'utente ha finito di scrivere. Quindi, se digitano "a" e si fermano, viene generato un evento. Tuttavia, se digitano "fino in fondo" l'evento si attiva dopo la chiave y.

Ho alcune idee che mi girano intorno ma sono sicuro che non siano le più efficienti. Come misurare il tempo trascorso dall'ultimo evento textchange e se era> di un certo valore, allora procedeva a eseguire il resto delle mie procedure.

fatemi sapere cosa ne pensate.

Lingua: VB.NET quadro: "battitura fatto" Net 2.0

--Edited per chiarire "fatto digitando"

risposta

34

Un approccio:

  1. Creare un Timer con Interval di millisecondi X

    L'intervallo dovrebbe essere di circa 300 ms; più di un tempo normale azionamento di tasto, e anche il tempo ragionevole di attesa tra la finitura e l'aggiornamento si verificano

  2. Nell'evento dell'ingresso TextChanged, Stop() e quindi Start() il Timer

    Ciò riavvierà la Timer se è già in esecuzione, quindi se l'utente continua a digitare a una frequenza normale, ogni modifica riavvierà il timer.

  3. Nel caso in cui il timer Tick, Stop() il Timer e fare la lunga operazione di

  4. Opzionale: Maneggiare le Leave e KeyDown eventi in modo che lasciare il controllo o premendo Enter sarà Stop() il Timer e fare il lungo transazione.

Ciò causerà un aggiornamento se il testo è stato modificato e l'utente non ha apportato alcuna modifica in X millisecondi.

Un problema con l'approccio "Misurare il tempo dall'ultimo aggiornamento" che stai considerando è che se l'ultima modifica viene apportata rapidamente, l'aggiornamento non avverrà e non ci saranno ulteriori modifiche all'attivazione un altro assegno.

Nota: Ci deve essere un 1-1 abbinamento tra TextBox es e Timer s; se hai intenzione di farlo con più di un input, prenderei in considerazione la creazione di un UserControl che avvolge questa funzionalità.

+0

Praticamente l'approccio che usiamo. –

+0

Buon piano, grazie per il suggerimento. Dare il valore ms è anche molto utile grazie. –

+0

@Cj Anderson: potresti dover giocare un po 'per trovare il giusto equilibrio tra reattività e velocità di digitazione –

1

Dipende da cosa si intende per C'è un evento per farti sapere quando l'utente ha lasciato il focus su quel particolare controllo. Inoltre, c'è anche un cambiamento che ti dice quando il testo cambia. Che cosa si potrebbe fare è trappola due cose:

1) persa fuoco

2) Ogni volta che l'utente cambia il testo, avviare un timer per dire, 20 secondi, e se l'utente è fatto entro tale termine, quindi l'utente è "finito" digitando. Cioè se l'utente non ha fatto nulla entro quel tempo quindi si assume che l'utente sia "finito".

Se una di queste due cose si verifica, l'utente ha terminato, assicurarsi di arrestare e riavviare il timer in modo appropriato. Ovviamente, è possibile modificare il timeout.

Tutto dipende da come si desidera definirlo.

+0

Che cosa significa "e se l'utente è fatto entro quel tempo "significa? Che non hanno digitato alcun carattere aggiuntivo? –

+0

Sì, mi dispiace, ho modificato il post per riflettere questo. – BobbyShaftoe

0

L'approccio che ho usato con successo in passato utilizza un evento di reset manuale e invocazioni asincrone per rilevare quando l'utente ha smesso di digitare. Il codice simile a questa

// use manual reset event to Q up waiting threads. 
// each new text changed event clears the Q 
// only the last changed will hit the timeout, triggering the action 
private ManualResetEvent _delayMSE; 
private Func<bool> TBDelay =() => !_delayMSE.WaitOne(600, false); 
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) 
{ 
    SendOrPostCallback ActionToRunWhenUserStopsTyping = o => 
    { 
     // ... 
    }; 

    _delayMSE.Set(); // open the ResetEvent gate, to discard these delays 
    Thread.Sleep(0); // let all pending through the gate 
    _delaySearchMSE.Reset(); // close the gate 
    TBDelay.BeginInvoke(res => 
    { 
     // callback code 
     // check how we exited, via timeout or signal. 
     bool timedOut = TBDelay.EndInvoke(res); 
     if (timedOut) 
      Dispatcher.Invoke(DispatcherPriority.Input, 
          ActionToRunWhenUserStopstyping,null); 
    }, null); 
} 
3

ho finito per cercare Scott Weinstein risposta anche se richiesto una certa conoscenza più profonda threading, i delegati e la sintassi lambda di base. E ha funzionato abbastanza bene. La sua risposta originale non era pura copia-incolla quindi ho dovuto giocare un po 'per farlo funzionare.

Ho aggiunto pochissimo tempo a Thread.Sleep, dal momento che ho notato che il metodo invoke può accadere due volte se l'utente sta digitando molto velocemente ma con un piccolo ritardo casuale tra alcuni dei tasti premuti. È inoltre necessario aggiungere un riferimento all'assembly di WindowsBase per l'utilizzo di Dispatcher.

Io uso 1,5 secondi per attendere la fine della scrittura da parte dell'utente.

// use manual reset event to Q up waiting threads. 
    // each new text changed event clears the Q 
    // only the last changed will hit the timeout, triggering the action 
    private ManualResetEvent _delayMSE; 
    private Func<bool> TBDelay; 
    private delegate void ActionToRunWhenUserStopstyping(); 

    public Form1() 
    { 
     InitializeComponent(); 

     _delayMSE = new ManualResetEvent(false); 
     TBDelay =() => !_delayMSE.WaitOne(1500, false); 
    } 

    private void textBox1_TextChanged(object sender, EventArgs e) 
    { 
     _delayMSE.Set(); 

     // open the ResetEvent gate, to discard these delays  
     Thread.Sleep(20); 
     // let all pending through the gate  
     _delayMSE.Reset(); 
     // close the gate 
     TBDelay.BeginInvoke(res =>  
     {   
      // callback code   
      // check how we exited, via timeout or signal.   
      bool timedOut = TBDelay.EndInvoke(res); 
      if (timedOut) 
       Dispatcher.CurrentDispatcher.Invoke(
        new ActionToRunWhenUserStopstyping(DoWhatEverYouNeed), 
        DispatcherPriority.Input); 
     }, null); 
    } 

    private void DoWhatEverYouNeed() 
    { 
     MessageBox.Show(textBox1.Text); 
    } 
5

Per coloro che hanno bisogno di qualcosa di simile in .NET 2.0, qui ho fatto un controllo che deriva dal TextBox e utilizza lo stesso approccio .. Spero che questo aiuto

public partial class TextBox : System.Windows.Forms.TextBox 
{ 

    private ManualResetEvent _delayMSE; 
    public event EventHandler OnUserStopTyping; 
    private delegate bool TestTimeout(); 

    public TextBox() 
    { 
     _delayMSE = new ManualResetEvent(false); 
     this.TextChanged += new EventHandler(TextBox_TextChanged); 
    } 

    void TextBox_TextChanged(object sender, EventArgs e) 
    { 


     _delayMSE.Set(); 
     Thread.Sleep(20); 
     _delayMSE.Reset(); 

     TestTimeout tester = new TestTimeout(TBDelay); 
     tester.BeginInvoke(new AsyncCallback(Test), tester); 

    } 


    private void Test(IAsyncResult pResult) 
    { 
     bool timedOut = (bool)((TestTimeout)pResult.AsyncState).EndInvoke(pResult); 
     if (timedOut) 
     { 
      if (OnUserStopTyping != null) 
       OnUserStopTyping(this, null); 
     } 
    } 

    private bool TBDelay() 
    { 
     return !_delayMSE.WaitOne(500, false); 
    } 

} 
+0

Buona soluzione. Ho adattato il tuo codice al mio. Esteso l'evento OnUserStopTyping e ora funziona per me. Grazie per l'aiuto. –