In-Depth

XAML: Rethink How You Code UIs

XAML (and WPF and WF) promise to change how we program. But before you can put it to use, you need a firm understanding of what this technology is and what its strengths are.

Technology Toolbox: VB .NET, C#

Developers at Microsoft have been busy revolutionizing how we program of late. Nowhere are these changes more evident today than when creating user interfaces (UIs), where, instead of CodeDOM generation of untouchable files, you'll program with the intrinsically declarative model of XAML. No doubt about it: It takes a bit of time to get used to the XAML syntax and other new features. But this powerful core creates a foundation for a wide array of exciting programming possibilities with the potential to upend how we present information in Windows Presentation Foundation (WPF) and manage tasks in Windows Workflow Foundation (WF).

The core features are interesting mostly because of what they empower you to achieve. But you have to step back and understand the sometimes grimy details to use the core pieces to your advantage. XAML is part of a bigger picture that also includes dependency properties, commands, and routed events. This article will help you understand these core features and lay the groundwork for coming articles in WPF and WF.

XAML isn't anything fancy per se; basically, it's the new markup language that describes WPF output and can describe WF workflows. It's a declarative format, which is hard to get excited about because we've been using designers and ignoring the code they generate for years. The hidden partial classes for Windows Forms, Web Forms, and XAML all do the same thing--they describe .NET objects. While XAML may look a lot like HTML they don't do the same things. HTML is interpreted through an engine, rather than by defining runtime objects. Objects defined through XAML are loaded by a XAML reader, such as the ones supporting the WPF and the WF runtimes. XAML is nothing more than object definitions, so it has no flow control or anything that you'd perceive as "code."

Microsoft has implied that XAML makes creating design tools easier, but the current crop of tools using XAML haven't quite lived up to that promise. I am a huge XML fan, so I love that object definitions have moved from obscure and unpredictably formatted .NET code into easily read XML files that emphasize nested containers and properties. I'm looking forward to visual design tools, but not waiting until they get here.

XAML is worth understanding for several of reasons. It creates a language-neutral description of your layout. With a minimum of .NET code in your user interface (most will be in business objects), you can use XAML to minimize investment in today's language technologies. XAML is metadata, so you can also use it to limit your investment in WF and WPF. It will be far easier to write translators from XAML into the next great thing. Another plus: You can create and load XAML at runtime, which makes it easier to create dynamic user interfaces. Finally, XAML enables automatic conversions that simplify code and markup extensions, which in turn simplifies access to shared data and the properties of other controls.

Drill Down on XAML
Like all XML, XAML supports namespaces, and you can map .NET namespaces to XML namespaces, whether framework namespaces or your own. There are two syntaxes for this. You can add attributes to your assembly and write traditional XML namespace declarations, or, more commonly, use the clr-namespace token to specify your .NET namespace, along with a friendly XML prefix such as "local":

<Window x:Class="CustomerWindow"
	xmlns="http://schemas.microsoft.com/
		winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/
		winfx/2006/xaml"
	xmlns:local=
		"clr-namespace:CSharp_WpfApplication">

With these namespace declarations in place, you can access custom controls and code in your .NET assembly.

XAML is made of elements and attributes. Elements are .NET objects, and attributes are .NET properties or events. In almost all cases, attributes can alternatively be stated as child elements; you can declare the same thing in different ways depending on the complexity of your situation and your preference:

<Button>Cut</Button>
<Button Content="Copy"/>
<Button>
	<Button.Content>Paste</Button.Content>
</Button>

XAML defines .NET objects, but it doesn't compile to Intermediate Language (IL). Visual Studio and a separate compiler both transform XAML to BAML (Binary Application Markup Language), which is a pre-tokenized version of XAML for faster loading. You can find BAML files in the obj directory along with filename.g.cs or filename.g.vb files. The .g files are generated and contain any script contained in your XAML and any code your XAML implies through its declarations.

XAML attributes are property values and can contain literal strings, strings for conversion, and markup extensions. Sometimes you'll set the actual value as a literal string, such as setting the text of a Content property. Attributes can also contain a string that isn't valid for the property and that you could not set in .NET code:

<Button Content="Cut"  Background="Yellow"/>

The Background property is of type Brush. You must instantiate a brush object explicitly in C# or VB code, and you can't simply assign a string. In XAML, you can assign the string directly because of XAML's intrinsic support for type converters. The XAML loader looks for a converter to supply a brush from a string. WPF supplies several converters, and you can write your own.

You can also set property values using markup extensions. When you set the value of a property to a literal or use a type converter explicitly, XAML always creates an object. Sometimes, you want to reuse an existing object. This can sometimes save resources, but, more importantly, reusing objects means sharing. This feature is essential for applying the same look across your application and binding data. Reusing existing objects lets you isolate a definition (such as a style) from the place you use it. Reusing existing objects also enables you to access application resources such as images. It also enables you to share data, as when binding multiple text boxes to the same data source.

Use Markup Extensions
Markup extensions are classes that derive from MarkupExtension and override the ProvideValue method. The syntax for using markup extensions in XAML is enclosing curly braces. Within the curly braces, you state the name of the markup extension that calls the constructor for the markup extension class, passing any parameters. Constructor parameters don't specify the argument name, but you can set additional properties by specifying the name, followed by an equals sign. There are often several equivalent ways to set values:

<TextBlock Text=
	"{Binding Path=Name}"/>
<TextBlock Text=
	"{Binding Name}"/>

The Binding object searches upward in the logical tree of the TextBlock's containers to find additional information required to complete the binding operation. In this case, it searches for a DataContext that points to the object used for the binding. Path defines the property on that object to bind.

XAML with markup extensions offers an unprecedented level of language organization. Buttons created with simple code can look like literally anything with little impact on the local layout (Figure 1):

<StackPanel Orientation="Horizontal">
	<Button Content="Cut" Style=
		"{StaticResource button1}"/>
	<Button Content="Copy" Style=
		"{StaticResource button2}"/>
	<Button Content="Paste" 
		Template="{StaticResource button3}"/>
	<Button Content="Undo" 
		Template="{StaticResource button4}">
	</Button>
</StackPanel>

The magic behind this is styling that you access through a separately defined template through the StaticResource markup extension. The StaticResource object searches upwards in the logical tree for the nearest definition of the named static resource. This code demonstrates flexibility, although in a real application all your buttons would probably have the same appearance, perhaps evolving over time. Several additional markup extensions are included in XAML and WPF (Table 1).

The WPF implementation relies heavily on defaults for things like styling. If the style isn't specified, WPF looks for a style that matches the element name. Whether using implicit or explicit styles, you can specify a UI element's appearance in the scope of the container, window, or application as styles or individual properties. If a control specifies a property, that value overrides the properties set in styles, and style values override the ambient values inherited from the container. This provides a powerful mechanism for achieving the visual look you want, and also gives you the ability to change this look as fashion or requirements change.

Markup extensions let you declare data binding that is more powerful in WPF than in previous UI tools. This power is delivered through dependency properties, which are held in dependency objects. The DependencyObject class holds the dictionary and is a base for important classes, such as Workflow activities and WPF controls.

Keeping state (data) for properties in a common dictionary allows infrastructure code to watch for changes. In addition to binding, this plumbing allows attached properties, and meta-properties. Both Windows Forms and Web Forms provide data binding without dependency properties, but they use a lot of ugly reflection magic. Dependency properties are designed specifically for background plumbing. In WPF and WF, the binding target must be a dependency property (Figure 2).

An example of this binding is linking an input value of a workflow activity to an output value of a previous activity. Binding also empowers advanced features of WPF, such as data triggers.

Leverage Dependency Properties
Attached properties let you add properties to contained members automatically. The Grid.Row and Grid.Column properties that appear on controls placed within a WPF grid are attached properties. You create attached dependency properties with the Register-Attached shared method of the DependencyProperty class.

You can change most dependency properties at runtime. Occasionally, you'll encounter a dependency property that would cause chaos (or at least bad behavior) if changed at runtime. For example, you wouldn't want users to be able to change the name or other identifier on a control or activity. Properties that cannot be changed at runtime are called meta-properties in WF. Setting the IsMetaProperty of the PropertyMetadata object passed as a parameter of the Register method creates a meta-property. WPF solves this problem more directly with read only properties. This is just one of several differences between WPF and WF dependency properties.

You create dependency properties with a rather messy syntax:

Public Property Name() As String
     Get
          Return (CType((MyBase.GetValue( _
               ClassName.NameProperty)), String))
     End Get
     Set(ByVal Value As String)
          MyBase.SetValue( _
               ClassName.NameProperty, Value)
     End Set
End Property

Public Shared NameProperty _
     As DependencyProperty = _
     DependencyProperty.Register("Name", _
          GetType(String), GetType(ClassName))
A .NET property lets you access the dependency property. This backing .NET property doesn't access a private field; instead, it accesses the class's dictionary through the base class's GetValue and SetValue methods. The key to accessing the dictionary is in using an instance of the DependencyProperty class. You need to declare the DependencyProperty as a shared field in your class and create it using either the Register or RegisterAttached shared methods of the DependencyProperty class (the DependencyProperty class includes these as its factory methods). The Dependency-Property instance can also contain other information about your property, including its default value.

An important implication of dependency properties is that you can set the dictionary value many different ways, including through linked properties and bidirectional binding. Your backing .NET property code might not run, so you should never perform work there.

One of the WPF features empowered by dependency properties is triggers. Triggers let you set properties such as background color based on the value of other properties, such as whether there are validation errors. Unlike when setting the background in an event handler, properties set using triggers reset automatically when the triggering property no longer matches the condition.

WPF has additional core features specific to UI behavior. Routed events are similar to standard .NET events, except that you can fire them on multiple elements up and down the logical tree that defines containers, and they can drive animation actions. You define new routed events with another somewhat messy syntax (see Listing 1). What causes the mess is the fact you must code the handler assignment, as well as create the routed event.

Use Commands in WPF
Commands are similar to events in that they accomplish something for the user. Events respond directly to a user's action, not the user's intent. Events indicate the user "clicked" on a button that happens to be called Paste. A command indicates that the user "wants to paste," regardless of how the user indicated his or her intent. WPF includes more than 75 commands, such as Cut, Copy, Paste, Open, Save, SelectAll, ScrollPageRight, MoveRight, PreviousTrack, BrowseBack, Search, and Zoom. You can also create your own commands.

You use a command by creating a CommandBinding between the command and two delegates. One optional delegate returns whether the command can currently be executed and the other executes the command. The infrastructure defines many commands, but you control what happens when they execute:

<Window.CommandBindings>
	<CommandBinding Command="Save"
		CanExecute="SaveCanExecute"
		Executed="SaveExecuted"/>
</Window.CommandBindings>

For example, assume you have a private field named "customers" that includes a HasErrors property and Save method:

Private Sub SaveCanExecute( _
	ByVal sender As Object, _
	ByVal e As CanExecuteRoutedEventArgs)
	e.CanExecute = Not customers.HasErrors
End Sub
Private Sub SaveExecuted( _
	ByVal sender As Object, _
	ByVal e As ExecutedRoutedEventArgs)
	MessageBox.Show("Save now")
End Sub
You can hook this command up to any number of controls such as buttons and menu items. As in routed events, a handler can indicate it's handled the event and no further work is needed. You can also hook the command up to any number of user interface elements:

<Button  Command="Save"
	Content="Save"/>

The enabled status of all linked UI elements remains synchronized, and all linked handlers execute when any of the user interface elements are clicked.

Commands can also help globalization. Features like Cut, Copy and Paste need to be localized to predictable text for the user. All of the built-in commands offer the localized text for the action in the Text property, which you can bind to so the caption of a button is localized with no further work:

<Button  
	Command="Save"
	Content="{Binding 
		RelativeSource={
		RelativeSource Self}, 
		Path=Command.Text}" />

XAML, commands, dependency properties and routed events aren't disparate features solving different problems, but a closely linked infrastructure. In this button definition, XAML defines the command and binding that links the button's content back to its own Command property's Text sub-property, which is a dependency property. This provides powerful behavior with very little code.

Over the coming months you'll see many interesting ways to apply the myriad of new features, including techniques to create effective workflows and exciting UIs. As you explore these techniques, take the time to understand the core fundamentals of each tool. This will build your personal programming foundation and help you get the most out of time-saving tools like visual designers. It will also enable you to take your own applications beyond designer limitations.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube