Welcome to MSDN Blogs Sign in | Join | Help

How do I programmatically interact with template-generated elements? Part II

This post shows you how to find a named element within a DataTemplate.

 

In Part I, we discussed how to find a named element within a ControlTemplate. That was fairly simple; you’d call Template.FindName on the control that the ControlTemplate has been applied to. But if the template is a DataTemplate, then the scenario is a bit more complex. For instance, if you have a data-bound ListBox that uses a DataTemplate, each generated list item has a tree of generated elements (as described by your DataTemplate). In this post, we’ll walk through that scenario and retrieve a named element within the DataTemplate of a certain list item.

 

Before showing the code that finds the named element, let’s set up our scenario. We have a Button and a ListBox that’s data-bound (if you want to see how this particular ListBox is bound, you can download the attached zip file).

 

    <Border Margin="15" BorderBrush="Aqua" BorderThickness="2" Padding="8" CornerRadius="5">

      <StackPanel>

        <ListBox Name="myListBox" ItemTemplate="{StaticResource myDataTemplate}"

                 IsSynchronizedWithCurrentItem="True">

          <ListBox.ItemsSource>

            <Binding Source="{StaticResource InventoryData}" XPath="Books/Book"/>

          </ListBox.ItemsSource>

        </ListBox>

        <Button Margin="10"

                Click="DataTemplateFindElement">Get text of textBlock in DataTemplate</Button>

      </StackPanel>

    </Border>

 

The ListBox uses a simple DataTemplate. The DataTemplate has an element that’s given the name textBlock:

 

    <DataTemplate x:Key="myDataTemplate">

      <TextBlock Name="textBlock" FontSize="14">

        <TextBlock.Text>

          <Binding XPath="Title"/>

        </TextBlock.Text>

      </TextBlock>  

    </DataTemplate>

 

This screenshot shows our simple UI:

 

 

 

Now let’s write the button event handling code so that when we click the button, we retrieve the TextBlock that’s within the DataTemplate of the current list item. To do that, we need to perform the following steps:

 

1. Get a hold of the current list item:

 

    // Note that the ListBox must have

    // IsSynchronizedWithCurrentItem set to True for this to work

    ListBoxItem myListBoxItem =

        (ListBoxItem)(myListBox.ItemContainerGenerator.ContainerFromItem(myListBox.Items.CurrentItem));

 

2. Find the ContentPresenter of that list item by walking through its visual tree [1]:

 

    ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);

 

3. Now you can call FindName on the DataTemplate of that ContentPresenter:

 

    DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;

    TextBlock myTextBlock = (TextBlock)myDataTemplate.FindName("textBlock", myContentPresenter);

 

4. Finally, you can do whatever you want to the element you just retrieved. For demonstration purposes, we create a message box to show the content of that the TextBlock:

 

    MessageBox.Show("The text of the named TextBlock in the DataTemplate of the selected list item: "

        + myTextBlock.Text);

 

Now we can select an item, click the button, and see the message box that shows the text content of the TextBlock retrieved from the corresponding DataTemplate!

 

 

 

You can download this project from the attached zip file. Enjoy!

 

 

[1] The FindVisualChild method called in step 2:

 

    private childItem FindVisualChild<childItem>(DependencyObject obj)

        where childItem : DependencyObject

    {

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

        {

            DependencyObject child = VisualTreeHelper.GetChild(obj, i);

            if (child != null && child is childItem)

                return (childItem)child;

            else

            {

                childItem childOfChild = FindVisualChild<childItem>(child);

                if (childOfChild != null)

                    return childOfChild;

            }

        }

        return null;

    }

Published Monday, April 16, 2007 6:10 PM by wcsdkteam
Attachment(s): FindElementinDataTemplate_C#_VB.zip

Comments

# A gem of a WPF blog &laquo; Josh Smith on WPF

Tuesday, April 17, 2007 12:28 AM by A gem of a WPF blog « Josh Smith on WPF

# re: How do I programmatically interact with template-generated elements? Part II

One question:

What changes should I make in the code if my dataTemplate is used by ContentControl's ContentTemplate rather than a ListBox's ItemTemplate?

<ContentControl Name="myContentControl"         Content="{Binding Path=Name}"                 ContentTemplate="{StaticResource myDataTemplate}" />

I've been struggling with this for days. Your help would be greatly appreciated.

Thursday, April 19, 2007 10:56 AM by rnair

# re: How do I programmatically interact with template-generated elements? Part II

If it is a ContentControl, the underlying technique is the same. You first look for the ContentPresenter of the ContentControl, then you find the element on the DataTemplate that's set on that ContentPresenter.

In your case your code will be something like this:

ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myContentControl);

DataTemplate myTemplate = (DataTemplate)this.Resources["myDataTemplate"];

// "textBlock" is the name of the element within the DataTemplate

TextBlock retrievedTextBlock = (TextBlock)myTemplate.FindName("textBlock", myContentPresenter);

// then do whatever to retrievedTextBlock

Hope that helps!

Thursday, April 19, 2007 8:00 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

I'm not sure if it provides any performance improvement but I think changing these lines

DependencyObject child = VisualTreeHelper.GetChild(obj, i);

           if (child != null && child is childItem)

To this

childItem child= VisualTreeHelper.GetChild(

 obj,i) as childItem;

if (child!=null)

Will avoid the extra cast below. This might save some cycles especially in a deep, bushy visual tree.

Monday, May 07, 2007 11:22 AM by Ivolved

# re: How do I programmatically interact with template-generated elements? Part II

What changes do to I need to make if I'm using a ListView/GridView combination and each column uses a different DataTemplate - I've modified the code and it will only retrieve the correct info when the first column is click for any other column that is click I recieve the error "This operation is valid only on elements that have this template applied."

<GridViewColumn Header="Company" CellTemplate="{StaticResource CompanyName}"/>

<GridViewColumn Header="Insured" CellTemplate="{StaticResource InsuredName}"/>

For example click on column "Company" works OK,receive good textBlock data click on "insured" and receive above error message

I've been looking at this a weeks now, so any input you can give would be greatly appreciated.

Monday, June 04, 2007 7:21 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

I'am still writing i VB :(  (Firms wish)

I wound really like it translated to VB. Is that possible for any of you :).

Dont know what refers to the where command in vb . bostilling at hotmail.com for a VB example

Monday, June 04, 2007 8:57 AM by bomanjii

# re: How do I programmatically interact with template-generated elements? Part II

Hi pgw1959, you want to start by getting to the ListViewItem for one of the non header rows in the ListView. The tree structure for each ListViewItem is as follows:

ListViewItem

|

Border

|

Grid

|

GridViewRowPresenter

|                                |            

ContentPresenter           ContentPresenter

Notice how the GridViewRowPresenter hosts the ContentPresenters for the different columns. The CellTemplate you want is applied to the ContentPresenter at the same index (within the VisualChildren collection) as the column# for the selected Header column.

Also, I should point out that instead of looking up the DataTemplate resource, you can do this to get the DataTemplate.

DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;

That should help as well. This makes more sense and I fixed the blog post accordingly.

Let me know if I can help further. Thanks!

Tuesday, June 05, 2007 7:26 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi bomanjii,

I just added the VB version of the sample to the zip file. Enjoy!

Tina

Tuesday, June 05, 2007 7:36 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Thank you Tina just thought you saved me :D .. But can see I´am using the ItemContainerStyle to set the style of the elements instead of the controlTemplate like your example.

How Can I tweak the codebehind to look for ItemContainerStyle contra controlTemplate ?

/Bo

Wednesday, June 06, 2007 5:54 AM by bomanjii

# re: How do I programmatically interact with template-generated elements? Part II

Thanks wcsdkteam,

I've got this working now thanks very much.

One last question hopefully; how do I code for column swaping on the grid?

Wednesday, June 06, 2007 11:14 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi Bo,

I may be misunderstanding your question but this blog post is only applicable to templates. Styles only set properties; they do not generate a tree of elements. If you have the style name you can do something like:

Dim myStyle As Style = Me.Resources("myContainerStyle")

Alternatively, you can do:

' to get the container

Dim myListBoxItem As ListBoxItem = Me.myListBox.ItemContainerGenerator.ContainerFromItem(currentListBoxItem)

' to get the style set on the container

Dim myStyle As Style = myListBoxItem.Style

but in either case, you can't do FindName to find an element within there because it is not a tree of elements.

Again, I may have completely missed your question. What is your scenario? Feel free to email "wcsdkblg" and leave a small sample that demonstrates the problem and we can help you out further. Or perhaps you want to use the WPF forum:

http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=119&SiteID=1

Wednesday, June 06, 2007 8:38 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi pgw,

If you use the index of the selected header within its row presenter, then things should work correctly. Is that not the case for you?

Thursday, June 07, 2007 1:00 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi wcsdkteam,

I use the header index (as code below) and findname() returns null

// Column index

int index = view.Columns.IndexOf(column);

// Get the non header row

ListViewItem listViewItem =

(ListViewItem)(ResultListView.ItemContainerGenerator.ContainerFromIndex(index));

// Find the GridViewRowPresenter

GridViewRowPresenter contentPresenter = FindVisualChild<GridViewRowPresenter>(listViewItem);

// Get the column details same index as header

DependencyObject child = VisualTreeHelper.GetChild(contentPresenter, index);

// Get the template used for this column

cellTemplate = (child as ContentPresenter).ContentTemplate;

// Get 1st TextBlock details

TextBlock textBlock =

cellTemplate.FindName(name, (FrameworkElement)child) as TextBlock;

// Get the binding details

bind = BindingOperations.GetBinding( textBlock, TextBlock.TextProperty);

// Extract the binding details - XML node

sortNode = bind.XPath.ToString();

Thanks, pgw1959

Friday, June 08, 2007 4:33 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi pgw1959, just wanted to let you know that we're investigating the issue.

Tuesday, June 12, 2007 2:47 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi wcsdkteam,

If it helps I posted the code in the WPF forum http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1691968&SiteID=1

Thanks, pgw1959

Wednesday, June 13, 2007 8:00 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi pgw1959,

Turns out the GridViewRowPresenter does not change the sequence of its children when the columns are swapped. To work around this you need to do this (this solution relies on a certain internal implementation (ordering of children in the GridViewRowPresenter) which can be changed in the future):

// GridViewRowPresenter has several ContentPresenters as its visual children

// Get the ContentPresenter that is at the column of the clicked Header (using the workaround)

// gvrPresenter is the GridViewRowPresenter of the ListViewItem

// clickedHeader is the GridViewColumnHeader that's been selected

ContentPresenter contentPresenter = VisualTreeHelper.GetChild(gvrPresenter, _actualIndexMap[clickedHeader.Column]) as ContentPresenter;

// Get the DataTemplate on that ContentPresenter

DataTemplate cellTemplate = contentPresenter.ContentTemplate;

The workaround:

GridView gridView = ((GridView)myListView.View);

for (int i = 0; i < gridView.Columns.Count; i++)

{

   _actualIndexMap[gridView.Columns[i]] = i;

}

with _actualIndexMap being:

Dictionary<GridViewColumn, int> _actualIndexMap = new Dictionary<GridViewColumn, int>();

This came from my example so you may have to tweak it a bit to apply it on your code.

Wednesday, June 13, 2007 1:51 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi wcsdkteam,

Thanks as always for getting back to me.

I've implemented your "work around" and it seems to work well, however I'm now suffering from another problem I have several columns define in the gridview (some using templates other not) as below ....

 <ListView x:Name="ResultListView" IsSynchronizedWithCurrentItem="true"

       ItemsSource="{Binding Source={StaticResource DatasetDataView}, XPath=Row}"

       GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler">

   <ListView.View>

     <GridView x:Name="RiskDisplayView" >

       <GridViewColumn Header="Treaty" DisplayMemberBinding="{Binding XPath=PolicyReference}"/>

       <!-- Company -->

       <GridViewColumn Header="Company" CellTemplate="{StaticResource CompanyName}"/>

       <!-- Insured -->

       <GridViewColumn Header="Insured" CellTemplate="{StaticResource InsuredName}"/>

       <GridViewColumn Header="Their Ref" DisplayMemberBinding="{Binding XPath=SICSReference}"/>

       <!-- Type -->

       <GridViewColumn Header="Type" CellTemplate="{StaticResource TreatyType}"/>

       <!-- Broker Details -->

       <GridViewColumn Header="Bkr" CellTemplate="{StaticResource BrokerTemplate}"/>

       <!-- Share -->

       <GridViewColumn Header="S.Share %" DisplayMemberBinding="{Binding XPath=SignedShare, Converter={StaticResource fourpointConverter}}"/>

       <!-- Inception and Expiry Dates -->

       <GridViewColumn Header="Valid From" DisplayMemberBinding="{Binding XPath=InceptionDate, Converter={StaticResource dateConverter}}"/>

       <GridViewColumn Header="Expiry" DisplayMemberBinding="{Binding XPath=ExpiryDate, Converter={StaticResource dateConverter}}"/>

       <GridViewColumn Header="Cancel Year" DisplayMemberBinding="{Binding XPath=CancelYear}"/>

       <GridViewColumn Header="St" DisplayMemberBinding="{Binding XPath=Status}"/>

       <!-- Currency Code, Premium/Limit/Excess/Out Limit -->

       <GridViewColumn Header="CCY" DisplayMemberBinding="{Binding XPath=LimitCurrency}"/>

       <GridViewColumn Header="Premium" DisplayMemberBinding="{Binding XPath=Premium, Converter={StaticResource twopointConverter}}"/>

       <GridViewColumn Header="Limit" DisplayMemberBinding="{Binding XPath=LimitAmount, Converter={StaticResource zeropointConverter}}"/>

       <GridViewColumn Header="Excess/Ded" DisplayMemberBinding="{Binding XPath=ExcessAmount, Converter={StaticResource zeropointConverter}}"/>

       <GridViewColumn Header="Our Limit" DisplayMemberBinding="{Binding XPath=LimitAmount, Converter={StaticResource zeropointConverter}}"/>

     </GridView>

   </ListView.View>

 </ListView>

When I click column "Bkr" the line...

ListViewItem listViewItem = (ListViewItem)(ResultListView.ItemContainerGenerator.ContainerFromIndex(index));

returns null, however if I swap columns "Bkr" and "Type" around, "Bkr" works fine but the line above returns null for "Type" and vice-versa.

Is there a limit on the number of Cell Templates you can use or I'm coding it wrong?, the basic example (without latest workarounds) I'm using is posted at http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1691968&SiteID=1

Thanks again for you help, pgw1959.

Thursday, June 14, 2007 11:10 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi wcsdkteam,

From further tests it appears that the 5th column that uses a CellTemplate, no matter where it positoned within all columns always return NULL for the call to...

ListViewItem listViewItem = (ListViewItem)(ResultListView.ItemContainerGenerator.ContainerFromIndex(index));

Thanks, pgw1959

Friday, June 15, 2007 4:46 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi pgw1959,

Unfortunately, I'm not able to repro this problem. What is index in the above example? Note that index should be ResultsListView.SelectedIndex.

Also, do you mean you're encountering this problem after the workaround? Or the problem occurs with or without the workaround?

If you're interested, please send a small sample with the steps that demonstrate the exact problem you're encountering to us at wcsdkblg@microsoft.com. Alternatively, you may want to try the WPF forum as well, so more people may share their insights.

Thanks!

Friday, June 15, 2007 5:44 PM by wcsdkteam

# re: How do I programmatically interact with template-generated elements? Part II

Hi wcsdkteam,

I have change the line...

ListViewItem listViewItem = (ListViewItem)(ResultListView.ItemContainerGenerator.ContainerFromIndex(index));

to this...

ListViewItem listViewItem = (ListViewItem)(ResultListView.ItemContainerGenerator.ContainerFromIndex(ResultListView.SelectedIndex));

The index was coming from

index = gridView.Columns.IndexOf(column);

My little example is now working, including column swapping so thanks very much for your help on this.

Thanks, pgw1959

Monday, June 18, 2007 6:47 AM by pgw1959

# re: How do I programmatically interact with template-generated elements? Part II

Hi,

I found above solution, this helped a lot. In the context of TreeViewItem.HeaderTemplate (used to display tree nodes with image+text) it works almost as desired. Still: When I try to find the templated image of a node (TreeViewItem) and set the Image programatically, ALL(!) nodes change their image, not just the one I work on.

Can somebody explain why?

Thanks

NRN

Tuesday, February 05, 2008 12:38 PM by NRN

# WPF中如何使用代码操作数据模板生成的控件

有一个Listbox,里面的Item是通过数据模板生成的,如下所示:

Wednesday, May 07, 2008 1:46 AM by pdfw

# re: How do I programmatically interact with template-generated elements? Part II

Hi ,,,

Using the method which is described in the blog . i m triying to bind a custom dropdown(which is inside a expander ) in my code behind .

the Control hierarchy is--

ListBox--->DataTemplate -->StackPanel--->Expander---> StackPanel---> StackPanel -->Custom DDL(i have to bind this in code behind)

but the problem is that , i m getting the listboxitem as null...

Here is my XAML ...

<Window.Resources>

       <DataTemplate x:Key="DataTemplateAlertList">

           <StackPanel x:Name="stplAlertList"  Orientation="Vertical">

               <Expander  x:Name="expAlert" Width="650" BorderBrush="Silver"  BorderThickness="1" Opacity="1">

                   <StackPanel  x:Name="stplAlert"  Orientation="Horizontal">

                       <StackPanel  x:Name="stplSms" Margin="140,0,0,0" Orientation="Vertical">

                           <ComboBox Margin="0,10,0,0" Height="20" Width="80" x:Name="ddlSmsTime">

                               <ComboBox.ItemsPanel>

                                   <ItemsPanelTemplate>

                                       <VirtualizingStackPanel />

                                   </ItemsPanelTemplate>

                               </ComboBox.ItemsPanel>

                               <ComboBox.ItemTemplate>

                                   <DataTemplate>

                                       <TextBlock Text="{Binding}" FontSize="10" Height="20" />

                                       </DataTemplate>

                               </ComboBox.ItemTemplate>

                           </ComboBox>

                           <ComboBox Margin="0,10,0,0" Height="20" Width="80" x:Name="ddlEmailTime">

                               <ComboBox.ItemsPanel>

                                   <ItemsPanelTemplate>

                                       <VirtualizingStackPanel />

                                   </ItemsPanelTemplate>

                               </ComboBox.ItemsPanel>

                               <ComboBox.ItemTemplate>

                                   <DataTemplate>

                                       <TextBlock Text="{Binding}" FontSize="10" Height="20" />

                                       </DataTemplate>

                               </ComboBox.ItemTemplate>

                           </ComboBox>

                           <ComboBox Margin="0,10,0,0" Height="20" Width="80" x:Name="ddlIVRTime">

                               <ComboBox.ItemsPanel>

                                   <ItemsPanelTemplate>

                                       <VirtualizingStackPanel />

                                   </ItemsPanelTemplate>

                               </ComboBox.ItemsPanel>

                               <ComboBox.ItemTemplate>

                                   <DataTemplate>

                                       <TextBlock Text="{Binding}" FontSize="10" Height="20" />

                                       </DataTemplate>

                               </ComboBox.ItemTemplate>

                           </ComboBox>

                       </StackPanel>  

                    </StackPanel>

                   <Expander.Header >

                       <StackPanel Orientation="Horizontal">

                           <TextBlock  Text="{Binding Path=Alert_Name }"  Width="70" />

                                   <CheckBox Margin="80,0,0,0" IsEnabled="{Binding Path=Allow_EMail }"  x:Name="chkSmsHeader">SMS</CheckBox>

                                   <CheckBox Margin="115,0,0,0" IsEnabled="{Binding Path=Allow_SMS }"  x:Name="chkEmailHeader">Email</CheckBox>

                                   <CheckBox Margin="120,0,0,0" IsEnabled="{Binding Path=Allow_IVR }"  x:Name="chkIvrHeader">Ivr</CheckBox>

                               </StackPanel>

                   </Expander.Header>

               </Expander>

           </StackPanel>

       </DataTemplate>

   </Window.Resources>

 <Grid ShowGridLines="False"   Height="540" Width="800">

       <Grid.RowDefinitions>

           <RowDefinition Height="20"/>

           <RowDefinition/>

           <RowDefinition Height="20"/>

       </Grid.RowDefinitions>

       <Grid.ColumnDefinitions>

           <ColumnDefinition Width="20" />

           <ColumnDefinition/>

           <ColumnDefinition Width="20"/>

       </Grid.ColumnDefinitions>

       <ListBox   ItemTemplate="{StaticResource  DataTemplateAlertList}" Grid.Row="1" Grid.Column="1" IsSynchronizedWithCurrentItem="True" ScrollViewer.VerticalScrollBarVisibility="Visible" x:Name="LiAlertList" >

       </ListBox>

   </Grid>

My Code BEhind

void  Window1_Loaded(object sender, RoutedEventArgs e)

   {

/*------here i get the ListBoxItem  as null */

   ListBoxItem myListBoxItem = (ListBoxItem)(LiAlertList.ItemContainerGenerator.ContainerFromItem(LiAlertList.Items.CurrentItem));

   ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);

   DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;

   ComboBox myComboBox = (ComboBox)myDataTemplate.FindName("ddlSmsTime", myContentPresenter);

        /*------Binding the DropDownlist with a data source*/

   myComboBox.ItemsSource = frequencylist;

  }

 private childItem FindVisualChild<childItem>(DependencyObject obj)where childItem : DependencyObject

  {

    if (VisualTreeHelper.GetChildrenCount(obj) > 0)

      {

       for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)

       {

         DependencyObject child = VisualTreeHelper.GetChild(obj, i);

         if (child != null && child is childItem)

        return (childItem)child;

        else

        {

         childItem childOfChild = FindVisualChild<childItem>(child);

         if (childOfChild != null)

          return childOfChild;

        }

       }

     }

   return null;

  }

I m not able to bind  the custom dropdownlist.

Can any body plase help me in this , I m bit new to WPF .

Regards

Gaurav Chaturvedi

Tuesday, September 02, 2008 10:43 AM by Gaurav Chaturvedi
Anonymous comments are disabled
 
Page view tracker