[.net] Appending to RichTextBox in secondary TabPage (C#) - Threading Issue

Started by
4 comments, last by jods 18 years, 11 months ago
Well, Im new to C#, but a big c++ coder. I like it very much so far though. My problem here is that I have 2 RichTextBoxs. One is on the first tab, and the other on the second tab. When the user hits the Go button at the bottem it writes(.Appends()) to both of these RichTextBoxes (one is for status info the other for the output). Well if the user just hits go without selecting the second tab ever, no output is ever put (appended) to that second RichTextBox. Now if you click that tab first, and then can even switch it back (doesnt matter) then it will work just fine, writing to both. But Id like to clear up this bug so a user doesnt have to select that tabpage first. I tried things like programmaly selecting that second tabpage (and then maybe even setting it back to the first) but this causes some wierd overlay problems and then leads to it crashing often. (Tabs.SelectedIndex = 1;) I tried may things to try and just get focus to that non-visible second RichTextBox to make it able to write to. Any ideas on how to solve this problem would be great. Thanks a bunch. *EDIT* ok, I found out the problem, It has to do with the fact that I am running my recursive algorithm in a separate thread (to not lock up the programs other controls). If i dont run this in a thread, and just call the function, It works fine. I still dont understand why though... I really want it to run in a separate thread. A cheap fix I have for it now is: int currentTabIndex = Tabs.SelectedIndex; Tabs.SelectedIndex = 1; Tabs.SelectedIndex = currentTabIndex; but again, this cannot be done in the thread, but must be done before it. This is the function that does all the Appends the the second RichTextBox (StatusTB2). In the SendCommand() and RecieveCode() functions all the Appends to the first RTBox (StatusTB) occur. private void RecursiveDirectoryList(int level, String parentDir) { //MAKE DATA CONNECTION DataConnect(); if(networkStream2 != null && networkStream2.CanRead) { //LIST CURRENT DIR CONTENTS SendCommand("LIST", ""); RecieveCode(); int bytesRead = 0; String listString = ""; do { byte[] list = new Byte[tcpClient2.ReceiveBufferSize]; bytesRead += networkStream2.Read(list, 0, tcpClient2.ReceiveBufferSize); listString += System.Text.Encoding.Default.GetString(list).TrimEnd('\0'); } while(networkStream2.DataAvailable || (bytesRead > 0 && listString[bytesRead - 1] != '\n')); RecieveCode(); //SECOND RECIEVE VERIFIES ALL DATA RECIEVED - 226 listString += '\0'; for(int i = 0; listString != '\0';) { int currentEndPos = listString.IndexOf('\r', i); if(listString.ToLower() == 'd') { String currentDir = listString.Substring(i + 55, currentEndPos - (i + 55)); if(currentDir != "." && currentDir != "..") { if(level == 0) { if(BBTags) { if(Bold) StatusTB2.AppendText(""); if(Italic) StatusTB2.AppendText(""); if(Underline) StatusTB2.AppendText(""); } else { System.Drawing.Fontstyle fontstyle = System.Drawing.Fontstyle.Regular; if(Bold) fontstyle |= System.Drawing.Fontstyle.Bold; if(Italic) fontstyle |= System.Drawing.Fontstyle.Italic; if(Underline) fontstyle |= System.Drawing.Fontstyle.Underline; StatusTB2.SelectionFont = new System.Drawing.Font( StatusTB2.SelectionFont.FontFamily, StatusTB2.SelectionFont.Size, fontstyle); } } if(PrefixDir && level > 1) { StatusTB2.AppendText(parentDir); StatusTB2.AppendText("\\"); } StatusTB2.AppendText(currentDir); if(level == 0) { if(BBTags) { if(Bold) StatusTB2.AppendText(""); if(Italic) StatusTB2.AppendText(""); if(Underline) StatusTB2.AppendText(""); } else { } } StatusTB2.AppendText("\r\n"); StatusScrollBottom(StatusTB2.Handle); SendCommand("CWD", currentDir); RecieveCode(); RecursiveDirectoryList(level + 1, currentDir); if(level == 0) StatusTB2.AppendText("\r\n"); SendCommand("CDUP", ""); RecieveCode(); } } else return; i = currentEndPos + 2; } } } Here is what starts my algorithm off.. the 'Go' buttons click event private void Go_Click(object sender, System.EventArgs e) { //CLEAR STATUS WINDOW StatusTB.Clear(); StatusTB2.Clear(); //RUN FTP PROC IN OWN THREAD if(!thread.IsAlive) { //******** I FOUND OUT THE PROBLEM, ITS WITH RUNNING IT IN ANOTHER THREAD... BUT WHY? ******** thread = new Thread(new ThreadStart(FTPProc)); thread.Start(); } } void FTPProc() { try { //Tabs.SelectedIndex = 1; //CONNECT TO FTP StatusTB.AppendText("Connecting...\r\n"); ftp.Connect(IPTB.Text, System.Convert.ToInt32(PortTB.Text)); StatusTB.AppendText("Connected\r\n"); //RECIEVE SERVER INFO ftp.RecieveCode(); //LOGIN - USER ftp.SendCommand("USER", UserNameTB.Text); ftp.RecieveCode(); //LOGIN - PASS ftp.SendCommand("PASS", PasswordTB.Text); ftp.RecieveCode(); //CHANGE WORKING PATH if(PathTB.Text.Length > 0) { ftp.SendCommand("CWD", PathTB.Text); ftp.RecieveCode(); } //PRINT WORKING DIRECTORY ftp.SendCommand("PWD", ""); ftp.RecieveCode(); //SET TYPE ftp.SendCommand("TYPE", "A"); ftp.RecieveCode(); //LIST FULL(RECURSIVE) FTP CONTENTS StatusTB.AppendText("Starting Directory Listing...\r\n"); ftp.StatusScrollBottom(StatusTB.Handle); ftp.RecursiveDirectoryList(); StatusTB.AppendText("Directory Listing Complete\r\n"); ftp.StatusScrollBottom(StatusTB.Handle); StatusTB2.AppendText("<Listing Created with CoodFTP Lister v"); StatusTB2.AppendText(version.ToString()); StatusTB2.AppendText(">"); ftp.StatusScrollBottom(StatusTB2.Handle); } catch (Exception e) { StatusTB.AppendText(String.Concat("Error: ", e.Message)); ftp.StatusScrollBottom(StatusTB.Handle); } ftp.SendCommand("QUIT", ""); ftp.Close(); ftp.StatusScrollBottom(StatusTB.Handle); } [Edited by - cood on May 17, 2005 2:19:12 PM]
Advertisement
This is strange. Hard to help you without bits of code...

Use the debugger, and check when the call to Append is done... Is your rtBox instantiated ? is Append *really* called, with the correct params ? isn't there any error ?

Also, you don't have any events that may clear the rtBox when you first switch to it ?

I don't have any other idea... except that it might be a bug in .NET (but I highly doubt it is).
hmmm... it shouldnt be a problem... and an error in .NET seems unlikely especially in a thing so trivial.

must be some error in the code... can u post your code here
Quote:Original post by jods
Is your rtBox instantiated ? is Append *really* called, with the correct params ? isn't there any error ?

Yes, It is instantiated, and when debuging it is filling the Text member variable correctly. Its just not being put in the RTBox to the screen. Ive made sure to do Updates() and Refreshs() but that doesnt do anything.

Quote:Original post by jods
Also, you don't have any events that may clear the rtBox when you first switch to it ?

Nope, nothing here either. And when I select the tab with this second RTBox in it and then run my algorithm that writes to these boxs, I can then switch back and forth from the different tabs and the Text stays there.

I will post my code in my first post in just a sec.

*EDIT* ok, I found out the problem, It has to do with the fact that I am running my recursive algorithm in a separate thread (to not lock up the programs other controls). If i dont run this in a thread, and just call the function, It works fine. I still dont understand why though... I really want it to run in a separate thread.

[Edited by - cood on May 17, 2005 11:52:27 AM]
I haven't read the entire thread, and I apologize if I understand your question incorrectly. Just browsing quickly I got the understanding that want a separate thread to update a control in your main form from a separate thread.

When you call Application.Run(new MyForm()) from your Main method, this means that the process' "main thread" will pump windows messages. This is the and only thread allowed to work on controls in the MyForm class.

However it is possible to use background threads. To update the UI from those threads you need to "invoke" back to the main thread.

Below are some useful links on this topic.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp
http://msdn.microsoft.com/library/en-us/dnforms/html/winforms08162002.asp
http://msdn.microsoft.com/library/en-us/vbcon/html/vbtskmanipulatingcontrolsfromthreads.asp

http://www.bearcanyon.com/dotnet/
http://www.bearcanyon.com/dotnet/AsyncControlUpdates.zip
http://www.bearcanyon.com/dotnet/MultithreadingHazards.zip
Yup, the AP is perfectly right.
I didn't thought about multithreading issues first, but since you mentionned it I wanted to explain what's going on. The AP was faster ;-)

Windows.Forms is not thread safe and it's required that all access to its classes is done via the main thread of you application.

As the AP mentionned, the easiest way to achieve that is to use the Invoke method of the Control class. Note that from .NET 2.0 on, you can also use Invoke asynchronously for best perfomances with BeginInvoke and EndInvoke. Also note that there's standard invoke methods for the most commonly used functions:
InvokeGotFocus, InvokeLostFocus, InvokeClick, InvokePaint, InvokePaintBackground.

Finally, if you're not sure if the current thread must use Invoke or can manipulate a control directly, the InvokeRequired property tells you.

jods

This topic is closed to new replies.

Advertisement