Chapter 1. The Application and the Window


An application written for the Microsoft Windows Presentation Foundation (WPF) generally begins its seconds or hours on the Windows desktop by creating objects of type Application and Window. A simple WPF program looks like this:

SayHello.cs

//----------------------------------------- // SayHello.cs (c) 2006 by Charles Petzold //----------------------------------------- using System; using System.Windows; namespace Petzold.SayHello {     class SayHello     {         [STAThread]         public static void Main()         {             Window win = new Window();             win.Title = "Say Hello";             win.Show();             Application app = new Application();             app.Run();         }     } } 



You're familiar with the System namespace, I assume. (If not, you should probably read my online book .NET Book Zero available on my Web site at www.charlespetzold.com.) The SayHello program also includes a using directive for System.Windows, which is the namespace that includes all the basic WPF classes, structures, interfaces, delegates, and enumerations, including the classes Application and Window. Other WPF namespaces begin with the preface System.Windows, such as System.Windows.Controls, System.Windows.Input, and System.Windows.Media. A notable exception is the namespace System.Windows.Forms, which is the primary Windows Forms namespace. All namespaces that begin with System.Windows.Forms are also Windows Forms namespaces, except for System.Windows.Forms.Integration, which includes classes that can help you integrate Windows Forms and WPF code.

The sample programs shown in this book have a consistent naming scheme. Each program is associated with a Microsoft Visual Studio project. All code in the project is enclosed in a namespace definition. The namespace always consists of my last name followed by the name of the project. For this first example, the project name is SayHello and the namespace is then Petzold.SayHello. Each class in the project is given a separate source code file, and the name of the file generally matches the name of the class. If the project consists of only one class, which is the case for this first example, that class is usually given the same name as the project.

In any WPF program, the [STAThread] attribute must precede Main or the C# compiler will complain. This attribute directs the threading model of the initial application thread to be a single-threaded apartment, which is required for interoperability with the Component Object Model (COM). "Single-threaded apartment" is an old COM-era, pre-.NET programming term, but for our purposes you could imagine it to mean our application won't be using multiple threads originating from the runtime environment.

In the SayHello program, Main begins by creating an object of type Window, which is the class you use for creating a standard application window. The Title property indicates the text that will appear in the window's caption bar, and the Show method displays the window on the screen.

The final important step is to call the Run method of a new Application object. In Windows programming parlance, this method creates the message loop that allows the application to receive user input from the keyboard and mouse. If the program is running on a Tablet PC, the application also receives input from the stylus.

You'll probably use Visual Studio 2005 to create, compile, and run applications written for the Windows Presentation Foundation. If so, you can re-create the SayHello program by following these steps:

1.

Select New Project from the File menu.

2.

In the New Project dialog, select Visual C#, Windows Presentation Foundation, and Empty Project. Find a good home for the project and give it the name SayHello. Uncheck the Create Directory For Solution option. Click OK.

3.

In the Solution Explorer on the right, the References section must include PresentationCore, PresentationFramework, System, and WindowsBase. If the References section doesn't include these DLLs, add them. Right-click References and select Add Reference. (Or, select Add Reference from the Project menu.) In the Add Reference dialog, click the .NET tab and select the required DLLs. Click OK.

4.

In the Solution Explorer on the right, right-click the SayHello project name and then select New Item from the Add menu. (Or, select Add New Item from the Project menu.) In the Add New Item dialog, select Code File. Type in the file name SayHello.cs. Click OK.

5.

Type the program shown earlier in the chapter into the SayHello.cs file.

6.

Select Start Without Debugging from the Debug menu (or press Ctrl+F5) to compile and run the program.

For most of the example programs shown in Part I of this book, the process of creating projects will follow basically the same steps, except that some projects (including one in this chapter) have multiple source code files.

When you close the window created by SayHello, you'll discover that a console window has also been running. The presence of this console window is governed entirely by a compiler flag that you can control in the project's properties. Right-click the project name at the right and select Properties from the menu. (Or, select Properties from the Project menu.) Now you can explore or alter aspects of the project. Note that the Output Type is set to Console Application. Obviously this setting hasn't affected the program's ability to go far beyond the console in creating a window. Change the Output Type to Windows Application, and the program will run as before but without the console window. I personally find the console window very useful in program development. I use it for displaying textual information while the program is running, and for debugging. If a program is so buggy that it doesn't even display a window, or if it displays a window but enters an infinite loop, it's easy to terminate the application by typing Ctrl+C in the console window.

The Window and Application classes used in SayHello both derive from DispatcherObject, but Window has a much longer pedigree, as shown in the class hierarchy:

Object

       DispatcherObject (abstract)

                Application

                DependencyObject

                           Visual (abstract)

                                     UIElement

                                                FrameworkElement

                                                          Control

                                                                    ContentControl

                                                                                Window

Of course, it's not necessary to be intimately familiar with the class hierarchy just yet, but as you make your way through the Windows Presentation Foundation, you'll encounter these classes again and again.

A program can create only one Application object, which exists as a constant anchor for the rest of the program. The Application object is invisible; the Window object is notit appears on the screen in all its glory as a standard Windows window. It has a caption bar displaying the text indicated in the Title property. The caption bar has a system menu icon on the left, and minimize, maximize, and close icons on the right. The window has a sizing border, and it has a client area occupying the vast interior of the window.

Within limits, you can mix up the order of the statements in the Main method of the SayHello program and the program will still work. For example, you can set the Title property after calling Show. In theory, that change causes the window to be displayed initially without a title in its caption bar, but the change will probably happen too quickly to see.

You can create the Application object before creating the Window object, but the call to Run must be last. The Run method does not return until the window is closed. At that point, the Main method ends and Windows cleans up after your program. If you remove the call to Run, the Window object is still created and displayed, but it's immediately destroyed when Main ends.

Instead of calling the Show method of the Window object, you can pass the Window object as an argument to the Run method:

app.Run(win); 


In this case, the Run method takes the responsibility of calling Show for the Window object.

A program doesn't really get started until it calls the Run method. Only then can the Window object respond to user input. When the user closes the window and the Run method returns, the program is ready to terminate. Thus, the program spends almost all of its existence deep within the Run call. But how can the program do anything if it is spending all its time in Run?

Following initialization, virtually everything a program does is in response to an event. These events usually indicate keyboard, mouse, or stylus input from the user. The UIElement class (which refers to the user interface, of course) defines a number of keyboard-, mouse-, and stylus-related events; the Window class inherits all those events. One of those events is named MouseDown. A window's MouseDown event occurs whenever the user clicks the client area of the window with the mouse.

Here's a program that mixes up the order of the statements in Main a bit, and also installs an event handler for the MouseDown event of the window:

HandleAnEvent.cs

[View full width]

//---------------------------------------------- // HandleAnEvent.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.HandleAnEvent { class HandleAnEvent { [STAThread] public static void Main() { Application app = new Application(); Window win = new Window(); win.Title = "Handle An Event"; win.MouseDown += WindowOnMouseDown; app.Run(win); } static void WindowOnMouseDown(object sender, MouseButtonEventArgs args) { Window win = sender as Window; string strMessage = string.Format("Window clicked with {0} button at point ({1})", args.ChangedButton, args.GetPosition(win)); MessageBox.Show(strMessage, win.Title); } } }



I like to give my event handlers names that begin with the class or object responsible for the event, followed by the word On, followed by the event name itself (hence WindowOnMouseDown). But you can name your event handlers whatever you want.

The MouseDown event is documented as requiring an event handler defined in accordance with the MouseButtonEventHandler delegate, which has a first argument of type object, and a second argument of type MouseButtonEventArgs. That class is defined in the System.Windows.Input namespace, so the program includes another using directive. This program must define the event handler as static because the handler is referred to from the static method Main.

Most subsequent programs in this book will include a using directive for System.Windows.Input even if they don't need it.

The MouseDown event is fired whenever the user clicks the client area of the window with any mouse button. The first argument to the event handler is the object firing the event, which is the Window object. The event handler can safely cast that object to an object of type Window.

The event handler in the HandleTheEvent needs the Window object for two purposes: First, it uses the Window object as an argument to the GetPosition method defined by the MouseButtonEventArgs class. This method returns an object of type Point (a structure defined in System.Windows) with the mouse coordinates relative to the top-left corner of the GetPosition argument. Second, the event handler accesses the Title property of the Window object and uses that property as the title of the message box.

The MessageBox class is defined in the System.Windows namespace as well. It contains 12 overloads of the static Show method that allow lots of options for displaying buttons and images. By default, only an OK button appears.

The message box in the HandleAnEvent program displays the position of the mouse cursor relative to the upper-left corner of the client area. You may have naturally assumed that those coordinates were pixels. They are not. They are device-independent units of 1/96 inch. I'll have more to say about this odd coordinate system later in this chapter.

The event handler in HandleAnEvent casts the sender argument to an object of type Window, but there are other ways for the event handler to obtain this Window object. The Window object created in Main could have been saved as a static field and the event handler could have used that. Or, the event handler could have taken advantage of some properties of the Application class. Application has a static property named Current that returns the Application object created by the program. (As I mentioned, a program can create only one Application object.) Application also includes an instance property named MainWindow that returns a Window object. So the event handler could have set a local Window variable like this:

Window win = Application.Current.MainWindow; 


If the only purpose for obtaining the Window object in this event handler was to access the Title text for the message box, the MessageBox.Show method could have included an argument of Application.Current.MainWindow.Title.

The Application class defines several events that may be useful. As is customary in .NET, most of these events are associated with protected methods that generally have the responsibility for firing the event. The Startup event defined by Application is fired from the protected OnStartup method and occurs soon after the program calls the Run method of the Application object. A call to the OnExit method (and the firing of the corresponding Exit event) occurs when the Run method is about to return. You can use these two occasions to perform application-wide initialization or cleanup.

The OnSessionEnding method and SessionEnding event indicate that the user has chosen to log off the Windows session or shut down the computer. This event is delivered with an argument of type SessionEndingCancelEventArgs, a class that derives from CancelEventArgs and which includes a property named Cancel. If your application wants to prevent Windows from shutting down, set that property to true. This event is only received if you compile your program as a Windows Application rather than as a Console Application.

If your program needs to handle some events of the Application class, it can install event handlers for those events, but it is most convenient to define a class that inherits from Application, such as the class in this next example, InheritTheApp. A class that inherits from Application can simply override the underlying methods responsible for firing the events.

InheritTheApp.cs

[View full width]

//---------------------------------------------- // InheritTheApp.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.InheritTheApp { class InheritTheApp : Application { [STAThread] public static void Main() { InheritTheApp app = new InheritTheApp(); app.Run(); } protected override void OnStartup (StartupEventArgs args) { base.OnStartup(args); Window win = new Window(); win.Title = "Inherit the App"; win.Show(); } protected override void OnSessionEnding (SessionEndingCancelEventArgs args) { base.OnSessionEnding(args); MessageBoxResult result = MessageBox.Show("Do you want to save your data?", MainWindow.Title, MessageBoxButton.YesNoCancel, MessageBoxImage .Question, MessageBoxResult.Yes); args.Cancel = (result == MessageBoxResult.Cancel); } } }



The InheritTheApp class derives from Application and overrides the OnStartup and OnSessionEnding methods defined by the Application class. In this program, the Main method doesn't create an object of type Application, but rather an object of type InheritTheApp. Yet Main is a member of this very class. It may seem a little odd that Main creates an instance of a class to which it belongs, but it's perfectly legitimate because Main is defined as static. The Main method exists even if no InheritTheApp object has yet been created.

InheritTheApp overrides both the OnStartup method (which is called soon after the program calls Run) and OnSessionEnding. It is during the OnStartup method that the program takes the opportunity to create a Window object and show it. The InheritTheApp class could alternatively have performed this task in its constructor.

In the OnSessionEnding override, the program displays a message box with Yes, No, and Cancel buttons. Notice that the title of the message box is set to MainWindow.Title. Because this is an instance method of a class that derives from Application, the simple reference to MainWindow obtains the value of that property for this instance. You could preface MainWindow with the this keyword to make it more explicit that MainWindow is a property of Application.

Of course, the program has no data to save, so it ignores the Yes and No responses and allows the application to shut down and Windows to continue terminating the user session. If the response is Cancel, it sets the Cancel flag of the SessionEndingCancelEventArgs object to true. This, in turn, prevents Windows from shutting down or logging off at this time. You can tell which action is specified, shutdown or logoff, by accessing the ReasonSessionEnding property of SessionEndingCancelEventArgs. ReasonSessionEnding provides you with an enumerated value, which is either ReasonSessionEnding.Logoff or ReasonSessionEnding.Shutdown.

Both OnStartup and OnSessionEnding in the program begin by calling the method in the base classes. These calls are not strictly needed in these cases, but they can't hurt. It's generally wise, however, to call the base class method unless you have specific reasons for not doing so.

As you know, you can run programs from the Command Prompt window, and in doing so you can give command-line arguments to the program. Windows programs are no different. To process command-line arguments, you must define the Main method a little differently:

public static void Main(string[] args) 


Any command-line arguments are passed to Main as an array of strings. This array of strings can also be accessed in the OnStartup method as the Args property of the StartupEventArgs argument.

The fact that Application has a property named MainWindow suggests that a program can have multiple windows. This is certainly true. Typically, many of these extra windows take the form of transitory dialog boxes, but dialog boxes are basically additional Window objects with just a few differences in the way they're displayed and the way they interact with the user.

Here's a program that throws a party by inviting several more windows to the desktop:

ThrowWindowParty.cs

[View full width]

//------------------------------------------------- // ThrowWindowParty.cs (c) 2006 by Charles Petzold //------------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.ThrowWindowParty { class ThrowWindowParty: Application { [STAThread] public static void Main() { ThrowWindowParty app = new ThrowWindowParty(); app.Run(); } protected override void OnStartup (StartupEventArgs args) { Window winMain = new Window(); winMain.Title = "Main Window"; winMain.MouseDown += WindowOnMouseDown; winMain.Show(); for (int i = 0; i < 2; i++) { Window win = new Window(); win.Title = "Extra Window No. " + (i + 1); win.Show(); } } void WindowOnMouseDown(object sender, MouseButtonEventArgs args) { Window win = new Window(); win.Title = "Modal Dialog Box"; win.ShowDialog(); } } }



Like the InheritTheApp class, the ThrowWindowParty class inherits from Application and creates a Window object in the override of its OnStartup method. It then creates two more Window objects and shows them as well. (I'll discuss what goes on in the MouseDown event handler shortly.)

The first thing you'll notice is that the three windows created in the OnStartup override are equal citizens in this application. You can click any window and that window will come to the foreground. You can close them in any order, and only when the last window is closed will the program terminate. If one of the windows did not have the caption "Main Window," you'd be hard-pressed to identify it.

However, if your program were to examine the MainWindow property of the Application object, it would find that the first window for which Show was called is considered the main window of the application (at least initially).

The Application class also includes a property named Windows (notice the plural) that is of type WindowCollection. The WindowCollection class is a typical .NET collection class that implements the ICollection and IEnumerable interfaces and (as the name implies) stores multiple Window objects. The class includes a property named Count and an indexer that lets you obtain all the individual Window objects that your program has called Show for and that still exist. At the end of the OnStartup override, the Windows.Count property would return 3, and Windows[0] would be the window with the caption "Main Window."

One of the oddities of this program is that all three windows show up in the Windows taskbar, which (if you're like most users) sits at the bottom of your Windows screen. It is considered very uncool for programs to occupy multiple slots in the Windows taskbar. To suppress the display of those extra windows, you'll want to include the following statement in the for loop:

win.ShowInTaskbar = false; 


But now something else is peculiar. If you close the window labeled "Main Window" first, you'll see the taskbar entry disappearbut the program is obviously still running and still displaying two windows!

A program generally chooses to terminate when the Run method returns, and by default, the Run method returns when the user closes the last window. This behavior is governed by the ShutdownMode property of Application, which you set to a member of the ShutdownMode enumeration. Besides the default ShutdownMode.OnLastWindowClose member, you can specify ShutdownMode.OnMainWindowClose. Try inserting the following statement right before the call to Run:

app.ShutdownMode = ShutdownMode.OnMainWindowClose; 


Or, try inserting the following statement anywhere in the OnStartup override. (In Main, you must preface the property with the name of the Application object; in the OnStartup method, you just specify the property and optionally preface it with the keyword this.)

ShutdownMode = ShutdownMode.OnMainWindowClose; 


Now Run returns and the program terminates whenever the main window is closed.

Without removing the change to the Shutdown property, try inserting this statement in the for loop:

MainWindow = win; 


MainWindow is, you'll recall, a property of the Application class. This is how your program can specify which window you choose as the main window. At the conclusion of the for loop, the window labeled "Extra Window No. 2" will be considered the main window, and that's the window you must close to terminate the program.

There's a third option for ShutdownMode: You can set the property to the enumeration member ShutdownMode.OnExplicitShutdown, in which case Run returns only when the program explicitly calls the Shutdown method of Application.

Now remove any code you've inserted involving the ShutdownMode and MainWindow properties of the Application class. There's another way to establish a hierarchy among multiple windows, and that's by using the Owner property defined by the Window class. By default, this property is null, which means that the window has no owner. You can set the Owner property to and other Window object in the application (with the exception that ownership cannot be circular). For example, try inserting this code in the for loop:

win.Owner = winMain; 


Now the two extra windows are owned by the main window. You can still switch back and forth between all three windows, but as you do so you'll see that the owned windows always appear in the foreground of their owner. When you minimize the owner, the owned windows disappear from the screen, and when you close the owner, the owned windows are also automatically closed. These two extra windows have become modeless dialog boxes.

Modeless dialogs are the less common of the two main categories of dialog boxes. Much more common is the modal dialog box. You can see an example of a modal dialog box by clicking the client area of the ThrowWindowParty main window with the mouse. The WindowOnMouseDown method creates another Window object and gives it a Title property, but instead of calling Show it calls ShowDialog. Unlike Show, ShowDialog doesn't immediately return, and the modal dialog box it displays doesn't let you switch to other windows in the program. (It will allow you to switch to other programs running under Windows, however.) Only when you close the modal dialog box does the call to ShowDialog return.

Modeless dialog boxes, on the other hand, do allow you to work with the main application with the dialog window in place. A good example of a modeless dialog is the Quick Find dialog in Visual Studio. It allows you to find strings in your source code, but yet also allows you to edit the source file with the Quick Find dialog still active. Modal dialogs capture user input to the application and force you to dismiss the dialog before you can work with other application windows. Modeless dialog boxes do not.

Try this. Go back to the first example program, SayHello, and in the source code change Show to ShowDialog and comment out all references to the Application object. The program still works because ShowDialog implements its own message loop to handle input events. Modal dialog boxes become modal by not participating in the message loop of the application and hence not allowing the application to obtain user input events.

The previous two programs defined a class that inherited from Application. It is possible (and quite common) for a program to define a class that inherits from Window. The following program contains three classes and three source code files. To add additional empty source code files to an existing project in Visual Studio 2005, right-click the project name in the Solution Explorer and then select Add New Item from the shortcut menu. Or, select Add New Item from the Project menu. In either case, the item you want to add is a Code File, which is initially empty.

The project name is InheritAppAndWindow, which is also the name of a class that contains only Main:

InheritAppAndWindow.cs

//---------------------------------------------------- // InheritAppAndWindow.cs (c) 2006 by Charles Petzold //---------------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.InheritAppAndWindow {     class InheritAppAndWindow     {         [STAThread]         public static void Main()         {             MyApplication app = new MyApplication();             app.Run();         }     } } 



Main creates an object of type MyApplication and calls Run on that object. The MyApplication class derives from Application and is defined like this:

MyApplication.cs

[View full width]

//---------------------------------------------- // MyApplication.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.InheritAppAndWindow { class MyApplication : Application { protected override void OnStartup (StartupEventArgs args) { base.OnStartup(args); MyWindow win = new MyWindow(); win.Show(); } } }



In the override of the OnStartup method, the class creates an object of type MyWindow, which is the third class in the project and derives from Window:

MyWindow.cs

[View full width]

//----------------------------------------- // MyWindow.cs (c) 2006 by Charles Petzold //----------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.InheritAppAndWindow { public class MyWindow : Window { public MyWindow() { Title = "Inherit App & Window"; } protected override void OnMouseDown (MouseButtonEventArgs args) { base.OnMouseDown(args); string strMessage = string.Format("Window clicked with {0} button at point ({1})", args.ChangedButton, args.GetPosition(this)); MessageBox.Show(strMessage, Title); } } }



Classes that derive from Window generally use the constructor of the class to initialize themselves. The only custom initialization this particular window performs is setting the Title property. Notice that the property need not be prefaced with any object name because MyWindow inherits that property from Window. You can optionally preface the property with the keyword this:

this.Title = "Inherit App & Window"; 


Rather than installing an event handler for the MouseDown event, the class can override the OnMouseDown method. Because OnMouseDown is an instance method, it can pass the keyword this to the GetPosition method to refer to the Window object, and it can refer to the Title property directly.

Although there's nothing wrong with the program just shown, it's somewhat more common (and easier) in a code-only WPF program to define a class that inherits from Window, but not a class that inherits from Application. Here's a typical single-file program:

InheritTheWin.cs

//---------------------------------------------- // InheritTheWin.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.InheritTheWin {     class InheritTheWin : Window     {         [STAThread]         public static void Main()         {             Application app = new Application();             app.Run(new InheritTheWin());         }         public InheritTheWin()         {             Title = "Inherit the Win";         }     } } 



This is the structure I'll use for many of the sample programs in Part I of this book. It's fairly short, as you can see, and if you really wanted to strip the Main method down, you could cram all its functionality into a single statement:

new Application().Run(new InheritTheWin()); 


Let's play around with this program. I'll make some suggestions for how to change the program, and you can either follow along or (even better) try some others on your own.

The window is positioned and sized on the screen by the Windows operating system itself, but you can override that behavior. The Window class inherits Width and Height properties from FrameworkElement, and you can set those properties in the constructor:

Width = 288; Height = 192; 


In setting these two properties, you aren't limited to integers. The properties are defined as double values, so you can set them like so:

Width = 100 * Math.PI; Height = 100 * Math.E; 


The Width and Height properties are initially undefined, and if your program never sets them, they remain undefined, which means they have values of NaN, the abbreviation immortalized by the IEEE (Institute of Electrical and Electronics Engineers, Inc.) floating-point standard for "not a number."

So if you ever need to obtain the actual size of the window, don't use the Width and Height properties. Use the read-only properties ActualWidth and ActualHeight instead. However, these latter two properties will equal 0 in the constructor of the window; they only become applicable when the window is displayed on the screen.

It may have seemed like I chose two numbers at random when I showed these two statements earlier:

Width = 288; Height = 192; 


These numbers are not pixels. If the Width and Height properties were specified in units of pixels, they wouldn't have to be defined as double-precision floating-point values. The units in which you specify all dimensions and locations in Windows Presentation Foundation are sometimes called device-independent pixels or logical pixels but it's probably best not to refer to pixels at all. I will call them device-independent units. Each unit is 1/96 inch, so those values of 288 and 192 actually indicate that the window is to be 3 inches wide by 2 inches tall.

If you take a ruler to your monitor, you probably won't measure precisely those dimensions, however. The relationship between pixels and inches is established by Windows and changeable by the user. Right-click your Windows screen and select Properties from the drop-down menu. Click the Settings tab and then click the Advanced button, and then click the General tab if you have a choice.

By default, Windows establishes a display resolution of 96 dots per inch, and if that's the case on your machine, the Width and Height values of 288 and 192 correspond precisely to pixels.

However, if you have your video display set to 120 DPI (a common alternative among users born before the first release of Star Wars) and a WPF program sets window Width and Height properties of 288 and 192, the pixel width of the window will be 360, and the height will be 240, consistently implying a window size of 3 inches by 2 inches.

As monitors become available in the future with much higher resolutions than we're accustomed to now, WPF programs should be able to run without change. For example, suppose you have a monitor that achieves approximately 200 pixels to the inch. To avoid everything becoming very tiny on the screen, users will need to use Display Properties to set a commensurate resolution, perhaps 192 DPI. When a WPF program sets a Width and Height of 288 and 192 device-independent units, those dimensions now become 576 pixels and 384 pixelsstill 3 inches by 2 inches.

The use of these device-independent units is pervasive throughout the Windows Presentation Foundation. For example, some programs shown earlier in this chapter used a message box to display the location of a mouse click relative to the upper-left corner of the client area. That location was not in units of pixels, but rather in device-independent units of 1/96 inch.

If you experiment with very small values of Width and Height, you'll discover that the window always displays at least part of the caption bar. These minimum dimensions of the windowagain in device-independent units of 1/96 inchcan be obtained from the static read-only properties SystemParameters.MinimumWindowWidth and SystemParameters.MinimumWindowHeight. The SystemParameters class has a number of static properties with information like this.

If you'd like to position your window at a particular location of the screen, you can do that as well by setting the Left and Top properties defined by the Window class:

Left = 500; Top = 250; 


These two properties specify the location of the top-left corner of the window relative to the top-left corner of the screen. Again, these are double values in device-independent units, and if your program does not set these properties, they remain values of NaN. The Window class does not define Right and Bottom properties. The right and bottom location of the window is implied by the Left and Top properties and the size of the window.

Suppose your video adapter and monitor are capable of displaying 1600 pixels horizontally and 1200 pixels vertically, and that's what you've set as your display resolution in the Display Properties dialog. Suppose you examine the values returned from the static properties SystemParameters.PrimaryScreenWidth and SystemParameters.PrimaryScreenHeight. Will these values be 1600 and 1200? Only if your screen DPI setting is 96. In that case, you've expressed a wish that your display be 16-2/3 inches by 12-1/2 inches.

However, if you've set your assumed screen DPI as 120, SystemParameters.PrimaryScreenWidth and SystemParameters.PrimaryScreenHeight return values of 1280 by 960 device-independent units, implying a metrical size of 13-1/3 inches by 10 inches.

Because SystemParameters reports nearly all dimensions in device-independent unitsexceptions are the SystemParameters properties SmallIconWidth and SmallIconHeight, which are in units of pixelsyou can safely use most values in calculations without any conversions. For example, you can position a window at the lower-right corner of the screen using the following code:

Left = SystemParameters.PrimaryScreenWidth - Width; Top = SystemParameters.PrimaryScreenHeight - Height; 


This code implies that you've already set the Width and Height properties. But you may not like the results. If you have a taskbar at the bottom of your screen, it will obscure the bottom part of your window. You might prefer to position your window instead at the lower-right corner of the work area, which is that area of the screen not occupied by any application desktop toolbars (of which the Windows taskbar is the most common example).

The SystemParameters.WorkArea property returns an object of type Rect, a structure that defines a rectangle in terms of the coordinate location of its upper-left corner and its size. This WorkArea property must be defined as a Rect rather than just a width and height because the user can put the taskbar at the left of the screen. In that case, the Left property of the Rect structure will be non-zero, and the Width property will equal the screen width minus the Left value.

Here's code to position your window in the lower-right corner of the work area:

Left = SystemParameters.WorkArea.Width - Width; Top = SystemParameters.WorkArea.Height - Height; 


And here's code that positions your window in the center of the work area:

Left = (SystemParameters.WorkArea.Width - Width) / 2 +                 SystemParameters.WorkArea.Left; Top = (SystemParameters.WorkArea.Height - Height) / 2 +                 SystemParameters.WorkArea.Top; 


As an alternative to this code, you can use the WindowStartupLocation property defined by the Window class. You set this property to a member of the WindowStartupLocation enumeration. The default value is WindowStartupLocation.Manual, which means that either the program or the Windows operating system manually positions the window. You can set the property to WindowStartupLocation.CenterScreen to center the window. Despite the name of this enumeration member, the window is centered in the work area rather than on the screen. (The third option is WindowStartupLocation.CenterOwner, which you use with modal dialog boxes to set them in the center of their owners.)

Here's a little program that positions itself in the center of the work area and lets you change its size by 10 percent with each press of the up or down arrow key:

GrowAndShrink.cs

[View full width]

//---------------------------------------------- // GrowAndShrink.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.GrowAndShrink { public class GrowAndShrink : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new GrowAndShrink()); } public GrowAndShrink() { Title = "Grow & Shrink"; WindowStartupLocation = WindowStartupLocation.CenterScreen; Width = 192; Height = 192; } protected override void OnKeyDown (KeyEventArgs args) { base.OnKeyDown(args); if (args.Key == Key.Up) { Left -= 0.05 * Width; Top -= 0.05 * Height; Width *= 1.1; Height *= 1.1; } else if (args.Key == Key.Down) { Left += 0.05 * (Width /= 1.1); Top += 0.05 * (Height /= 1.1); } } } }



The OnKeyDown method (and the related KeyDown event) report on keystrokes. Each time you press and release a key on the keyboard, the OnKeyDown and OnKeyUp methods are called. You can process keys by overriding the methods. The Key property of the KeyEventArgs object is a member of the large Key enumeration and tells you what key is involved. Because the Left, Top, Width, and Height properties are all floating-point values, no information is lost as you increase and decrease the size of the window. You'll reach certain minimums and maximums imposed by Windows, but the properties still keep their calculated values.

The OnKeyDown and OnKeyUp methods are useful for obtaining keystrokes of the cursor movement keys and function keys, but for obtaining actual Unicode characters from the keyboard you should override the OnTextInput method. The Text property of the TextCompositionEventArgs argument is a string of Unicode characters. In general, the string will be just one character, but speech and handwriting input can also generate calls to OnTextInput, and the string might be longer.

The following program doesn't set a Title property. Instead, you can type your own.

TypeYourTitle.cs

[View full width]

//---------------------------------------------- // TypeYourTitle.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Windows; using System.Windows.Input; namespace Petzold.TypeYourTitle { public class TypeYourTitle : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new TypeYourTitle()); } protected override void OnTextInput (TextCompositionEventArgs args) { base.OnTextInput(args); if (args.Text == "\b" && Title.Length > 0) Title = Title.Substring(0, Title .Length - 1); else if (args.Text.Length > 0 && !Char .IsControl(args.Text[0])) Title += args.Text; } } }



The only control character that the method allows is a backspace ('\b') and then only when the Title is at least one character in length. Otherwise, the method simply appends text typed from the keyboard to the Title property.

The Window class defines other properties that affect the appearance and behavior of the window. You can set the WindowStyle property to a member of the WindowStyle enumeration. The default is WindowStyle.SingleBorderWindow. The WindowStyle.ThreeDBorderWindow is a little fancier, but actually decreases the size of the client area a smidgen. You generally use WindowStyle.ToolWindow for dialog boxes. The caption bar is a little shorter, and the window has a close button but no minimize and maximize buttons. However, you can still minimize and maximize the window by pressing Alt+Spacebar to invoke the system menu. You can also resize the tool window. The WindowStyle.None also has a sizing border but doesn't even display a caption bar. You can still invoke the system menu by typing Alt+Spacebar. The Title property is not displayed by the window but it does appear in the task bar.

The presence or absence of the sizing border is governed by the ResizeMode property, which you set to a member of the ResizeMode enumeration. The default is ResizeMode.CanResize, which lets the user resize the window, minimize it, or maximize it. You can display a little grip in the lower-left corner of the client area with ResizeMode.CanResizeWithGrip. The option ResizeMode.CanMinimize suppresses the sizing border and disables the maximize box, but still allows the window to be minimized. This option is useful for windows that have a fixed size. Finally, ResizeMode.NoResize suppresses the minimize and maximize buttons as well as the sizing border.

You can set the WindowState property to a member of the WindowState enumeration to govern how your window is initially displayed. The options are WindowState.Normal, WindowState.Minimized, or WindowState.Maximized.

Set the Topmost property to true to make your window appear in the foreground of all other windows. (You should use this with discretion, and only with windows for which this feature serves a purpose. Of course, give the user an option to turn it off.)

Another important property of the Window class is Background. This is a property that Window inherits from Control and it governs the color of the client area. Yet color is far too mild a term for what you can actually do with that Background property. The Background property is an object of type Brush, and the types of brushes you can use to color the background of your window include gradient brushes, and brushes based on bitmaps and other images. Brushes play such an important role in the Windows Presentation Foundation that two chapters in this book are devoted to them. The first of those two chapters is the next chapter, so let's plunge into it.




Applications = Code + Markup. A Guide to the Microsoft Windows Presentation Foundation
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro - Developer)
ISBN: 0735619573
EAN: 2147483647
Year: 2006
Pages: 72

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net