Google

Friday, May 11, 2007

Dialogs in Compact Framework and Multithreaded Environment

Recently, I got stuck with uncontrolled dialogs on POCKET PC having it pop up anywhere in my App. As the process have been like popping up dialogs in the UI using new thread have been useful as I need to do other task after popping up that message box. Dotnet developers already know showdialog in winforms are blocking, so I have to take advantage of the showdialog-blocking thing and then continue to do the later task of the code.

My code goes like this…
static void Main()
{
Application.Run( new Form1());

PopItUpAnywhere();
PopItUpAnywhere2();
}

// Pops up messagebox anywhere in the UI.
Private void PopItUpAnywhere()
{
Thread t = new Thread( new ThreadStart(RunMessageNotification ));
t.Start();
}

// Pops up a dialog form.
Private void RunMessageNotification()
{
// will popup form that shaped like a message box.
Form2 dialog = new Form2();
dialog.ShowDialog();
}

// Pops up messagebox anywhere in the UI.
Private void PopItUpAnywhere2()
{
Thread t2 = new Thread( new ThreadStart(RunMessageNotification2 ));
t2.Start();
}

// Pops up a dialog form.
Private void RunMessageNotification2()
{
// will popup form that shaped like a message box.
Form3 dialog2 = new Form3();
dialog2.ShowDialog();
}


As you might think, after popping up using ShowDialog() method without the IWin32Window type owner, thus internal process will only get active window on runtime making it its parent.

Viewing inside CF Dotnet Form ShowDialog code, here it is.

public DialogResult ShowDialog()
{
if (this.m_fShown)
{
this.OnLoad(new EventArgs());
}
bool visible = base.Visible;
base.Visible = true;
if (!base.m_fDisposed)
{
bool fModal = this.m_fModal;
this.m_fModal = true;
PAL_ERROR ar = EVL.EnterModalDialog(base.m_hwn);
this.m_fModal = fModal;
if (!base.m_fDisposed)
{
base.Visible = visible;
}
MISC.HandleAr(ar);
}

return this.m_dlgres;
}

As you might notice, method EVL.EnterModalDialog is trying to identify the parent this dialog before showing it up.

EnterModalDialog has these possible values in return:

BadParam = -2147483642,
Empty = 0,
Failed = -2147483648,
Handled = 0x1001,
InvalidHandle = -2147483645,
NotEmpty = 0x1001,
NotHandled = 0,
NYI = -2147483640,
OOM = -2147483647,
Success = 0,
Unimplemented = -2147483640

If the method cannot find its parent, said method will return -2147483642, which is BadParam causing to throw ArgumentException. But luckily, you won’t get this exception while showing it up first.

THE BAD EXCEPTION HAPPEN.

Talking about method PopItUpAnywhere() which is first executed on the code then follows the PopItUpAnywhere2(), you expect things will go right. But if dialog in PopItUpAnywhere exits first, exiting dialog PopItUpAnywhere2 will likely to fail.

Why?

Its because Form3 cannot find its parent because Form2 has been terminated earlier.
Again by executing this.DialogResult = DialogResult.OK on dialog forms, you actually let that form find it’s parent form again.

Let’s take a look on the code inside Dotnet Compact Framework,

public DialogResult DialogResult
{
get
{
return this.m_dlgres;
}
set
{
this.m_dlgres = value;
if (this.m_fModal && !this.m_fClosing)
{
this._CloseModal();
}
}
}

private void _CloseModal()
{
if (this._FCanClose())
{
MISC.HandleAr(EVL.LeaveModalDialog(base.m_hwn));
}
}

Method EVL.LeaveModalDialog(base.m_hwn), finds again the parent the Form it is associated to. Since having variable m_hwn refer to non-existent object, method MISC.HandleAr will throw ArgumentException causing your app like somewhat deadlocked in a dialog that’s not well terminated.

POSSIBLE SOLUTIONS

1. If you are forced to exactly do the above execution of dialogs, it’s likely you have to implement Monitor class in threading to control the termination of simultaneous dialogs. Monitor class in CF 1.1 has only two methods – Enter() and Exit(), but enough to feed the control of those dialogs.

2. You can hinder dialog pop up one a time by your own logic.

My case I did all those two.

No comments: