2016-07-08 8 views
5

मुझे आंशिक रूप से संपादन योग्य RichTextBox बनाने के साथ काम सौंपा गया है। मैंने ReadOnly अनुभागों के लिए TextBlock तत्वों को जोड़कर Xaml में सुझावों को देखा है, हालांकि इसमें अच्छी तरह से लपेटने का अवांछित दृश्य प्रभाव नहीं है। (यह निरंतर पाठ का एक ब्लॉक के रूप में दिखाई देनी चाहिए।)TextChanged में हटाए जाने से RichTextBox इनलाइन को कैसे रोकें?

मैं एक साथ प्रतिबंधित करने के लिए/कुछ reverse string formatting का उपयोग कर समझौता कर लिया है एक कार्यशील प्रोटोटाइप की अनुमति देने के संपादन और युग्मित प्रयोजनों प्रदर्शित करने के लिए inline Run elements के गतिशील निर्माण के साथ कि। किसी भी TextChanged ईवेंट ट्रिगर के अनुसार Run तत्वों के अनुसार Run तत्वों को अपडेट करने के लिए एक शब्दकोश का उपयोग करके, इस विचार के साथ कि यदि एक संपादन योग्य सेक्शन का टेक्स्ट पूरी तरह से हटा दिया गया है, तो इसे वापस अपने डिफ़ॉल्ट मान में बदल दिया जाएगा।

स्ट्रिंग में: "हाय NAME, स्पोर्ट शिविर में आपका स्वागत है।", केवल NAME और SPORT संपादन योग्य हैं।

   ╔═══════╦════════╗     ╔═══════╦════════╗ 
Default values: ║ Key ║ Value ║ Edited values: ║ Key ║ Value ║ 
       ╠═══════╬════════╣     ╠═══════╬════════╣ 
       ║ NAME ║ NAME ║     ║ NAME ║ John ║ 
       ║ SPORT ║ SPORT ║     ║ SPORT ║ Tennis ║ 
       ╚═══════╩════════╝     ╚═══════╩════════╝ 

"Hi NAME, welcome to SPORT camp." "Hi John, welcome to Tennis camp." 

समस्या:

एक विशेष समय में पूरे पाठ मूल्य को हटाया जा रहा RichTextBox Document से कि रन (और निम्नलिखित रन) को हटा। भले ही मैं उन्हें वापस जोड़ता हूं, फिर भी वे स्क्रीन पर सही ढंग से प्रदर्शित नहीं होते हैं। उदाहरण के लिए, ऊपर सेटअप से संपादित स्ट्रिंग का उपयोग:

  • उपयोगकर्ता पाठ "जॉन" पर प्रकाश डाला गया और क्लिक हटाएँ, बजाय रिक्त मान बचाने के, इसके बारे में डिफ़ॉल्ट टेक्स्ट के साथ प्रतिस्थापित किया जाना चाहिए "NAME"। आंतरिक रूप से ऐसा होता है। शब्दकोश को सही मान मिलता है, Run.Text में मूल्य है, Document में सभी सही Run तत्व शामिल हैं। लेकिन स्क्रीन प्रदर्शित करता है:

    • अपेक्षित: "हाय NAME, टेनिस शिविर में आपका स्वागत है।"
    • वास्तविक: "हाय NAMETennis शिविर।"

Actual vs Expected screenshot

Sidenote: इस नुकसान के-रन-तत्व व्यवहार भी जब चिपकाने दोहराया जा सकता है। हाइलाइट करें "स्पोर्ट" और पेस्ट "टेनिस" और रन जिसमें "कैंप" है। खो गया है।

प्रश्न:

कैसे मैं हर Run तत्व दिखाई भी विनाशकारी कार्यों के माध्यम से, रहते हो एक बार वे प्रतिस्थापित किया गया है?

कोड:

मैं कम से कम उदाहरण के लिए कोड नीचे पट्टी की कोशिश की है, तो मैं निकाल दिया है:

  • हर DependencyProperty और XAML में बाध्यकारी जुड़े
  • तर्क recalculating देखभाल की स्थिति (क्षमा करें)
  • लिंक किए गए स्ट्रिंग स्वरूपण एक्सटेंशन विधियों को पहले लिंक से दोहराया गया एक विधि में कक्षा के भीतर निहित है। (नोट: यह विधि सरल उदाहरण स्ट्रिंग प्रारूपों के लिए काम करेगी। अधिक मजबूत स्वरूपण के लिए मेरा कोड बहिष्कृत कर दिया गया है। इसलिए कृपया इन परीक्षण उद्देश्यों के लिए प्रदान किए गए उदाहरण से चिपके रहें।)
  • संपादन योग्य अनुभाग स्पष्ट रूप से दिखाई देते हैं, रंग योजना को कभी नहीं मानते ।

परीक्षण करने के लिए, कक्षा को अपने WPF प्रोजेक्ट संसाधन फ़ोल्डर में ड्रॉप करें, नामस्थान को ठीक करें, और दृश्य को नियंत्रण जोड़ें।

using System.Collections.Generic; 
using System.Linq; 
using System.Text.RegularExpressions; 
using System.Windows.Controls; 
using System.Windows.Documents; 
using System.Windows.Media; 

namespace WPFTest.Resources 
{ 
    public class MyRichTextBox : RichTextBox 
    { 
    public MyRichTextBox() 
    { 
     this.TextChanged += MyRichTextBox_TextChanged; 
     this.Background = Brushes.LightGray; 

     this.Parameters = new Dictionary<string, string>(); 
     this.Parameters.Add("NAME", "NAME"); 
     this.Parameters.Add("SPORT", "SPORT"); 

     this.Format = "Hi {0}, welcome to {1} camp."; 
     this.Text = string.Format(this.Format, this.Parameters.Values.ToArray<string>()); 

     this.Runs = new List<Run>() 
     { 
     new Run() { Background = Brushes.LightGray, Tag = "Hi " }, 
     new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "NAME" }, 
     new Run() { Background = Brushes.LightGray, Tag = ", welcome to " }, 
     new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "SPORT" }, 
     new Run() { Background = Brushes.LightGray, Tag = " camp." }, 
     }; 

     this.UpdateRuns(); 
    } 

    public Dictionary<string, string> Parameters { get; set; } 
    public List<Run> Runs { get; set; } 
    public string Text { get; set; } 
    public string Format { get; set; } 

    private void MyRichTextBox_TextChanged(object sender, TextChangedEventArgs e) 
    { 
     string richText = new TextRange(this.Document.Blocks.FirstBlock.ContentStart, this.Document.Blocks.FirstBlock.ContentEnd).Text; 
     string[] oldValues = this.Parameters.Values.ToArray<string>(); 
     string[] newValues = null; 

     bool extracted = this.TryParseExact(richText, this.Format, out newValues); 

     if (extracted) 
     { 
     var changed = newValues.Select((x, i) => new { NewVal = x, Index = i }).Where(x => x.NewVal != oldValues[x.Index]).FirstOrDefault(); 
     string key = this.Parameters.Keys.ElementAt(changed.Index); 
     this.Parameters[key] = string.IsNullOrWhiteSpace(newValues[changed.Index]) ? key : newValues[changed.Index]; 

     this.Text = richText; 
     } 
     else 
     { 
     e.Handled = true; 
     } 

     this.UpdateRuns(); 
    } 

    private void UpdateRuns() 
    { 
     this.TextChanged -= this.MyRichTextBox_TextChanged; 

     foreach (Run run in this.Runs) 
     { 
     string value = run.Tag.ToString(); 

     if (this.Parameters.ContainsKey(value)) 
     { 
      run.Text = this.Parameters[value]; 
     } 
     else 
     { 
      run.Text = value; 
     } 
     } 

     Paragraph p = this.Document.Blocks.FirstBlock as Paragraph; 
     p.Inlines.Clear(); 
     p.Inlines.AddRange(this.Runs); 

     this.TextChanged += this.MyRichTextBox_TextChanged; 
    } 

    public bool TryParseExact(string data, string format, out string[] values) 
    { 
     int tokenCount = 0; 
     format = Regex.Escape(format).Replace("\\{", "{"); 
     format = string.Format("^{0}$", format); 

     while (true) 
     { 
     string token = string.Format("{{{0}}}", tokenCount); 

     if (!format.Contains(token)) 
     { 
      break; 
     } 

     format = format.Replace(token, string.Format("(?'group{0}'.*)", tokenCount++)); 
     } 

     RegexOptions options = RegexOptions.None; 

     Match match = new Regex(format, options).Match(data); 

     if (tokenCount != (match.Groups.Count - 1)) 
     { 
     values = new string[] { }; 
     return false; 
     } 
     else 
     { 
     values = new string[tokenCount]; 

     for (int index = 0; index < tokenCount; index++) 
     { 
      values[index] = match.Groups[string.Format("group{0}", index)].Value; 
     } 

     return true; 
     } 
    } 
    } 
} 
+0

'RichTextBox'' FlowDocument' क्लास के माध्यम से केवल टेक्स्ट एडिटर से कहीं अधिक प्रदान करता है। यह स्वरूपित पाठ, छवियों, तालिकाओं, और यहां तक ​​कि 'UIElements' को प्रदर्शित करने के लिए यह एक आसान नियंत्रण बनाता है। हालांकि ये सभी जटिलता की लागत के साथ आते हैं। इसलिए, 'RichTextBox' से निपटने के बजाय बदसूरत हो सकता है। क्या कोई मौका है कि आप 'एवलॉन एडिट' जैसे अधिक सुविधाजनक नियंत्रण में स्विच कर सकते हैं जो टेक्स्ट प्रोसेसिंग के लिए बेहतर है? –

+0

समस्या से मैं क्या देख सकता हूं कि 'TryParseExact (richText' विफल हो जाता है। क्या इसे ठीक करना शुरू करना उचित है? –

+0

@ qqww2 मैं 'AvalonEdit' से परिचित नहीं हूं या इसकी लाइसेंसिंग आवश्यकताएं हैं, लेकिन मैं इसे मानता हूं। – OhBeWise

उत्तर

2

अपने कोड के साथ समस्या यह है कि जब आप यूजर इंटरफेस के माध्यम से पाठ बदलने के लिए, आंतरिक Run वस्तुओं संशोधित कर रहे हैं है, बनाया, हटाया गया, और सभी पागल चीजें दृश्यों के पीछे होती हैं। आंतरिक संरचना बहुत जटिल है।

private int DeleteContentFromSiblingTree(SplayTreeNode containingNode, TextPointer startPosition, TextPointer endPosition, bool newFirstIMEVisibleNode, out int charCount) 
{ 
    SplayTreeNode leftSubTree; 
    SplayTreeNode middleSubTree; 
    SplayTreeNode rightSubTree; 
    SplayTreeNode rootNode; 
    TextTreeNode previousNode; 
    ElementEdge previousEdge; 
    TextTreeNode nextNode; 
    ElementEdge nextEdge; 
    int symbolCount; 
    int symbolOffset; 

    // Early out in the no-op case. CutContent can't handle an empty content span. 
    if (startPosition.CompareTo(endPosition) == 0) 
    { 
     if (newFirstIMEVisibleNode) 
     { 
      UpdateContainerSymbolCount(containingNode, /* symbolCount */ 0, /* charCount */ -1); 
     } 
     charCount = 0; 
     return 0; 
    } 

    // Get the symbol offset now before the CutContent call invalidates startPosition. 
    symbolOffset = startPosition.GetSymbolOffset(); 

    // Do the cut. middleSubTree is what we want to remove. 
    symbolCount = CutContent(startPosition, endPosition, out charCount, out leftSubTree, out middleSubTree, out rightSubTree); 

    // We need to remember the original previous/next node for the span 
    // we're about to drop, so any orphaned positions can find their way 
    // back. 
    if (middleSubTree != null) 
    { 
     if (leftSubTree != null) 
     { 
      previousNode = (TextTreeNode)leftSubTree.GetMaxSibling(); 
      previousEdge = ElementEdge.AfterEnd; 
     } 
     else 
     { 
      previousNode = (TextTreeNode)containingNode; 
      previousEdge = ElementEdge.AfterStart; 
     } 
     if (rightSubTree != null) 
     { 
      nextNode = (TextTreeNode)rightSubTree.GetMinSibling(); 
      nextEdge = ElementEdge.BeforeStart; 
     } 
     else 
     { 
      nextNode = (TextTreeNode)containingNode; 
      nextEdge = ElementEdge.BeforeEnd; 
     } 

     // Increment previous/nextNode reference counts. This may involve 
     // splitting a text node, so we use refs. 
     AdjustRefCountsForContentDelete(ref previousNode, previousEdge, ref nextNode, nextEdge, (TextTreeNode)middleSubTree); 

     // Make sure left/rightSubTree stay local roots, we might 
     // have inserted new elements in the AdjustRefCountsForContentDelete call. 
     if (leftSubTree != null) 
     { 
      leftSubTree.Splay(); 
     } 
     if (rightSubTree != null) 
     { 
      rightSubTree.Splay(); 
     } 
     // Similarly, middleSubtree might not be a local root any more, 
     // so splay it too. 
     middleSubTree.Splay(); 

     // Note TextContainer now has no references to middleSubTree, if there are 
     // no orphaned positions this allocation won't be kept around. 
     Invariant.Assert(middleSubTree.ParentNode == null, "Assigning fixup node to parented child!"); 
     middleSubTree.ParentNode = new TextTreeFixupNode(previousNode, previousEdge, nextNode, nextEdge); 
    } 

    // Put left/right sub trees back into the TextContainer. 
    rootNode = TextTreeNode.Join(leftSubTree, rightSubTree); 
    containingNode.ContainedNode = rootNode; 
    if (rootNode != null) 
    { 
     rootNode.ParentNode = containingNode; 
    } 

    if (symbolCount > 0) 
    { 
     int nextNodeCharDelta = 0; 
     if (newFirstIMEVisibleNode) 
     { 
      // The following node is the new first ime visible sibling. 
      // It just moved, and loses an edge character. 
      nextNodeCharDelta = -1; 
     } 

     UpdateContainerSymbolCount(containingNode, -symbolCount, -charCount + nextNodeCharDelta); 
     TextTreeText.RemoveText(_rootNode.RootTextBlock, symbolOffset, symbolCount); 
     NextGeneration(true /* deletedContent */); 

     // Notify the TextElement of a content change. Note that any full TextElements 
     // between startPosition and endPosition will be handled by CutTopLevelLogicalNodes, 
     // which will move them from this tree to their own private trees without changing 
     // their contents. 
     Invariant.Assert(startPosition.Parent == endPosition.Parent); 
     TextElement textElement = startPosition.Parent as TextElement; 
     if (textElement != null) 
     {    
      textElement.OnTextUpdated();      
     } 
    } 

    return symbolCount; 
} 

आप here से स्रोत कोड देख सकते हैं यदि आप रुचि रखते हैं: उदाहरण के लिए यहाँ एक तरीका है जिसके निर्दोष एकल लाइन p.Inlines.Clear(); गहरे अंदर कहा जाता है।

एक समाधान ऑब्जेक्ट्स का उपयोग नहीं करता है जो आपने तुलनात्मक उद्देश्यों के लिए सीधे FlowDocument में बनाया है। उन्हें जोड़ने से पहले हमेशा इसकी एक प्रति बनाएं:

private void UpdateRuns() 
{ 
    TextChanged -= MyRichTextBox_TextChanged; 

    List<Run> runs = new List<Run>(); 
    foreach (Run run in Runs) 
    { 
     Run newRun; 
     string value = run.Tag.ToString(); 

     if (Parameters.ContainsKey(value)) 
     { 
      newRun = new Run(Parameters[value]); 
     } 
     else 
     { 
      newRun = new Run(value); 
     } 

     newRun.Background = run.Background; 
     newRun.Foreground = run.Foreground; 

     runs.Add(newRun); 
    } 

    Paragraph p = Document.Blocks.FirstBlock as Paragraph; 
    p.Inlines.Clear(); 
    p.Inlines.AddRange(runs); 

    TextChanged += MyRichTextBox_TextChanged; 
} 
+0

यह लगभग ठीक है जो मैंने इसे हल करने के लिए किया था, लेकिन मुझे वास्तव में समझ में नहीं आया * क्यों * यह एक समाधान था, लेकिन मुझे संदेह था कि हुड के नीचे कुछ * याद रखना * पुनः जोड़ा गया रन हटा दिया गया था। मैं दृश्यों के पीछे "पागल चीजें" के साथ और अधिक सहमत नहीं हो सका। " उदाहरण के लिए कैरेट पोजीशनिंग को ठीक करने की कोशिश कर रहा है ... एल्गोरिदमिक दुःस्वप्न जिसे मैं सरल बनाने की कोशिश कर रहा हूं - इतने सारे किनारे के मामले। स्रोत के लिंक के लिए धन्यवाद। – OhBeWise

1

मैं UpdateRuns में रन बनाने के लिए कोड को स्थानांतरित करने के सुझाव है

private void UpdateRuns() 
    { 
     this.TextChanged -= this.MyRichTextBox_TextChanged; 

     this.Runs = new List<Run>() 
    { 
    new Run() { Background = Brushes.LightGray, Tag = "Hi " }, 
    new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "NAME" }, 
    new Run() { Background = Brushes.LightGray, Tag = ", welcome to " }, 
    new Run() { Background = Brushes.Black, Foreground = Brushes.White, Tag = "SPORT" }, 
    new Run() { Background = Brushes.LightGray, Tag = " camp." }, 
    }; 

     foreach (Run run in this.Runs) 
+0

मैं आपके उत्तर की सराहना करता हूं। यह मेरे जैसा ही था और qqww2 ने एम के लिए पुष्टि की थी ई।अन्य दृष्टिकोण इस सरल उदाहरण के बाहर बेहतर अनुकूल थे जब बाइंडिंग और 'निर्भरता प्रॉपर्टी' डेटा स्रोत थे और इससे * मुझे * क्यों * की आवश्यकता होती है। फिर भी, अपनी परेशानियों के लिए एक +1 है और मेरा धन्यवाद! – OhBeWise

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