Page 1 of 1

Exploring the NameScope class

#1 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 19 March 2010 - 07:49 AM

I've been playing with 3D and Animation recently trying to better understand all of the parts and pieces. And the 'NameScope' class seems to be an important part of the glue that eases the transition between xaml and the C# code behind.

My objective in this tutorial is to better understand the NameScope class and how it enables xaml code and C# code to obtain the same results.

In .Net 3.5, NameScope implements the INameScope interface methods which are 'FindName', 'RegisterName', and 'UnregisterName'. The 'Name' the methods refer to are the 'names' of the objects defined in the project's xaml. In the code behind, you can find these objects using 'FindName' and providing the object's text name.

So I started out with a simple WPF Application project. I named mine "WPFNameScopeProject". Then I added 'Name="mainWindow"' and 'Loaded="mainWindow_Loaded"' to the <Window> element, changed the <Grid> element to <StackPanel>.

I want to be able to write information to the Console so I also changed the application's 'Output type' in 'Project>Properties' from 'Windows Application' to 'Console Application'.

Here is the code:
<Window x:Class="WPFNameScopeProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
        Name="mainWindow" Loaded="mainWindow_Loaded">
    <StackPanel Name="stackPanel"> 
        
    </StackPanel>
</Window>


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFNameScopeProject
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void mainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            NameScope ns = (NameScope)NameScope.GetNameScope(this);
            Console.WriteLine("{0}", ns.ToString());

            object obj = ns.FindName("mainWindow");
            Console.WriteLine("mainWindow {0}", obj.GetType().ToString());

            obj = ns.FindName("stackPanel");
            Console.WriteLine("stackPanel {0}", obj.GetType().ToString());
        }
    }
}



Attached Image

I've attached a screenshot of the Console window so you can see that in a normal WPF Application, there is already a 'NameScope' object defined. It turns out that if your application is not a WPF Application, you do not automatically have a NameScope object. If you are writing a Console Application or a WPF Application from scratch but you can create one with the line
NameScope.SetNameScope(mainWindow, new NameScope());



The interesting thing here is that the names of the mainWindow and stackPanel objects in the NameScope object are the same as the names of the objects you would refer to in the code behind.

But you might have noticed that not all classes have a 'Name=""' property that you can specify. For instance, create a button in our <StackPanel> with the following xaml code:
        <Button Name="button" Width="120" Height="70" >
            <Button.RenderTransform>
                <TransformGroup >
                    <TranslateTransform  X="40" Y="100"/>
                    <ScaleTransform ScaleY=".8"/>
                    <RotateTransform Angle="45" CenterX="60" CenterY="35"/>
                </TransformGroup>
            </Button.RenderTransform>
            Button
        </Button>



If you try to specify a 'Name=""' property for the <TransformGroup>, <TranslateTransform>, <ScaleTransform> and RotateTransform> elements the intellisense doesn't show you a 'Name=""' property. But you can specify an 'x:Name=""' property. I've added the x:Name properties in the following xaml code:
        <Button Name="button" Width="120" Height="70" >
            <Button.RenderTransform>
                <TransformGroup x:Name="_transformGroup">
                    <TranslateTransform x:Name="_translateTransform" X="40" Y="100"/>
                    <ScaleTransform x:Name="_scaleTransform" ScaleY=".8"/>
                    <RotateTransform x:Name="_rotateTransform" Angle="45" CenterX="60" CenterY="35"/>
                </TransformGroup>
            </Button.RenderTransform>
            Button
        </Button>



Now if we go back to our C# code we can add the 'Console.WriteLine' statements to out mainWindow_Loaded function to find and display the x:Name'd objects:
            obj = ns.FindName("button");
            Console.WriteLine("button {0}", obj.GetType().ToString());

            obj = ns.FindName("_transformGroup");
            Console.WriteLine("_transformGroup {0}", obj.GetType().ToString());

            obj = ns.FindName("_scaleTransform");
            Console.WriteLine("_scaleTransform {0}", obj.GetType().ToString());

            obj = ns.FindName("_rotateTransform");
            Console.WriteLine("_rotateTransform {0}", obj.GetType().ToString());



So some objects don't support a 'Name' property but if we specify an 'x:Name' property both types of names are available in the NameScope object.

So now lets add a 'Click' event handler, 'button_Click' to our button and add some animation using our NameScope object.

        private void button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = (Button)sender;
            Transform xfrm = btn.RenderTransform;
            if (xfrm.GetType() == typeof(TransformGroup))
            {
                TransformGroup xformGroup = (TransformGroup)xfrm;

                foreach (Transform xform in xformGroup.Children)
                {
                    if (xform.GetType() == typeof(RotateTransform))
                    {
                        RotateTransform rotateXform = (RotateTransform)xform;

                        DoubleAnimation dblAni = new DoubleAnimation();
                        dblAni.From = rotateXform.Angle;
                        dblAni.To = rotateXform.Angle + 360;

                        Storyboard storyboard = new Storyboard();
                        storyboard.Children.Add(dblAni);
                        Storyboard.SetTargetName(dblAni, "_rotateTransform");
                        Storyboard.SetTargetProperty(dblAni, new PropertyPath(RotateTransform.AngleProperty));
                        storyboard.Begin(btn);
                    }
                }
            }
        }



I've enumerated through each of the TransformGroup's Children to find the RotateTransform object. Then I've simply created a DoubleAnimation object from the button's current angle to 360 degrees. Then I've created a 'Storyboard object, added the DoubleAnimation object to its Children. But notice the next two statements. These are Storyboard static functions that set the target name for the DoubleAnimation object to "_rotateTransform" which is the name we've given the RotateTransform object with the x:Name="" notation in xaml. Then we set the target property for the DoubleAnimation object to 'new PropertyPath(RotateTransform.AngleProperty);"

Then we call storyboard's Begin function specifying our button object.

If you run this application and click the button, sure enough, it rotates, translates, scales as you would expect.

It occurred to me that I don't really need to enumerate through the TransformGroup's children to find the RotateTransform object. I can call NameScope's FindName function and get the object. So I modified my 'button_Click' handler so it now looks like:
        private void button_Click(object sender, RoutedEventArgs e)
        {
            Button btn = (Button)sender;
            /*
            Transform xfrm = btn.RenderTransform;
            if (xfrm.GetType() == typeof(TransformGroup))
            {
                TransformGroup xformGroup = (TransformGroup)xfrm;

                foreach (Transform xform in xformGroup.Children)
                {
                    if (xform.GetType() == typeof(RotateTransform))
                    {
                        RotateTransform rotateXform = (RotateTransform)xform;

                        DoubleAnimation dblAni = new DoubleAnimation();
                        dblAni.From = rotateXform.Angle;
                        dblAni.To = rotateXform.Angle + 360;

                        Storyboard storyboard = new Storyboard();
                        storyboard.Children.Add(dblAni);
                        Storyboard.SetTargetName(dblAni, "_rotateTransform");
                        Storyboard.SetTargetProperty(dblAni, new PropertyPath(RotateTransform.AngleProperty));
                        storyboard.Begin(btn);
                    }
                }
            }
            */
            NameScope nameScope = (NameScope)NameScope.GetNameScope(this);
            if (nameScope != null)
            {
                RotateTransform rotateXform = (RotateTransform)nameScope.FindName("_rotateTransform");
                if (rotateXform != null)
                {
                    DoubleAnimation dblAni = new DoubleAnimation();
                    dblAni.From = rotateXform.Angle;
                    dblAni.To = rotateXform.Angle + 360;

                    Storyboard storyboard = new Storyboard();
                    storyboard.Children.Add(dblAni);
                    Storyboard.SetTargetName(dblAni, "_rotateTransform");
                    Storyboard.SetTargetProperty(dblAni, new PropertyPath(RotateTransform.AngleProperty));
                    storyboard.Begin(btn);
                }
            }
        }



Pretty cool!

So now let's go back and remove the button_Click handler and do it all in xaml code. All we need to do is add a <Button.Triggers > element. Here is what the final xaml code looks like. Notice that we are using the same name in the storyboard's 'TargetProperty, _rotateTransform, that we defined with the x:Name="" notation.
<Window x:Class="WPFNameScopeProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
        Name="mainWindow" Loaded="mainWindow_Loaded">
    <StackPanel Name="stackPanel"> 
        <Button Name="button" Width="120" Height="70" >
            <Button.RenderTransform>
                <TransformGroup x:Name="_transformGroup">
                    <TranslateTransform x:Name="_translateTransform" X="40" Y="100"/>
                    <ScaleTransform x:Name="_scaleTransform" ScaleY=".8"/>
                    <RotateTransform x:Name="_rotateTransform" Angle="45" CenterX="60" CenterY="35"/>
                </TransformGroup>
            </Button.RenderTransform>
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.Click">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation From="45" To="405" Duration="0:0:3"
                                             Storyboard.TargetName="_rotateTransform"
                                             Storyboard.TargetProperty="Angle"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Button.Triggers>
            Button
        </Button>
    </StackPanel> 
</Window>



Is This A Good Question/Topic? 0
  • +

Page 1 of 1