Sunday, February 10, 2013

Writing Windows 8 Apps with C# and XAML

671768cvr.indd
XAML Syntax
A Windows 8 application is divided into code and markup because each has its own strength.
Despite the limitations of markup in performing complex logic or computational tasks, it’s good
to get as much of a program into markup as possible. Markup is easier to edit with tools and shows
a clearer sense of the visual layout of a page. Of course, everything in markup is a string, so markup
sometimes becomes cumbersome in representing complex objects. Because markup doesn’t have the
loop processing common in programming languages, it can also be prone to repetition.
These issues have been addressed in the syntax of XAML in several ways, the most important of
which are explored in this chapter. But let me begin this vital subject with a topic that will at first
appear to be completely off topic: defining a gradient brush.
The Gradient Brush in Code
The Background property in Grid and the Foreground property of the TextBlock are both of
type Brush. The programs shown so far have set these properties to a derivative of Brush called
SolidColorBrush. As demonstrated in Chapter 1, “Markup and Code,” you can create a SolidColorBrush
in code and give it a Color value; in XAML this is done for you behind the scenes.
SolidColorBrush is only one of four available brushes, as shown in this class hierarchy:
Object
        DependencyObject
            Brush
                SolidColorBrush
                GradientBrush
                    LinearGradientBrush
                TileBrush
                    ImageBrush
                    WebViewBrush
Only SolidColorBrush, LinearGradientBrush, ImageBrush, and WebViewBrush are instantiable. Like
many other graphics-related classes, most of these brush classes are defined in the Windows.UI.Xaml
.Media namespace, although WebViewBrush is defined in Windows.UI.Xaml.Controls.
The LinearGradientBrush creates a gradient between two or more colors. For example, suppose
you want to display some text with blue at the left gradually turning to red at the right. While we’re at
it, let’s set a similar gradient on the Background property of the Grid but going the other way.
In the GradientBrushCode program, a TextBlock is instantiated in XAML, and both the Grid and the
TextBlock have names:
Project: GradientBrushCode | File: MainPage.xaml (excerpt)
<Grid Name="contentGrid"
      Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
     
    <TextBlock Name="txtblk"
               Text="Hello, Windows 8!"
               FontSize="96"
               FontWeight="Bold"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
</Grid>
The constructor of the code-behind file creates two separate LinearGradientBrush objects to set to
the Background property of the Grid and Foreground property of the TextBlock:
Project: GradientBrushCode | File: MainPage.xaml.cs (excerpt)
public MainPage()
{
    this.InitializeComponent();

    // Create the foreground brush for the TextBlock
    LinearGradientBrush foregroundBrush = new LinearGradientBrush();
    foregroundBrush.StartPoint = new Point(0, 0);
    foregroundBrush.EndPoint = new Point(1, 0);

    GradientStop gradientStop = new GradientStop();
    gradientStop.Offset = 0;
    gradientStop.Color = Colors.Blue;
    foregroundBrush.GradientStops.Add(gradientStop);

    gradientStop = new GradientStop();
    gradientStop.Offset = 1;
    gradientStop.Color = Colors.Red;
    foregroundBrush.GradientStops.Add(gradientStop);

    txtblk.Foreground = foregroundBrush;

    // Create the background brush for the Grid
    LinearGradientBrush backgroundBrush = new LinearGradientBrush
    {
         StartPoint = new Point(0, 0),
         EndPoint = new Point(1, 0)
    };
    backgroundBrush.GradientStops.Add(new GradientStop
    {
        Offset = 0,
        Color = Colors.Red
    });   CHAPTER 2  XAML Syntax  33
    backgroundBrush.GradientStops.Add(new GradientStop
    {
        Offset = 1,
        Color = Colors.Blue
    });

    contentGrid.Background = backgroundBrush;
}
The two brushes are created with two different styles of property initialization, but otherwise
they’re basically the same. The LinearGradientBrush class defines two properties named StartPoint and
EndPoint of type Point, which is a structure with X and Y properties representing a two-dimensional
coordinate point. The StartPoint and EndPoint properties are relative to the object to which the brush
is applied based on the standard windowing coordinate system: X values increase to the right and Y
values increase going down. The relative point (0, 0) is the upper-left corner and (1, 0) is the upper-
right corner, so the brush gradient extends along an imaginary line between these two points, and
all lines parallel to that line. The StartPoint and EndPoint defaults are (0, 0) and (1, 1), which defines a
gradient from the upper-left to the lower-right corners of the target object.
LinearGradientBrush also has a property named GradientStops that is a collection of GradientStop
objects. Each GradientStop indicates an Offset relative to the gradient line and a Color at that offset.
Generally the offsets range from 0 to 1, but for special purposes they can go beyond the range
encompassed by the brush. LinearGradientBrush defines additional properties to indicate how the
gradient is calculated and what happens beyond the smallest Offset and the largest Offset.
Here’s the result:
G02xx01
If you now consider defining these same brushes in XAML, all of a sudden the limitations of
markup become all too evident. XAML lets you define a SolidColorBrush by just specifying the color,
but how on earth do you set a Foreground or Background property to a text string defining two
points and two or more offsets and colors?
Property-Element Syntax
Fortunately, there is a way. As you’ve seen, you normally indicate that you want a SolidColorBrush in
XAML simply by specifying the color of the brush:
<TextBlock Text="Hello, Windows 8!"
           Foreground="Blue"
           FontSize="96" />
The SolidColorBrush is created for you behind the scenes.
However, it’s possible to use a variation of this syntax that gives you the option of being more
explicit about the nature of this brush . Remove that Foreground property, and separate the TextBlock
element into start and end tags:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
 
</TextBlock>
Within those tags, insert additional start and end tags consisting of the element name, a period,
and a property name:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
     
    </TextBlock.Foreground>
</TextBlock>
And within those tags put the object you want to set to that property:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <SolidColorBrush Color="Blue" />
    </TextBlock.Foreground>
</TextBlock>
Now it’s explicit that Foreground is being set to an instance of a SolidColorBrush.
This is called property-element syntax, and it’s an important feature of XAML. At first it might
seem to you (as it did to me) that this syntax is an extension or aberration of standard XML, but it’s
definitely not. Periods are perfectly valid characters in XML element names.
In reference to that last little snippet of XAML it is now possible to categorize three types of XAML
syntax:
  • The TextBlock and SolidColorBrush are both examples of “object elements” because they are
    XML elements that result in the creation of objects.
  • The Text, FontSize, and Color settings are examples of “property attributes.” They are XML
      attributes that specify the settings of properties.
  • The TextBlock.Foreground tag is a “property element.” It is a property expressed as an XML
    element.
XAML poses a restriction on property-element tags: Nothing else can go in the start tag. The
object being set to the property must be content that goes between the start and end tags.
The following example uses a second set of property-element tags for the Color property of the
SolidColorBrush:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <SolidColorBrush>
            <SolidColorBrush.Color>
                Blue
            </SolidColorBrush.Color>
        </SolidColorBrush>
    </TextBlock.Foreground>
</TextBlock>
If you want, you can set the other two properties of the TextBlock similarly:
<TextBlock>
    <TextBlock.Text>
        Hello, Windows 8
    </TextBlock.Text>
                 
    <TextBlock.FontSize>
         96
    </TextBlock.FontSize>
                 
    <TextBlock.Foreground>
        <SolidColorBrush>
            <SolidColorBrush.Color>
                Blue
            </SolidColorBrush.Color>
        </SolidColorBrush>
    </TextBlock.Foreground>
</TextBlock>
But there’s really no point. For these simple properties, the property attribute syntax is shorter and
clearer. Where property-element syntax comes to the rescue is in expressing more complex objects
like LinearGradientBrush. Let’s begin again with the property-element tags:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
     
    </TextBlock.Foreground>
</TextBlock>
Put a LinearGradientBrush in there, separated into start tags and end tags. Set the StartPoint and
EndPoint properties in this start tag:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">

        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
Notice that the two properties of type Point are specified with two numbers separated by a space.
You can separate the number pair with a comma if you choose.
The LinearGradientBrush has a GradientStops property that is a collection of GradientStop objects,
so include the GradientStops property with another property element:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <LinearGradientBrush.GradientStops>

            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
The GradientStops property is of type GradientStopCollection, so let’s add that in as well:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>

                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
Finally, add the two GradientStop objects to the collection:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Offset="0" Color="Blue" />
                    <GradientStop Offset="1" Color="Red" />
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
And there we have it: a rather complex property setting expressed entirely in markup.
Content Properties
The syntax I’ve just shown you for instantiating and initializing the LinearGradientBrush is actually a
bit more extravagant than what you actually need. You might be persuaded of this fact when you
consider that all the XAML files we’ve seen so far have apparently been missing some properties and
elements. Look at this little snippet of markup:
<Page ... >
    <Grid ... >
        <TextBlock ... />
        <TextBlock ... />
        <TextBlock ... />
    </Grid>
</Page>
We know from working with the classes in code that the TextBlock elements are added to the Children
collection of the Grid, and the Grid is set to the Content property of the Page. But where are those
Children and Content properties in the markup?
Well, you can include them if you want. Here are the Page.Content and Grid.Children property
elements as they are allowed to appear in a XAML file:
<Page ... >
    <Page.Content>
        <Grid ... >
            <Grid.Children>
                <TextBlock ... />
                <TextBlock ... />
                <TextBlock ... />
            </Grid.Children>
        </Grid>
    </Page.Content>
</Page>
This markup is still missing the UIElementCollection object that is set to the Children property of the
Grid. That cannot be explicitly included because only elements with parameterless public constructors
can be instantiated in XAML files, and the UIElementCollection class is missing such a constructor.
The real question is this: Why aren’t the Page.Content and Grid.Children property elements
required in the XAML file?
Simple: All classes referenced in XAML are allowed to have one (and only one) property that is
designated as a “content” property. For this content property, and only this property, property-
element tags are not required.
The content property for a particular class is specified as a .NET attribute. Somewhere in the actual
class definition of the Panel class (from which Grid derives) is an attribute named ContentProperty . If
these classes were defined in C#, it would look like this:
[ContentProperty(Name="Children")]
public class Panel : FrameworkElement
{
    ...
}
What this means is simple. Whenever the XAML parser encounters some markup like this:
<Grid ... >
    <TextBlock ... />
    <TextBlock ... />
    <TextBlock ... />
</Grid>
then it checks the ContentProperty attribute of the Grid and discovers that these TextBlock elements
should be added to the Children property.
Similarly, the definition of the UserControl class (from which Page derives) defines the Content
property as its content property (which might sound appropriately redundant if you say it out loud):
[ContentProperty(Name="Content")]
public class UserControl : Control
{
    ...
}
You can define a ContentProperty attribute in your own classes. The ContentPropertyAttribute class
required for this is in the Windows.UI.Xaml.Markup namespace.
Unfortunately, at the time I’m writing this book, the documentation for the Windows Runtime
indicates only when a ContentProperty attribute has been set on a class—look in the Attributes
section of the home page for the Panel class, for example—but not what that property actually is!
Perhaps the documentation will be enhanced in the future, but until then, you’ll just have to learn by
example and retain by habit.
Fortunately, many content properties are defined to be the most convenient property of the class.
For LinearGradientBrush, the content property is GradientStops . Although GradientStops is of type
GradientStopCollection, XAML does not require collection objects to be explicitly included. Here’s the
excessively wordy form of the LinearGradientBrush syntax:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Offset="0" Color="Blue" />
                    <GradientStop Offset="1" Color="Red" />
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
Neither the LinearGradientBrush.GradientStops property elements nor the GradientStopCollection tags
are required, so it simplifies to this:
<TextBlock Text="Hello, Windows 8!"
           FontSize="96">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <GradientStop Offset="0" Color="Blue" />
            <GradientStop Offset="1" Color="Red" />
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>
Now it’s difficult to imagine how it can get any simpler and still be valid XML.
It is now possible to rewrite the GradientBrushCode program so that everything is done in XAML:
Project: GradientBrushMarkup | File: MainPage.xaml (excerpt)
<Grid>
    <Grid.Background>
        <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
            <GradientStop Offset="0" Color="Red" />
            <GradientStop Offset="1" Color="Blue" />
        </LinearGradientBrush>
    </Grid.Background>
     
    <TextBlock Name="txtblk"
               Text="Hello, Windows 8!"
               FontSize="96"
               FontWeight="Bold"
               HorizontalAlignment="Center"
               VerticalAlignment="Center">
        <TextBlock.Foreground>
            <LinearGradientBrush StartPoint="0 0" EndPoint="1 0">
                <GradientStop Offset="0" Color="Blue" />
                <GradientStop Offset="1" Color="Red" />
            </LinearGradientBrush>
        </TextBlock.Foreground>
    </TextBlock>
</Grid>
Even with the property-element syntax, it’s more readable than the code version. What code
illustrates most clearly is how something is built. Markup shows the completed construction.
Here’s something to watch out for. Suppose you define a property element on a Grid with multiple
children:
<Grid>
    <Grid.Background>
        <SolidColorBrush Color="Blue" />
    </Grid.Background>
     
    <TextBlock Text="one" />
    <TextBlock Text="two" />
    <TextBlock Text="three" />
</Grid>
You can alternatively put the property element at the bottom:
<Grid>
    <TextBlock Text="one" />
    <TextBlock Text="two" />
    <TextBlock Text="three" />
 
    <Grid.Background>
        <SolidColorBrush Color="Blue" />
    </Grid.Background>
</Grid>
But you can’t have some content before the property element and some content after it:
<!-- This doesn't work! --> 
<Grid>
    <TextBlock Text="one" />
 
    <Grid.Background>
        <SolidColorBrush Color="Blue" />
    </Grid.Background>

    <TextBlock Text="two" />
    <TextBlock Text="three" />
</Grid
>
Why the prohibition? The problem becomes very apparent when you include the property-element
tags for the Children property:
<!-- This doesn't work! --> 
<Grid>
    <Grid.Children>
        <TextBlock Text="one" />
    </Grid.Children>
    <Grid.Background>
        <SolidColorBrush Color="Blue" />
    </Grid.Background>
     <Grid.Children>
        <TextBlock Text="two" />
        <TextBlock Text="three" />
    </Grid.Children>
</Grid>
Now it’s obvious that the Children property is defined twice with two separate collections, and that’s
not legal.

No comments:

Post a Comment