WinForms
This section will provides some common questions and answers related to WinForms
applications.
FAQs
Q: How can I generate a list of open MDI child windows in my MDI
parent form? (Forums) Answer
A: The MdiWindowListItem property of MenuStrip
is used to specify the menu that will hold the list of MDI child windows.
As MDI child windows are opened and closed the menu is automatically updated.
The following code creates a Windows menu and marks it as the MDI child
window list. As children are opened and closed the list is updated.
protected override void OnLoad ( EventArgs e )
{
base.OnLoad(e);
this.IsMdiContainer
= true;
MenuStrip menu = new MenuStrip();
ToolStripMenuItem mnuWindows = new ToolStripMenuItem("Windows");
menu.Items.Add(mnuWindows);
menu.Dock = DockStyle.Top;
this.Controls.Add(menu);
this.MainMenuStrip = menu;
this.MainMenuStrip.MdiWindowListItem = mnuWindows;
//
Create an MDI child
Form frmChild1 = new Form();
frmChild1.Text = "Child 1";
frmChild1.MdiParent
= this;
frmChild1.Show();
}
A word of warning. There is a bug/feature that occurs when cancelling the
close request of an MDI child. When a form starts to close for any reason
the read-only CloseReason property is set to indicate why the form
is being closed. This property is never reset even if the form close request
is cancelled. The code used to generate the MDI window list will not display
an MDI child if CloseReason is set to anything other than its default
value. Therefore if you prevent an MDI child window from closing it will disappear
from the MDI window list even though it will still be open.
Q: I need to update my UI on a worker thread. How can I do
it?
Answer
A fundamental rule of Windows programming from the earliest days has been that you
CAN NOT access a UI component on any thread other than the thread that created it.
In .NET this still holds true. In v2, under the debugger, you will get an
MDA alerting you to cross-thread access to UI component members. There are
only a handful of members that can be accessed on any thread. The important
one is InvokeRequired. If this property is true then you
must use Invoke to marshal the member access from the current thread
to the UI thread. To do this you would define a delegate to represent the
method to invoke. Then use the Invoke method to call a method
matching the delegate on the UI thread. Finally in the method access the UI
member.
Here is some sample code that creates a new thread when the form loads. Within
the new thread it updates a label on the form each time through the loop.
private void Form1_Load ( object sender, EventArgs e )
{
Thread thread = new Thread(DoWork);
thread.Start();
}
private delegate void UpdateUIDelegate ( string message );
private
void UpdateUI ( string message )
{
if (label1.InvokeRequired)
label1.Invoke(new
UpdateUIDelegate(UpdateUI), message);
else
label1.Text = message;
}
private void
DoWork ( )
{
for (int nIdx = 0; nIdx < 10; ++nIdx)
{
UpdateUI(nIdx.ToString());
Thread.Sleep(500);
};
}
Notice in the above code that the same method is invoked twice. The first
time occurs on the worker thread and the second on the UI thread. This re-use
of an existing method is commonly done. Do not be tempted to add this logic
to all the members of your own custom control. Controls are, by definition,
designed only to work from one thread. If you create a control that is thread-aware
then it will add unnecessary overhead to the control and may confuse users.
Q: Updating the UI requires a lot of code. Surely there is
an easier way?
Answer
Thankfully in v2 there is. It is called BackgroundWorker.
This component executes an arbitrary method on a worker thread (complete with progress
and completion events). The events are raised on the UI thread. Therefore
you can use this component to perform some work on a worker thread while still updating
the UI. Here is the code from the previous question re-written to use
BackgroundWorker.
private void Form1_Load ( object sender, EventArgs e )
{
BackgroundWorker bcw =
new BackgroundWorker();
bcw.DoWork += DoWork;
bcw.WorkerReportsProgress = true;
bcw.ProgressChanged += OnProgressChanged;
bcw.RunWorkerAsync();
}
private void OnProgressChanged
( object sender, ProgressChangedEventArgs e )
{
label1.Text = e.ProgressPercentage.ToString();
}
private void DoWork ( object sender, DoWorkEventArgs e )
{
BackgroundWorker bcw
= sender as BackgroundWorker;
for (int nIdx = 0; nIdx < 10; ++nIdx)
{
bcw.ReportProgress(nIdx
* 10);
Thread.Sleep(500);
};
}