- Elements and Attributes
- Namespaces
- Property Elements
- Type Converters
- Markup Extensions
- Children of Object Elements
- Mixing XAML with Procedural Code
- XAML Keywords
- Summary
Mixing XAML with Procedural Code
XAML-based Windows Store apps are a mix of XAML and procedural code. This section covers the two ways that XAML and code can be mixed together: dynamically loading and parsing XAML yourself, or leveraging the built-in support in Visual Studio projects.
Loading and Parsing XAML at Runtime
The Windows.UI.Xaml.Markup namespace contains a simple XamlReader class with a simple static Load method. Load can parse a string containing XAML, create the appropriate Windows Runtime objects, and return an instance of the root element. So, with a string containing XAML content somewhat like MainPage.xaml from the preceding chapter, the following code could be used to load and retrieve the root Page object:
string
xamlString = ...;// Get the root element, which we know is a Page
Page
p = (Page
)(xamlString);
XamlReader
.Load
After Load returns, the entire hierarchy of objects in the XAML file is instantiated in memory, so the XAML itself is no longer needed. Now that an instance of the root element exists, you can retrieve child elements by making use of the appropriate content properties or collection properties. The following code assumes that the Page has a StackPanel object as its content, whose fifth child is a Stop button:
string
xamlString = ...;// Get the root element, which we know is a Page
Page
p = (Page
)XamlReader
.Load(xamlString);
// Grab the Stop button by walking the children (with hard-coded knowledge!)
StackPanel
panel = (StackPanel
)p.Content;
Button
stopButton = (Button
)panel.Children[4];
With a reference to the Button control, you can do whatever you want: Set additional properties (perhaps using logic that is hard or impossible to express in XAML), attach event handlers, or perform additional actions that you can’t do from XAML, such as calling its methods.
Of course, the code that uses a hard-coded index and other assumptions about the user interface structure isn’t satisfying, because simple changes to the XAML can break it. Instead, you could write code to process the elements more generically and look for a Button element whose content is a "Stop" string, but that would be a lot of work for such a simple task. In addition, if you want the Button to contain graphical content, how can you easily identify it in the presence of multiple Buttons?
Fortunately, XAML supports naming of elements so they can be found and used reliably from procedural code.
Naming XAML Elements
The XAML language namespace has a Name keyword that enables you to give any element a name. For the simple Stop button that we’re imagining is embedded somewhere inside a Page, the Name keyword can be used as follows:
<
Button
x:Name
=
"stopButton"
>
Stop</
Button
>
With this in place, you can update the preceding C# code to use Page’s FindName method that searches its children (recursively) and returns the desired instance:
string
xamlString = ...;// Get the root element, which we know is a Page
Page
p = (Page
)XamlReader
.Load(xamlString);
// Grab the Stop button, knowing only its name
Button
stopButton = (Button
)p.FindName("stopButton
");
FindName is not unique to Page; it is defined on FrameworkElement, a base class for many important classes in the XAML UI Framework.
Visual Studio’s Support for XAML and Code-Behind
Loading and parsing XAML at runtime can be interesting for some limited dynamic scenarios. Windows Store projects, however, leverage work done by MSBuild and Visual Studio to make the combination of XAML and procedural code more seamless. When you compile a project with XAML files, the XAML is included as a resource in the app being built and the plumbing that connects XAML with procedural code is generated automatically.
The automatic connection between a XAML file and a code-behind file is enabled by the Class keyword from the XAML language namespace, as seen in the preceding chapter. For example, MainPage.xaml had the following:
<
Page
...
x:Class
="BlankApp.MainPage"
>
...</
Page
>
This causes the XAML content to be treated as a partial class definition for a class called MainPage (in the BlankApp namespace) derived from Page. The other pieces of the partial class definition reside in auto-generated files as well as the MainPage.xaml.cs code-behind file. Visual Studio’s Solution Explorer ties these two files together by making the code-behind file a subnode of the XAML file, but that is an optional cosmetic effect enabled by the following XML inside of the .csproj project file:
<
Compile
Include
="MainPage.xaml.cs">
<
DependentUpon
>MainPage.xaml
</
DependentUpon
>
</
Compile
>
You can freely add members to the class in the code-behind file. And if you reference any event handlers in XAML (via event attributes such as Click on Button), this is where they should be defined.
Whenever you add a page to a Visual Studio project (via Add New Item...), Visual Studio automatically creates a XAML file with x:Class on its root, creates the code-behind source file with the partial class definition, and links the two together so they are built properly.
The additional auto-generated files alluded to earlier contain some “glue code” that you normally never see and you should never directly edit. For a XAML file named MainPage.xaml, they are:
- MainPage.g.cs, which contains code that attaches event handlers to events for each event attribute assigned in the XAML file.
- MainPage.g.i.cs, which contains a field definition (private by default) for each named element in the XAML file, using the element name as the field name. It also contains an InitializeComponent method that the root class’s constructor must call in the code-behind file. This file is meant to be helpful to IntelliSense, which is why it has an “i” in its name.
The “g” in both filenames stands for generated. Both generated source files contain a partial class definition for the same class partially defined by the XAML file and code-behind file.
If you peek at the implementation of InitializeComponent inside the auto-generated file, you’ll see that the hookup between C# and XAML isn’t so magical after all. It looks a lot like the code shown previously for manually loading XAML content and grabbing named elements from the tree of instantiated objects. Here’s what the method looks like for the preceding chapter’s MainPage if a Button named stopButton were added to it:
public void
InitializeComponent() {if
(_contentLoaded)return
; _contentLoaded =true
;Application
.LoadComponent(this
,new
System.Uri
("ms-appx:///MainPage.xaml"
), Windows.UI.Xaml.Controls.Primitives.ComponentResourceLocation
.Application); stopButton = (Windows.UI.Xaml.Controls.Button
)this
.FindName("stopButton
"); }
The LoadComponent method is much like XamlReader’s Load method, except it works with a reference to an app’s resource file.