© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
A. Troelsen, P. JapiksePro C# 9 with .NET 5https://doi.org/10.1007/978-1-4842-6939-8_24

24. Introducing Windows Presentation Foundation and XAML

Andrew Troelsen1   and Phillip Japikse2
(1)
Minneapolis, MN, USA
(2)
West Chester, OH, USA
 

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 24-1).
Table 24-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 24-2, which illustrates the desktop development model ushered in as of .NET 3.0.
Table 24-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 Core assemblies. Table 24-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 24-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 Core classes, interfaces, structures, enumerations, and delegates. Table 24-4 describes the role of some (but certainly not all) of the important namespaces.
Table 24-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 24-5 details some of the key properties.
Table 24-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 24-1, which shows the inheritance chain (and implemented interfaces) for System.Windows.Window as seen through the Visual Studio Object Browser.
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig1_HTML.jpg
Figure 24-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 5 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):
<!-- Setting the Content value in the opening element -->
<Button Height="80" Width="100" Content="OK"/>
Note

The Content property can also be set in C# code, which allows you to change the interior of a control at runtime.

However, content can be almost anything. For example, let’s say you want to have a “button” that has something more interesting than a simple string, perhaps a custom graphic and a blurb of text. In other UI frameworks such as Windows Forms, you would be required to build a custom control, which could entail quite a bit of code and a whole new class to maintain. With the WPF content model, there is no need to do so.

When you want to assign the Content property to a value that cannot be captured as a simple array of characters, you cannot assign it using an attribute in the control’s opening definition. Rather, you must define the content data implicitly, within the element’s scope. For example, the following <Button> contains a <StackPanel> as content, which itself contains some unique data (an <Ellipse> and <Label>, to be exact):
<!-- Implicitly setting the Content property with complex data -->
<Button Height="80" Width="100">
  <StackPanel>
    <Ellipse Fill="Red" Width="25" Height="25"/>
    <Label Content ="OK!"/>
  </StackPanel>
</Button>
You can also make use of XAML’s property-element syntax to set complex content. Consider the following functionally equivalent <Button> definition, which sets the Content property explicitly using property-element syntax (again, you will find more information on XAML later in this chapter, so don’t sweat the details just yet):
<!-- Setting the Content property using property-element syntax -->
<Button Height="80" Width="100">
  <Button.Content>
    <StackPanel>
      <Ellipse Fill="Red" Width="25" Height="25"/>
      <Label Content ="OK!"/>
    </StackPanel>
  </Button.Content>
</Button>

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 25 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 24-6 documents some key members of the Control type, grouped by related functionality.
Table 24-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 24-7 hits the highlights.
Table 24-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 24-8.
Table 24-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 25 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 <Page> control, as follows:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
  </Grid>
</Page>

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 <Page> or <Window> scope.

Note

If you change the <Page> and </Page> elements in the Kaxaml markup window to <Window> and </Window>, 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:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <!-- A button with custom content -->
    <Button Height="100" Width="100">
      <Ellipse Fill="Green" Height="50" Width="50"/>
    </Button>
  </Grid>
</Page>
You should now see your page render at the upper part of the Kaxaml editor (see Figure 24-2).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig2_HTML.jpg
Figure 24-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 <Window>, <Page>, <UserControl>, or <Application> definition) will almost always reference the following two predefined XML namespaces:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
  </Grid>
</Page>

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.
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:XamlSpecificStuff="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <!-- A button with custom content -->
    <Button XamlSpecificStuff:Name="button1" Height="100" Width="100">
      <Ellipse Fill="Green" Height="50" Width="50"/>
    </Button>
  </Grid>
</Page>

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 24-9.
Table 24-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 <Button> within a <Window> element). A named element is defined using the Name XAML keyword.

x:Key

Allows you to establish a key value for a XAML item that will be placed into a dictionary element.

x:Name

Allows you to specify the generated C# name of a given XAML element.

x:Null

Represents a null reference.

x:Static

Allows you to refer to a static member of a type.

x:Type

The XAML equivalent of the C# typeof operator (it will yield a System.Type based on the supplied name).

x:TypeArguments

Allows you to establish an element as a generic type with a specific type parameter (e.g., List<int> vs. List<bool>).

In addition to these two necessary XML namespace declarations, it is possible, and sometimes necessary, to define additional tag prefixes in the opening element of a XAML document. You will typically do so whenever you need to describe in XAML a .NET Core class defined in an external assembly.

For example, say you have built a few custom WPF controls and packaged them in a library named MyControls.dll. Now, if you want to create a new Window that uses these controls, you can establish a custom XML namespace that maps to your library using the clr-namespace and assembly tokens. Here is some example markup that creates a tag prefix named myCtrls, which can be used to access controls in your library:
<Window x:Class="WpfApplication1.MainWindow"
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:myCtrls="clr-namespace:MyControls;assembly=MyControls"
  Title="MainWindow" Height="350" Width="525">
  <Grid>
    <myCtrls:MyCustomControl />
  </Grid>
</Window>

The clr-namespace token is assigned to the name of the .NET Core namespace in the assembly, while the assembly token is set to the friendly name of the external *.dll assembly. You can use this syntax for any external .NET Core library you would like to manipulate in markup. While there is no need to do so at the current time, future chapters will require you to define custom XML namespace declarations to describe types in markup.

Note

If you need to define a class in markup that is part of the current assembly but in a different .NET Core namespace, your xmlns tag prefix is defined without the assembly= attribute, like so: xmlns:myCtrls="clr-namespace:SomeNamespaceInMyApp".

Controlling Class and Member Variable Visibility

You will see many of these keywords in action where required in the chapters to come; however, by way of a simple example, consider the following XAML <Window> definition that makes use of the ClassModifier and FieldModifier keywords, as well as x:Name and x:Class (remember that kaxaml.exe will not allow you to make use of any XAML keyword that entails code compilation, such as x:Code, x:FieldModifier, or x:ClassModifier):
<!-- This class will now be declared internal in the *.g.cs file -->
<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal"
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <!-- This button will be public in the *.g.cs file -->
  <Button x:Name ="myButton" x:FieldModifier ="public" Content = "OK"/>
</Window>
By default, all C#/XAML type definitions are public, while members default to internal. However, based on your XAML definition, the resulting autogenerated file contains an internal class type with a public Button variable.
internal partial class MainWindow :
System.Windows.Window,
  System.Windows.Markup.IComponentConnector
{
  public System.Windows.Controls.Button myButton;
...
}

XAML Elements, XAML Attributes, and Type Converters

After you have established your root element and any required XML namespaces, your next task is to populate the root with a child element. In a real-world WPF application, the child will be a layout manager (such as a Grid or StackPanel) that contains, in turn, any number of additional UI elements that describe the user interface. The next chapter examines these layout managers in detail, so for now just assume that your <Window> type will contain a single Button element.

As you have already seen over the course of this chapter, XAML elements map to a class or structure type within a given .NET Core namespace, while the attributes within the opening element tag map to properties or events of the type. To illustrate, enter the following <Button> definition into Kaxaml:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <!-- Configure the look and feel of a Button -->
    <Button Height="50" Width="100" Content="OK!"
            FontSize="20" Background="Green" Foreground="Yellow"/>
  </Grid>
</Page>
Notice that the values assigned to each property have been captured as a simple text value. This may seem like a complete mismatch of data types because if you were to make this Button in C# code, you would not assign string objects to these properties but would make use of specific data types. For example, here is the same button authored in code:
public void MakeAButton()
{
  Button myBtn = new Button();
  myBtn.Height = 50;
  myBtn.Width = 100;
  myBtn.FontSize = 20;
  myBtn.Content = "OK!";
  myBtn.Background = new SolidColorBrush(Colors.Green);
  myBtn.Foreground = new SolidColorBrush(Colors.Yellow);
}

As it turns out, WPF ships with several type converter classes, which will be used to transform simple text values into the correct underlying data type. This process happens transparently (and automatically).

While this is all well and good, there will be many times when you need to assign a much more complex value to a XAML attribute, which cannot be captured as a simple string. For example, let’s say you want to build a custom brush to set the Background property of the Button. If you are building the brush in code, it is quite straightforward, as shown here:
public void MakeAButton()
{
...
  // A fancy brush for the background.
  LinearGradientBrush fancyBruch =
    new LinearGradientBrush(Colors.DarkGreen, Colors.LightGreen, 45);
  myBtn.Background = fancyBruch;
  myBtn.Foreground = new SolidColorBrush(Colors.Yellow);
}

How can you represent your complex brush as a string? Well, you cannot! Thankfully, XAML provides a special syntax that can be used whenever you need to assign a property value to a complex object, termed property-element syntax .

Understanding XAML Property-Element Syntax

Property-element syntax allows you to assign complex objects to a property. Here is a XAML description for a Button that makes use of a LinearGradientBrush to set its Background property:
<Button Height="50" Width="100" Content="OK!"
        FontSize="20" Foreground="Yellow">
  <Button.Background>
    <LinearGradientBrush>
      <GradientStop Color="DarkGreen" Offset="0"/>
      <GradientStop Color="LightGreen" Offset="1"/>
    </LinearGradientBrush>
  </Button.Background>
</Button>

Notice that within the scope of the <Button> and </Button> tags, you have defined a subscope named <Button.Background>. Within this scope, you have defined a custom <LinearGradientBrush>. (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:
<DefiningClass>
  <DefiningClass.PropertyOnDefiningClass>
    <!-- Value for Property here! -->
  </DefiningClass.PropertyOnDefiningClass>
</DefiningClass>
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:
<Button Height="50" Content="OK!"
        FontSize="20" Foreground="Yellow">
...
  <Button.Width>
    100
  </Button.Width>
</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:
<ParentElement>
  <ChildElement ParentElement.PropertyOnParent = "Value">
</ParentElement>
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:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Canvas Height="200" Width="200" Background="LightBlue">
    <Ellipse Canvas.Top="40" Canvas.Left="40" Height="20" Width="20" Fill="DarkBlue"/>
  </Canvas>
</Page>

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:
<!-- Error! Set Background property on Canvas via attached property? -->
<Canvas Height="200" Width="200">
  <Ellipse Canvas.Background="LightBlue"
           Canvas.Top="40" Canvas.Left="90"
           Height="20" Width="20" Fill="DarkBlue"/>
</Canvas>

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:
<Element PropertyToSet = "{MarkUpExtension}"/>
To see some markup extensions in action, author the following into Kaxaml:
<Page
  xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:CorLib="clr-namespace:System;assembly=mscorlib">
  <StackPanel>
    <!-- The Static markup extension lets us obtain a value
         from a static member of a class -->
    <Label Content ="{x:Static CorLib:Environment.OSVersion}"/>
    <Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>
    <!-- The Type markup extension is a XAML version of
         the C# typeof operator -->
    <Label Content ="{x:Type Button}" />
    <Label Content ="{x:Type CorLib:Boolean}" />
    <!-- Fill a ListBox with an array of strings! -->
    <ListBox Width="200" Height="50">
      <ListBox.ItemsSource>
        <x:Array Type="CorLib:String">
          <CorLib:String>Sun Kil Moon</CorLib:String>
          <CorLib:String>Red House Painters</CorLib:String>
          <CorLib:String>Besnard Lakes</CorLib:String>
        </x:Array>
      </ListBox.ItemsSource>
    </ListBox>
  </StackPanel>
</Page>

First, notice that the <Page> 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:
<x:Array Type="CorLib:String">
  <CorLib:String>Sun Kil Moon</CorLib:String>
  <CorLib:String>Red House Painters</CorLib:String>
  <CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
Note

The previous XAML example is used only to illustrate a markup extension in action. As you will see in Chapter 25, there are much easier ways to populate ListBox controls!

Figure 24-3 shows the markup of this <Page> in Kaxaml.
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig3_HTML.jpg
Figure 24-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 App, WPF Custom Control Library, and WPF User Control Library. Create a new WPF App (.NET) 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 have “(.NET)” in the title, and not “(.NET Framework).” The current version of .NET Core has been renamed to simply .NET 5. If you select a template with “(.NET Framework)” in the title, you will be building your app using .NET Framework 4.x.

Beyond setting the project SDK to Microsoft.NET.Sdk, you will be provided with initial Window- and Application-derived classes, 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 24-4).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig4_HTML.jpg
Figure 24-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 24-5, you do get IntelliSense support, which can help simplify the authoring of the markup. For example, try to add the Background property to the opening <Window> element.
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig5_HTML.jpg
Figure 24-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 24-6; you will learn more about the Brushes editor in Chapter 26, during your examination of WPF graphics).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig6_HTML.jpg
Figure 24-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:
<Button Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75">
  <Button.Background>
    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
      <GradientStop Color="Black" Offset="0"/>
      <GradientStop Color="#FFE90E0E" Offset="1"/>
      <GradientStop Color="#FF1F4CE3"/>
    </LinearGradientBrush>
  </Button.Background>
</Button>

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 24-7).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig7_HTML.jpg
Figure 24-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 <Window> 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 24-8).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig8_HTML.jpg
Figure 24-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 28 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 <Grid>. 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 24-9).
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig9_HTML.jpg
Figure 24-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 24-10.
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig10_HTML.jpg
Figure 24-10.

XAML UI debugging

If you want to turn this off, you will find the entries for XAML debugging under Tools ➤ Options ➤ Debugging ➤ Hot Reload. Deselect the top box to prevent the debugger window from overlaying your windows. Figure 24-11 shows the entries.
../images/340876_10_En_24_Chapter/340876_10_En_24_Fig11_HTML.jpg
Figure 24-11.

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 27), 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:
<Application x:Class="WpfTesterApp.App"
             xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfTesterApp"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
    </Application.Resources>
</Application>
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):
<Application x:Class="WpfTesterApp.App"
             xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfTesterApp"
             StartupUri="MainWindow.xaml" Startup="App_OnStartup" Exit="App_OnExit">
    <Application.Resources>
    </Application.Resources>
</Application>
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 objDebug 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:
<Button Name="ClickMe" Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0"
       VerticalAlignment="Top" Width="75" Click="Button_Click">
//omitted for brevity
</Button>
<Calendar Name="MyCalendar" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/>

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 <Window> 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 <Button> declaration. What you do not see is a variable for the Calendar control. This is because msbuild.exe creates a variable for each named control in your XAML that has related code in the code-behind. If there is not any code, there isn’t any need for a variable. To make matters more confusing, if you had not named the Button control, there wouldn’t be a variable for it either. This is part of the magic of WPF and is tied into the IComponentConnector interface implementation.

The compiler-generated class also explicitly implements the WPF IComponentConnector interface defined in the System.Windows.Markup namespace. This interface defines a single method called Connect(), which has been implemented to prep each control defined in the markup and rig up the event logic as specified within the original MainWindow.xaml file. You can see the handler being set up for the ClickMe click event. Before the method completes, the _contentLoaded member variable is set to true. Here is the crux of the method:
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target)
{
  switch (connectionId)
  {
    case 1:
    this.ClickMe = ((System.Windows.Controls.Button)(target));
    #line 11 "....MainWindow.xaml"
    this.ClickMe.Click += new System.Windows.RoutedEventHandler(this.Button_Click);
    #line default
    #line hidden
    return;
  }
  this._contentLoaded = true;
}
To show the effects of unnamed controls with code, add an event handler for the SelectedDatesChanged event on the calendar. Rebuild the application, refresh the files, and reload the MainWindow.g.cs file. In the Connect() method, you now see the following code block:
#line 20 "....MainWindow.xaml"
this.MyCalendar.SelectedDatesChanged += new
     System.EventHandler<System.Windows.Controls.SelectionChangedEventArgs>(
         this.MyCalendar_OnSelectedDatesChanged);

This tells the framework that the control on line 20 of the XAML file has the SelectedDatesChanged event handler assigned, as shown in the preceding code.

Finally, the MainWindow class defines and implements a method named InitializeComponent(). You might expect that this method contains code that sets up the look and feel of each control by setting various properties (Height, Width, Content, etc.). However, this is not the case! How then do the controls take on the correct UI? The logic with InitializeComponent() resolves the location of an embedded assembly resource that is named identical to the original *.xaml file, like so:
public void InitializeComponent() {
  if (_contentLoaded) {
    return;
    }
    _contentLoaded = true;
    System.Uri resourceLocater = new System.Uri("/WpfTesterApp;component/mainwindow.xaml",
        System.UriKind.Relative);
    #line 1 "....MainWindow.xaml"
    System.Windows.Application.LoadComponent(this, resourceLocater);
    #line default
    #line hidden
}

At this point, the question becomes what exactly is this embedded resource?”

The Role of BAML

As you might have guessed from the name, Binary Application Markup Language (BAML) is a compact, binary representation of the original XAML data. This *.baml file is embedded as a resource (via a generated *.g.resources file) into the compiled assembly. This BAML resource contains all the data needed to establish the look and feel of the UI widgets (again, such as the Height and Width properties).

The important takeaway here is to understand that a WPF application contains within itself a binary representation (the BAML) of the markup. At runtime, this BAML will be plucked out of the resource container and used to make sure all windows and controls are initialized to the correct look and feel.

Also, remember that the name of these binary resources are identical to the name of the stand-alone *.xaml files you authored. However, this does not imply in any way that you must distribute the loose *.xaml files with your compiled WPF program. Unless you build a WPF application that will dynamically load and parse *.xaml files at runtime, you will never need to ship the original markup.

Solving the Mystery of Main()

Now that you know how the MSBuild process works, open the App.g.cs file. Here you will find the autogenerated Main() method that initializes and runs your application object.
public static void Main() {
  WpfTesterApp.App app = new WpfTesterApp.App();
  app.InitializeComponent();
  app.Run();
}
The InitializeComponent() method configures the application properties, including the StartupUri and the event handlers for the Startup and Exit events.
public void InitializeComponent() {
    #line 5 "....App.xaml"
    this.Startup += new System.Windows.StartupEventHandler(this.App_OnStartup);
    #line default
    #line hidden
    #line 5 "....App.xaml"
    this.Exit += new System.Windows.ExitEventHandler(this.App_OnExit);
    #line default
    #line hidden
    #line 5 "....App.xaml"
    this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative);
    #line default
    #line hidden
}

Interacting with Application-Level Data

Recall that the Application class defines a property named Properties, which allows you to define a collection of name-value pairs via a type indexer. Because this indexer has been defined to operate on type System.Object, you are able to store any sort of item within this collection (including your custom classes) to be retrieved at a later time using a friendly moniker. Using this approach, it is simple to share data across all windows in a WPF application.

To illustrate, you will update the current Startup event handler to check the incoming command-line arguments for a value named /GODMODE (a common cheat code for many PC video games). If you find this token, you will establish a bool value set to true within the properties collection of the same name (otherwise, you will set the value to false).

This sounds simple enough, but how are you going to pass the incoming command-line arguments (typically obtained from the Main() method) to your Startup event handler? One approach is to call the static Environment.GetCommandLineArgs() method. However, these same arguments are automatically added to the incoming StartupEventArgs parameter and can be accessed via the Args property. Here is the first update to the current code base:
private void App_OnStartup(object sender, StartupEventArgs e)
{
  Application.Current.Properties["GodMode"] = false;
  // Check the incoming command-line arguments and see if they
  // specified a flag for /GODMODE.
  foreach (string arg in e.Args)
  {
    if (arg.Equals("/godmode",StringComparison.OrdinalIgnoreCase))
    {
      Application.Current.Properties["GodMode"] = true;
      break;
    }
  }
}
Application-wide data can be accessed from anywhere within the WPF application. All you are required to do is obtain an access point to the global application object (via Application.Current) and investigate the collection. For example, you could update the Click event handler of the Button as so:
private void Button_Click(object sender, RoutedEventArgs e)
{
  // Did user enable /godmode?
  if ((bool)Application.Current.Properties["GodMode"])
  {
    MessageBox.Show("Cheater!");
  }
}
With this, if you enter the /godmode command-line argument into the Debug tab in the project properties and then run the program, you will be shamed, and the program will exit. You can also run the program from a command line by entering the following command (open a command prompt and navigate to the bin/debug directory):
WpfAppAllCode.exe /godmode

You will see the shameful message box displayed when terminating the application.

Note

Recall that you can supply command-line arguments within Visual Studio. Simply double-click the Properties icon within Solution Explorer, click the Debug tab from the resulting editor, and enter /godmode within the “Command line arguments” editor.

Handling the Closing of a Window Object

End users can shut down a window by using numerous built-in system-level techniques (e.g., clicking the X close button on the window’s frame) or by indirectly calling the Close() method in response to some user interaction element (e.g., File ➤ Exit). In either case, WPF provides two events that you can intercept to determine whether the user is truly ready to shut down the window and remove it from memory. The first event to fire is Closing, which works in conjunction with the CancelEventHandler delegate.

This delegate expects target methods to take System.ComponentModel.CancelEventArgs as the second parameter. CancelEventArgs provides the Cancel property, which when set to true will prevent the window from actually closing (this is handy when you have asked the user if he really wants to close the window or if perhaps he would like to save his work first).

If the user does indeed want to close the window, CancelEventArgs.Cancel can be set to false (which is the default setting). This will then cause the Closed event to fire (which works with the System.EventHandler delegate), making it the point at which the window is about to be closed for good.

Update the MainWindow class to handle these two events by adding these code statements to the current constructor, like so:
public MainWindow()
{
  InitializeComponent();
  this.Closed+=MainWindow_Closed;
  this.Closing += MainWindow_Closing;
}
Now, implement the corresponding event handlers as so:
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
  // See if the user really wants to shut down this window.
  string msg = "Do you want to close without saving?";
  MessageBoxResult result = MessageBox.Show(msg,
    "My App", MessageBoxButton.YesNo, MessageBoxImage.Warning);
  if (result == MessageBoxResult.No)
  {
    // If user doesn't want to close, cancel closure.
    e.Cancel = true;
  }
}
private void MainWindow_Closed(object sender, EventArgs e)
{
  MessageBox.Show("See ya!");
}

Now, run your program and attempt to close the window, either by clicking the X icon in the upper right of the window or by clicking the button control. You should see the confirmation dialog asking if you really want to leave. If you answer Yes, you will then see the farewell message. Clicking the No button will keep the window in memory.

Intercepting Mouse Events

The WPF API provides several events you can capture to interact with the mouse. Specifically, the UIElement base class defines mouse-centric events such as MouseMove, MouseUp, MouseDown, MouseEnter, MouseLeave, and so forth.

Consider, for example, the act of handling the MouseMove event. This event works in conjunction with the System.Windows.Input.MouseEventHandler delegate, which expects its target to take a System.Windows.Input.MouseEventArgs type as the second parameter. Using MouseEventArgs, you can extract the (x, y) position of the mouse and other relevant details. Consider the following partial definition:
public class MouseEventArgs : InputEventArgs
{
...
  public Point GetPosition(IInputElement relativeTo);
  public MouseButtonState LeftButton { get; }
  public MouseButtonState MiddleButton { get; }
  public MouseDevice MouseDevice { get; }
  public MouseButtonState RightButton { get; }
  public StylusDevice StylusDevice { get; }
  public MouseButtonState XButton1 { get; }
  public MouseButtonState XButton2 { get; }
}
Note

The XButton1 and XButton2 properties allow you to interact with “extended mouse buttons” (such as the “next” and “previous” buttons found on some mouse controls). These are often used to interact with a browser’s history list to navigate between visited pages.

The GetPosition() method allows you to get the (x, y) value relative to a UI element on the window. If you are interested in capturing the position relative to the activated window, simply pass in this. Handle the MouseMove event in the constructor of your MainWindow class, like so:
public MainWindow(string windowTitle, int height, int width)
{
...
  this.MouseMove += MainWindow_MouseMove;
}
Here is an event handler for MouseMove that will display the location of the mouse in the window’s title area (notice you are translating the returned Point type into a text value via ToString()):
private void MainWindow_MouseMove(object sender,
  System.Windows.Input.MouseEventArgs e)
{
  // Set the title of the window to the current (x,y) of the mouse.
  this.Title = e.GetPosition(this).ToString();
}

Intercepting Keyboard Events

Processing keyboard input for the focused window is also straightforward. UIElement defines events that you can capture to intercept keypresses from the keyboard on the active element (e.g., KeyUp, KeyDown). The KeyUp and KeyDown events both work with the System.Windows.Input.KeyEventHandler delegate, which expects the target’s second event handler to be of type KeyEventArgs, which defines several public properties of interest, shown here:
public class KeyEventArgs : KeyboardEventArgs
{
...
  public bool IsDown { get; }
  public bool IsRepeat { get; }
  public bool IsToggled { get; }
  public bool IsUp { get; }
  public Key Key { get; }
  public KeyStates KeyStates { get; }
  public Key SystemKey { get; }
}
To illustrate handling the KeyDown event in the constructor of MainWindow (just like you did for the previous events), implement the following event handler that changes the content of the button with the currently pressed key:
private void MainWindow0s_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
  // Display key press on the button.
  ClickMe.Content = e.Key.ToString();
}

At this point in the chapter, WPF might look like nothing more than yet another GUI framework that is providing (more or less) the same services as Windows Forms, MFC, or VB6. If this were in fact the case, you might question the need for yet another UI toolkit. To truly see what makes WPF so unique, you require an understanding of the XML-based grammar, XAML.

Summary

Windows Presentation Foundation (WPF) is a user interface toolkit introduced with the release of .NET 3.0. The major goal of WPF is to integrate and unify previously unrelated desktop technologies (2D graphics, 3D graphics, window and control development, etc.) into a single, unified programming model. Beyond this point, WPF programs typically make use of XAML, which allows you to declare the look and feel of your WPF elements via markup.

Recall that XAML allows you to describe trees of .NET objects using a declarative syntax. During this chapter’s investigation of XAML, you were exposed to several new bits of syntax, including property-element syntax and attached properties, as well as the role of type converters and XAML markup extensions.

XAML is a key aspect for any production-level WPF application. The final example of this chapter gave you a chance to build a WPF application that showed many of the concepts discussed in this chapter. The next chapters will dive deeper into these concepts as well as introduce many more.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.210.17