private void DoBackgroundColorizing(object sender, DoWorkEventArgs e) { // Design Notes: // ------------------------------------------------------------------- // // It takes a long time, maybe 10s or more, to colorize the XML // syntax in an xml file 100k in size. Therefore, to maintain UI // responsiveness, it's necessary to perform the syntax highlighting // asynchronously. // // This method runs endlessly. The first thing it does is wait for a // signal on the wantFormat event. This event is set when an XML file // is loaded, or when the text in the richtextbox changes. // // When the signal is received, execution continues, and the // highlighting begins. The approach is this: read a segment of the // XML, decide how to highlight it, then place a description of that // formatting change into a list. Then, continue by reading the next // segment of XML, until the entire XML document has been processed. // // On an interval that is normally every 1/48th of the lines - if // there are 960 lines, then every 20 lines - this method does two // things: call the progress update for the BG worker, and also apply // the queued changes. Using this approach the progress bar magically // appears while highlighting is happening, and disappears when // highlighting finishes. // // The reason for the batch approach: the control.Invoke() method, // necessary to apply changes to the richtextbox, can be costly. Batching // changes amortizes the cost of that method across a number of format // changes. // // The ApplyChanges method applies the batch of changes. It first // does a richtextbox.Invoke() to get on the proper thread. Once // there, it saves the scroll and selection state, applies each // formatting change in the list to the rtb text, restores the scroll // and selection state, and then calls Refresh() on the RTB. After // that method returns, this method clears the list of format changes // and continues reading and analyzing the next segment of XML. // // If at any time, the user changes the RTB text, either through a new // load of an XML file, or through direct editing in the richtextbox, // the main UI signals the wantFormat event again. This method treats // the raising of that signal as a "cancel-and-restart" message. Upon // detecting that signal, this method stops working, if it was // working, and then starts reading and highlighting at the beginning // again. // // There's also some logic to delay the formatting, to wait for the // user to stop typing, if that is what caused the wantFormat to get // signalled. // // When this method finishes highlighting, this method waits for the // wantFormat signal again. This method never returns. // BackgroundWorker self = sender as BackgroundWorker; // var xmlReaderSettings = new XmlReaderSettings // { // ProhibitDtd = false, // XmlResolver = new Ionic.Xml.XhtmlResolver() // // // this works in .NET 4.0 ?? // //DtdProcessing = DtdProcessing.Parse, // //XmlResolver = // //new XmlPreloadedResolver(new XmlXapResolver(), // //XmlKnownDtds.Xhtml10) // }; do { InvokeActionProperly(SaveCaretPosition); try { InvokeActionProperly(resume); wantFormat.WaitOne(); wantFormat.Reset(); progressCount = 0; var list = new List<FormatChange>(); // We want a re-format, but let's wait til // the user stops 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; } InvokeActionProperly(suspend); // Get the text ONCE. In a RichTextBox, this is // expensive, so we do it once, until a change is detected // in the richtextbox. string txt = (this.richTextBox1.InvokeRequired) ? (string)this.richTextBox1.Invoke((System.Func<string>)(() => this.richTextBox1.Text)) : this.richTextBox1.Text; ResetXmlTextBox(); var lc = new LineCalculator(txt); float maxLines = (float)lc.CountLines(); int reportingInterval = (maxLines > 96) ? (int)(maxLines / 48) : 4; int lastReport = -1; var sr = new StringReader(txt); XmlReader reader = XmlReader.Create(sr, readerSettings); IXmlLineInfo rinfo = (IXmlLineInfo)reader; if (!rinfo.HasLineInfo()) continue; int rcount = 0; int ix = 0; int t = 0; while (reader.Read()) { if ((rcount % 8) == 0) { // If another format is pending, that means // the text has changed and we should stop this // formatting effort and start again. if (wantFormat.WaitOne(0, false)) break; } rcount++; // report progress if ((rinfo.LineNumber / reportingInterval) > lastReport) { int pct = (int)((float)rinfo.LineNumber / maxLines * 100); self.ReportProgress(pct); lastReport = (rinfo.LineNumber / reportingInterval); ApplyChanges(list); list.Clear(); progressCount++; } switch (reader.NodeType) { case XmlNodeType.XmlDeclaration: // The node is an declaration. ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; t = 2 + reader.Name.Length + 1 + reader.Value.Length + 2; list.Add(new FormatChange(ix - 2, t, Color.Blue)); break; case XmlNodeType.DocumentType: // DTD break; case XmlNodeType.ProcessingInstruction: // eg, <?xml-stylesheet type="text/xsl" href="http://www.codeplex.com/rss.xsl"?> ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; t = 2 + reader.Name.Length + 1 + reader.Value.Length + 2; list.Add(new FormatChange(ix - 2, t, Color.Blue)); break; case XmlNodeType.Element: // The node is an element. ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; list.Add(new FormatChange(ix - 1, 1, Color.Blue)); list.Add(new FormatChange(ix, reader.Name.Length, Color.DarkRed)); if (reader.HasAttributes) { reader.MoveToFirstAttribute(); do { //string s = reader.Value; ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + + rinfo.LinePosition - 1; list.Add(new FormatChange(ix, reader.Name.Length, Color.Red)); ix += reader.Name.Length; ix = txt.IndexOf('=', ix); // make the equals sign blue list.Add(new FormatChange(ix, 1, Color.Blue)); // skip over the quote char (it remains black) ix = txt.IndexOf(reader.QuoteChar, ix); ix++; // highlight the value of the attribute as blue if (txt.Substring(ix).StartsWith(reader.Value)) { list.Add(new FormatChange(ix, reader.Value.Length, Color.Blue)); } else { // Difference in escaping. The InnerXml may include // \" where " is in the doc. string s = reader.Value.XmlEscapeQuotes(); int delta = s.Length - reader.Value.Length; list.Add(new FormatChange(ix, reader.Value.Length + delta, Color.Blue)); } } while (reader.MoveToNextAttribute()); ix = txt.IndexOf('>', ix); // the close-angle-bracket if (txt[ix - 1] == '/') { list.Add(new FormatChange(ix - 1, 2, Color.Blue)); } else { list.Add(new FormatChange(ix, 1, Color.Blue)); } } break; case XmlNodeType.Text: // Leave these black - no addl formatting break; case XmlNodeType.EndElement: ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + +rinfo.LinePosition - 1; list.Add(new FormatChange(ix - 2, 2, Color.Blue)); list.Add(new FormatChange(ix, reader.Name.Length, Color.DarkRed)); list.Add(new FormatChange(ix + reader.Name.Length, 1, Color.Blue)); break; case XmlNodeType.Attribute: // These are handed within XmlNodeType.Element break; case XmlNodeType.Comment: ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + +rinfo.LinePosition - 1; list.Add(new FormatChange(ix, reader.Value.Length, Color.Green)); break; } } // in case there are more ApplyChanges(list); self.ReportProgress(100); } catch (Exception exc1) { // this can happen when editing an XML file, and the // xurrent state of the document is not valid xml. NotifyStopFormatting(exc1.Message); // System.Windows.Forms.MessageBox.Show("Exception: " + exc1.Message, // "Exception while formatting", // System.Windows.Forms.MessageBoxButtons.OK, // System.Windows.Forms.MessageBoxIcon.Error); } finally { //RestoreCaretPosition(); } } while (true); }
private void DoBackgroundColorizing(object sender, DoWorkEventArgs e) { // Design Notes: // ------------------------------------------------------------------- // // It takes a long time, maybe 10s or more, to colorize the XML // syntax in an xml file 100k in size. Therefore, to maintain UI // responsiveness, it's necessary to perform the syntax highlighting // asynchronously. // // This method runs endlessly. The first thing it does is wait for a // signal on the wantFormat event. This event is set when an XML file // is loaded, or when the text in the richtextbox changes. // // When the signal is received, execution continues, and the // highlighting begins. The approach is this: read a segment of the // XML, decide how to highlight it, then place a description of that // formatting change into a list. Then, continue by reading the next // segment of XML, until the entire XML document has been processed. // // On an interval that is normally every 1/48th of the lines - if // there are 960 lines, then every 20 lines - this method does two // things: call the progress update for the BG worker, and also apply // the queued changes. Using this approach the progress bar magically // appears while highlighting is happening, and disappears when // highlighting finishes. // // The reason for the batch approach: the control.Invoke() method, // necessary to apply changes to the richtextbox, can be costly. Batching // changes amortizes the cost of that method across a number of format // changes. // // The ApplyChanges method applies the batch of changes. It first // does a richtextbox.Invoke() to get on the proper thread. Once // there, it saves the scroll and selection state, applies each // formatting change in the list to the rtb text, restores the scroll // and selection state, and then calls Refresh() on the RTB. After // that method returns, this method clears the list of format changes // and continues reading and analyzing the next segment of XML. // // If at any time, the user changes the RTB text, either through a new // load of an XML file, or through direct editing in the richtextbox, // the main UI signals the wantFormat event again. This method treats // the raising of that signal as a "cancel-and-restart" message. Upon // detecting that signal, this method stops working, if it was // working, and then starts reading and highlighting at the beginning // again. // // There's also some logic to delay the formatting, to wait for the // user to stop typing, if that is what caused the wantFormat to get // signalled. // // When this method finishes highlighting, this method waits for the // wantFormat signal again. This method never returns. // BackgroundWorker self = sender as BackgroundWorker; // var xmlReaderSettings = new XmlReaderSettings // { // ProhibitDtd = false, // XmlResolver = new Ionic.Xml.XhtmlResolver() // // // this works in .NET 4.0 ?? // //DtdProcessing = DtdProcessing.Parse, // //XmlResolver = // //new XmlPreloadedResolver(new XmlXapResolver(), // //XmlKnownDtds.Xhtml10) // }; do { InvokeActionProperly(SaveCaretPosition); try { InvokeActionProperly(resume); wantFormat.WaitOne(); wantFormat.Reset(); progressCount = 0; var list = new List <FormatChange>(); // We want a re-format, but let's wait til // the user stops 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; } } InvokeActionProperly(suspend); // Get the text ONCE. In a RichTextBox, this is // expensive, so we do it once, until a change is detected // in the richtextbox. string txt = (this.richTextBox1.InvokeRequired) ? (string)this.richTextBox1.Invoke((System.Func <string>)(() => this.richTextBox1.Text)) : this.richTextBox1.Text; ResetXmlTextBox(); var lc = new LineCalculator(txt); float maxLines = (float)lc.CountLines(); int reportingInterval = (maxLines > 96) ? (int)(maxLines / 48) : 4; int lastReport = -1; var sr = new StringReader(txt); XmlReader reader = XmlReader.Create(sr, readerSettings); IXmlLineInfo rinfo = (IXmlLineInfo)reader; if (!rinfo.HasLineInfo()) { continue; } int rcount = 0; int ix = 0; int t = 0; while (reader.Read()) { if ((rcount % 8) == 0) { // If another format is pending, that means // the text has changed and we should stop this // formatting effort and start again. if (wantFormat.WaitOne(0, false)) { break; } } rcount++; // report progress if ((rinfo.LineNumber / reportingInterval) > lastReport) { int pct = (int)((float)rinfo.LineNumber / maxLines * 100); self.ReportProgress(pct); lastReport = (rinfo.LineNumber / reportingInterval); ApplyChanges(list); list.Clear(); progressCount++; } switch (reader.NodeType) { case XmlNodeType.XmlDeclaration: // The node is an declaration. ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; t = 2 + reader.Name.Length + 1 + reader.Value.Length + 2; list.Add(new FormatChange(ix - 2, t, Color.Blue)); break; case XmlNodeType.DocumentType: // DTD break; case XmlNodeType.ProcessingInstruction: // eg, <?xml-stylesheet type="text/xsl" href="http://www.codeplex.com/rss.xsl"?> ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; t = 2 + reader.Name.Length + 1 + reader.Value.Length + 2; list.Add(new FormatChange(ix - 2, t, Color.Blue)); break; case XmlNodeType.Element: // The node is an element. ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + rinfo.LinePosition - 1; list.Add(new FormatChange(ix - 1, 1, Color.Blue)); list.Add(new FormatChange(ix, reader.Name.Length, Color.DarkRed)); if (reader.HasAttributes) { reader.MoveToFirstAttribute(); do { //string s = reader.Value; ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + +rinfo.LinePosition - 1; list.Add(new FormatChange(ix, reader.Name.Length, Color.Red)); ix += reader.Name.Length; ix = txt.IndexOf('=', ix); // make the equals sign blue list.Add(new FormatChange(ix, 1, Color.Blue)); // skip over the quote char (it remains black) ix = txt.IndexOf(reader.QuoteChar, ix); ix++; // highlight the value of the attribute as blue if (txt.Substring(ix).StartsWith(reader.Value)) { list.Add(new FormatChange(ix, reader.Value.Length, Color.Blue)); } else { // Difference in escaping. The InnerXml may include // \" where " is in the doc. string s = reader.Value.XmlEscapeQuotes(); int delta = s.Length - reader.Value.Length; list.Add(new FormatChange(ix, reader.Value.Length + delta, Color.Blue)); } }while (reader.MoveToNextAttribute()); ix = txt.IndexOf('>', ix); // the close-angle-bracket if (txt[ix - 1] == '/') { list.Add(new FormatChange(ix - 1, 2, Color.Blue)); } else { list.Add(new FormatChange(ix, 1, Color.Blue)); } } break; case XmlNodeType.Text: // Leave these black - no addl formatting break; case XmlNodeType.EndElement: ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + +rinfo.LinePosition - 1; list.Add(new FormatChange(ix - 2, 2, Color.Blue)); list.Add(new FormatChange(ix, reader.Name.Length, Color.DarkRed)); list.Add(new FormatChange(ix + reader.Name.Length, 1, Color.Blue)); break; case XmlNodeType.Attribute: // These are handed within XmlNodeType.Element break; case XmlNodeType.Comment: ix = lc.GetCharIndexFromLine(rinfo.LineNumber - 1) + +rinfo.LinePosition - 1; list.Add(new FormatChange(ix, reader.Value.Length, Color.Green)); break; } } // in case there are more ApplyChanges(list); self.ReportProgress(100); } catch (Exception exc1) { // this can happen when editing an XML file, and the // xurrent state of the document is not valid xml. NotifyStopFormatting(exc1.Message); // System.Windows.Forms.MessageBox.Show("Exception: " + exc1.Message, // "Exception while formatting", // System.Windows.Forms.MessageBoxButtons.OK, // System.Windows.Forms.MessageBoxIcon.Error); } finally { //RestoreCaretPosition(); } }while (true); }