|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionApplications have special support in Windows Forms. For starters, you can manage and tailor your application's lifetime, and, when the work flow is disrupted by an unhandled exception, you can choose from several methods of response. Then, there are several application models that you can employ, including Single Document Interface (SDI) and Multiple Document Interface (MDI) applications, each of which can support either multiple-instance or single-instance mode, the former the VS05 default and the latter requiring special consideration. All applications, however, can discover and use a wide variety of information about the system and environment they execute in. This chapter focuses on these topics in depth, and starts by defining what an application actually is. ApplicationsAn application is anything with an .exe extension that can be started from the Windows shell. However, applications are also provided for directly in Windows Forms by the namespace System.Windows.Forms {
sealed class Application {
// Properties
public static bool AllowQuit { get; }
public static string CommonAppDataPath { get; }
public static RegistryKey CommonAppDataRegistry { get; }
public static string CompanyName { get; }
public static CultureInfo CurrentCulture { get; set; }
public static InputLanguage CurrentInputLanguage { get; set; }
public static string ExecutablePath { get; }
public static string LocalUserAppDataPath { get; }
public static bool MessageLoop { get; }
public static FormCollection OpenForms { get; } // New
public static string ProductName { get; }
public static string ProductVersion { get; }
public static bool RenderWithVisualStyles { get; } // New
public static string SafeTopLevelCaptionFormat { get; set; }
public static string StartupPath { get; }
public static string UserAppDataPath { get; }
public static RegistryKey UserAppDataRegistry { get; }
public static bool UseWaitCursor { get; set; } // New
public static VisualStyleState VisualStyleState { get; set; } // New
// Methods
public static void AddMessageFilter(IMessageFilter value);
public static void DoEvents();
public static void EnableVisualStyles();
public static void Exit();
public static void Exit(CancelEventArgs e); // New
public static void ExitThread();
public static bool FilterMessage(ref Message message); // New
public static ApartmentState OleRequired();
public static void OnThreadException(Exception t);
public static void RaiseIdle(EventArgs e); // New
public static void RegisterMessageLoop(MessageLoopCallback callback); // New
public static void RemoveMessageFilter(IMessageFilter value);
public static void Restart(); // New
public static void Run();
public static void Run(ApplicationContext context);
public static void Run(Form mainForm);
public static void SetCompatibleTextRenderingDefault(bool defaultValue); // New
public static bool SetSuspendState(
PowerState state, bool force, bool disableWakeEvent); // New
public static void SetUnhandledExceptionMode(
UnhandledExceptionMode mode); // New
public static void SetUnhandledExceptionMode(
UnhandledExceptionMode mode, bool threadScope); // New
public static void UnregisterMessageLoop();// New
// Events
public static event EventHandler ApplicationExit;
public static event EventHandler EnterThreadModal; // New
public static event EventHandler Idle;
public static event EventHandler LeaveThreadModal; // New
public static event ThreadExceptionEventHandler ThreadException;
public static event EventHandler ThreadExit;
}
}
Notice that all the members of the Application LifetimeA Windows Forms application starts when the The first is simply to call // Program.cs
static class Program {
[STAThread]
static void Main() {
...
// Create and show the main form modelessly
MainForm form = new MainForm();
form.Show();
// Run the application
Application.Run();
}
}
When you call // MainForm.cs
partial class MainForm : Form {
...
void MainForm_FormClosed(object sender, FormClosedEventArgs e) {
// Close the application when the main form goes away
// Only for use when Application.Run is called without
// any arguments
Application.Exit();
}
...
}
Typically, you call // Program.cs
static class Program {
[STAThread]
static void Main() {
...
// Create the main form
MainForm form = new MainForm();
// Run the application until the main form is closed
Application.Run(form);
}
}
In this case, there is no need for explicit code to exit the application. Instead, Application ContextInternally, the namespace System.Windows.Forms {
class ApplicationContext {
// Constructors
public ApplicationContext();
public ApplicationContext(Form mainForm);
// Properties
public Form MainForm { get; set; }
public object Tag { get; set; } // New
// Events
public event EventHandler ThreadExit;
// Methods
public void ExitThread();
protected virtual void ExitThreadCore();
protected virtual void OnMainFormClosed(object sender, EventArgs e);
}
}
In fact, the // Program.cs
static class Program {
[STAThread]
static void Main() {
...
// Run the application with a context
ApplicationContext ctx = new ApplicationContext(new MainForm());
Application.Run(ctx);
}
}
This is useful if you'd like to derive from the // TimedApplicationContext.cs
class TimedApplicationContext : ApplicationContext {
Timer timer = new Timer();
public TimedApplicationContext(Form mainForm) : base(mainForm) {
timer.Tick += timer_Tick;
timer.Interval = 5000; // 5 seconds
timer.Enabled = true;
}
void timer_Tick(object sender, EventArgs e) {
timer.Enabled = false;
timer.Dispose();
DialogResult res =
MessageBox.Show(
"OK to charge your credit card?",
"Time's Up!",
MessageBoxButtons.YesNo);
if( res == DialogResult.No ) {
// See ya...
this.MainForm.Close();
}
}
}
// Program.cs
static class Program {
[STAThread]
static void Main() {
...
// Run the application with a custom context
TimedApplicationContext ctx =
new TimedApplicationContext(new MainForm());
Application.Run(ctx);
}
}
This custom context class waits for five seconds after an application has started and then asks to charge the user's credit card. If the answer is no, the main form of the application is closed (available from the You might also encounter situations when you'd like to stop the application from exiting when the main form goes away, such as an application that's serving .NET remoting clients and needs to stick around even if the user has closed the main form.1 In these situations, you override the // RemotingServerApplicationContext.cs
class RemotingServerApplicationContext : ApplicationContext {
public RemotingServerApplicationContext(Form mainForm) :
base(mainForm) {}
protected override void OnMainFormClosed(object sender, EventArgs e) {
// Don't let base class exit application
if( this.IsServicingRemotingClient() ) return;
// Let base class exit application
base.OnMainFormClosed(sender, e);
}
protected bool IsServicingRemotingClient() {...}
}
When all the .NET remoting clients have exited, you must make sure that Application EventsDuring the lifetime of an application, several key application events— // Program.cs
static class Program {
[STAThread]
static void Main() {
...
Application.Idle += App_Idle;
Application.ThreadExit += App_ThreadExit;
Application.ApplicationExit += App_ApplicationExit;
// Run the application
Application.Run(new MainForm());
}
static void App_Idle(object sender, EventArgs e) {...}
static void App_ThreadExit(object sender, EventArgs e) {...}
static void App_ApplicationExit(object sender, EventArgs e) {...}
}
The When a UI thread is about to exit, it receives a notification via the UI Thread ExceptionsOne other application-level event that is fired as necessary by the The typical .NET unhandled-exception behavior on a user's machine yields a dialog, as shown in Figure 14.1.2
Figure 14.1 Default .NET Unhandled-Exception Dialog This kind of exception handling tends to make users unhappy. This dialog isn't necessarily explicit about what actually happened, even if you view the data in the error report. And worse, there is no way to continue the application to attempt to save the data being worked on at the moment. On the other hand, a Windows Forms application that experiences an unhandled exception during the processing of an event shows a more specialized default dialog like the one in Figure 14.2.
Figure 14.2 Default Windows Forms Unhandled-Exception Dialog This dialog is the However, if an unhandled exception is caught, the application could be in an inconsistent state, so it's best to encourage your users to save their files and restart the application. To implement this, you replace the Windows Forms unhandled-exception dialog with an application-specific dialog by handling the application's thread exception event: // Program.cs
static class Program {
[STAThread]
static void Main() {
// Handle unhandled thread exceptions
Application.ThreadException += App_ThreadException;
...
// Run the application
Application.Run(new MainForm());
}
static void App_ThreadException(
object sender, ThreadExceptionEventArgs e) {
// Does user want to save or quit?
string msg =
"A problem has occurred in this application:\r\n\r\n" +
"\t" + e.Exception.Message + "\r\n\r\n" +
"Would you like to continue the application so that\r\n" +
"you can save your work?";
DialogResult res = MessageBox.Show(
msg,
"Unexpected Error",
MessageBoxButtons.YesNo);
...
}
}
Notice that the thread exception handler takes a
Figure 14.3 Custom Unhandled-Exception Dialog If the user wants to return to the application to save work, all you need to do is return from the // Program.cs
static class Program {
...
static void App_ThreadException(
object sender, ThreadExceptionEventArgs e) {
...
// Save or quit
DialogResult res = MessageBox.Show(...);
// If save: returning to continue the application and allow saving
if( res == DialogResult.Yes ) return;
// If quit: shut 'er down, Clancy, she's a'pumpin' mud!
Application.Exit();
}
Handling exceptions in this way gives users a way to make decisions about how an application will shut down, if at all, in the event of an exception. However, if it doesn't make sense for users to be involved in unhandled exceptions, you can make sure that the Application.SetUnhandledExceptionMode(
UnhandledExceptionMode.ThrowException);
Although it's not obvious from the enumeration value's name, this code actually prevents namespace System.Windows.Forms {
enum UnhandledExceptionMode {
Automatic = 0, // default
ThrowException = 1, // Never fire Application.ThreadException
CatchException = 2, // Always fire Application.ThreadException
}
}
In general, the behavior exhibited by Going the other way, you can also use command line arguments to let users make decisions about how they want their application to start up. Passing Command Line ArgumentsCommand line arguments allow users to determine an application's initial state and operational behavior when launched.3 Before command line arguments can be processed to express a user's wishes, they need to be accessed. To do this, you change your application's entry point method, // Program.cs
static class Program {
[STAThread]
static void Main(string[] args) {
...
}
}
.NET constructs the string array by parsing the command line string, which means extracting substrings, delimited by spaces, and placing each substring into an element of the array. Command line syntax, which dictates which command line arguments your application can process and the format they should be entered in, is left up to you. Here is one simple approach: // Program.cs
static class Program {
[STAThread]
static void Main(string[] args) {
...
bool flag = false;
string name = "";
int number = 0;
// *Very* simple command line parsing
for( int i = 0; i != args.Length; ++i ) {
switch( args[i] ) {
case "/flag": flag = true; break;
case "/name": name = args[++i]; break;
case "/number": number = int.Parse(args[++i]); break;
default: MessageBox.Show("Invalid args!"); return;
}
}
...
}
}
If your static // Program.cs
static class Program {
[STAThread]
static void Main() {
...
string[] args = Environment.GetCommandLineArgs();
// *Very* simple command line parsing
// Note: Starting at item [1] because args item [0] is exe path
for( int i = 1; i != args.Length; ++i ) {
...
}
...
}
}
You can see that Processing command line arguments is relatively straightforward, although special types of applications, known as single-instance applications, need to process command line arguments in special ways. Single-Instance ApplicationsBy default, each EXE is an application that has an independent lifetime, even if multiple instances of the same application are running at the same time. However, it's common to want to limit an EXE to a single instance, whether it's an SDI application with a single top-level window, an MDI application, or an SDI application with multiple top-level windows. All these kinds of applications require that another instance detect the initial instance and then cut its own lifetime short. Single-Instance Detection and ManagementYou could build a custom single-instance application using custom code that incorporates threading and .NET remoting. However, the VB.NET runtime library, Microsoft.VisualBasic.dll, contains a class that provides such an implementation for you: If you are using C#, you add a reference to this assembly by right-clicking the project and selecting Add Reference from the context menu. From the .NET tab of the subsequently loaded Add Reference dialog, select Microsoft.VisualBasic.dll. When this DLL is referenced, you derive from // SingleInstanceApplication.cs
using Microsoft.VisualBasic.ApplicationServices;
...
class SingleInstanceApplication : WindowsFormsApplicationBase {...}
Next, you configure // SingleInstanceApplication.cs
class SingleInstanceApplication : WindowsFormsApplicationBase {
// Must call base constructor to ensure correct initial
// WindowsFormsApplicationBase configuration
public SingleInstanceApplication() {
// This ensures the underlying single-SDI framework is employed,
// and OnStartupNextInstance is fired
this.IsSingleInstance = true;
}
}
// Program.cs
static class Program {
[STAThread]
static void Main(string[] args) {
Application.EnableVisualStyles();
SingleInstanceApplication application =
new SingleInstanceApplication();
application.Run(args);
}
}
To specify which form is the main application form, you override // SingleInstanceApplication.cs
class SingleInstanceApplication : WindowsFormsApplicationBase {
...
protected override void OnCreateMainForm() {
this.MainForm = new MainForm();
}
}
As a final flourish, you can expose your custom // SingleInstanceApplication.cs
class SingleInstanceApplication : WindowsFormsApplicationBase {
static SingleInstanceApplication application;
internal static SingleInstanceApplication Application {
get {
if( application == null ) {
application = new SingleInstanceApplication();
}
return application;
}
}
...
}
// Program.cs
static class Program {
...
[STAThread]
static void Main(string[] args) {
Application.EnableVisualStyles();
SingleInstanceApplication.Application.Run(args);
}
}
The effect of Multiple-SDI ApplicationsA multiple-SDI application has multiple windows for content, although each window is a top-level window. Internet Explorer and Office 2003 are popular examples of multiple-SDI applications.6 Figure 14.4 shows an example of a multiple-SDI application.
Figure 14.4 A Sample Multiple-SDI Application A multiple-SDI application typically has the following features:
When a document is created or opened, it is loaded into a new window each time, whether the file was requested via the menu system or the command line. The first time the application is called, the first new instance of the top-level form is created and set as the main application form instance; if a file was requested, it is also opened by the form. Subsequent requests to the application are routed to the custom
Figure 14.5 Work Flow of a Multiple-SDI Application with Support for Command Line Argument Passing Multiple SDI requires single-instance support, which we acquire by deriving from // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
static MultiSDIApplication application;
internal static MultiSDIApplication Application {
get {
if( application == null ) {
application = new MultiSDIApplication();
}
return application;
}
}
public MultiSDIApplication() {
// This ensures the underlying single-SDI framework is employed,
// and OnStartupNextInstance is fired
this.IsSingleInstance = true;
// Needed for multiple SDI because no form is the main form
this.ShutdownStyle = ShutdownMode.AfterAllFormsClose;
}
}
By default, the Next, // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
public MultiSDIApplication() {...}
// Create first top-level form
protected override void OnCreateMainForm() {
this.MainForm = this.CreateTopLevelWindow(this.CommandLineArgs);
}
TopLevelForm CreateTopLevelWindow(
ReadOnlyCollection<string> args) {
// Get file name, if provided
string fileName = (args.Count > 0 ? args[0] : null);
// Create a new top-level form
return TopLevelForm.CreateTopLevelWindow(fileName);
}
}
In this code, if a file argument was passed, a request is made to the main form to open it. Because all forms in a multiple-instance SDI application are top-level, however, no form is actually the main form. However, we must specify one if we override To cope with subsequent requests to launch the application, we again override // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
public MultiSDIApplication() {...}
// Create first top-level form
protected override void OnCreateMainForm() {...}
// Create subsequent top-level form
protected override void OnStartupNextInstance(
StartupNextInstanceEventArgs e) {
this.CreateTopLevelWindow(e.CommandLine);
}
TopLevelForm CreateTopLevelWindow(
ReadOnlyCollection<string> args) {...}
}
Here, the helper Multiple-instance SDI applications also allow files to be opened from existing top-level forms via the File | Open menu, something we implement using the same static // TopLevelForm.cs
partial class TopLevelForm : Form {
...
string fileName;
...
public static TopLevelForm CreateTopLevelWindow(string fileName) {
// Detect whether file is already open
if( !string.IsNullOrEmpty(fileName) ) {
foreach( TopLevelForm openForm in Application.OpenForms ) {
if( string.Compare(openForm.FileName, fileName, true) == 0 ) {
// Bring form to top
openForm.Activate();
return openForm;
}
}
}
// Create new top-level form and open file
TopLevelForm form = new TopLevelForm();
form.OpenFile(fileName);
form.Show();
// Bring form to top
openForm.Activate();
return form;
}
void openToolStripMenuItem_Click(object sender, EventArgs e) {
// Open new window
if( this.openFileDialog.ShowDialog() == DialogResult.OK ) {
TopLevelForm.CreateTopLevelWindow(this.openFileDialog.FileName);
}
}
...
void OpenFile(string fileName) {
this.fileName = fileName;
using( StreamReader reader = new StreamReader(fileName) ) {
textBox.Text = reader.ReadToEnd();
}
this.Text = this.Text + " (" + this.fileName + ")";
}
string FileName {
get { return this.fileName; }
}
}
Multiple-instance SDI applications also typically allow the creation of new files from the command line or from the File | New Window menu of a currently open top-level form. We tweak the // TopLevelForm.cs
partial class TopLevelForm : Form {
...
static int formCount = 0;
public TopLevelForm() {
InitializeComponent();
// Set form count
++formCount;
this.Text += ": " + formCount.ToString();
}
...
public static TopLevelForm CreateTopLevelWindow(string fileName) {
...
// Create new top-level form and open file
TopLevelForm form = new TopLevelForm();
form.OpenFile(fileName);
form.Show();
...
}
void newWindowToolStripMenuItem_Click(
object sender, EventArgs e) {
// Open new window
TopLevelForm.CreateTopLevelWindow(null);
}
...
void OpenFile(string fileName) {
this.fileName = fileName;
if( !string.IsNullOrEmpty(fileName) ) {
using( StreamReader reader = new StreamReader(fileName) ) {
textBox.Text = reader.ReadToEnd();
}
}
else this.fileName = "Untitled" + formCount.ToString();
this.Text = this.Text + " (" + this.fileName + ")";
}
...
}
Because a new file doesn't have a name, the top-level form gives it one; the standard naming convention for a new file is the concatenation of some default text with a version number. In this example, we use a combination of "Untitled" and an incremental count of the number of opened top-level forms, for uniqueness. As mentioned before, a multiple-SDI application should implement a menu that allows users to navigate between open top-level forms as this is easier when files have unique names. // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
public void AddTopLevelForm(Form form) {
// Add form to collection of forms and
// watch for it to activate and close
form.Activated += Form_Activated;
form.FormClosed += Form_FormClosed;
// Set initial top-level form to activate
if( this.OpenForms.Count == 1 ) this.MainForm = form;
}
void Form_Activated(object sender, EventArgs e) {
// Set the currently active form
this.MainForm = (Form)sender;
}
void Form_ FormClosed(object sender, FormClosedEventArgs e) {
// Set a new "main" if necessary
if( ((Form)sender == this.MainForm) &&
(this.OpenForms.Count > 0) ) {
this.MainForm = (Form)this.OpenForms[0];
}
form.Activated -= Form_Activated;
form.FormClosed -= Form_FormClosed;
}
}
The To keep the context up-to-date with the current list of top-level forms, the custom context watches for the // TopLevelForm.cs
partial class TopLevelForm : Form {
...
public TopLevelForm() {
...
// Add new top-level form to the application context
MultiSDIApplication.Application.AddTopLevelForm(this);
...
}
...
}
The only remaining task is to designate and populate the Window menu with one menu item for each top-level form. The forms themselves can do this by handling the // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
public void AddWindowMenu(ToolStripMenuItem windowMenu) {
// Handle tool strip menu item's drop-down opening event
windowMenu.DropDownOpening += windowMenu_DropDownOpening;
}
}
Each top-level form with a Window menu can add it to the context, along with itself, when it's created: // TopLevelForm.cs
partial class TopLevelForm : Form {
...
public TopLevelForm() {
...
// Add Window ToolStripMenuItem to the application context
MultiSDIApplication.Application.AddWindowMenu(
this.windowToolStripMenuItem);
...
}
...
}
Now, when the Window menu is shown on any top-level window, the // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
void windowMenu_DropDownOpening(object sender, EventArgs e) {
ToolStripMenuItem menu = (ToolStripMenuItem)sender;
// Clear current menu
if( menu.DropDownItems.Count > 0 ) {
menu.DropDown.Dispose();
}
menu.DropDown = new ToolStripDropDown();
// Populate menu with one item for each open top-level form
foreach( Form form in this.OpenForms ) {
ToolStripMenuItem item = new ToolStripMenuItem();
item.Text = form.Text;
item.Tag = form;
menu.DropDownItems.Add(item);
item.Click += WindowMenuItem_Click;
// Check menu item that represents currently active window
if( form == this.MainForm ) item.Checked = true;
}
}
}
As each menu item is added to the Window menu, a handler is added to the // MultiSDIApplication.cs
class MultiSDIApplication : WindowsFormsApplicationBase {
...
void WindowMenuItem_Click(object sender, EventArgs e) {
// Activate top-level form based on selection
((Form)((ToolStripMenuItem)sender).Tag).Activate();
}
...
}
That's it. The extensible lifetime management of Windows Forms applications via a custom application context, along with a helper to find and activate application instances already running, provides all the help we need to build a multiple-SDI application in only a few lines of code. The result is shown in Figure 14.6.
Figure 14.6 Multiple-Instance SDI Application in Action Multiple-SDI applications share much in common with MDI applications, although each document in an MDI application is loaded into a child window rather than a new main window. The key similarities include the requirement for MDI applications to be managed from a single executable and the ability to handle command line parameters. Single-MDI ApplicationsConsider an MDI application like Microsoft Excel; files opened from the file system (by double-clicking) are all opened as separate child windows within the parent Excel window.7 For the first instance of an MDI application to open a new child window to display the file that was passed to the second instance of the application, the second instance must be able to communicate with the initial instance. A single-MDI application exhibits the characteristics we described in Chapter 2: Forms, as well as the following features:
The work flow for a single-MDI application ensures that a new MDI child form is opened each time the application is called, whether or not a file was requested for opening. The first time the application is called, the MDI parent is created and set as the main application form instance; if a file was requested, it is also opened into a new MDI child form. Subsequent requests to the application are routed through the MDI parent form to create a new MDI child form and build up the appropriate menu structures to support navigation between top-level instances, as well as opening and closing existing top-level instances. Figure 14.7 illustrates the work flow.
Figure 14.7 Work Flow of a Single-MDI Application with Support for Passing Command Line Arguments With Handling the first scenario requires a main application form that's an MDI parent and can open a new or existing file into an MDI child form: // MDIParentForm.cs
partial class MDIParentForm : Form {
...
// This is necessary to bring the MDI parent window to the front,
// because Activate and BringToFront don't seem to have any effect.
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
public void CreateMDIChildWindow(string fileName) {
SetForegroundWindow(this.Handle);
// Detect whether file is already open
if( !string.IsNullOrEmpty(fileName) ) {
foreach( MDIChildForm openForm in this.MdiChildren ) {
if( string.Compare(openForm.FileName, fileName, true) == 0 ) {
openForm.Activate();
return;
}
}
}
// If file not open, open it
MDIChildForm form = new MDIChildForm();
form.OpenFile(fileName);
form.MdiParent = this;
form.Show();
}
void newToolStripMenuItem_Click(object sender, EventArgs e) {
this.CreateMDIChildWindow(null);
}
void openToolStripMenuItem_Click(object sender, EventArgs e) {
if( this.openFileDialog.ShowDialog() == DialogResult.OK ) {
this.CreateMDIChildWindow(this.openFileDialog.FileName);
}
}
...
}
This code allows users to open a file using a menu strip item, and it lays the foundation for opening a file from the command line, including preventing the reopening of an already open file. We continue using // SingleMDIApplication.cs
class SingleMDIApplication : WindowsFormsApplicationBase {
static SingleMDIApplication application;
internal static SingleMDIApplication Application {
get {
if( application == null ) {
application = new SingleMDIApplication();
}
return application;
}
}
public SingleMDIApplication() {
// This ensures the underlying single-SDI framework is employed,
// and OnStartupNextInstance is fired
this.IsSingleInstance = true;
}
// Load MDI parent form and first MDI child form
protected override void OnCreateMainForm() {
this.MainForm = new MDIParentForm();
this.CreateMDIChildWindow(this.CommandLineArgs);
}
void CreateMDIChildWindow(ReadOnlyCollection<string> args) {
// Get file name, if provided
string fileName = (args.Count > 0 ? args[0] : null);
// Ask MDI parent to create a new MDI child
// and open file
((MDIParentForm)this.MainForm).CreateMDIChildWindow(fileName);
}
}
During construction, we specify that this application is a single-instance application. Unlike with multiple-SDI applications, however, we don't need to set the
In the second scenario, the desired processing is for the command line arguments to be passed from the second instance to the first, to which the first instance responds by processing the command line arguments and, if required, creating a new MDI child form. // SingleMDIApplication.cs
class SingleMDIApplication : WindowsFormsApplicationBase {
...
// Must call base constructor to ensure correct initial
// WindowsFormsApplicationBase configuration
public SingleMDIApplication() {...}
// Load MDI parent form and first MDI child form
protected override void OnCreateMainForm() {...}
// Load subsequent MDI child form
protected override void OnStartupNextInstance(
StartupNextInstanceEventArgs e) {
this.CreateMDIChildWindow (e.CommandLine);
}
void CreateMDIChildWindow(ReadOnlyCollection<string> args) {...}
}
As you can see, centralizing That's the complete solution, so let's look at how it operates. Suppose we start the application for the first time by executing the following statement from the command line: C:\SingleInstanceSample.exe C:\file1.txt
The result is to load the application, configure the single-instance command line argument (passing support from our derivation of
Figure 14.8 Result of Creating a First Instance of a Single-Instance Application Now, consider the next statement being called while the first instance is still executing: C:\SingleInstanceSample.exe C:\file2.txt
This time, a second instance of the application is created, but—thanks to
Figure 14.9 Result of Creating a Second Instance of a Single-Instance Application Although it would be difficult to code single-instance applications such as single MDI and multiple SDI by hand, the presence of support in the Visual Basic runtime assembly makes life a lot easier. This is one of the strengths of Windows Forms; unlike forms packages of old, Windows Forms is only one part of a much larger, integrated whole. When its windowing classes don't meet your needs, you still have all the rest of the .NET Framework Class Library to fall back on. Where Are We?The seemingly simple application architecture in Windows Forms and .NET provides some useful capabilities, including tailored lifetime support and support for building SDI and MDI applications, whether multiple or single-instance.
© Copyright Pearson Education. All rights reserved.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||