How To: Attach event handler to element in DataTemplate?

This question/problem is a little bit esoteric, but hopefully someone has seen it before...

I need to attach an event handler to the Loaded event of Image elements within a DataTemplate. The Loaded event handling method is in a utility class, so I can't use XAML to attach the handler. In a Window's Loaded event handling method I call this method in the utility class :

FrameworkElementFactory factory = listBox.ItemTemplate.VisualTree;

while( factory != null )

{

if( factory.Type == typeof( Image ) )

{

factory.AddHandler( Image.LoadedEvent, new RoutedEventHandler( OnImageLoaded ) );

break;

}

factory = factory.FirstChild;

}

There are two problems, one is not too bad (just odd) and the other is a blocking issue for me.

1) If I assign the ListBox's ItemTemplate in XAML (via StaticResource markup ext) then the ItemTemplate property on the ListBox is null. I can get around this by looking up the datatemplate via FindResource() and explicitly assigning it to the ItemTemplate property. Ugly, but workable.

2) The VisualTree property is always null. I dont' understand why. How else can I get to the FrameworkElementFactory which creates an Image Calling LoadContent works just fine (it returns the expanded template) but how can I get to the objects that (I thought should be) in VisualTree Is this even possible to acheive, or do DataTemplates erase their visual tree at some point (optimization ).

Here's the template:

<DataTemplate x:Key="TestItemTemplate" DataType="{x:Type data:Foo}">

<StackPanel>

<Border BorderBrush="Black" BorderThickness="1" Padding="4" Margin="4">

<Image Source="{Binding SomeImageUri}" Width="100" Height="100" />

</Border>

</StackPanel>

</DataTemplate>

Thanks a lot for any help or suggestions. Have a great weekend!




Answer this question

How To: Attach event handler to element in DataTemplate?

  • kbradl1

    Well, I have blogged about how to use the VisualTreeHelper to traverse through the data template to add event handlers to the elements inside data template, you can take it as a reference to solve your problem:

    DataTemplate Is A Bit Naughtier To Play With



    Actually, traversal of the visual tree is the only solution currently I can think of, I still ponder why DataTemplate works quite different from ControlTemplate, because for ControlTemplate, you can use the Template.FindName() method, but for DataTemplate, FindName doesn't work.

    Sheva


  • bohly

    did you try creating the ItemTemplate in code

  • Duncan Woods

    Thanks for the thoughts, Michael. Unfortunately neither of the suggestions will work in my situation.

    - I can't make the event handling method in the utility class static. That wouldn't work with the way things are set up in that class.

    - I need to know whenever a new Image is created in a ListBox, not just the ones that exist when the window loads. During the run of the app, new items might be added to the ListBox's data source, and those new items would force the creation of a new Image element. That's why I needed to install a handler on the DataTemplate itself, so that no matter when the template is expanded, the resultant Image's Load event would be hooked.

    Thanks for the ideas, anyways. Also, that's a great blog entry you pointed out.



  • Pilsener

    Hi Lee, the original poster has mentioned that he cannot explicitly specify the event handler in xaml

    I think DataTemplate should have the same behaviour as ControlTemplate, because theoretically both of them are derived from FrameworkTemplate, they should expose the same behaviours.
    This has been a known issue, just as the FrameworkElement.Triggers only accepts EventTriggers.

    I really hope WPF team can improve triggers and data templates in the V2.0 of WPF.

    Sheva


  • lagu2653

    Hi, Michael, good example of using ItemContainerGenerator, but you assume that when the generator's status is set to GeneratorStatus.ContainersGenerated, the container has been initialized but not loaded. actually, it's untrue, when the status is GeneratorStatus.ContainersGenerated, the container already has done with layout, not to mention, it's done with loading. I know this because last time, I want to programmatically expand TreeView's nodes, I use the ItemContainerGenerator to do the trick, you know if the container is not loaded, and laid out, I even cannot expand it.

    Sheva


  • Catalin Zima

    Hi Sheva,

    That's not the behaviour I'm seeing. When a new item is added to the ListBox and the container is initially generated, ItemsChanged is called with GeneratorStatus.ContainersGenerated and on this first call item.IsLoaded is false. The item IsInitialised, ie created, but loaded hasn't been fired. I don't know whether layout has been called at this point, I haven't tested that. There are of course lots of subsequent calls to ItemsChanged after the item has been loaded and rendered. Anyway, that's the behaviour I'm getting in RC1. Are you seeing something different

    Michael


  • MarikaT

    Did you try this

    <DataTemplate x:Key="dt1a">

    <Image Loaded="img_Loaded" Name="img1" Source="{}"></Image>

    </DataTemplate>

    void img_Loaded(object sender, RoutedEventArgs e)

    {

    ...

    }



  • rmiao

    Josh,

    I'm using the code below for a slightly different purpose but it might give you some ideas:-

    protected override void OnInitialized(EventArgs e)

    {

    base.OnInitialized(e);

    this.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);

    }

    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)

    {

    if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)

    {

    for (int idx = 0; idx <= Items.Count-1; ++idx)

    {

    DependencyObject container = this.ItemContainerGenerator.ContainerFromIndex(idx);

    if (container is ListBoxItem)

    {

    ListBoxItem item = (ListBoxItem)container;

    // At this point, new items have been Initialized but not Loaded

    if (item.IsLoaded == false)

    {

    item.Loaded += new RoutedEventHandler(item_Loaded);

    }

    // Or call item.ApplyTemplate and walk the visual tree

    // to attach Loaded handler to your image element.

    }

    }

    }

    }

    void item_Loaded(object sender, RoutedEventArgs e)

    {

    ListBoxItem item = (ListBoxItem)sender;

    // walk visual tree. image.IsLoaded should still be false

    }

    As you said, containers are generated as needed in a VirtualisingStackPanel so the above can get called on panel resize, scroll or almost anytime.

    I still don't see why you can't do as lee suggested. Handle Image.Loaded in your codebehind class and call your utility class from that. Or is the XAML standalone with no x:Class

    --Michael


  • Driskell

    My mistake, I remember reading that, but after reading through all the posts, I forgot that



  • DellS

    A couple of thoughts...

    Can you make the event handler on your utility class static You could then use it from XAML and use the sender argument to identify the Image element.

    I think the VisualTree problem maybe that the ListBox.ItemContainerGenerator doesn't generate the ListBoxItems until Layout has been called. Try calling UpdateLayout after adding items or setting the ItemsSource if you do this in code. You can then get the visual tree for an each ListBoxItem from item.ContentTemplate.VisualTree. However, this is may be too late for you. See Mike Hillberg's recent blog http://blogs.msdn.com/mikehillberg/archive/2006/09/19/LoadedVsInitialized.aspx

    --Michael


  • Sequel2k5

    Is ItemContainerGernerator.ItemsChanged event any use You might be able to get hold of the item container and call ApplyTemplate early to build the visual tree and work from there.

    Or listen on the ListBox.LayoutUpdated event and iterate over the items to see what has changed

    --Michael


  • ShadowRayz

    Josh, I'm sure you already know my answer, attached properties . Add an attached dependency property to your UtilityClass (UtilityClass.HandleLoaded). In the RegisterAttached call, define a DependencyPropertyChangedHandler that is responsible for adding a handler to the sender's (the element the property is attached to) Loaded event.

    Unfortunately the DPChangedHandler has to be Static. If you don't have a static way to get to the correct instance of your UtilityClass, then this method won't work because you've already mentioned that you can't make the Event Handler for Loaded static.

    Once you've defined the DP, your XAML will look like this...note the only change is adding the UtilityClass.HandleLoaded attribute.

    <DataTemplate x:Key="TestItemTemplate" DataType="{x:Type data:Foo}">

    <StackPanel>

    <Border BorderBrush="Black" BorderThickness="1" Padding="4" Margin="4">

    <Image UtilityClass.HandleLoaded="True" Source="{Binding SomeImageUri}" Width="100" Height="100" />

    </Border>

    </StackPanel>

    </DataTemplate>



  • ron nash

    Sheva,

    Thanks for the input and nice blog entry. Unfortunately that won't work for me because it is possible that items will be added to the ListBox at any time. Just walking the visual tree at Loaded time is insufficient for hooking the events of those newly created objects. Also, if the ListBox's default VirtualizingStackPanel is used as the items panel, then the Images created on-demand will not be hooked at Loaded time either.

    Thanks for the help, though.



  • ReneeC

    Mike,

    I had tried using attached properties, but there are issues with using them in data templates. :( This explains the situation pretty well: http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=623575&SiteID=1 The solution used in that post does not apply to my situaiton, I think, because it requires the attached prop to be on item container, i.e. the ListBoxItem. Perhaps I might be able to walk from the ListBoxItem down to the corresponding Image element... Thanks for the help.



  • How To: Attach event handler to element in DataTemplate?