Silverlight Behavior that Accesses Children

Silverlight Behaviors are a very powerful way to extend a control to do more than in natively does, without having to fully subclass.  I was writing a behavior to attach to a Canvas that would allow for a page turn effect.  In my case I wanted the Canvas to contain other Canvases that it would manipulate.  But I ran into two main dilemmas:

1. The OnAttached event fires before the Children are created.  So how do you manipulate the Children when the control loads?

2.  How do you let the UI Designer tell you which child elements to manipulate?

The answers are quite easy.

Timing of Accessing Children

The OnAttached event happens as the control is first created, before and children are created and added to the collection.  The best thing to do is wait until the control is fully loaded by attaching to the AttachedObject’s Loaded event.

protected override void OnAttached()
{
     base.OnAttached();
     this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}

Then put all your logic in AssociatedObject_Loaded method – the Children will all have been created.

Accessing Specific Children

To get to specific children (without making arbitrary rules such as “it must be the first child”), you want to make a property of the behavior where the UI Designer can specify a name.  In other words, I want my XAML to look something like:

 

<Canvas x:Name="myCanvas" Width="300" Height="300" Background="Yellow">
    <i:Interaction.Behaviors>
        <b:MyBehavior SubCanvasName="mySubCanvas" />                    
    </i:Interaction.Behaviors>
            
    <Canvas x:Name="mySubCanvas" Width="100" Height="100" Background="AliceBlue">
                
    </Canvas>
</Canvas>

 

So in your behavior, you need to expose a property.  You can either do a DependencyProperty or INotifyPropertyChanged (I chose the latter).

private string _subCanvasName;

public string SubCanvasName
{
     get { return _subCanvasName; }
     set { _subCanvasName = value;
             if (PropertyChanged != null) 
                 PropertyChanged(this, new PropertyChangedEventArgs("SubCanvasName"));
     }
}

I wrote a function to help find a child of an element by name.  This works with any panel (grids, canvases, panels, etc), and it will recursively search any of its children panels.  It makes sure the matching child is of the desired type.

private static T GetChildByName<T>(Panel parent, string childName) where T : FrameworkElement
       {
           if (parent != null)
           {
               foreach (UIElement element in parent.Children)
               {
                   if (element is T && ((T)element).Name == childName)
                       return (T)element;
                   else if (element is Panel)
                   {
                       T result = GetChildByName<T>((Panel)element, childName);
                       if (result != null) return result;
                   }
               }
           }
           return null;
       }

 

Note that in my case I wanted to find all possible children, not just the visual ones.  Sometimes using VisualTreeHelper.GetChild() might make more sense.

With this in place, it’s easy to put code in your AssociatedObject’s Loaded event to find the element with the matching name, throw an error if not found, and then manipulate it at will.

void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            if (String.IsNullOrEmpty(_subCanvasName))
                throw new ArgumentNullException(_subCanvasName);

            _subCanvas = GetChildByName<Canvas>(this.AssociatedObject, _subCanvasName);

            if (_subCanvas == null)
                throw new ArgumentException("No child element named " + _subCanvasName + " could be found.");

            _subCanvas.Background = new SolidColorBrush(Colors.Red);
        }

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s