2009-09-21 11 views
6

फायरिंग के बिना यह
WinForms RichTextBox: how to perform a formatting on TextChanged?WinForms RichTextBox: एसिंक्रोनस रूप से पुन: फ़ॉर्मेट करने के लिए कैसे, TextChanged घटना

करने के लिए एक अनुवर्ती मैं एक RichTextBox के साथ एक winforms अनुप्रयोग है, एप्लिकेशन ऑटो प्रकाश डाला की सामग्री बॉक्स कहा। चूंकि फ़ॉर्मेटिंग में एक बड़े दस्तावेज़ के लिए लंबा समय लग सकता है, 10 सेकंड या उससे अधिक, मैंने एक RichWextBox के पुन: स्वरूपण करने के लिए पृष्ठभूमिवर्कर स्थापित किया है। यह पाठ के माध्यम से चलता है और इन की एक श्रृंखला करता है:

rtb.Select(start, length); 
rtb.SelectionColor = color; 

हालांकि यह इस कर रहा है, यूआई उत्तरदायी बनी हुई है।

पृष्ठभूमिवर्कर को टेक्स्ट चेंजेड ईवेंट से निकाल दिया गया है। इस तरह:

private ManualResetEvent wantFormat = new ManualResetEvent(false); 
private void richTextBox1_TextChanged(object sender, EventArgs e) 
{ 
    xpathDoc = null; 
    nav = null; 
    _lastChangeInText = System.DateTime.Now; 
    if (this.richTextBox1.Text.Length == 0) return; 
    wantFormat.Set(); 
} 

पृष्ठभूमि कार्यकर्ता विधि इस प्रकार है:

private void DoBackgroundColorizing(object sender, DoWorkEventArgs e) 
{ 
    do 
    { 
     wantFormat.WaitOne(); 
     wantFormat.Reset(); 

     while (moreToRead()) 
     { 
      rtb.Invoke(new Action<int,int,Color>(this.SetTextColor, 
         new object[] { start, length, color}) ; 
     }     

    } while (true); 
} 

private void SetTextColor(int start, int length, System.Drawing.Color color) 
{ 
    rtb.Select(start, length); 
    rtb.SelectionColor= color; 
} 

लेकिन, SelectionColor के लिए हर काम करने के लिए आग TextChanged घटना का कारण बनता है: एक अंतहीन लूप।

मैं टेक्स्ट परिवर्तनों के बीच अंतर कैसे कर सकता हूं जो टेक्स्टवर्किंग से उत्पन्न होने वाले पाठ परिवर्तनों से बाहरी रूप से उत्पन्न होते हैं?

यदि मैं पाठ स्वरूप परिवर्तन से स्वतंत्र रूप से टेक्स्ट सामग्री परिवर्तन का पता लगा सकता हूं तो मैं इसे हल भी कर सकता हूं।

उत्तर

6

दृष्टिकोण मैं एक BackgroundWorker में फ़ॉर्मेटर तर्क को चलाने के लिए था ले लिया। मैंने इसे चुना क्योंकि प्रारूप में "लंबा" समय लगेगा, 1 सेकंड या दो से अधिक, इसलिए मैं इसे यूआई थ्रेड पर नहीं कर सका।

बस समस्या को पुन: स्थापित करने के लिए: BackgroundWorker द्वारा RichTextBox पर सेटटर पर किए गए प्रत्येक कॉल। चयनकॉलर ने टेक्स्ट चेंजेड ईवेंट को फिर से निकाल दिया, जो फिर से बीजी थ्रेड शुरू कर देगा। टेक्स्ट चेंजेड इवेंट के भीतर, मुझे "प्रोग्राम ने टेक्स्ट टाइप किया है" ईवेंट से "उपयोगकर्ता ने कुछ टाइप किया है" ईवेंट को अलग करने का कोई तरीका नहीं ढूंढ पाया। तो आप देख सकते हैं कि यह परिवर्तनों की अनंत प्रगति होगी।

सरल दृष्टिकोण काम नहीं करता

एक आम दृष्टिकोण (as suggested by Eric) करने के लिए "अक्षम" पाठ परिवर्तन करते हुए पाठ परिवर्तन हैंडलर के भीतर चल निपटने घटना है।लेकिन निश्चित रूप से यह मेरे मामले के लिए काम नहीं करेगा, क्योंकि टेक्स्ट बदलता है (चयन रंग परिवर्तन) पृष्ठभूमि थ्रेड द्वारा उत्पन्न किया जा रहा है। वे टेक्स्ट चेंज हैंडलर के दायरे में नहीं किए जा रहे हैं। तो उपयोगकर्ता द्वारा शुरू की गई घटनाओं को फ़िल्टर करने का सरल दृष्टिकोण मेरे मामले के लिए काम नहीं करेगा, जहां पृष्ठभूमि धागा परिवर्तन कर रहा है।

अन्य उपयोगकर्ता द्वारा शुरू किए परिवर्तन

मैं एक तरह से द्वारा किए गए richtextbox में बदलाव से मेरी फ़ॉर्मेटर धागा से होने वाले richtextbox में परिवर्तन भेद करने के लिए के रूप में उपयोग करने की कोशिश RichTextBox.Text.Length पता लगाने का प्रयास उपभोक्ता। यदि लंबाई बदल नहीं गई थी, तो मैंने तर्क दिया, तो परिवर्तन मेरे कोड द्वारा एक प्रारूप परिवर्तन किया गया था, न कि उपयोगकर्ता संपादन। लेकिन RichTextBox को पुनर्प्राप्त करना। टेक्स्ट संपत्ति महंगी है, और प्रत्येक टेक्स्ट चेंज ईवेंट के लिए ऐसा करने से पूरे यूआई को अस्वीकार्य रूप से धीमा कर दिया जाता है। यहां तक ​​कि यदि यह पर्याप्त तेज़ था, तो यह सामान्य मामले में काम नहीं करता है, क्योंकि उपयोगकर्ता स्वरूप परिवर्तन भी करते हैं। और, एक उपयोगकर्ता संपादन एक ही लंबाई पाठ का उत्पादन कर सकता है, अगर यह एक प्रकार का ऑपरेशन था।

मैं उपयोगकर्ता से उत्पन्न परिवर्तनों का पता लगाने के लिए केवल टेक्स्ट चेंज ईवेंट को पकड़ने और संभालने की उम्मीद कर रहा था। चूंकि मैं ऐसा नहीं कर सका, मैंने ऐप को कीप्रेस ईवेंट और पेस्ट इवेंट का उपयोग करने के लिए बदल दिया। नतीजतन अब मुझे स्वरूपण परिवर्तनों के कारण नकली TextChange घटनाएं नहीं मिलती हैं (जैसे RichTextBox.SelectionColor = Color.Blue)।

अपने कार्य

ठीक करने के लिए कार्यकर्ता धागा सिग्नलिंग, मैं एक धागा चल रहा है कि स्वरूपण परिवर्तन कर सकते हैं मिल गया है। संकल्पनात्मक रूप से, यह यह करता है:

while (forever) 
    wait for the signal to start formatting 
    for each line in the richtextbox 
     format it 
    next 
next 

मैं बीजी थ्रेड को स्वरूपण शुरू करने के लिए कैसे कह सकता हूं?

मैंने ManualResetEvent का उपयोग किया। जब एक कुंजीपटल का पता चला है, तो कुंजीपटल हैंडलर उस ईवेंट को सेट करता है (इसे चालू करता है)। पृष्ठभूमि कार्यकर्ता एक ही घटना पर इंतजार कर रहा है। जब यह चालू होता है, बीजी थ्रेड इसे बंद कर देता है, और स्वरूपण शुरू करता है।

लेकिन अगर बीजी कार्यकर्ता पहले से ही स्वरूपण है तो क्या होगा? उस स्थिति में, एक नई कीप्रेस ने टेक्स्टबॉक्स की सामग्री को बदल दिया हो सकता है, और अब तक किए गए किसी भी स्वरूपण को अब अमान्य हो सकता है, इसलिए प्रारूपण को पुनरारंभ करना आवश्यक है। क्या मैं सच में फ़ॉर्मेटर थ्रेड के लिए चाहते हैं कुछ इस तरह है:

while (forever) 
    wait for the signal to start formatting 
    for each line in the richtextbox 
     format it 
     check if we should stop and restart formatting 
    next 
next 
इस तर्क के साथ

, जब ManualResetEvent सेट किया गया है (चालू), फ़ॉर्मेटर धागा पता लगाता है कि, और फिर सेट करता है यह (यह बंद कर देता है) , और स्वरूपण शुरू होता है। यह पाठ के माध्यम से चलता है और यह तय करता है कि इसे कैसे प्रारूपित किया जाए। समय-समय पर फॉर्मेटर थ्रेड मैन्युअल रीसेट इवेंट को फिर से जांचता है। यदि स्वरूपण के दौरान एक और कीप्रेस घटना होती है, तो ईवेंट फिर से संकेतित स्थिति पर जाता है। जब फॉर्मेटर देखता है कि यह फिर से संकेत दिया गया है, तो फॉर्मेटर बाहर निकलता है और पाठ की शुरुआत से फिर से स्वरूपण शुरू करता है, जैसे सिसिफस। एक और बुद्धिमान तंत्र दस्तावेज़ में बिंदु से प्रारूपण को पुनरारंभ करेगा जहां परिवर्तन हुआ था।

विलंबित शुरुआत प्रारूपण

एक और मोड़: मैं फ़ॉर्मेटर हर कुंजी दबाने के साथ अपने स्वरूपण काम तुरंत शुरू करने के लिए नहीं करना चाहती। मानव प्रकार के रूप में, कीस्ट्रोक के बीच सामान्य विराम 600-700ms से कम है। अगर फॉर्मेटर विलंब के बिना स्वरूपण शुरू करता है, तो यह कीस्ट्रोक के बीच स्वरूपण शुरू करने का प्रयास करेगा। सुंदर व्यर्थ।

तो फॉर्मेटर तर्क केवल अपने स्वरूपण कार्य को शुरू करता है अगर यह 600ms से अधिक लंबे समय के कीस्ट्रोक में रोक देता है। सिग्नल प्राप्त करने के बाद, यह 600ms इंतजार कर रहा है, और यदि कोई हस्तक्षेप करने वाली कीप्रेस नहीं है, तो टाइपिंग बंद हो गई है और स्वरूपण शुरू होना चाहिए। यदि कोई हस्तक्षेप परिवर्तन हुआ है, तो फॉर्मेटर कुछ भी नहीं करता है, यह निष्कर्ष निकाला है कि उपयोगकर्ता अभी भी टाइप कर रहा है। कोड में:

private System.Threading.ManualResetEvent wantFormat = new System.Threading.ManualResetEvent(false); 

कुंजी दबाने घटना:

private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e) 
{ 
    _lastRtbKeyPress = System.DateTime.Now; 
    wantFormat.Set(); 
} 

colorizer विधि में, जो पृष्ठभूमि धागा में चलता है:

.... 
do 
{ 
    try 
    { 
     wantFormat.WaitOne(); 
     wantFormat.Reset(); 

     // We want a re-format, but let's make sure 
     // the user is no longer typing... 
     if (_lastRtbKeyPress != _originDateTime) 
     { 
      System.Threading.Thread.Sleep(DELAY_IN_MILLISECONDS); 
      System.DateTime now = System.DateTime.Now; 
      var _delta = now - _lastRtbKeyPress; 
      if (_delta < new System.TimeSpan(0, 0, 0, 0, DELAY_IN_MILLISECONDS)) 
       continue; 
     } 

     ...analyze document and apply updates... 

     // during analysis, periodically check for new keypress events: 
     if (wantFormat.WaitOne(0, false)) 
      break; 

उपयोगकर्ता अनुभव है कि कोई भी स्वरूपण होता है, जबकि वे टाइप कर रहे हैं एक बार विराम लिखने के बाद, स्वरूपण शुरू होता है। यदि टाइपिंग फिर से शुरू होती है, तो स्वरूपण बंद हो जाता है और फिर प्रतीक्षा करता है।

प्रारूप दौरान स्क्रॉल को अक्षम करने से बदलता है

एक अंतिम समस्या थी: एक RichTextBox में पाठ स्वरूपण RichTextBox.Select() के लिए एक कॉल, जब RichTextBox ध्यान केंद्रित है जो, चयनित पाठ को RichTextBox to automatically scroll का कारण बनता है की आवश्यकता है। चूंकि स्वरूपण एक ही समय में हो रहा है क्योंकि उपयोगकर्ता नियंत्रण, पढ़ने और पाठ को संपादित करने में केंद्रित है, मुझे स्क्रॉलिंग को दबाने के लिए एक तरीका चाहिए। मुझे आरटीबी के सार्वजनिक इंटरफ़ेस का उपयोग करके स्क्रॉलिंग को रोकने का कोई तरीका नहीं मिला, हालांकि मुझे इसके बारे में पूछने वाले इंटरबेट में कई लोगों को मिला। कुछ प्रयोग करने के बाद, मैंने पाया कि Win32 SendMessage() कॉल (user32.dll से) का उपयोग करके, चयन() से पहले और बाद में WM_SETREDRAW भेजना, चयन() को कॉल करते समय RichTextBox में स्क्रॉल को रोक सकता है।

क्योंकि मैं स्क्रॉल को रोकने के लिए PInvoke का सहारा था, मैं भी PInvoke SendMessage पर मिलता है या चयन या पाठ बॉक्स (EM_GETSEL या EM_SETSEL) में कैरट स्थापित करने के लिए, और चयन (EM_SETCHARFORMAT) पर स्वरूपण सेट करने के लिए इस्तेमाल किया। प्रबंधित इंटरफ़ेस का उपयोग करने से पिनवोक दृष्टिकोण थोड़ा तेज़ हो गया।

जवाबदेही

के लिए और क्योंकि कुछ गणना भूमि के ऊपर किए गए स्क्रॉल रोकने

बैच अपडेट, मैं दस्तावेज़ में किए गए परिवर्तनों अप बैच का फैसला किया। एक संगत खंड या शब्द को हाइलाइट करने के बजाय, तर्क हाइलाइट की सूची रखता है या बनाने के लिए प्रारूपों को स्वरूपित करता है। प्रत्येक बार, यह दस्तावेज़ में एक समय में शायद 30 परिवर्तन लागू होता है। फिर यह सूची को साफ़ करता है और विश्लेषण और कतार में वापस जाता है कि कौन से स्वरूप परिवर्तन किए जाने की आवश्यकता है। यह इतना तेज़ है कि परिवर्तन के इन बैचों को लागू करते समय दस्तावेज़ में टाइपिंग बाधित नहीं होती है।

उपरोक्त दस्तावेज ऑटो-स्वरूपित हो जाता है और अलग-अलग हिस्सों में रंगीन हो जाता है, जब कोई टाइपिंग नहीं होती है। यदि उपयोगकर्ता कुंजीपटल के बीच पर्याप्त समय गुजरता है, तो संपूर्ण दस्तावेज़ अंततः स्वरूपित हो जाएगा। यह 1k एक्सएमएल दस्तावेज़ के लिए 200ms से कम है, शायद 30k डॉक्टर के लिए 2 एस, या 100k डॉक्टर के लिए 10s। यदि उपयोगकर्ता दस्तावेज़ को संपादित करता है, तो प्रगति पर मौजूद किसी भी स्वरूपण को निरस्त कर दिया गया है, और प्रारूपण फिर से शुरू होता है।


पुhew!

मुझे आश्चर्य है कि एक समृद्ध टेक्स्टबॉक्स स्वरूपण के रूप में प्रतीत होता है जैसा कि उपयोगकर्ता इसमें टाइप करता है।लेकिन मैं कुछ भी आसान नहीं कर सका जिसने टेक्स्ट बॉक्स को लॉक नहीं किया, फिर भी अजीब स्क्रॉलिंग व्यवहार से परहेज किया।


आप बात मैं ऊपर वर्णित के लिए view the code कर सकते हैं।

+0

मुझे खुशी है कि मेरा समाधान और टिप्पणियां आपके अंतिम समाधान के लिए कुछ प्रेरणा प्रदान कर सकती हैं। –

+0

बहुत बहुत धन्यवाद, एरिक। – Cheeso

+0

शायद मेरे उत्तर को अन-डाउन-वोट दें? –

3

आमतौर पर जब मैं एक ईवेंट हैंडलर में इस तरह से प्रतिक्रिया करता हूं कि एक ही घटना को फिर से निकाल दिया जा सकता है, तो मैंने एक ध्वज सेट किया है जो दर्शाता है कि मैं पहले से ही ईवेंट हैंडलर को संसाधित कर रहा हूं, ईवेंट हैंडलर के शीर्ष पर ध्वज की जांच करें ,

bool processing = false; 

TextChanged(EventArgs e) 
{ 
    if (processing) return; 

    try 
    { 
     processing = true; 
     // You probably need to lock the control here briefly in case the user makes a change 
     // Do your processing 
    } 
    finally 
    { 
     processing = false; 
    } 
} 

यदि यह करते समय अपने प्रसंस्करण प्रदर्शन नियंत्रण लॉक करने के लिए अस्वीकार्य है आप अपने नियंत्रण पर KeyDown घटना के लिए जांच कर सकता है और प्रसंस्करण झंडा स्पष्ट जब आप इसे प्राप्त (शायद:, और तुरंत लौट यदि ध्वज सेट है यदि यह संभावित रूप से लंबा है तो भी अपने वर्तमान टेक्स्ट चेंज प्रोसेसिंग को समाप्त करें)।

संपादित करें:

पूरा, काम कर कोड

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 
using System.ComponentModel; 

namespace BgWorkerDemo 
{ 
    public class FormatRichTextBox : RichTextBox 
    { 
     private bool processing = false; 

     private BackgroundWorker worker = new BackgroundWorker(); 

     public FormatRichTextBox() 
     { 
      worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
     } 

     delegate void SetTextCallback(string text); 
     private void SetText(string text) 
     { 
      Text = text; 
     } 

     delegate string GetTextCallback(); 
     private string GetText() 
     { 
      return Text; 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     { 
      try 
      { 
       GetTextCallback gtc = new GetTextCallback(GetText); 
       string text = (string)this.Invoke(gtc, null); 

       StringBuilder sb = new StringBuilder(); 
       for (int i = 0; i < text.Length; i++) 
       { 
        sb.Append(Char.ToUpper(text[i])); 
       } 

       SetTextCallback stc = new SetTextCallback(SetText); 
       this.Invoke(stc, new object[]{ sb.ToString() }); 
      } 
      finally 
      { 
       processing = false; 
      } 
     } 

     protected override void OnTextChanged(EventArgs e) 
     { 
      base.OnTextChanged(e); 

      if (processing) return; 

      if (!worker.IsBusy) 
      { 
       processing = true; 
       worker.RunWorkerAsync(); 
      } 
     } 

     protected override void OnKeyDown(KeyEventArgs e) 
     { 
      if (processing) 
      { 
       BeginInvoke(new MethodInvoker(delegate { this.OnKeyDown(e); })); 
       return; 
      } 

      base.OnKeyDown(e); 
     } 

    } 
} 
+0

फ़ॉर्मेटिंग असीमित रूप से किया जाता है तो यह काम नहीं करेगा। – Cheeso

+0

हां यह होगा। स्वरूपण के दौरान आपको इनपुट के लिए नियंत्रण को लॉक करने की आवश्यकता है (अन्यथा विवादित परिवर्तन संदेश कतार थ्रेड और स्वरूपण धागे से आ सकते हैं)। आप नियंत्रण को संक्षेप में केवल पढ़ने के लिए या इसे ईवेंट को हुक करके लॉक कर सकते हैं जो इसे बदल सकता है (कुंजी से संबंधित घटनाएं, कुछ माउस ईवेंट [चूंकि वे किसी क्षेत्र का चयन कर सकते हैं] और यदि आप अनुमति देते हैं तो ईवेंट खींचें/छोड़ें उस)। –

+0

मेरी विधि का उपयोग कर पूर्ण, कामकाजी कोड के साथ मेरा उत्तर अपडेट किया गया। –

संबंधित मुद्दे