Pro C#10 CHAPTER 25 Introducing Windows Presentation Foundation and XAML

PART VIII

Windows Client Development

CHAPTER 25

Introducing Windows Presentation Foundation and XAML

When version 1.0 of the .NET platform was released, programmers who needed to build graphical desktop applications made use of two APIs named Windows Forms and GDI+, packaged up primarily in the System. Windows.Forms.dll and System.Drawing.dll assemblies. While Windows Forms and GDI+ are still viable APIs for building traditional desktop GUIs, Microsoft shipped an alternative GUI desktop API named Windows Presentation Foundation (WPF) beginning with the release of .NET 3.0. WPF and Windows Forms joined the .NET Core family with the release of .NET Core 3.0.
This initial WPF chapter begins by examining the motivation behind this new GUI framework, which will help you see the differences between the Windows Forms/GDI+ and WPF programming models.
Next, you will come to know the role of several important classes, including Application, Window, ContentControl, Control, UIElement, and FrameworkElement.
This chapter will then introduce you to an XML-based grammar named Extensible Application Markup Language (XAML; pronounced “zammel”). Here, you will learn the syntax and semantics of XAML (including attached property syntax and the role of type converters and markup extensions).
This chapter wraps up by investigating the integrated WPF designers of Visual Studio by building your first WPF application. During this time, you will learn to intercept keyboard and mouse activities, define application-wide data, and perform other common WPF tasks.

The Motivation Behind WPF
Over the years, Microsoft has created numerous graphical user interface toolkits (raw C/C++/Windows 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, etc. With the initial release of the .NET platform, the Windows Forms API quickly became the preferred model for UI development, given its simple yet 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 feature-rich desktop application. To illustrate this point, consider the ad hoc nature of GUI desktop development before the release of WPF (see Table 25-1).

© Andrew Troelsen, Phil Japikse 2022
A. Troelsen and P. Japikse, Pro C# 10 with .NET 6, https://doi.org/10.1007/978-1-4842-7869-7_25

1099

Table 25-1. Pre-WPF Solutions to Desired Functionalities

Desired Functionality Technology
Building windows 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 several unrelated APIs and object models. While it is true that making use of these diverse APIs might look similar syntactically (it is just
C# code, after all), you might also agree that each technology requires a radically different mindset. 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 difficult for a Windows Forms programmer to master the diverse nature of each API.

Unifying Diverse APIs
WPF was purposely created to merge these previously 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) because 3D functionality is baked directly into WPF. To see how well things have cleaned up, consider Table 25-2, which illustrates the desktop development model ushered in as of .NET 3.0.

Table 25-2. .NET 3.0+ Solutions to Desired Functionalities

Desired Functionality Technology
Building forms with controls WPF
2D graphics support WPF
3D graphics support WPF
Support for streaming video WPF
Support for flow-style documents WPF

The obvious benefit here is that .NET programmers now have a single, symmetrical API for all common GUI desktop programming needs. After you become comfortable with the functionality of the key WPF assemblies and the grammar of XAML, you will be amazed how quickly you can create sophisticated UIs.

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 GUI application from the programming logic that drives it. Using XAML, it is possible to define the UI of an application via XML markup. This markup (ideally generated using tools such as Microsoft Visual Studio or Blend for Visual Studio) can then be connected to a related C# code file 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.

As you dig into WPF, you might be surprised how much flexibility this “desktop markup” provides. XAML allows you to define not only simple UI elements (buttons, grids, list boxes, etc.) in markup but also interactive 2D and 3D graphics, animations, data-binding logic, and multimedia functionality (such as video playback).
XAML also makes it easy to customize how a control should render its visual appearance. For example, defining a circular button control that animates your company logo requires just a few lines of markup.
As shown in Chapter 27, WPF controls 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. Unlike Windows Forms development, the only compelling reason to build a custom WPF control from the ground up 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). 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.

Providing an Optimized Rendering Model
GUI toolkits such as Windows Forms, MFC, or VB6 performed all graphical rendering requests (including the rendering of UI elements such as buttons and list boxes) using a low-level, C-based API (GDI), which has been part of the Windows OS for years. GDI provides adequate performance for typical business
applications or simple graphical programs; however, if a UI application needed to tap into high-performance graphics, DirectX was required.
The WPF programming model is quite different, in that GDI is not used when rendering graphical data. All rendering operations (e.g., 2D graphics, 3D graphics, animations, control rendering, etc.) now make use of the DirectX API. The first obvious benefit is that your WPF applications will automatically take advantage of hardware and software optimizations. As well, WPF applications can tap into rich graphical services (blur effects, anti- aliasing, transparency, etc.) without the complexity of programming directly against the DirectX API.

■Note although WpF does push all rendering requests to the directX layer, I don’t want to suggest that a WpF application will perform as fast as building an application using unmanaged C++ and directX directly. although significant performance advances have been made in WpF with every release, if you are intending to build a desktop application that requires the fastest possible execution speed (such as a 3d video game), unmanaged C++ and directX are still the best approach.

Simplifying Complex UI Programming
To recap the story thus far, Windows Presentation Foundation (WPF) is an API for building 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 a simple way to integrate services into your programs, which historically were quite complex to account for. The following is a quick rundown of the core WPF features:
•Multiple layout managers (far more than Windows Forms) to provide extremely flexible control over the placement and repositioning of content.
•Use of an enhanced data-binding engine to bind content to UI elements in a variety of ways.

•A built-in style engine, which allows you to define “themes” for a WPF application.
•Use of vector graphics, which allows content to be automatically resized to fit the size and resolution of the screen hosting the application.
•Support for 2D and 3D graphics, animations, and video and audio playback.
•A rich typography API, such as support for XML Paper Specification (XPS) documents, fixed documents (WYSIWYG), flow documents, and document annotations (e.g., a Sticky Notes API).
•Support for interoperating with legacy GUI models (e.g., Windows Forms, ActiveX, and Win32 HWNDs). For example, you can incorporate custom Windows Forms controls into a WPF application and vice versa.
Now that you have some idea of what WPF brings to the table, let’s look at the various types of applications that can be created using this API. Many of these features will be explored in detail in the chapters to come.

Investigating the WPF Assemblies
WPF is ultimately little more than a collection of types bundled within .NET assemblies. Table 25-3 describes the key assemblies used to build WPF applications, each of which must be referenced when creating a new project. As you would hope, Visual Studio WPF projects reference these required assemblies automatically.

Table 25-3. Core WPF Assemblies

Assembly Meaning in Life
PresentationCore This assembly defines numerous namespaces that constitute the foundation of the WPF GUI layer. For example, this assembly contains support for the WPF Ink API, animation primitives, and numerous graphical rendering types.
PresentationFramework This assembly contains a majority of the WPF controls, the Application and Window classes, support for interactive 2D graphics, and numerous types used in data binding.
System.Xaml.dll This assembly provides namespaces that allow you to program against a XAML document at runtime. By and large, this library is useful only if you are authoring WPF support tools or need absolute control over XAML at runtime.
WindowsBase.dll This assembly defines types that constitute the infrastructure of the WPF API, including those representing WPF threading types, security types, various type converters, and support for dependency properties and routed events (described in Chapter 27).

Collectively, these four assemblies define new namespaces and .NET classes, interfaces, structures, enumerations, and delegates. Table 25-4 describes the role of some (but certainly not all) of the important namespaces.

Table 25-4. Core WPF Namespaces

Namespace Meaning in Life
System.Windows This is the root namespace of WPF. Here, you will find core classes (such as
Application and Window) that are required by any WPF desktop project.
System.Windows.Controls This contains all the expected WPF widgets, including types to build menu systems, tooltips, and numerous layout managers.
System.Windows.Data This contains types to work with the WPF data-binding engine, as well as support for data-binding templates.
System.Windows.Documents This contains types to work with the documents API, which allows you to integrate PDF-style functionality into your WPF applications, via the XML Paper Specification (XPS) protocol.
System.Windows.Ink This provides support for the Ink API, which allows you to capture input from a stylus or mouse, respond to input gestures, and so forth. This is useful for Tablet PC programming; however, any WPF can make use of this API.
System.Windows.Markup This namespace defines several types that allow XAML markup (and the equivalent binary format, BAML) to be parsed and processed programmatically.
System.Windows.Media This is the root namespace to several media-centric namespaces. Within these namespaces you will find types to work with animations, 3D rendering, text rendering, and other multimedia primitives.
System.Windows.Navigation This namespace provides types to account for the navigation logic employed by XAML browser applications (XBAPs) as well as standard desktop applications that require a navigational page model.
System.Windows.Shapes This defines classes that allow you to render interactive 2D graphics that automatically respond to mouse input.

To begin your journey into the WPF programming model, you will examine two members of the System.Windows namespace that are commonplace to any traditional desktop development effort: Application and Window.

■Note If you have created desktop uIs using the Windows Forms apI, be aware that the System.Windows. Forms. and System.Drawing. assemblies are not related to WpF. these libraries represent the original
.net guI toolkit, Windows Forms/gdI+.

The Role of the Application Class
The System.Windows.Application class represents a global instance of a running WPF application. This class supplies a Run() method (to start the application), a series of events that you can handle in order to interact with the application’s lifetime (such as Startup and Exit). Table 25-5 details some of the key properties.

Table 25-5. Key Properties of the Application Type

Property Meaning in Life
Current This static property allows you to gain access to the running Application object from anywhere in your code. This can be helpful when a window or dialog box needs to gain access to the Application object that created it, typically to access application-wide variables and functionality.
MainWindow This property allows you to programmatically get or set the main window of the application.
Properties This property allows you to establish and obtain data that is accessible throughout all aspects of a WPF application (windows, dialog boxes, etc.).
StartupUri This property gets or sets a URI that specifies a window or page to open automatically when the application starts.
Windows This property returns a WindowCollection type, which provides access to each window created from the thread that created the Application object. This can be helpful when you want to iterate over each open window of an application and alter its state (such as minimizing all windows).

Constructing an Application Class
Any WPF application will need to define a class that extends Application. Within this class, you will define your program’s entry point (the Main() method), which creates an instance of this subclass and typically handles the Startup and Exit events (as necessary). Here is an example:

// Define the global application object
// for this WPF program.
class MyApp : Application
{
[STAThread]
static void Main(string[] args)
{
// Create the application object.
MyApp app = new MyApp();

// Register the Startup/Exit events.
app.Startup += (s, e) => { / Start up the app / }; app.Exit += (s, e) => { / Exit the app / };
}
}

Within the Startup handler, you will most often process any incoming command-line arguments and launch the main window of the program. The Exit handler, as you would expect, is where you can author any necessary shutdown logic for the program (e.g., save user preferences, write to the Windows registry).

■Note the Main() method of a WpF application must be attributed with the [STAThread] attribute, which ensures any legacy CoM objects used by your application are thread-safe. If you do not annotate Main() in this way, you will encounter a runtime exception. even with the introduction of top-level statements in C# 9.0, you will still want to use the more traditional Main() method in your WpF applications. In fact, the Main() method is autogenerated for you.

Enumerating the Windows Collection
Another interesting property exposed by Application is Windows, which provides access to a collection representing each window loaded into memory for the current WPF application. As you create new Window objects, they are automatically added into the Application.Windows collection. Here is an example method that will minimize each window of the application (perhaps in response to a given keyboard gesture or menu option triggered by the end user):

static void MinimizeAllWindows()
{
foreach (Window wnd in Application.Current.Windows)
{
wnd.WindowState = WindowState.Minimized;
}
}

You will build some WPF applications shortly, but until then, let’s check out the core functionality of the
Window type and learn about a number of important WPF base classes in the process.

The Role of the Window Class
The System.Windows.Window class (located in the PresentationFramework.dll assembly) represents a single window owned by the Application-derived class, including any dialog boxes displayed by the main window. Not surprisingly, Window has a series of parent classes, each of which brings more functionality
to the table. Consider Figure 25-1, which shows the inheritance chain (and implemented interfaces) for
System.Windows.Window as seen through the Visual Studio Object Browser.

Figure 25-1. The hierarchy of the Window class

You will come to understand the functionality provided by many of these base classes as you progress through this chapter and the chapters to come. However, to whet your appetite, the following sections present a breakdown of the functionality provided by each base class (consult the .NET 6 documentation for full details).

The Role of System.Windows.Controls.ContentControl
The direct parent of Window is ContentControl, which is quite possibly the most enticing of all WPF classes. This base class provides derived types with the ability to host a single piece of content, which, simply put, refers to the visual data placed within the interior of the control’s surface area via the Content property. The WPF content model makes it quite simple to customize the basic look and feel of a content control.
For example, when you think of a typical “button” control, you tend to assume that the content is a simple string literal (OK, Cancel, Abort, etc.). If you are using XAML to describe a WPF control and the value you want to assign to the Content property can be captured as a simple string, you may set the Content property within the element’s opening definition as so (don’t fret over the exact markup at this point):


You can also make use of XAML’s property-element syntax to set complex content. Consider the following functionally equivalent

Do be aware that not every WPF element derives from ContentControl and, therefore, not all controls support this unique content model (however, most do). As well, some WPF controls add a few refinements to the basic content model you have just examined. Chapter 26 will examine the role of WPF content in much more detail.

The Role of System.Windows.Controls.Control
Unlike ContentControl, all WPF controls share the Control base class as a common parent. This base class provides numerous core members that account for basic UI functionality. For example, Control defines properties to establish the control’s size, opacity, tab order logic, the display cursor, background color, and so forth. Furthermore, this parent class provides support for templating services. As explained in Chapter 27, WPF controls can completely change the way they render their appearance using templates and styles.
Table 25-6 documents some key members of the Control type, grouped by related functionality.

Table 25-6. Key Members of the Control Type

Members Meaning in Life
Background, Foreground, BorderBrush, BorderThickness, Padding, HorizontalContentAlignment, VerticalContentAlignment These properties allow you to set basic settings regarding how the control will be rendered and positioned.
FontFamily, FontSize, FontStretch, FontWeight These properties control various font-centric settings.
IsTabStop, TabIndex These properties are used to establish tab order among controls on a window.
MouseDoubleClick, PreviewMouseDoubleClick These events handle the act of double-clicking a widget.
Template This property allows you to get and set the control’s template, which can be used to change the rendering output of the widget.

The Role of System.Windows.FrameworkElement
This base class provides a number of members that are used throughout the WPF framework, such as support for storyboarding (used within animations) and support for data binding, as well as the ability to name a member (via the Name property), obtain any resources defined by the derived type, and establish the overall dimensions of the derived type. Table 25-7 hits the highlights.

Table 25-7. Key Members of the FrameworkElement Type

Members Meaning in Life
ActualHeight, ActualWidth, MaxHeight, MaxWidth, MinHeight, MinWidth, Height, Width These properties control the size of the derived type.
ContextMenu Gets or sets the pop-up menu associated with the derived type.
Cursor Gets or sets the mouse cursor associated with the derived type.
HorizontalAlignment, VerticalAlignment Gets or sets how the type is positioned within a container (such as a panel or list box).
Name Allows to you assign a name to the type in order to access its functionality in a code file.
Resources Provides access to any resources defined by the type (see Chapter 29 for an examination of the WPF resource system).
ToolTip Gets or sets the tooltip associated with the derived type.

The Role of System.Windows.UIElement
Of all the types within a Window’s inheritance chain, the UIElement base class provides the greatest amount of functionality. The key task of UIElement is to provide the derived type with numerous events to allow the derived type to receive focus and process input requests. For example, this class provides numerous events to account for drag-and-drop operations, mouse movement, keyboard input, stylus input, and touch.
Chapter 25 digs into the WPF event model in detail; however, many of the core events will look quite familiar (MouseMove, KeyUp, MouseDown, MouseEnter, MouseLeave, etc.). In addition to defining dozens of events, this parent class provides several properties to account for control focus, enabled state, visibility, and hit-testing logic, as shown in Table 25-8.

Table 25-8. Key Members of the UIElement Type

Members Meaning in Life
Focusable, IsFocused These properties allow you to set focus on a given derived type.
IsEnabled This property allows you to control whether a given derived type is enabled or disabled.
IsMouseDirectlyOver, IsMouseOver These properties provide a simple way to perform hit-testing logic.
IsVisible, Visibility These properties allow you to work with the visibility setting of a derived type.
RenderTransform This property allows you to establish a transformation that will be used to render the derived type.

The Role of System.Windows.Media.Visual
The Visual class type provides core rendering support in WPF, which includes hit-testing of graphical data, coordinate transformation, and bounding box calculations. In fact, the Visual class interacts with the underlying DirectX subsystem to draw data on the screen. As you will examine in Chapter 26, WPF provides three possible manners in which you can render graphical data, each of which differs in terms of functionality and performance. Use of the Visual type (and its children, such as DrawingVisual) provides
the most lightweight way to render graphical data, but it also entails the greatest amount of manual code to account for all the required services. Again, more details to come in Chapter 28.

The Role of System.Windows.DependencyObject
WPF supports a particular flavor of .NET properties termed dependency properties. Simply put, this style of property provides extra code to allow the property to respond to several WPF technologies such as styles, data binding, animations, and so forth. For a type to support this new property scheme, it will need to derive from the DependencyObject base class. While dependency properties are a key aspect of WPF development, much of the time their details are hidden from view. Chapter 26 dives further into the details of dependency properties.

The Role of System.Windows.Threading.DispatcherObject
The final base class of the Window type (beyond System.Object, which I assume needs no further explanation at this point in the book) is DispatcherObject. This type provides one property of interest, Dispatcher, which returns the associated System.Windows.Threading.Dispatcher object. The Dispatcher class is the entry point to the event queue of the WPF application, and it provides the basic constructs for dealing with concurrency and threading. The Dispatcher class was explored in Chapter 15.

Understanding the Syntax of WPF XAML
Production-level WPF applications will typically make use of dedicated tools to generate the necessary XAML. As helpful as these tools are, it is a good idea to understand the overall structure of XAML markup. To help in your learning process, allow me to introduce a popular (and free) tool that allows you to easily experiment with XAML.

Introducing Kaxaml
When you are first learning the grammar of XAML, it can be helpful to use a free tool named Kaxaml. You can obtain this popular XAML editor/parser from https://github.com/punker76/kaxaml.

■Note For many editions of this book, I’ve pointed users to www.kaxaml.com, but unfortunately, that site has been retired. Jan Karger (https://github.com/punker76) has forked the old code and has done some work on improving it. You can find his version of the tool on github https://github.com/punker76/ kaxaml/releases. Much respect and thanks to the original developers of Kaxaml and to Jan for keeping it alive; it is a great tool and has helped countless developers learn XaML.

Kaxaml is helpful, in that it has no clue about C# source code, event handlers, or implementation logic.
It is a much more straightforward way to test XAML snippets than using a full-blown Visual Studio WPF project template. As well, Kaxaml has several integrated tools, such as a color chooser, a XAML snippet manager, and even an “XAML scrubber” option that will format your XAML based on your settings. When you first open Kaxaml, you will find simple markup for a control, as follows:

<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

Like a Window, a Page contains various layout managers and controls. However, unlike a Window, Page objects cannot run as stand-alone entities. Rather, they must be placed inside a suitable host such as a NavigationWindow or a Frame. The good news is that you can type identical markup within a or

scope.

■Note If you change the and elements in the Kaxaml markup window to and
, you can press the F5 key to load a new window onto the screen.

As an initial test, enter the following markup into the XAML pane at the bottom of the tool:






You should now see your page render at the upper part of the Kaxaml editor (see Figure 25-2).

Figure 25-2. Kaxaml is a helpful (and free) tool used to learn the grammar of XAML

As you work with Kaxaml, remember that this tool does not allow you to author any markup that entails code compilation (however, using x:Name is allowed). This includes defining an x:Class attribute (for specifying a code file), entering event handler names in markup, or using any XAML keywords that also entail code compilation (such as FieldModifier or ClassModifier). Any attempt to do so will result in a markup error.

XAML XML Namespaces and XAML “Keywords”
The root element of a WPF XAML document (such as a , , , or
definition) will almost always reference the following two predefined XML namespaces:



The first XML namespace, http://schemas.microsoft.com/winfx/2006/xaml/presentation, maps a slew of WPF .NET namespaces for use by the current *.xaml file (System.Windows, System.Windows.
Controls, System.Windows.Data, System.Windows.Ink, System.Windows.Media, System.Windows. Navigation, etc.).
This one-to-many mapping is hard-coded within the WPF assemblies (WindowsBase.dll, PresentationCore.dll, and PresentationFramework.dll) using the assembly-level [XmlnsDefinition] attribute. For example, if you open the Visual Studio Object Browser and select the PresentationCore.dll assembly, you will see listings such as the following, which essentially imports System.Windows:

[assembly: XmlnsDefinition(“http://schemas.microsoft.com/winfx/2006/xaml/presentation”,
“System.Windows”)]

The second XML namespace, http://schemas.microsoft.com/winfx/2006/xaml, is used to include XAML-specific “keywords” (for lack of a better term) as well as the inclusion of the System.Windows.Markup namespace, as follows:

[assembly: XmlnsDefinition(“http://schemas.microsoft.com/winfx/2006/xaml”,
“System.Windows.Markup”)]

One rule of any well-formed XML document (remember, XAML is an XML-based grammar) is that the opening root element designates one XML namespace as the primary namespace, which is the namespace that contains the most common items. If a root element requires the inclusion of additional secondary namespaces (as seen here), they must be defined using a unique tag prefix (to resolve any possible name clashes). As a convention, the prefix is simply x; however, this can be any unique token you require, such as XamlSpecificStuff.





The obvious downside of defining wordy XML namespace prefixes is you are required to type XamlSpecificStuff each time your XAML file needs to refer to one of the items defined within this XAML-centric XML namespace. Given that XamlSpecificStuff requires many additional keystrokes, just stick with x.
In any case, beyond the x:Name, x:Class, and x:Code keywords, the http://schemas.microsoft.com/ winfx/2006/xaml XML namespace also provides access to additional XAML keywords, the most common of which are shown in Table 25-9.

Table 25-9. XAML Keywords

XAML Keyword Meaning in Life
x:Array Represents a .NET array type in XAML.
x:ClassModifier Allows you to define the visibility of the C# class (internal or public) denoted by the
Class keyword.
x:FieldModifier Allows you to define the visibility of a type member (internal, public, private, or protected) for any named subelement of the root (e.g., a

Notice that within the scope of the tags, you have defined a subscope named

. Within this scope, you have defined a custom . (Do not worry about the exact code for the brush; you’ll learn about WPF graphics in Chapter 28.)
Any property can be set using property-element syntax, which always breaks down to the following pattern:





While any property could be set using this syntax, if you can capture a value as a simple string, you will save yourself typing time. For example, here is a much more verbose way to set the Width of your Button:

Understanding XAML Attached Properties
In addition to property-element syntax, XAML defines a special syntax used to set a value to an attached property. Essentially, an attached property allows a child element to set the value for a property that is defined in a parent element. The general template to follow looks like this:



The most common use of attached property syntax is to position UI elements within one of the WPF layout manager classes (Grid, DockPanel, etc.). The next chapter dives into these panels in some detail; for now, enter the following in Kaxaml:





Here, you have defined a Canvas layout manager that contains an Ellipse. Notice that the Ellipse can inform its parent (the Canvas) where to position its top-left position using attached property syntax.
There are a few items to be aware of regarding attached properties. First and foremost, this is not an all- purpose syntax that can be applied to any property of any parent. For example, the following XAML cannot be parsed without error:




Attached properties are a specialized form of a WPF-specific concept termed a dependency property. Unless a property was implemented in a specific manner, you cannot set its value using attached property syntax. You will explore dependency properties in detail in Chapter 25.

■Note Visual studio has Intellisense, which will show you valid attached properties that can be set by a given element.

Understanding XAML Markup Extensions
As explained, property values are most often represented using a simple string or via property-element syntax. There is, however, another way to specify the value of a XAML attribute, using markup extensions. Markup extensions allow a XAML parser to obtain the value for a property from a dedicated, external class. This can be beneficial given that some property values require several code statements to execute to figure out the value.
Markup extensions provide a way to cleanly extend the grammar of XAML with new functionality. A markup extension is represented internally as a class that derives from MarkupExtension. Note that the chances of you ever needing to build a custom markup extension will be slim to none. However, a subset of XAML keywords (such as x:Array, x:Null, x:Static, and x:Type) are markup extensions in disguise!
A markup extension is sandwiched between curly brackets, like so:

To see some markup extensions in action, author the following into Kaxaml:








Sun Kil Moon
Red House Painters
Besnard Lakes




First, notice that the definition has a new XML namespace declaration, which allows you to gain access to the System namespace of mscorlib.dll. With this XML namespace established, you first make
use of the x:Static markup extension and grab values from OSVersion and ProcessorCount of the System. Environment class.
The x:Type markup extension allows you to gain access to the metadata description of the specified item. Here, you are simply assigning the fully qualified names of the WPF Button and System.Boolean types.
The most interesting part of this markup is the ListBox. Here, you are setting the ItemsSource property to an array of strings declared entirely in markup! Notice here how the x:Array markup extension allows you to specify a set of subitems within its scope:


Sun Kil Moon
Red House Painters
Besnard Lakes

■Note the previous XaML example is used only to illustrate a markup extension in action. as you will see in Chapter 26, there are much easier ways to populate ListBox controls!

Figure 25-3 shows the markup of this in Kaxaml.

Figure 25-3. Markup extensions allow you to set values via the functionality of a dedicated class

You have now seen numerous examples that showcase each of the core aspects of the XAML syntax. As you might agree, XAML is interesting, in that it allows you to describe a tree of .NET objects in a declarative manner. While this is extremely helpful when configuring graphical user interfaces, do remember that XAML can describe any type from any assembly, provided it is a nonabstract type containing a default constructor.

Building WPF Applications Using Visual Studio
Let’s examine how Visual Studio can simplify the construction of WPF programs. While you can build WPF applications using Visual Studio Code, Visual Studio Code does not have any designer support for building WPF applications. Visual Studio, with its rich XAML support, is a more productive IDE when building WPF applications.

■Note here, I will point out some key features of using Visual studio to build WpF applications. Forthcoming chapters will illustrate additional aspects of the Ide where necessary.

The WPF Project Templates
The New Project dialog box of Visual Studio defines a set of WPF project templates, including WPF Application, WPF Class Library, WPF Custom Control Library, and WPF User Control Library. Create a new WPF Application project named WpfTesterApp.

■Note When selecting WpF projects from the Visual studio “add a new project” screen, be sure to select the WpF project templates that do not have “(.net Framework)” in the title. If you select a template with “(.net Framework)” in the title, you will be building your app using .net Framework 4.x.

Examining the project file, you can see that the SDK is set to Microsoft.NET.Sdk. The TargetFramework
value is set to net6.0-windows, and the project will build an executable that uses WPF:



WinExe
net6.0-windows
true
disable
enable

At the time of this writing, the WPF template doesn’t set the flags for either implicit using statements or nullable reference types. Update the project file to enable implicit using statements and disables nullable reference types:



WinExe
net6.0-windows
true
enable
disable

Beyond the project file, the template also provides an initial Window- and the Application-derived class, each represented using a XAML and C# code file.

The Toolbox and XAML Designer/Editor
Visual Studio provides a Toolbox (which you can open via the View menu) that contains numerous WPF controls. The top part of the panel holds the most common controls, and the bottom contains all controls (see Figure 25-4).

Figure 25-4. The Toolbox contains the WPF controls that can be placed on the designer surface

Using a standard drag-and-drop operation, you can place any of these controls onto the window’s designer surface or drag the control into the XAML markup editor at the bottom of the designer. When you do, the initial XAML will be authored on your behalf. Use your mouse to drag a Button control and a
Calendar control onto the designer surface. After you have done so, notice how you can relocate and resize
your controls (and be sure to examine the resulting XAML generated based on your edits).
In addition to building the UI via the mouse and toolbox, you can manually enter your markup using the integrated XAML editor. As you can see in Figure 25-5, you do get IntelliSense support, which can help simplify the authoring of the markup. For example, try to add the Margin property to the element.

Figure 25-5. The WPF Window designer

Take a few moments to add some property values directly in the XAML editor. Be sure you take the time to become comfortable using this aspect of the WPF designer.

Setting Properties Using the Properties Window
After you have placed some controls onto your designer (or manually defined them in the editor), you can then make use of the Properties window to set property values for the selected control, as well as rig up event handlers for the selected control. By way of a simple test, select your Button control on the designer. Now, use the Properties window to change the Background color of the Button using the integrated Brushes editor (see Figure 25-6; you will learn more about the Brushes editor in Chapter 27, during your examination of WPF graphics).

Figure 25-6. The Properties window can be used to configure the UI of a WPF control

■Note the properties window provides a search text area at the top. type in the name of a property you would like to set to quickly find the item in question.

After you have finished tinkering with the Brushes editor, check out the generated markup. It might look something like this:

Handling Events Using the Properties Window
If you want to handle events for a given control, you can also make use of the Properties window, but this time you need to click the Events button at the upper right of the Properties window (look for the lightning bolt icon). Ensure that the button is selected on your designer and locate the Click event. Once you do, double-click directly on the Click event entry. This will cause Visual Studio to automatically build an event handler that takes the following general form:

NameOfControl_NameOfEvent

Since you did not rename your button, the Properties window shows that it generated an event handler named Button_Click (see Figure 25-7).

Figure 25-7. Handling events using the Properties window

As well, Visual Studio generated the corresponding C# event handler in your window’s code file. Here, you can add any sort of code that must execute when the button is clicked. For a quick test, just enter the following code statement:

private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(“You clicked the button!”);
}

Handling Events in the XAML Editor
You can also handle events directly in the XAML editor. By way of an example, place your mouse within the
element and type in the MouseMove event, followed by the equal sign. Once you do, you will see that Visual Studio displays any compatible handlers in your code file (if they exist), as well as the option to create a new event handler (see Figure 25-8).

Figure 25-8. Handling events using the XAML editor

Let the IDE create the MouseMove event handler, enter the following code, and then run the application to see the result:

private void MainWindow_MouseMove (object sender, MouseEventArgs e)
{
this.Title = e.GetPosition(this).ToString();
}

■Note Chapter 29 covers MVVM and the Command pattern, which is a much better way to handle click events in enterprise applications. But if you need only a simple app, handling click events with a straight event handler is perfectly acceptable.

The Document Outline Window
When you work with any XAML-based project, you will certainly make use of a healthy amount of markup to represent your UIs. When you begin to work with more complex XAML, it can be useful to visualize the markup to quickly select an item to edit on the Visual Studio designer.
Currently, your markup is quite tame because you have defined only a few controls within the initial
. Nevertheless, locate the Document Outline window in your IDE, mounted by default on the left side of Visual Studio (if you cannot locate it, simply activate it using the View ➤ Other Windows menu option). Now, make sure your XAML designer is the active window in the IDE (rather than the C# code file), and you will notice the Document Outline window displays the nested elements (see Figure 25-9).

Figure 25-9. Visualizing your XAML via the Document Outline window

This tool also provides a way to temporarily hide a given item (or set of items) on the designer as well as lock items to prevent additional edits from taking place. In the next chapter, you will see how the Document Outline window also provides many other features to group selected items into new layout managers (among other features).

Enable or Disable the XAML Debugger
When you run the application, you will see the MainWindow on the screen. You will also see the interactive debugger, as shown in Figure 25-10 (minimized) and Figure 25-11 (expanded).

Figure 25-10. XAML UI debugger (minimized)

Figure 25-11. XAML UI debugger

If you want to turn this off, you will find the entries for XAML debugging under Tools ➤ Options ➤ Debugging ➤ XAML Hot Reload. Deselect the top box to prevent the debugger window from overlaying your windows. Figure 25-12 shows the entries.

Figure 25-12. Enabling/disabling XAML UI debugging

Examining the App.xaml File
How did the project know what window to launch? Even more intriguing, if you examine the code files in your application, you will also see that there is not a Main() method anywhere to be found. You have learned throughout this book that applications must have an entry point, so how does .NET know how to launch your app? Fortunately, both plumbing items are handled for you through the Visual Studio templates and the WPF framework.
To solve the riddle of which window to launch, the App.xaml file defines an application class through markup. In addition to the namespace definitions, it defines application properties such as the StartupUri, application-wide resources (covered in Chapter 28), and specific handlers for application events such as Startup and Exit. The StartupUri indicates which window to load on startup. Open the App.xaml file and examine the markup, shown here:




Using the XAML designer and using Visual Studio code completion, add handlers for the Startup and
Exit events. Your updated XAML should look like this (notice the change in bold):




If you look at the App.xaml.cs file, it should look like this:

public partial class App : Application
{
private void App_OnStartup(object sender, StartupEventArgs e)
{
}
private void App_OnExit(object sender, ExitEventArgs e)
{
}
}

Note that the class is marked as partial. In fact, all the code-behind windows for XAML files are marked partial. That is key to solving the riddle of where the Main() method lives. But first, you need to examine what happens when msbuild.exe processes XAML files.

Mapping the Window XAML Markup to C# Code
When msbuild.exe processed your *.csproj file, it produced three files for each XAML file in your project with the form of *.g.cs (where g denotes autogenerated), *.g.i.cs (where i denotes IntelliSense), and
*.baml (for Binary Application Markup Language). These are saved into the \obj\Debug directory (and can be viewed in Solution Explorer by clicking the Show All Files button). You might have to hit the Refresh button in Solution Explorer to see them since they are not part of the actual project but build artifacts.
To make the most sense of the process, it is helpful to provide names for your controls. Go ahead and provide names for the Button and Calendar controls, as follows:


Now rebuild your solution (or project) and refresh the files in Solution Explorer. If you open the MainWindow.g.cs file into a text editor, you will find a class named MainWindow, which extends the Window base class. The name of this class is a direct result of the x:Class attribute in the start tag.
This class defines a private member variable of type bool (named _contentLoaded), which was not directly accounted for in the XAML markup. This data member is used to determine (and ensure) the content of the window is assigned only once. This class also contains a member variable of type System. Windows.Controls.Button, named ClickMe. The name of the control is based on the x:Name (or the shorthand form Name) attribute value within the opening

发表评论