I have a large list of offsets which I need to highlight in my RichTextBox. However this process is taking too long. I am using the following code:
foreach (int offset in offsets)
{
richTextBox.Select(offset, searchString.Length);
richTextBox.SelectionBackColor = Color.Yellow;
}
Is there a more efficient way to do so?
UPDATE:
Tried using this method but it doesn't highlight anything:
richTextBox.SelectionBackColor = Color.Yellow;
foreach (int offset in offsets)
{
richTextBox.Select(offset, searchString.Length);
}
I've googled your issue and I found that RichTextBox is getting very slow when having many lines. In my opinion, you have either buy a third part control which you can be satisfied by its performance or you may need threads to devide the whole selection task. I think they can accelerate things up.
Hope it helps !
I've had this same problem before. I ended up disregarding all of the methods they give you and manipulated the underlying RTF data. Also, the reason that your second block of code doesnt work is that RTF applies formatting as it goes, so if you call a function (or Property in this case) to change the selection color, it will only apply it for the currently selected block. Any changes made to the selection after that call become irrelavent.
You can play around with the RGB values, or here is a great source on how to do different things within the RTF control. Pop this function in your code and see how well it works. I use it to provide realtime syntax highlighting for SQL code.
public void HighlightText(int offset, int length)
{
String sText = richTextBox.Text.Trim();
sText = sText.Insert(offset + length - 1, #" \highlight0");
sText = sText.Insert(offset, #" \highlight1");
String s = #"{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
{\colortbl ;\red255\green255\blue0;}\viewkind4\uc1\pard";
s += sText;
s += #"\par}";
richTextBox.Rtf = s;
}
Does it make any difference if you set the SelectionBackColor outside of the loop?
Looking into the RichTextBox with Reflector shows, that a WindowMessage is sent to the control every time when the color is set. In the case of large number of offsets this might lead to highlighting the already highlighted words again and again, leading to O(n^2) behavior.
Related
I'm currently implementing a Custom Spell Check in WPF using NHunspell, because the native solution of .Net Framework doesn't fit my needs. But I'm having trouble when checking the words in a big text, such as a Lorem Ipsum with 10 pargraphs, because i need to check each word, see if it contains in the dictionary the Hunspell uses, and if not, I need to Underline that Word.
I have this current method, that checks all the text everytime the KeyUp is a Backspace or a Space Key.
var textRange = new TextRange(SpellCheckRichTextBox.Document.ContentStart,
SpellCheckRichTextBox.Document.ContentEnd);
textRange.ApplyPropertyValue(Inline.TextDecorationsProperty, null);
_viewModel.Text = textRange.Text;
var zzz = _viewModel.Text.Split(' ');
var kfeofe = zzz.Where(x => _viewModel.MisspelledWords.Contains(x));
foreach (var item in kfeofe)
{
TextPointer current = textRange.Start.GetInsertionPosition(LogicalDirection.Forward);
while (current != null)
{
string textInRun = current.GetTextInRun(LogicalDirection.Forward);
if (!string.IsNullOrWhiteSpace(textInRun))
{
int index = textInRun.IndexOf(item.ToString());
if (index != -1)
{
TextPointer selectionStart = current.GetPositionAtOffset(index, LogicalDirection.Forward);
TextPointer selectionEnd = selectionStart.GetPositionAtOffset(item.ToString().Length, LogicalDirection.Forward);
TextRange selection = new TextRange(selectionStart, selectionEnd);
selection.ApplyPropertyValue(Inline.TextDecorationsProperty, TextDecorations.Underline);
}
}
current = current.GetNextContextPosition(LogicalDirection.Forward);
}
}
But, I think I need a Async solution, so it doesn't block my main thread and the typing of the user.
- In theory I was thinking about running a parallel thread if the user spends more than 2 seconds without typing and then returning the checked TextRange to my RichTextBox (SpellCheckRichTextBox).
Can somebody suggest any solution so I can make the verification less slow when working with big texts? I'm really stuck at that, any help would be appreciated.
Thanks in advance!
The first improvement would be
zzz.AsParallel().Where(x => _viewModel.MisspelledWords.Contains(x)).ToList();
That obviously assumes that your .MisspelledWords.Contains(x) is something that can be done in parallel. It might be a ConcurrentDictionary already.
The fact that you have a collection of misspelled words, makes me believe you already parsed the text once. So why parse it twice? Why can't you combine those two passes? That would be another possible optimization.
And yes, doing all of this in another thread when the user stops typing would be preferable.
So, working in C#. I have a CSS-based drop-down menu. I open the menu, then grab a reference to it using FindElement by CSSSelector. I then grab the contents of the list using FindElements, again by CSSSelector.
Now here's where it get's interesting. I iterate the list, based on a file I have open in a streamreader.
Looks something like:
list = driver.FindElement(By.CSSSelector("dropdown-menu"));
list_items = list.FindElements(By.CSSSelector("LI > A"));
int row = 0;
while (data_file.read())
{
iWebElement item = list_items[row];
string label = item.text;
string url = item.getattribute("href");
assert.areequal("something", label);
assert.areequal("something else", url);
row++;
}
Now here's the thing: if the mouse pointer is placed over the drop-down, while this is executing, item.text returns value and the test succeeds. If the pointer is anywhere else, item.text will be blank and the test fails. Trying to understand what's going on, and taking a clue from the fact that though the test would fail when running, but would succeed while stepping, I modified the code with a loop:
while (data_file.read())
{
iWebElement item = list_items[row];
string label = item.text;
while (label == "")
{
label = item.text;
}
string url = item.getattribute("href");
assert.areequal("something", label);
assert.areequal("something else", url);
row++;
}
Now the test will always succeed, but if the pointer is not on the control it is SIGNIFICANTLY slower... we're talking a factor of 4 or 5... then when the pointer IS on the control. By wrapping a timer around this, I find that it typically takes between 2 and 4 seconds before .text returns anything but an empty string... sometimes longer.
Again, this delay only seems to apply when the mouse pointer is not over the drop-down. Otherwise, the value appears to be there instantaneously.
Can anyone suggest a possible explanation for why it's behaving this way, and a possible approach to solving it?
BTW, I'm not finding any difference between:
item = list_items[row];
label = item.text;
and
label = list_items[row].text;
Nor does .getattribute("value") produce any faster results than .text.
As for why the menus are acting this way, it's hard to tell with the code you've provided. If you could provide the code that displays your menu, that might help. As for solutions, there are a couple.
The label could be taking longer to display because of the while loop itself - it's just going crazy grabbing the text over and over very quickly. A better solution would be to wait for the element to be present. This may make your code run faster. See the Selenium Website for information on using WebDriverWait.
Alternatively, there is an "ugly" solution. You can just move the mouse to the menu with Selenium, to make sure the menu is always displayed when you need it. I've adapted some code from here as an example:
OpenQA.Selenium.Interactions.Actions builder = new
builder.MoveToElement(list).Build().Perform();
Hope this helps!
I'm using the regular System.Windows.Form.RichTextBox control for a WinForm application running on the .NET Framework 2.0 to show a status log. Since the Form and several other child controls, have AutoSize=True, the application does not always look the same way on different setups. I have no way to know in advance the exact size of the control and anyway I guess there are some implications related to the ratio (font appearence)/(gui dimensions) of each particular configuration.
So now let's depict the most dynamic scenario. I want to know what's the exact maximum length of the string, a given RichTextBox can show on the same line (without exceeding the border nor word wrapping) where such a RichTextBox is using a generic and known, monospaced font and size.
In case there's no any straightforward way to accomplish this result, does anyway know if I may use any kind of trick like injecting an incrementally growing test string till some weird event gets fired?
That's what I got till now. I used the TextRenderer class and the strategy I anticipated on my question above. Of course it makes sense only if the control uses a monospaced font. Despite it works for my problem, I'm still curious to know if anyone knows a better way to reach the same goal. So the question will still be open for a while.
int maxStringLength = GetMaxStringLengthPerLine(myRichTextBox);
int GetMaxStringLengthPerLine(RichTextBox textbox) {
return GetMaxStringLength(textbox.Size.Width, textbox.Font);
}
int GetMaxStringLength(int width, Font font) {
int i = 0;
while(TextRenderer.MeasureText(new string('A',++i), font).Width<=width);
return --i;
}
I have a .NET 4.5 WinForm program that queries a text-based database using ODBC. I then want to display every result in a multiline textbox and I want to do it in the quickest way possible.
The GUI does not have to be usable during the time the textbox is being updated/populated. However, it'd be nice if I could update a progress bar to let the user know that something is happening - I believe a background worker or new thread/task is necessary for this but I've never implemented one.
I initially went with this code and it was slow, as it drew out the result every line before continuing to the next one.
OdbcDataReader dbReader = com.ExecuteReader();
while (dbReader.Read())
{
txtDatabaseResults.AppendText(dbReader[0].ToString());
}
This was significantly faster.
string resultString = "";
while (dbReader.Read())
{
resultString += dbReader[0].ToString();
}
txtDatabaseResults.Text = resultString;
But there is a generous wait time before the textbox comes to life so I want to know if the operation can be even faster. Right now I'm fetching about 7,000 lines from the file and I don't think it's necessary to switch to AvalonEdit (correct me if my way of thinking is wrong, but I would like to keep it simple and use the built-in textbox).
You can make this far faster by using a StringBuilder instead of using string concatenation.
var results = new StringBuilder();
while (dbReader.Read())
{
results.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = results.ToString();
Using string and concatenation creates a lot of pressure on the GC, especially if you're appending 7000 lines of text. Each time you use string +=, the CLR creates a new string instance, which means the older one (which is progressively larger and larger) needs to be garbage collected. StringBuilder avoids that issue.
Note that there will still be a delay when you assign the text to the TextBox, as it needs to refresh and display that text. The TextBox control isn't optimized for that amount of text, so that may be a bottleneck.
As for pushing this into a background thread - since you're using .NET 4.5, you could use the new async support to handle this. This would work via marking the method containing this code as async, and using code such as:
string resultString = await Task.Run(()=>
{
var results = new StringBuilder();
while (dbReader.Read())
{
results.Append(dbReader[0].ToString());
}
return results.ToString();
});
txtDatabaseResults.Text = resultString;
Use a StringBuilder:
StringBuilder e = new StringBuilder();
while (dbReader.Read())
{
e.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = e.ToString();
Despite the fact that a parallel Thread is recommended, the way you extract the lines from file is somehow flawed. While string is immutable everytime you concatenate resulString you actually create another (bigger) string. Here, StringBuilder comes in very useful:
StringBuilder resultString = new StringBuilder ()
while (dbReader.Read())
{
resultString = resultString.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = resultString;
I am filling a regular TextBox (multiline=true) in a single call with a very long string (more than 200kB, loaded from a file. I just assign the Text property of TextBox with my string).
It's very slow (> 1 second).
The Textbox does anything else than display the huge string.
I used a very simple trick to improve performances : I replaced the multiline textbox by a RichTextBox (native control).
Now same loadings are instantaneous and RichTextBox has exactly the same appearance and behavior as TextBox with raw text (as long as you didn't tweaked it). The most obvious difference is RTB does not have Context menu by default.
Of course, it's not a solution in every case, and it's not aiming the OP question but for me it works perfectly, so I hope it could help other peoples facing same problems with Textbox and performance with big strings.
So I've been working on cobbling together a game and decided I'd like to have a little program to show a file with each character replaced by its byte equivalent for working with coding saves and whatnot. Figured it'd a layup. Three hours later, I've been wracking my brain trying to figure this out.
When I load a small (or perhaps short is the better term) file it looks like the window on top. When I load a larger file, it looks like the window on the bottom.
http://dl.dropbox.com/u/16985121/Images/ViewAsBytes.PNG
That's 10pt Courier New, but it seems to happen with any font I try. There's always that extra column, and if there wasn't enough room for the column, it'd just squeeze in whatever it could in that space that it previously didn't use. I've tried tweaking all kinds of variables, as well as comparing the textbox before and after it adds the text from the file (which is read in just as bytes from a FileStream and then fed into a StringBuilder) but nothing seems to change even though something is clearly different.
I can think of a bunch of different workarounds for this, but now I'm just more interested in what TextBox thinks it's doing exactly than getting my program done. Anyone got any idea?
Here's the code that reads in the data and puts that to the textbox:
FileStream stream = new FileStream(files[0], FileMode.Open);
StringBuilder sb = new StringBuilder();
int byteIn = stream.ReadByte();
while (byteIn != -1)
{
sb.Append('[');
if (byteIn < 100)
sb.Append('0');
if (byteIn < 10)
sb.Append('0');
sb.Append(byteIn.ToString());
sb.Append(']');
byteIn = stream.ReadByte();
}
txtView.Text = sb.ToString();
stream.Close();
This is because you set to the WordWrap property to True. Set it to False, set Multiline to True and ScrollBars to Both. Append Environment.NewLine to the string you generate, every 16 bytes is the norm for hex viewers. Use byte.ToString("X2") to generate a hex string instead of a decimal string.
You now have a full scrollable view of the data, any amount is supported. Allow the user to resize the window so she won't have to scroll horizontally. Or just make it big enough.