Freigeben über


Now children, is all that Name-calling really necessary? Using FrameworkElement.Name at runtime

Answering a recent silverlight.net forum question got me thinking again about how we've documented the use of FrameworkElement.Name, in particular how you might use it (or abuse it) at run time. Really, this is an area of API that has a few shades of gray, where those shades hadn't been very accurately painted in the SDK topics on MSDN. It is maybe a subject better tackled by a blog post ... like this one.

So, are FrameworkElement.Name and the x:Name attribute really the same thing?

Yes, and no. A lot of times you can get away with thinking of them as equivalent. The Microsoft-created XAML designers and various templates tend to use or generate x:Name (rather than Name) for everything, even FrameworkElements. This has the benefit of consistency, so that you don't end up with a mix of x:Name and Name in the same markup just because some of the objects were FrameworkElements and some weren't.

However, an x:Name in XAML is potentially like a write-once-at-parsetime operation, if it's set on a non-FrameworkElement. Even if you have an object instance, there's no DependencyObject (or deeper)"dot-Name" API available to the runtime. Of course, the field name of the object instance / reference is based on x:Name. Thus in a way the usefulness of the x:Name identifier has already run its course in the XAML world. From now on, in the code world, you can (and should) just use the field reference.

When it comes to run-time uses of either Name or x:Name, there are two APIs that are relevant: the FindName utility method, and the Storyboard.TargetName attached property and the related accessors that are used by the Silverlight animation system. Let's discuss these two APIs.

FindName: Once upon a time, back when Silverlight was 1.0 and its programming model was entirely JavaScript based and executed by the browser's script engine, FindName was crucial. This because the Silverlight 1.0 XAML parser was basically your only way to create new objects and get them into the UI tree, and the only way to execute the JavaScript on your objects was to get a prototype back from using FindName against your own app's parsed XAML tree. But in all Silverlight programming version 2 and beyond, it'sdefinitely more likely that you will just use the generated field references from your partial class - by knowing the name of your field reference, you have the actual object at your fingertips, and you don't need FindName. Nevertheless, you all are a creative lot, and the API is there, just tempting you to use it .... If you do change your UI and object tree around a lot at run time, it's legitimately possible to lose track of what objects are really available at any given time. FindName can be a useful catch-all (so long as you have null-checking, and a Plan B) for those kinds of situations. Also, you only have to CALL FindName on a FrameworkElement. The object you are FINDING does not have to be a FrameworkElement; it can be (and often is) one of those non-FrameworkElements where you explicitly needed x:Name to name it. It can also be an Inline with a Name, and you can call FindName from any Inline because it supports a parallel API. This is handy for some document object model scenarios.

TargetName and animations: Here you have an interesting situation. A XAML-specified Name can used for the animation's targeting, in order to keep the animation system as broadly applicable and flexible as possible. This includes the possibility of defining a storyboard resource in XAML, and targeting an animation to the name of an object that doesn't exist yet. Maybeit only gets created at run time. But to do that, the created object needs to be a FrameworkElement, such that you can actually set the Name at run time. More on this later ...

So long as an object IS a FrameworkElement, there are a few things that you CAN do with FrameworkElement.Name at runtime. Now just because you CAN do things with Name, SHOULD you? Let's examine some Brilliant Ideas.

Brilliant Idea #1: Use the Name property to change the name of an object that already had an x:Name (or Name) attribute applied in the original XAML.

I would say, don't do this, you will just confuse yourself. (Insert dizzy emoticon here.) You are creating a disconnect between the name of the field reference that was generated originally from the Name or x:Name value by the Silverlight build actions, and the hashtable entry of that object in the acting XAML namescope at runtime. In other words, the hashtable name  is what you are changing when you change a Name value, but the field reference stays the same name (the best you could do to change the markup-compiled field references would be make a copy with a new name). That hashtable controls the results of any runtime usage of the FindName API. By the way, changing a Name in real time will not update the source and target values of any ElementName bindings, so that's not a reason to be trying this trick.

Brilliant Idea #2: Instantiate some FrameworkElement-derived class at run time. Then, set the .Name property on it.

This so that you can find it with FindName later, and you have a parallel FindName experience between the parse-time objects and the run-time objects that exist in your application.

Not a BAD idea, but proceed with caution. Think about what you're trying to do with this scenario. And think also about the hoops that Silverlight has to jump through to support it. In this case, it is like you are editing the active XAML namescope on the fly. In order for the namescope to remain useful, it must remain valid, with no duplicates. Most of the time there is really only ONE XAML namescope that you care about - a XAML namescope that equates to the current page, loaded as the VisualRoot by the Silverlight runtime. When you use APIs such as Children.Add to hook up your new FrameworkElement to connect with the content/visual root of your Silverlight and thus make it visible in UI, you're adding  it to the main XAML namescope simultanously. If your attempt to add the object to a given XAML namescope produces a name collision, the API that is the attempt to add such an object will throw. This can be tricky to debug or even catch, because what's really a root cause of a namescope collision can be excepted by quite a range of possible APIs - anything that touches Content/Child/Childen in any object model, really.

The flippant answer to the name collision problem is "well, just choose a different name ..." But, alas, too late mostly, you've already thrown. You don't know about that collision until it happens. There is not an API such as "GetAllNamesInNamescope" to proactively check against a collision possibility. Try/catch may be your best hope.

If you intend to set run time Names, at the very least, you should learn the conventions that existing XAML designers use for generating the default x:Names of objects, so that you don't use those conventions straight-up for any object names you try to add. Also, the "XamlName Grammar" string format you can use for a XAML name is pretty restrictive; it's not too hard to accidentally come up with an invalid Name, especially if you are trying to come up with a deterministic name that is based on a ToString of some other property value of the object.

Take for example the Source value of an Image or MediaElement, which is ostensibly a URL. Even if you use only the filename portion, a filename allows more ASCII characters than does a XamlName, or nonASCII. So clipping Source strings past the last "/" and trying to set your Name with that string is not going to work, too fragile.

If you are down this road already, maybe a better idea is to write your own little factory class for generating and incrementing strings to serve as new XAML names. Crude but effective.

Brilliant Idea #3: Create an object, name it, then also create an animation that will target it

You CAN do this, but fact is, there is an easier way. Rather than using the API Storyboard.SetTargetName, just use the API Storyboard.SetTarget. Storyboard.SetTarget takes a DependencyObject parameter, and thus the object you want to target doesn't even need a Name. Moreover, in order for the storyboard to resolve SetTargetName, both the Storyboard and the target need to be in the same XAML namescope. Usually that means that you must add your on-the-fly Storyboard into the Resources collection to make the namescope connection, even if you have no intention of running the animation again or otherwise using ResourceDictionary capabilities. It's just plain easier to target with Storyboard.SetTarget. Plus, you can do this with any DependencyObject, whereas targeting by name would require that the target be a FrameworkElement so that the Name is settable.

That leaves the scenario of having an animation defined in XAML Resources that has a Storyboard.TargetName set as a XAML attribute, but referencing a XAML name that you intend to create later. To me, this seems like a pretty uncommon scenario, but there might be tooling or workflow reasons for why you find yourself in this situation. In this case, setting the Name on the new object so that the storyboard can resolve just before it is run makes sense, inasmuch as the scenario makes sense ... At least in this case, you know what you want to name the new object, because you've basically created your own naming dependency. Nevertheless, the same considerations of name collision possibilities when you are adjusting the runtime object tree apply - any object you add to any tree (and attendant XAML namescope) must maintain name uniqueness. Otherwise, exception, how awkward for you if that went uncaught, eh?

In WPF, it's notable that XAML namescopes are handled a little differently. They are NOT dynamic by default. To target an object that you added at run time in WPF, you have to actually manipulate the XAML namescope directly, by using WPF's NameScope.Add method.

Brilliant Idea #4: Read .Name, and present it in UI.

Don't even think about it. Major localization faux pas. Even if localizers COULD change the Name values, they would probably break a lot of your code if they did. Time to learn more about creating and localizing Silverlight applications, perhaps?

 More reading about Name and XAML namescopes here.

Hopefully, this blogpost will teach you kids to be careful about when and how you call your object friends Names. Names can never hurt you? That pesky run time exception from a name collision in the XAML namescope should tell you otherwise!

Comments

  • Anonymous
    July 10, 2010
    I think you left out mentioning "RegisterName", and it's importance when adding elements with names to the UI tree. as mentioned under "Adding Elements to Parsed Trees": msdn.microsoft.com/.../ms746659.aspx "Any additions to the element tree after initial loading and processing must call the appropriate implementation of RegisterName for the class that defines the XAML namescope. Otherwise, the added object cannot be referenced by name through methods such as FindName. Merely setting a Name property (or x:Name Directive) does not register that name into any XAML namescope."

  • Anonymous
    July 27, 2010
    Hmm registering names is more a WPF concept. I was trying to keep this post centered on the Silverlight behavior of XAML namespaces.