Winform Ui still blocking because of textbox operation - c#

I am going to build a dictionary. Everything was fine. But there is a little problem. To stop blocking UI of my winform I run my long code inside a thread. As I know when I give our operation for a worker thread UI is not blocked. But when I run the project it is still blocking for milliseconds. I did a lot of thing to find out it. The issue is the code has a textbox
textBox2.Invoke((MethodInvoker)((() => textBox2.Text += aa[i].Trim() + Environment.NewLine + foundwords + Environment.NewLine+ Environment.NewLine)));
It blocks the UI for 200ms if I run the project for 2 seconds...
Here is the whole code inbutton click event
private void button1_Click_2(object sender, EventArgs e)
{
textBox2.Text = "";
Thread btthread = new Thread(() => {
foundwords = "";
string words = "";
textBox1.Invoke((MethodInvoker)(() => words = textBox1.Text));
string[] aa = words.Trim().Split('\n');
for (int i = 0; i < aa.Length; i++)
{
string authors = DictionaryWords;
foundwords = "";
while (authors.Contains("\n"+aa[i].Trim().ToLower()+"\t"))
{
int index = authors.IndexOf("\n" + aa[i].Trim().ToLower() + "\t");// find where 1st hello is
string sub = authors.Substring(index + ("\n" + aa[i].Trim().ToLower() + "\t").Length);// remove all before hello get string we named it sub
int indexx = sub.IndexOf("\n"); // find \n index
int final = index + ("\n" + aa[i].Trim().ToLower() + "\t").Length; // find the index after 1st hello
string substr = authors.Substring(index + ("\n" + aa[i].Trim().ToLower() + "\t").Length, indexx); // select string after hello and before \n
authors = sub.Substring(indexx);// remove all after
foundwords += substr.Trim() + " ,";
}
textBox2.Invoke((MethodInvoker)((() => textBox2.Text += aa[i].Trim() + Environment.NewLine + foundwords + Environment.NewLine+ Environment.NewLine)));
}
});
btthread.Start();
}
DictionarywWords is a big file which has meaning for sinhala language
I want to stop Blocking the UI. could somebody tell me , what I should do?

forget threads and embrace Tasks and async await.
Put your long running code in an extra method that returns the data which be later used to fill the controls.
private WhatEverDataTypeSuitsYou CreateData()
{
WhatEverDataTypeSuitsYou returnValue;
// here goes your long running code
return returnValue;
}
now you can make your click event handler also async and await the results in there. Since the method createData returns on the UI thread there will be no need for invokation. You can simply fill the controls with the data as you see fit
private async void button1_Click_2(object sender, EventArgs e)
{
var result = await Task.Run(CreateData);
// now fill the controls without invoke
}
EDIT: Actually on the second glance at you code, I would suggest to use a StringBuilder instead of concatenating strings. This is probably the reason why your code need so much time.
This particular line is the evil one:
foundwords += substr.Trim() + " ,";

If I've understood what you're trying to do then I think I've got a solution that will run much much faster than your approach.
You are building a destroying many many strings in your search for the words that appear in the text DictionaryWords.
I gather that the file that DictionaryWords looks somewhat like this:
apple\ta fruit\n
Apple\ta computer\n
Banana\t\a fruit\n
fan\tone who follows a celebrity\n
Fan\ta device that moves air\n
NB: If you're on a PC then the line endings are \r\n, but I'll just assume you are on Linux.
A better data structure for working with this data would be this:
ILookup<string, string> dictionary =
File
.ReadLines(#"Path_to_DictionaryWords.txt")
.Select(x => x.Split('\t'))
.ToLookup(x => x[0].ToLower(), x => x[1]);
So, given my data above this would look like:
Now if you take textBox1 we can do this to get a list of words:
string[] words =
textBox1
.Text
.ToLower()
.Split(
Environment.NewLine,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
So if the user had entered this:
banana
apple
Then you'd have this:
Now, it's simple to get out the matching values:
string output =
String.Join(
Environment.NewLine + Environment.NewLine,
from word in words
where dictionary[word].Any()
select $"{word}{Environment.NewLine}{String.Join(", ", dictionary[word])}");
That looks like:
banana
a fruit
apple
a fruit, a computer
That code will run much faster than yours and will produce the entire output in one go.
You may not need tasks or threads with this approach.
If you do, try this:
private async void button1_Click_2(object sender, EventArgs e)
{
ILookup<string, string> dictionary = await Task.Run(() =>
File
.ReadLines(#"Path_to_DictionaryWords.txt")
.Select(x => x.Split('\t'))
.ToLookup(x => x[0].ToLower(), x => x[1]));
string[] words =
textBox1
.Text
.ToLower()
.Split(
Environment.NewLine,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
string output = await Task.Run(() =>
String.Join(
Environment.NewLine + Environment.NewLine,
words.Select(w => $"{w}{Environment.NewLine}{String.Join(", ", dictionary[w])}")));
textBox2.Text = output;
}

Related

Iterating over IEnumerable, special casing last element

I'm building a string based on an IEnumerable, and doing something like this:
public string BuildString()
{
var enumerable = GetEnumerableFromSomewhere(); // actually an in parameter,
// but this way you don't have to care
// about the type :)
var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
stringBuilder.Append("This is it: ");
foreach(var part in interestingParts)
{
stringBuilder.AppendPart(part);
if (part != interestingParts.Last())
{
stringBuilder.Append(", ");
}
}
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
stringBuilder.Append("[");
stringBuilder.Append(part.Something");
stringBuilder.Append("]");
if (someCondition(part))
{
// this is in reality done in another extension method,
// similar to the else clause
stringBuilder.Append(" = #");
stringBuilder.Append(part.SomethingElse");
}
else
{
// this is also an extension method, similar to this one
// it casts the part to an IEnumerable, and iterates over
// it in much the same way as the outer method.
stringBuilder.AppendInFilter(part);
}
}
I'm not entirely happy with this idiom, but I'm struggling to formulate something more succinct.
This is, of course, part of a larger string building operation (where there are several blocks similar to this one, as well as other stuff in between) - otherwise I'd probably drop the StringBuilder and use string.Join(", ", ...) directly.
My closest attempt at simplifying the above, though, is constructs like this for each iterator:
stringBuilder.Append(string.Join(", ", propertyNames.Select(prop => "[" + prop + "]")));
but here I'm still concatenating strings with +, which makes it feel like the StringBuilder doesn't really contribute much.
How could I simplify this code, while keeping it efficient?
You can replace this:
string.Join(", ", propertyNames.Select(prop => "[" + prop + "]"))
With c# 6 string interpolation:
string.Join(", ", propertyNames.Select(prop => $"[{prop}]"))
In both cases the difference is semantic only and it doesn't really matter. String concatenation like in your case in the select isn't a problem. The compiler still creates only 1 new string for it (and not 4, one for each segment and a 4th for the joint string).
Putting it all together:
var result = string.Join(", ", enumerable.Select(v => $"[{v.TheInterestingStuff}]"));
Because body of foreach is more complex that to fit in a String Interpolation scope you can just remove the last N characters of the string once calculated, as KooKiz suggested.
string separator = ", ";
foreach(var part in interestingParts)
{
stringBuilder.Append("[");
stringBuilder.Append(part);
stringBuilder.Append("]");
if (someCondition(part))
{
// Append more stuff
}
else
{
// Append other thingd
}
stringBuilder.Append(separator);
}
stringBuilder.Length = stringBuilder.Lenth - separator;
In any case I think that for better encapsulation the content of the loop's scope should sit in a separate function that will receive a part and the separator and will return the output string. It can also be an extension method for StringBuilder as suggested by user734028
Use Aggregate extension method with StringBuilder.
Will be more efficiently then concatenate strings if your collection are big
var builder = new StringBuilder();
list.Aggregate(builder, (sb, person) =>
{
sb.Append(",");
sb.Append("[");
sb.Append(person.Name);
sb.Append("]");
return sb;
});
builder.Remove(0, 1); // Remove first comma
As pure foreach is always more efficient then LINQ then just change logic for delimeter comma
var builder = new StringBuilder();
foreach(var part in enumerable.Select(v => v.TheInterestingStuff))
{
builder.Append(", ");
builder.Append("[");
builder.Append(part);
builder.Append("]");
}
builder.Remove(0, 2); //Remove first comma and space
Aggregate solution:
var answer = interestingParts.Select(v => "[" + v + "]").Aggregate((a, b) => a + ", " + b);
Serialization solution:
var temp = JsonConvert.SerializeObject(interestingParts.Select(x => new[] { x }));
var answer = temp.Substring(1, temp.Length - 2).Replace(",", ", ");
the code:
public string BuildString()
{
var enumerable = GetEnumerableFromSomewhere();
var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
stringBuilder.Append("This is it: ");
foreach(var part in interestingParts)
{
stringBuilder.AppendPart(part)
}
if (stringBuilder.Length>0)
stringBuilder.Length--;
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
if (someCondition(part))
{
stringBuilder.Append(string.Format("[{0}] = #{0}", part.Something));
}
else
{
stringBuilder.Append(string.Format("[{0}]", part.Something));
stringBuilder.AppendInFilter(part); //
}
}
much better now IMO.
Now a little discussion on making it very fast. We can use Parallel.For. But you would think (if you would think) the Appends are all happening to a single shareable resource, aka the StringBuilder, and then you would have to lock it to Append to it, not so efficient! Well, if we can say that each iteration of the for loop in the outer function creates one single string artifact, then we can have a single array of string, allocated to the count of interestingParts before the Parallel for starts, and each index of the Parallel for would store its string to its respective index.
Something like:
string[] iteration_buckets = new string[interestingParts.Length];
System.Threading.Tasks.Parallel.For(0, interestingParts.Length,
(index) =>
{
iteration_buckets[index] = AppendPart(interestingParts[index]);
});
your function AppendPart will have to be adjusted to make it a non-extension to take just a string and return a string.
After the loop ends you can do a string.Join to get a string, which is what you may be doing with the stringBuilder.ToString() too.

how to sort a multiline textbox without using an array

i just wrote a code that will sort the textbox but i want to know is there any other ways that does not require an intermediate array?!(just to improve performance)
here's the code i wrote:
string[] textbox = new string[textBox1.Lines.Length];
textbox = textBox1.Text.Split('\r');
textBox1.Clear();
Array.Sort(textbox);
foreach(string text in textbox)
{
textBox1.AppendText(text + "\r\n");
}
You can work directly with the Lines property (and this is already an array).
textBox1.Lines = textBox1.Lines.OrderBy(l => l).ToArray();
(Note that in any case you have to rebuild the Lines array so it is not really possible to avoid an array, it is just materialized after the OrderBy in this code)
Use OrderBy:
var s= textBox1.Text.Split('\r').OrderBy(c=>c);
textBox1.Clear();
foreach (string text in s)
{
textBox1.AppendText(text + "\r\n");
}
You could also do this without foreach loop just by using String.Join method:
var s = string.Join("\r\n", textBox1.Text.Split('\r').OrderBy(c => c));
textBox1.Clear();
textBox1.AppendText(s);

Reading specific data from file

I want to read specific data from bat file (set values) and then write them into Textbox1.Text (these are first lines of file I want to read from) and I want to get values
XXXXX write to Textbox1.Text
YYYYY write to Textbox2.Text
ZZZZZ write to Textbox3.Text
SSSSS write to Textbox4.Text
#echo off
:::::: DANE LOGOWANIA DO BAZY DANYCH
:: Baza danych
set DB=XXXXX
:: Serwer Bazy danych
set DBServer=YYYYY
:: Login do bazy danych
set DBUser=ZZZZZ
:: Hasło do bazy danych
set DBPassword=SSSSS
Reading from file should be triggered from
private void mssqlbutton_Click(object sender, EventArgs e)
{
}
Then if someone change Textbox1.Text value it writes those values into file in the position it previously read from.
I've tried finding for a solution using regex, reading by lines but it doesn't work for me or I don't know at all how to do it.
To be precise, I'm just starting my adventure with C#, I'm not that familiar with it yet, so If you could explain to me as well instead of only providing a code I would be grateful :)
EDIT:
Actually it is not that small, whole bat file is like that:
http://wklej.to/sCQ6i
(I know bat file delete log file at the end but i will handle it)
And AppCode:
http://wklej.to/Z5IFd
Where in AppCode
DBServerInput.Text was Textbox1.Text
DBInput.Text was Textbox2.Text
DBUserInput.Text was Textbox3.Text
DBPasswordInput was Textbox4.Texte
So where did you get stuck? However, the first requirement is relative easy:
private void ButtonReadBatch_Click(object sender, EventArgs e)
{
string[] linesToShowInTextBox = File.ReadLines(#"C:\Temp\batchTest.bat")
.Select(l => l.Trim())
.Where(l => l.StartsWith("set ", StringComparison.InvariantCultureIgnoreCase) && l.Contains('='))
.Select(l => l.Split('=').Last().TrimStart())
.ToArray();
TextBox1.Lines = linesToShowInTextBox;
}
The second is more complicated, this should give you an idea. It's tested rudimentarily:
private void ButtonSumbitTextboxChangesToBatch_Click(object sender, EventArgs e)
{
string[] lines = textBox1.Lines;
if (lines.Length != 0)
{
int matchIndex = 0;
var lineInfos = File.ReadLines(#"C:\Temp\batchTest.bat")
.Select(l => l.Trim())
.Select((line, index) => {
bool isSetLine = line.StartsWith("set ", StringComparison.InvariantCultureIgnoreCase) && line.Contains('=');
return new{
line, index, isSetLine,
setIndex = isSetLine ? matchIndex++ : -1
};
});
string[] newLines = lineInfos
.Select(x => !x.isSetLine || x.setIndex >= lines.Length
? x.line
: string.Format("set {0}={1}",
x.line.Split(' ')[1].Split('=')[0].Trim(),
lines[x.setIndex]))
.ToArray();
File.WriteAllLines(#"C:\Temp\batchTest.bat", newLines);
}
}
So don't use the TextChanged event but another button, otherwise the event is called on any change which causes undesired effects.
You can use the System.IO.File-class and its static Methods to read data from a file. Especially the Method ReadAllLines should help you. Thus, you can iterate over the lines or use an index to address. foreach and for loops should work.
The String-class gives you the tools to deconstruct the data read, think of using the Methods Split or IndexOf.
Writing the data back to the file can also be done using the System.IO.File-class. The easiest way should be overwriting the complete file with all you values, since the amount of data is pretty low. Consider using the AppendLine-Method from the System.IO.File-class.
The given MSDN-Links should give you a good starting point for your research. I won't post code since you said you want to go onto the adventure for yourself ;-)
I'm weak in Regular Expression , this code string pattern = #"=(?<after>\w+)"; is to match the word after equal sign = , it's better if some experts show for a better way :D
string txtBatFile= "<physical path for batfile>";
if (File.Exists(txtBatFile))
{
StreamReader SR = new StreamReader(txtBatFile);
string strFileText= SR.ReadToEnd();
SR.Close();
SR.Dispose();
string pattern = #"=(?<after>\w+)";
MatchCollection matches = Regex.Matches(strFileText, pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase);
ArrayList _strList = new ArrayList();
foreach (Match match in matches)
{
_strList.Add(match.Groups["after"].ToString());
}
Textbox1.Text = _strList[0].ToString();
Textbox2.Text = _strList[1].ToString();
Textbox3.Text = _strList[2].ToString();
Textbox4.Text = _strList[3].ToString();
}

C# -> Repeaters and Displaying them with Loops through Arrays

My question is part curiosity and part help, so bear with me.
My previous question had to do with passing text files as an argument to a function, which I managed to figure out with help, so thank you to all who helped previously.
So, consider this code bit:
protected bool FindWordInFile(StreamReader wordlist, string word_to_find)
{
// Read the first line.
string line = wordlist.ReadLine();
while (line != null)
{
if(line.Contains(word_to_find))
{
return true;
}
// Read the next line.
line = wordlist.ReadLine();
}
return false;
}
What happens with this particular function if you call in it the following way:
temp_sentence_string = post_words[i]; //Takes the first string in the array FROM the array and binds it to a temporary string variable
WordCount.Text = WordCount.Text + " ||| " + temp_sentence_string;
word_count = temp_sentence_string.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int word_pos = 0; word_pos < word_count.Length; word_pos++)
{
bool WhatEver = FindWordInFile(goodwords_string, word_count[word_pos]);
if (WhatEver == true)
{
WordTest.Text = WordTest.Text + "{" + WhatEver + "} ";
}
WordTest.Text = WordTest.Text + "{" + WhatEver + "}";
}
AND:
The string passed is "good times are good" and the text file has the word "good" in it is this:
good{True}times{False}are{False}good{False}
Pretty strange. It looks like what happened is that:
1. The sentence "good times are good" got put into an array, split by the detection of a space. This happened correctly.
2. The first array element, "good" was compared against the text file and returned True. So that worked.
3. It then went to the next word "times", compared it, came up False.
4. Went to the next word "are", compared it, came up False.
5. THEN it got to the final word, "good", BUT it evaluated to False. This should NOT have happened.
So, my question is - what happened? It looks like the function of FindWordInFile was perhaps not coded right on my end, and somehow it kept returning False even though the word "good" was in the text file.
Second Part: Repeaters in ASP.NET and C#
So I have a repeater object bound to an array that is INSIDE a for loop. This particular algorithm takes an array of sentences and then breaks them down into a temp array of words. The temp array of words is bound to the Repeater.
But what happens is, let's say I have two sentences to do stuff to...
And so it's inside a loop. It does the stuff to the first array of words, and then does it to the second array of words, but what happens in the displaying the contents of the array, it only shows the contents of the LAST array that was generated and populated. Even though it's in the for loop, my expectation was that it would show all the word arrays, one after the other. But it only shows the last one. So if there's 5 sentences to break up, it only shows the 5th sentence that was populated by words.
Any ideas why?
for (int i = 0; i < num_sentences; i++) //num_sentences is an int that counted the number of elements in the array of sentences that was populated before. It populates the array by splitting based on punctuation.
{
temp_sentence_string = post_words[i]; //Takes the first string in the array FROM the sentence array and binds it to a temporary string variable
WordCount.Text = WordCount.Text + " ||| " + temp_sentence_string;
word_count = temp_sentence_string.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); //create a word count array with one word as a singular element in the array
//We have the Word Count Array now. We can go through it with a loop, right?
for (int j = 0; j < word_count.Length; j++)
{
Boolean FoundIt = File
.ReadLines(#"c:\wordfilelib\goodwords.txt") // <- Your file name
.Any(line => line.Contains(word_count[j]));
WordTest.Text = WordTest.Text + FoundIt + "(" + word_count[j] + ")";
}
Repeater2.DataSource = word_count;
Repeater2.DataBind();
}
First Part
You are passing a StreamReader into the Find function. A StreamReader must be reset in order to be used multiple times. Test with the following sentence and you will see the result.
"good good good good"
you will get
good{true}good{false}good{false}good{false}
I would suggest reading the file into an array only one time and then do your processing over the array.
using System.Linq
using System.Collections.Generic;
public class WordFinder
{
private static bool FindWordInLines(string word_to_find, string[] lines)
{
foreach(var line in lines)
{
if(line.Contains(word_to_find)
return true;
}
return false;
}
public string SearchForWordsInFile(string path, string sentence)
{
// https://msdn.microsoft.com/en-us/library/s2tte0y1(v=vs.110).aspx
var lines = System.IO.File.ReadAllLines(path);
var words = sentence.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var result = string.Empty;
foreach(var word in words)
{
var found = FindWordInLines(word, lines);
// {{ in string.Format outputs {
// }} in string.Format outputs }
// {0} says use first parameter's ToString() method
result += string.Format("{{{0}}}", found);
}
return result;
}
}
Second Part:
If you bind it in the for loop like that it will only bind to the last result. If you accumulate the results in the outer loop you can pass the accumulated results to the repeater and bind outside the loop.
I created a sample loop class below that has two loops. The "resultList" is the variable that accumulates the results.
using System.Collections.Generic;
public class LoopExample
{
public void RunLoopExample()
{
var outerList = new string[]{"the", "quick", "brown", "fox"};
var innerList = new string[]{"jumps", "over", "the", "lazy", "dog"};
// define the resultList variable outside the outer loop
var resultList = new List<string>();
for(int outerIndex = 0; outerIndex < outerList.Length; outerIndex ++)
{
var outerValue = outerList[outerIndex];
for(int innerIndex = 0; innerIndex < innerList.Length; innerIndex++)
{
var innerValue = innerList[innerIndex];
resultList.Add(string.Format("{0}->{1}; ", outerValue, innerValue));
}
}
// use the resultList variable outside the outer loop
foreach(var result in resultList )
{
Console.WriteLine(result);
}
}
}
In your example, you would set the dataSource to the resultList
Repeater2.DataSource = resultList;
Repeater2.DataBind();

Why do I only see the last value from a List in my RichTextBox?

Why is this code only outputting one element from the list?
private void button3_Click(object sender, EventArgs e)
{
Tasks dosomething = new dosomething();
dosomething.calculate();
foreach(float num in dosomething.Display())
{
richTextBox1.Text = num.ToString();
}
}
Where dosomething.Display() is returning a List<float> list; with 10 floats.
You need to use
richTextBox1.Text += num.ToString();
By what you are doing, only the very last item will be displayed.
A better way would be to make use of a StringBuilder
StringBuilder sb = new StringBuilder();
foreach (float num in dosomething.Display())
{
sb.Append(num.ToString() + Environment.NewLine);
}
richTextBox1.Text = sb.ToString();
Each time the running code enters the foreach loop,
richTextBox1.Text = num.ToString();
clears the richTextBox1 and inserts num.ToString() into it. Next time the code in that foreach loop is executed, the contents are once again replaced.
I think that you are trying to append results here. There are many ways you could do this. For example, you could easily refactor that previous statement to:
richTextBox1.Text += num.ToString();
Note the += operator: += means append to. Thus, richTextBox1.Text = richTextBox1.Text + num.ToString();
Of course, you could also add whitespace to make it look pretty...
richTextBox1.Text += num.ToString()+" ";
Or, as astander suggested, you could use a StringBuilder. A StringBuilder is a very efficient class for manipulating strings without the ordinary memory penalties that doing this with regular string classes involves. In addition, the following code below (which is very similar to astander's code, as they essentially function the same way) only updates the richTextBox1 on the user interface once, rather than once for each element of the list.
StringBuilder code:
StringBuilder sbuilder = new StringBuilder(); //default StringBuilder() constructor
foreach (float num in dosomething.Display())
{
sb.Append(num.ToString() + " ");
}
//Foreach loop has ended - this means all elements of the list have been iterated through
//Now we set the contents of richTextBox1:
richTextBox1.Text = sb.ToString();
Note that in the foreach loop, we are appending to the StringBuilder, via the StringBuilder.Append() method.
Then, of course, you could, once again, change what appears between outputted floats. I just put a space in (" ").
So, to answer your question, only one appears because you are setting the value each time, rather than appending. When you set a value, the previous value is discarded.
Hope I helped!
P.S. Just saw a curious little thing in your code:
Tasks dosomething = new dosomething();
Are you sure that it wouldn't be:
Tasks dosomething = new Tasks();
But that's not related to your question.

Categories