Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

992 CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

Defining the ShapeData Type

Recall that our application will allow end users to select from two predefined shapes in a given color. Because we will provide a way to allow users to save their graphical data to a file, we will want to define a custom class type that encapsulates each of these details; for simplicity, we will do so using C# automatic properties (see Chapter 13). Add a new class to your project named ShapeData.cs. Implement this type as follows:

[Serializable] class ShapeData

{

//The upper left of the shape to be drawn. public Point UpperLeftPoint { get; set; }

//The current color of the shape to be drawn. public Color Color { get; set; }

//The type of shape.

public SelectedShape ShapeType { get; set; }

}

Here, ShapeData is making use of three automatic properties, two of which (Point and Color) are defined in the System.Drawing namespace, so be sure to import this namespace within your code file. Also notice that this type has been adorned with the [Serializable] attribute. In a later step, we will configure our MainWindow type to maintain a list of ShapeData types that will be persisted using object serialization services (see Chapter 21).

Defining the ShapePickerDialog Type

To allow the user to choose between the circle or rectangle image type, we will now create a simple custom dialog box named ShapePickerDialog (insert this new Form now). Beyond the obligatory OK and Cancel buttons (each of which has been assigned the correct DialogResult value), this dialog box will make use of a single GroupBox that maintains two RadioButton objects named radioButtonCircle and radioButtonRect. Figure 27-27 shows one possible design.

Figure 27-27. The ShapePickerDialog type

Now, open the code window for your dialog box by right-clicking the Forms designer and selecting the View Code menu option. Within the MyPaintProgram namespace, define an enumeration (named SelectedShape) that defines names for each possible shape:

public enum SelectedShape

{

Circle, Rectangle

}

CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

993

Now, update your current ShapePickerDialog class type as follows:

Add an automatic property of type SelectedShape. The caller will be able to use this property to determine which shape to render.

Handle the Click event for the OK button using the Properties window.

Implement this event handler to determine whether the circle radio button has been selected (via the Checked property). If so, set your currentShape variable to SelectedShape. Circle; otherwise, set this member variable to SelectedShape.Rectangle.

Here is the complete code:

public partial class ShapePickerDialog : Form

{

public SelectedShape SelectedShape { get; set; }

public ShapePickerDialog()

{

InitializeComponent();

}

private void btnOK_Click(object sender, EventArgs e)

{

if (radioButtonCircle.Checked) SelectedShape = SelectedShape.Circle;

else

SelectedShape = SelectedShape.Rectangle;

}

}

That wraps up the infrastructure of our program. Now we simply need to implement the Click event handlers for the remaining menu items on the main window.

Adding Infrastructure to the MainWindow Type

Returning to the construction of the main window, add three new member variables to this Form that allow you to keep track of the selected shape (via a SelectedShape enum type), the selected color (represented by a System.Drawing.Color type), as well as each of the rendered images held in a generic List<T>:

public partial class MainWindow : Form

{

//Current shape / color to draw. private SelectedShape currentShape;

private Color currentColor = Color.DarkBlue;

//This maintains each ShapeData.

private List<ShapeData> shapes = new List<ShapeData>();

...

}

Next, handle the MouseDown and Paint events for this Form-derived type using the Properties window. We will implement them in a later step; however, for the time being, you should find that the IDE has generated the following stub code:

private void MainWindow_Paint(object sender, PaintEventArgs e)

{

}

994CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

private void MainWindow_MouseClick(object sender, MouseEventArgs e)

{

}

Implementing the Tools Menu Functionality

To allow users to set the currentShape member variable, implement the Click handler for the ToolsPick Shape . . . menu option to launch your custom dialog box, and based on their selection, assign this member variable accordingly:

private void pickShapeToolStripMenuItem_Click(object sender, EventArgs e)

{

// Load our dialog box and set the correct shape type.

ShapePickerDialog dlg = new ShapePickerDialog(); if (DialogResult.OK == dlg.ShowDialog())

{

currentShape = dlg.SelectedShape;

}

}

To allow users to set the currentColor member variable, implement the Click event handler for the Tools Pick Color . . . menu to make use of the System.Windows.Forms.ColorDialog type:

private void pickColorToolStripMenuItem_Click(object sender, EventArgs e)

{

ColorDialog dlg = new ColorDialog();

if (dlg.ShowDialog() == DialogResult.OK)

{

currentColor = dlg.Color;

}

}

If you were to run your program as it now stands and select the Tools Pick Color menu option, you would get the dialog box shown in Figure 27-28.

Figure 27-28. The stock ColorDialog type

CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

995

Finally, implement the Tools Clear Surface menu handler to empty the contents of the List<T> member variable and programmatically fire the Paint event via a call to Invalidate():

private void clearSurfaceToolStripMenuItem_Click(object sender, EventArgs e)

{

shapes.Clear();

// This will fire the paint event.

Invalidate();

}

Capturing and Rendering the Graphical Output

Given that a call to Invalidate() will fire the Paint event, we will obviously need to author code within our Paint event handler. Our goal is to loop through each item in the (currently empty) List<T> member variable and render a circle or square at the current mouse location. The first step is to implement the MouseDown event handler to insert a new ShapeData type into our generic List<T> type, based on the user-selected color, shape type, and current location of the mouse:

private void MainWindow_MouseClick(object sender, MouseEventArgs e)

{

//Make a ShapeData type based on current user

//selections.

ShapeData sd = new ShapeData(); sd.ShapeType = currentShape; sd.Color = currentColor;

sd.UpperLeftPoint = new Point(e.X, e.Y);

// Add to the List<T> and force the form to repaint itself. shapes.Add(sd);

Invalidate();

}

With this, we can now implement our Paint event handler as follows:

private void MainWindow_Paint(object sender, PaintEventArgs e)

{

//Get the Graphics type for this window.

Graphics g = e.Graphics;

//Render each shape in the selected color. foreach (ShapeData s in shapes)

{

//Render a rectangle or circle 20 x 20 pixels in size

//using the correct color.

if (s.ShapeType == SelectedShape.Rectangle) g.FillRectangle(new SolidBrush(s.Color),

s.UpperLeftPoint.X, s.UpperLeftPoint.Y, 20, 20);

else

g.FillEllipse(new SolidBrush(s.Color), s.UpperLeftPoint.X, s.UpperLeftPoint.Y, 20, 20);

}

}

996 CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

If you were to run your application at this point, you should now be able to render any number of shapes in a variety of colors (see Figure 27-29).

Figure 27-29. MyPaintProgram in action

Implementing the Serialization Logic

The final aspect of our project involves implementing Click event handlers for the File Save . . .

and File Load . . . menu items. Given that ShapeData has been marked with the [Serialization] attribute (and given that List<T> itself is serializable), we can very quickly save out the current graphical data using the Windows Forms SaveFileDialog type. First, update your using directives to specify you are using the System.Runtime.Serialization.Formatters.Binary and System.IO namespaces.

// For the binary formatter.

using System.Runtime.Serialization.Formatters.Binary; using System.IO;

With this, update your File Save . . . handler as follows:

private void saveToolStripMenuItem_Click(object sender, EventArgs e)

{

using (SaveFileDialog saveDlg = new SaveFileDialog())

{

//Configure the look and feel of the save dialog box. saveDlg.InitialDirectory = ".";

saveDlg.Filter = "Shape files (*.shapes)|*.shapes"; saveDlg.RestoreDirectory = true;

saveDlg.FileName = "MyShapes";

//If they click the OK button, open the new

//file and serialize the List<T>.

if (saveDlg.ShowDialog() == DialogResult.OK)

{

Stream myStream = saveDlg.OpenFile(); if ((myStream != null))

{

// Save the shapes!

BinaryFormatter myBinaryFormat = new BinaryFormatter(); myBinaryFormat.Serialize(myStream, shapes); myStream.Close();

CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

997

}

}

}

}

The File Load event handler simply opens the selected file and deserializes the data back into the List<T> member variable with the help of the Windows Forms OpenFileDialog type:

private void loadToolStripMenuItem_Click(object sender, EventArgs e)

{

using (OpenFileDialog openDlg = new OpenFileDialog())

{

openDlg.InitialDirectory = ".";

openDlg.Filter = "Shape files (*.shapes)|*.shapes"; openDlg.RestoreDirectory = true;

openDlg.FileName = "MyShapes";

if (openDlg.ShowDialog() == DialogResult.OK)

{

Stream myStream = openDlg.OpenFile(); if ((myStream != null))

{

// Get the shapes!

BinaryFormatter myBinaryFormat = new BinaryFormatter();

shapes = (List<ShapeData>)myBinaryFormat.Deserialize(myStream); myStream.Close();

Invalidate();

}

}

}

}

Given your work in Chapter 21, I’d guess the overall serialization logic looks familiar. It is worth pointing out that the SaveFileDialog and OpenFileDialog types both support a Filter property that is assigned a rather cryptic string value. This filter controls a number of settings for the save/open dialog boxes such as the file extension (*.shapes). The FileName property is used to control what the default name of the file to be created should be, which in this example is MyShapes.

At this point, your painting application is complete. You should now be able to save and load your current graphical data to any number of *.shapes files. If you are interested in enhancing this Windows Forms program, you may wish to account for additional shapes, or allow the user to control the size of the shape to draw or perhaps select the format used to save the data (binary, XML, SOAP).

Summary

The purpose of this chapter was to examine the process of building traditional desktop applications using the Windows Forms and GDI+ APIs, which have been part of the .NET Framework since version 1.0. At minimum, a Windows Forms application consists of a type-extending Form and a Main() method that interacts with the Application type.

When you wish to populate your forms with UI elements (menu systems, GUI input controls, etc.), you do so by inserting new objects into the inherited Controls collection. This chapter also illustrated how to capture mouse, keyboard, and rendering events. Along the way, you were introduced to the Graphics type and numerous ways to generate graphical data at runtime.

As mentioned during the overview of this chapter, the Windows Forms API has been (in some ways) superseded by the WPF API introduced with the release of .NET 3.0 (which you will begin to

998CHAPTER 27 PROGRAMMING WITH WINDOWS FORMS

examine in the next chapter). While it is true that WPF will eventually become the toolkit of choice for supercharged UI front ends, the Windows Forms API is still the simplest (and in many cases, most direct) way to author standard business applications, in-house applications, and simple configuration utilities. For these reasons, Windows Forms will be part of the .NET base class libraries for years to come.

C H A P T E R 2 8

Introducing Windows Presentation

Foundation and XAML

In the previous chapter, you were introduced to the functionality contained within the System. Windows.Forms.dll and System.Drawing.dll assemblies. As explained, the Windows Forms API is the original GUI toolkit of the .NET platform, which provides numerous types that can be used to build sophisticated desktop user interfaces. While it is true that Windows Forms/GDI+ is still entirely supported under .NET 3.5, Microsoft shipped a brand-new desktop API termed Windows Presentation Foundation (WPF) beginning with the release of .NET 3.0.

This initial WPF chapter begins by examining the motivation behind this new UI framework and provides a brief overview of the various types of WPF applications supported by the API. After this point we will examine the core WPF programming model and come to know the role of the Application and Window types as well as the key WFP assemblies and namespaces.

The latter part of this chapter will introduce you to a brand-new XML-based grammar: Extensible Application Markup Language (XAML). As you will see, XAML provides WPF developers with a way to partition UI definitions from the logic that drives them. Here, you will be exposed to several critical XAML topics including attached property syntax, type converters, markup extensions, and understanding how to parse XAML at runtime. This chapter wraps up by examining the various WPF-specific tools that ship with the Visual Studio 2008 IDE and examines the role of Microsoft Expression Blend.

The Motivation Behind WPF

Over the years, Microsoft has developed numerous graphical user interface toolkits (raw C/C++/ Win32 API development, VB6, MFC, etc.) to build desktop executables. Each of these APIs provided a code base to represent the basic aspects of a GUI application, including main windows, dialog boxes, controls, menu systems, and other necessities. With the initial release of the .NET platform, the Windows Forms API (see Chapter 27) quickly became the preferred model for UI development, given its simple yet very powerful object model.

While many full-featured desktop applications have been successfully created using Windows Forms, the fact of the matter is that this programming model is rather asymmetrical. Simply put,

System.Windows.Forms.dll and System.Drawing.dll do not provide direct support for many additional technologies required to build a full-fledged desktop application. To illustrate this point, consider the ad hoc nature of GUI development prior to the release of WPF (e.g., .NET 2.0; see Table 28-1).

999

1000 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

Table 28-1. .NET 2.0 Solutions to Desired Functionalities

Desired Functionality

.NET 2.0 Solution

Building forms with controls

Windows Forms

2D graphics support

GDI+ (System.Drawing.dll)

3D graphics support

DirectX APIs

Support for streaming video

Windows Media Player APIs

Support for flow-style documents

Programmatic manipulation of PDF files

 

 

As you can see, a Windows Forms developer must pull in types from a number of different APIs and object models. While it is true that making use of these diverse APIs may look similar syntactically (it is just C# code, after all), you may also agree that each technology requires a radically different mind-set. For example, the skills required to create a 3D rendered animation using DirectX are completely different from those used to bind data to a grid. To be sure, it is very difficult for a Windows Forms programmer to master the diverse nature of each API.

Unifying Diverse APIs

WPF (introduced with .NET 3.0) was purposely created to merge these previous unrelated programming tasks into a single unified object model. Thus, if you need to author a 3D animation, you have no need to manually program against the DirectX API (although you could), as 3D functionality is baked directly into WPF. To see how well things have cleaned up, consider Table 28-2, which illustrates the desktop development model ushered in as of .NET 3.0.

Table 28-2. .NET 3.0 Solutions to Desired Functionalities

Desired Functionality

.NET 3.0 and Higher Solution

Building forms with controls

WPF

2D graphics support

WPF

3D graphics support

WPF

Support for streaming video

WPF

Support for flow-style documents

WPF

 

 

Providing a Separation of Concerns via XAML

Perhaps one of the most compelling benefits is that WPF provides a way to cleanly separate the look and feel of a Windows application from the programming logic that drives it. Using XAML, it is possible to define the UI of an application via markup. This markup (ideally created by those with an artistic mind-set using dedicated tools) can then be connected to a managed code base to provide the guts of the program’s functionality.

Note XAML is not limited to WPF applications! Any application can use XAML to describe a tree of .NET objects, even if they have nothing to do with a visible user interface. For example, it is possible to build custom activities for a Windows Workflow Foundation application using XAML.

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1001

As you dig into WPF, you may be surprised how much flexibility “desktop markup” provides. XAML allows you to define not only simple UI elements (buttons, grids, list boxes, etc.) in markup, but also graphical renderings, animations, data binding logic, and multimedia functionality (such as video playback). For example, defining a circular button control that animates a company logo requires just a few lines of markup. Even better, WPF elements can be modified through styles and templates, which allow you to change the overall look and feel of an application with minimum fuss and bother, independent of the core application processing code.

Given all these points, the need to build custom controls greatly diminishes under WPF. Unlike Windows Forms development, the only compelling reason to build a custom WPF control library is if you need to change the behaviors of a control (e.g., add custom methods, properties, or events; subclass an existing control to override virtual members; etc.). If you simply need to change the look and feel of a control (again, such as a circular animated button), you can do so entirely through markup.

Note Other valid reasons to build custom WPF controls include achieving binary reuse (via a WPF control library), as well as building controls that expose custom design-time functionality and integration with the Visual Studio 2008 IDE.

Providing an Optimized Rendering Model

Also be aware of the fact that WPF is optimized to take advantage of the new video driver model supported under the Windows Vista operating system. While WPF applications can be developed on and deployed to Windows XP machines (as well as Windows Server 2003 machines), the same application running on Vista will perform much better, especially when making use of animations/ multimedia services. This is due to the fact that the display services of WPF are rendered via the DirectX engine, allowing for efficient hardware and software rendering.

Note Allow me to reiterate this key point: WPF is not limited to Windows Vista! Although the Vista operating system has the .NET 3.0 libraries (which include WPF) installed out of the box, you can build and execute WPF applications on XP and Windows Server 2003 once you install the .NET Framework 3.5 SDK (for programmers) or

.NET 3.5 runtime (for end users).

WPF applications also tend to behave better under Vista. If one graphics-intensive application crashes, it will not take down the entire operating system (à la the blue screen of death); rather, the misbehaving application in question will simply terminate. As you may know, the most common cause of the infamous blue screen of death is misbehaving video drivers.

Additional WPF-Centric Bells and Whistles

To recap the story thus far, Windows Presentation Foundation (WPF) is a new API to build desktop applications that integrates various desktop APIs into a single object model and provides a clean separation of concerns via XAML. In addition to these major points, WPF applications also benefit from various other bells and whistles, many of which are explained over the next several chapters. Here is a quick rundown of the core services: