Working with Elements and Properties in Windows Phone 7

This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

Basic Shapes: The System.Windows.Shapes namespace includes elements for displaying vector graphics-the use of straight lines and curves for drawing and defining filled areas, to set the Width property of an Ellipse to the Height to create a circle. The Fill can then be set to a RadialGradientBrush that starts at White in the center and then goes to a gradient color at the perimeter. Normally the gradient center is the point (0.5, 0.5) relative to the ball's dimension, but you can offset that like so:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <
Ellipse Width="300" Height="300">
        <
Ellipse.Fill>
            <
RadialGradientBrush Center="0.4 0.4"
                                 GradientOrigin="0.4 0.4">
                <
GradientStop Offset="0" Color="White" />
                <
GradientStop Offset="1" Color="Red" />
            </
RadialGradientBrush>
        </
Ellipse.Fill>
    </
Ellipse>
</
Grid>

The offset white spot looks like reflection from a light source, suggesting a three dimensional shape and the offset white spot looks like reflection from a light source, suggesting a three dimensional shape:

8-1.gif

Transforms: Transforms apply a simple formula to all the coordinates of a visual object and cause that object to be shifted to a different location, or change size, or be rotated, you can apply transforms to any object that descends from UIElement, and that includes text, bitmaps, movies, panels, and all controls. The property defined by UIElement that makes transforms possible is RenderTransform, which you set to an object of type Transform.

The whole subject of transforms can be quite complex, particularly when transforms are combined, so I'm really only going to show the basics here. Although TransformGroup is normally an advanced option, I have nevertheless used TransformGroup in a little project named TransformExperiment that allows you to play with the four standard of transforms. It begins with all the properties set to their default values:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <
TextBlock Text="Transform Experiment"
               HorizontalAlignment="Center"
               VerticalAlignment="Center">
        <
TextBlock.RenderTransform>
            <
TransformGroup>
                <
ScaleTransform ScaleX="1" ScaleY="1"
                                CenterX="0" CenterY="0" />
                <
SkewTransform AngleX="0" AngleY="0"
                               CenterX="0" CenterY="0" />
                <
RotateTransform Angle="0"
                                 CenterX="0" CenterY="0" />
                <
TranslateTransform X="0" Y="0" />
            </
TransformGroup>
        </TextBlock.RenderTransform>
    </
TextBlock>
</
Grid>

You can experiment with this program right in Visual Studio. At first you'll want to try out each type of transform independently of the others.

TranslateTransform is useful for making drop shadows and effects where the text seems embossed or engraved. Simply put two TextBlock elements in the same location with the same text, and all the same text properties, but different Foreground properties. Without any transforms, the second TextBlock sits on top of the first TextBlock. On one or the other, apply a small ScaleTransform and the result is magic. The EmbossedText project demonstrates this technique. Here are two TextBlock elements in the same Grid:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <
TextBlock Text="EMBOSS"
               Foreground="{StaticResource PhoneForegroundBrush}"
              
FontSize="96"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
    <
TextBlock Text="EMBOSS"
               Foreground="{StaticResource PhoneBackgroundBrush}"
               FontSize="96"
               HorizontalAlignment="Center"
               VerticalAlignment="Center">
        <
TextBlock.RenderTransform>
            <
TranslateTransform X="2" Y="2" />
        </
TextBlock.RenderTransform>
    </
TextBlock>
</
Grid>

Notice I've used theme colors for the two Foreground properties. With the default dark theme, the TextBlock underneath is white, and the one on top is black like the background but shifted a little to let the white one peak through a bit:

8-2.gif

Generally this technique is applied to black text on a white background, but it looks pretty good with this color scheme as well.

If you have a need to combine transforms in the original order that I had them in TransformExperiment-the order scale, skew, rotate, translate-you can use CompositeTransform to set them all in one convenient class.

Let's make a clock. It won't be a digital clock, but it won't be entirely an analog clock either. That's why I call it HybridClock. The hour, minute, and second hands are actually TextBlock objects that are rotated around the center of the content grid. Here's the XAML:

        <
Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
              SizeChanged="OnContentPanelSizeChanged">
            <TextBlock Name="referenceText"
                       Text="THE SECONDS ARE 99"
                       Foreground="Transparent" /> 
            <TextBlock Name="hourHand">
                <TextBlock.RenderTransform>
                    <CompositeTransform />
                </TextBlock.RenderTransform>
            </TextBlock> 
            <TextBlock Name="minuteHand">
                <TextBlock.RenderTransform>
                    <CompositeTransform />
                </TextBlock.RenderTransform>
            </TextBlock> 
            <TextBlock Name="secondHand">
                <TextBlock.RenderTransform>
                    <CompositeTransform />
                </TextBlock.RenderTransform>
            </TextBlock>
        </Grid>

The code-behind file defines a few fields that will be used throughout the program, and the constructor sets up aDispatcherTimer, for which you'll need a usingdirective for System.Windows.Threading:

namespace HybridClock
{
    public partialclass MainPage : PhoneApplicationPage
    {
        Point gridCenter;
        Size textSize;
        double scale; 
        public MainPage()
        {
            InitializeComponent(); 
            DispatcherTimer tmr =new DispatcherTimer();
            tmr.Interval = TimeSpan.FromSeconds(1);
            tmr.Tick += OnTimerTick;
            tmr.Start();
        } 
        void OnContentPanelSizeChanged(object sender, SizeChangedEventArgs args)
        {
            gridCenter = newPoint(args.NewSize.Width / 2,

                                  args.NewSize.Height / 2);
             textSize = newSize(referenceText.ActualWidth,
                                referenceText.ActualHeight); 
            scale = Math.Min(gridCenter.X, gridCenter.Y) / textSize.Width; 
            UpdateClock();
        } 
        void OnTimerTick(object sender, EventArgs e)
        {
            UpdateClock();
        }
        void UpdateClock()
        {
            DateTime dt =DateTime.Now;
            double angle = 6 * dt.Second;
            SetupHand(secondHand, "THE SECONDS ARE " + dt.Second, angle);
            angle = 6 * dt.Minute + angle / 60;
            SetupHand(minuteHand, "THE MINUTE IS " + dt.Minute, angle);
            angle = 30 * (dt.Hour % 12) + angle / 12;
            SetupHand(hourHand, "THE HOUR IS " + (((dt.Hour + 11) % 12) + 1), angle);
        }
        void SetupHand(TextBlock txtblk, string text,double angle)
        {
            txtblk.Text = text;
            CompositeTransform xform = txtblk.RenderTransform asCompositeTransform;
            xform.CenterX = textSize.Height / 2;
            xform.CenterY = textSize.Height / 2;
            xform.ScaleX = scale;
            xform.ScaleY = scale;
            xform.Rotation = angle - 90;
            xform.TranslateX = gridCenter.X - textSize.Height / 2;
            xform.TranslateY = gridCenter.Y - textSize.Height / 2;
        }
    }
}

Both ScaleX and ScaleY are set to the scaling factor calculated earlier. The angle parameter passed to the method is relative to the high-noon position, but the TextBlock elements are positioned at 3:00. That's why the Rotation angle offsets the angle parameter by -90 degrees. Both scaling and rotation are relative to CenterX and CenterY, which is a point at the left end of the text, but offset from the upper-left corner by half the text height. Here's the clock in action:

8-3.gif

Animating at the Speed of Video

The use of the DispatcherTimer with a one-second interval makes sense for the HybridClock program because the positions of the clock hands need to be updated only once per second.

A timer that is synchronous with the video refresh rate is ideal for animations, and Silverlight provides one in the very easy-to-use CompositionTarget.Rendering event. The event handler looks something like this:

    void OnCompositionTargetRendering(object sender, EventArgs args)
    {
         TimeSpan renderingTime = (argsas RenderingEventArgs).RenderingTime;
        ...
    }

CompositionTarget is a static class with only one public member, which is the Rendering event. Install the event handler like so:

    CompositionTarget.Rendering += OnCompositionTargetRendering;

Unless you're coding a very animation-laden game, you probably don't want this event handler installed for the duration of your program, so uninstall it when you're done:

    CompositionTarget.Rendering -= OnCompositionTargetRendering;

The RotatingText project contains a TextBlock in the center of its content grid:

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
           
<TextBlock Text="ROTATE!"
                        FontSize="96"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        RenderTransformOrigin
="0.5 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform x:Name="rotate" />
                </TextBlock.RenderTransform>
            </TextBlock>
        </Grid>

Notice the x:Nameattribute on the RotateTransform. You can't use Name here because that's defined by FrameworkElement. The code-behind file starts a CompositionTarget.Renderingevent going in its constructor:

namespace RotatingText
{
    public partialclass MainPage : PhoneApplicationPage
    {
        TimeSpan startTime; 
        public MainPage()
        {
            InitializeComponent();
            CompositionTarget.Rendering += OnCompositionTargetRendering;
        } 
        void OnCompositionTargetRendering(object sender, EventArgs args)
        {
            TimeSpan renderingTime = (argsas RenderingEventArgs).RenderingTime; 
            if (startTime.Ticks == 0)
            {
                startTime = renderingTime;
            }
            else
            {
                TimeSpan elapsedTime = renderingTime - startTime;
                rotate.Angle = 180 * elapsedTime.TotalSeconds % 360;
            }
        }
    }
}

The event handler uses the renderingTime to pace the animation so there's one revolution every two seconds.


Similar Articles