Form loading then locking up

S

Steve Ricketts

I'm simply trying to open a form from another form and as soon as the new
form is shown, it locks up with a not responding message in the title bar.
However, I'm able to walk through the code behind the form and that still
works.

The scenario is that a learner is logging on to the application with a USB
wireless remote. The form I'm trying to open is just a template so they can
see what buttons correspond to what letters and show them what they've
entered (like texting on a phone).

Here is the basic flow in form (frmClient):

private frmTexting frmTexting1;
....
Later in the same frmClient:

private void onComm(object sender, SerialDataReceivedEventArgs e)
{
....
GotCommInput(buffer);
....
}

private void GotCommInput(byte[] buffer)
{
....
frmTexting1 = new frmTexting(); // Show is in the initialization of
frmTexting...
frmTexting1.Show(); // I've tried adding show
here as well
}

In debug mode, I can walk through the process from when the serial port data
is received, calls GotCommInput, creates the new frmTexting1, initializes
all the variables in frmTexting1, and as soon as it hits either
frmTexting.Show or frmTexting.visible=true (I tried both), the form opens
but nothing is on it. I can continue to walk the debugger through each
line, back to onComm, and out... call stack is empty. But the form is still
white and if you click on it you get the "not responding" message added to
the title bar.

The really odd part (at least to me) is that I can send another message
through the serial port and, using the debugger, I can step through each
line of code from onComm, to GotCommInput, to the code in frmTexting1...
which is the code behind the form that's not responding! So, what scenarios
could cause the form not respond but the code work?

Thanks for any ideas or direction,

Steve


PS. I didn't know if the real code would help, but it's so long I just cut
out the parts that don't come into play in this scenario and included it
below.

private void onComm(object sender, SerialDataReceivedEventArgs e)
{
int i = 0;
int bytes = 0;
int cCmd = 0;
int cNum = 0;
int cUnit = 0;
byte[] cData;
byte[] buffer;

// Read the data from the port and store it in our buffer
while (comport.BytesToRead > 0 || USBbuffer.Length > 0)
{
while (comport.BytesToRead > 0)
{
bytes = comport.BytesToRead;
buffer = new byte[bytes];
comport.Read(buffer, 0, bytes);
USBbuffer = CADEmain.concatArray(USBbuffer, buffer);
}

cCmd = USBbuffer[BT_CMD];
cUnit = USBbuffer[BT_UNIT] * 256;
cUnit = USBbuffer[BT_UNIT + 1] + 1;

switch (cCmd)
{
case BT_KEY: //key
cData = new byte[2];
cData[0] = USBbuffer[BT_DATA];
cData[1] = USBbuffer[BT_DATA + 1];
GotCommInput(cCmd, cUnit, cData,
USBbuffer[BT_VERSION], USBbuffer[BT_BATTERY]);
if (USBbuffer.Length == PK_LEN)
{
USBbuffer = new byte[0];
}
else {
buffer = new byte[USBbuffer.Length - PK_LEN];
Array.Copy(USBbuffer, PK_LEN, buffer, 0,
USBbuffer.Length - PK_LEN);
USBbuffer = new byte[buffer.Length];
Array.Copy(buffer, USBbuffer, buffer.Length);
}
break;
}
}
}

public void GotCommInput(int cCmd, int comUnit, byte[] cData, int
rmVersion, int cBattVolts)
{
int numPress = 0;
int CallIndex = 0;

numPress = cData[0] * 256;
numPress = numPress + cData[1];

CallIndex = thisRemote(comUnit.ToString());

if (CallIndex == -1 && remoteProperties != 15)
{
//Don't know who this is, use it to log on

if (dlClient.useTexting && !KeypadSelection)
{ //Use remote texting to logon
if (usersLogonRemoteId == "")
{ //Haven't established which remote
usersLogonRemoteId = comUnit.ToString();
frmTexting1 = new frmTexting();
// Bad thing happens here...
return;
}
if (comUnit.ToString() == usersLogonRemoteId )
{ //If logon remote, pass keys to texting
frmTexting1.btn_Click(numPress);
return;
}
}
}

Code in frmTexting

namespace dlTextCS
{
public partial class frmTexting : Form
{

public Control[] btn = new Button[14];

private int lastValue = 0;
private int keyPointer = 0;
private char DLMT = (char)1;
private string charSelection = "";
private string charHere = "";


public frmTexting()
{
InitializeComponent();
Form_Load();
}


private void Form_Load()
{
int i = 0;
int x = 0;

keyTimer.Tick += new EventHandler(keyTimer_Tick);
for (i = 0; i < this.Controls.Count; i++)
{
if (this.Controls.GetType().ToString() ==
"System.Windows.Forms.Button")
{
x = Convert.ToInt16(this.Controls.Tag);
btn[x] = (Button)this.Controls;
}
}
btn[11].Text = "num";
this.Show(); // tried visible = true but
this is where things go bad
}


public string btn_Click(int Index)
{
int i = 0;
string s = "";

if (btn[11].Text == "alpha") keyTimer.Enabled = false;

if (Index == 15 || Index == 0 || Index == 14) return "";

switch (Index)
{
case 11: //Alpha/Numeric toggle;
if (btn[11].Text == "alpha")
{
for (i = 1; i < 11; i++)
{
btn.Text = btn.Tag.ToString();
if (btn.Text == "")
{
btn.Visible = false;
}
else
{
btn.Visible = true;
}
}
btn[11].Text = "num";
}
else
{
for (i = 1; i < 11; i++)
{
btn.Visible = true;
btn.Tag = btn.Text;
btn.Text = i.ToString();
}
btn[10].Text = "0";
btn[11].Text = "alpha";
}
break;

case 12: //Back;
if (labID.Text == "_") return "";
labID.Text = labID.Text.Replace("_", "");
labID.Text = CADEmain.Left(labID.Text,
labID.Text.Length - 1) + "_";
break;

case 13: //Enter;
charSelection = labID.Text.Replace("_", "");
break;

default:
if (lastValue != Index)
{
s = labID.Text;
s = s.Replace("_", "");
labID.Invoke((MethodInvoker)delegate { labID.Text =
s + "_"; });

keyTimer.Enabled = false;
keyTimer.Stop();
}
if (keyTimer.Enabled)
{
charSelection = btn[Index].Text;
keyTimer.Enabled = false;
keyTimer.Stop();
keyPointer = keyPointer + 1;
if (keyPointer > charSelection.Length) keyPointer =
1;
charHere = CADEmain.Mid(charSelection, keyPointer,
1);
if (labID.Text.Length > 0)
{
labID.Text = CADEmain.Left(labID.Text,
labID.Text.Length - 1) + charHere.ToUpper();
}
}
else
{
lastValue = Index;
keyPointer = 1;
if (btn[11].Text == "alpha")
{
labID.Text = labID.Text.Replace("_", "");
labID.Text = labID.Text +
(CADEmain.Mid(btn[Index].Text.ToUpper(), 1, 1)) + "_";
}
else
{
if (Index < 11 && Index != 0)
{
labID.Text = CADEmain.Left(labID.Text,
labID.Text.Length - 1) + (CADEmain.Mid(btn[Index].Text.ToUpper(), 1, 1));
}
}
}
keyTimer.Enabled = true;
keyTimer.Start();
break;
}
return labID.Text;
}

private void keyTimer_Tick(object sender, EventArgs e)
{
keyTimer.Enabled = false;
keyTimer.Stop();
if (btn[11].Text == "num")
{
labID.Text = labID.Text + "_";
}
}
}
}
 
P

Peter Duniho

Steve said:
[...]
In debug mode, I can walk through the process from when the serial port
data is received, calls GotCommInput, creates the new frmTexting1,
initializes all the variables in frmTexting1, and as soon as it hits
either frmTexting.Show or frmTexting.visible=true (I tried both), the
form opens but nothing is on it. I can continue to walk the debugger
through each line, back to onComm, and out... call stack is empty. But
the form is still white and if you click on it you get the "not
responding" message added to the title bar.

The really odd part (at least to me) is that I can send another message
through the serial port and, using the debugger, I can step through each
line of code from onComm, to GotCommInput, to the code in frmTexting1...
which is the code behind the form that's not responding! So, what
scenarios could cause the form not respond but the code work?

Code is code. Threads are threads. The two are not the same.

So, you can have code from a class that in one thread has gotten stuck
somewhere, while code from the same class (theoretically even the same
code!) continues to execute fine in a different thread.

The "not responding" happens to a form when the form is owned by a
thread that is "stuck" as far as the GUI is concerned. This happens one
of two ways: the thread owning the object is being blocked from
returning to its message pump loop, or the thread owning the object
doesn't even have a message pump loop.

For a System.Windows.Forms.Control sub-class, ownership is determined
when the window handle for the Control instance is created, which
happens any time the instance has to do anything remotely interesting,
such as be shown.

Unfortunately, you didn't post a concise-but-complete code example.
It's not compilable and runnable as-is, but it still contains quite a
bit of code that is clearly unnecessary for demonstrating the issue. So
I can only guess at the exact problem. But, I suspect you are creating
your new form on a thread other than the main GUI thread, and thus the
thread that owns that form does not have a message pump loop.

The best way to solve the problem, if that turns out to be a correct
guess, is to wrap the display of the form in a call to Control.Invoke():

private void GotCommInput(byte[] buffer)
{
Invoke((MethodInvoker)delegate
{
frmTexting1 = new frmTexting();
});
}

This assumes that the above method is in a Form sub-class; you indicated
it's in a class named "frmClient", so I presume that's the case. If
that's an incorrect assumption, you'll have to have some way of getting
a Control instance (such as the actual "frmClient" reference) to use to
call Invoke().

Even in the code you posted, there are lots of other things I'd change.
You have very little encapsulation of your data structures, it
appears, and you've got that one monster "btn_Click" method, that at the
very least should be refactored so it's simpler, and I'll bet that the
actual implementation could be simplified as well, by quite a bit.

But hopefully the above simple change is enough to get your program
working again. :)

Pete
 
S

Steve Ricketts

If I could... I'd give up! ;-) You were correct and your suggestion
worked. I just don't get who owns what thread.
For a System.Windows.Forms.Control sub-class, ownership is determined when
the window handle for the Control instance is created, which happens any
time the instance has to do anything remotely interesting, such as be
shown.

So, if frmMain creates frmClient1 and frmClient1 creates frmTexting1,
shouldn't frmClient1 own the thread for frmTexting1 and be able to write to
it?...

It also seems like when in doubt, put an invoke on it and it will work...
whatever "it" is. Basically, it's looking like I have an invoke anytime
something writes to a form. Is that normal?.. or just me?

Absolutely confused in Ann Arbor...

sr


Peter Duniho said:
Steve said:
[...]
In debug mode, I can walk through the process from when the serial port
data is received, calls GotCommInput, creates the new frmTexting1,
initializes all the variables in frmTexting1, and as soon as it hits
either frmTexting.Show or frmTexting.visible=true (I tried both), the
form opens but nothing is on it. I can continue to walk the debugger
through each line, back to onComm, and out... call stack is empty. But
the form is still white and if you click on it you get the "not
responding" message added to the title bar.

The really odd part (at least to me) is that I can send another message
through the serial port and, using the debugger, I can step through each
line of code from onComm, to GotCommInput, to the code in frmTexting1...
which is the code behind the form that's not responding! So, what
scenarios could cause the form not respond but the code work?

Code is code. Threads are threads. The two are not the same.

So, you can have code from a class that in one thread has gotten stuck
somewhere, while code from the same class (theoretically even the same
code!) continues to execute fine in a different thread.

The "not responding" happens to a form when the form is owned by a thread
that is "stuck" as far as the GUI is concerned. This happens one of two
ways: the thread owning the object is being blocked from returning to its
message pump loop, or the thread owning the object doesn't even have a
message pump loop.

For a System.Windows.Forms.Control sub-class, ownership is determined when
the window handle for the Control instance is created, which happens any
time the instance has to do anything remotely interesting, such as be
shown.

Unfortunately, you didn't post a concise-but-complete code example. It's
not compilable and runnable as-is, but it still contains quite a bit of
code that is clearly unnecessary for demonstrating the issue. So I can
only guess at the exact problem. But, I suspect you are creating your new
form on a thread other than the main GUI thread, and thus the thread that
owns that form does not have a message pump loop.

The best way to solve the problem, if that turns out to be a correct
guess, is to wrap the display of the form in a call to Control.Invoke():

private void GotCommInput(byte[] buffer)
{
Invoke((MethodInvoker)delegate
{
frmTexting1 = new frmTexting();
});
}

This assumes that the above method is in a Form sub-class; you indicated
it's in a class named "frmClient", so I presume that's the case. If
that's an incorrect assumption, you'll have to have some way of getting a
Control instance (such as the actual "frmClient" reference) to use to call
Invoke().

Even in the code you posted, there are lots of other things I'd change.
You have very little encapsulation of your data structures, it appears,
and you've got that one monster "btn_Click" method, that at the very least
should be refactored so it's simpler, and I'll bet that the actual
implementation could be simplified as well, by quite a bit.

But hopefully the above simple change is enough to get your program
working again. :)

Pete
 
P

Peter Duniho

Steve said:
If I could... I'd give up! ;-) You were correct and your suggestion
worked. I just don't get who owns what thread.

Your process owns all of the threads in it. But ownership of a thread
isn't what's important here. It's what _thread_ owns a control that
matters.
So, if frmMain creates frmClient1 and frmClient1 creates frmTexting1,
shouldn't frmClient1 own the thread for frmTexting1 and be able to write
to it?...

A control instance, such as "frmClient1", does not own a thread. A
thread owns it. But the important thing here is actually what thread
owns the "frmTexting1" instance, not the "frmClient1" instance (the
latter is important too, but it's not related to the problem you
originally asked about).

When you create the "frmTexting1" instance and try to show it in the
same thread that is handling the serial port i/o, that thread is _not_
your main GUI thread, the one where Application.Run() was called when
your program started, and thus is not the thread which has a message
pump loop (Application.Run() includes an implementation of the necessary
message pump loop).

You could fix the problem in one of two ways:

– Put a message pump loop on the thread that owns the control, or
– Create the control in a thread that already has a message pump loop

But since you don't own the thread where the i/o code is executing, you
really don't have the right to create a message pump loop there. All
that doing that would accomplish is to stop the i/o class from working
even as you get the control instance (your "frmTexting1" form) to work,
because then that thread would get stuck dealing with the message pump
loop, and never get back to the i/o stuff.

So the second option is the correct one. And the way to do that is to
make sure that the code that creates and shows the control executes in
the thread where you already have a message pump loop. That's done
using the Invoke() method.
It also seems like when in doubt, put an invoke on it and it will
work... whatever "it" is. Basically, it's looking like I have an
invoke anytime something writes to a form. Is that normal?.. or just me?

You need Invoke() any time you access a member of a Control instance
from a thread other than the thread that owns that instance.

A large number of Forms applications are able to be single-threaded
completely. They never start another thread, or if they do, it's done
in a way such that they are never executing code that touches Control
instances from a thread other than that main GUI thread (e.g. they use
BackgroundWorker, which handles the cross-thread invocation on your
behalf). Most fall into the former category: they just don't even ever
start or use another thread (not counting those created implicitly by
the .NET runtime and which don't have any noticeable effect on the
behavior of your own code).

In code like that, you never need Invoke(), because everything's already
happening on the single GUI thread that owns everything.

But as soon as you start using asynchronous APIs, such as those found in
the various i/o classes, you wind up with code that executes in a thread
other than that main GUI thread. And yes, every single time you do
that, you will have to use Invoke() or BeginInvoke() for any code that
needs to manipulate the UI, so that _that_ code can still be executed on
the original main GUI thread where it belongs.

So, is it normal? Well…it's definitely a normal issue for code like
yours where you've got code executing in non-GUI threads that needs to
manipulate the GUI. It's definitely not Abby Normal. :)

Pete
 
S

Steve Ricketts

Pete, I don't know where you find the time to respond like this but it is
really appreciated. All makes much more sense... mostly! ;-) I suppose
if I were writing from scratch, I'd rethink how the various components come
together, but maybe next time. All in all, the application is coming
along very well thanks in no small part to your help. Only one more
component to tackle... the video. But how hard can that be! ;-)

Thanks again for all your time,

Steve
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top