Let's say we have a listbox databound to a list of Book instances. The Book class looks like this:
namespace DockOfTheBay{ using System; using System.Collections.Generic; using System.Linq; using System.Text;/// <summary>
/// Sample book class.
/// </summary>
public class Book
{/// <summary>
/// Gets or sets the title of the book.
/// </summary>
public string Title { get; set; }
/// <summary>
/// Get or sets a value indicating whether the book is still on sale.
/// </summary>
public bool Discontinued { get; set; }
}
}
Books are displayed via an itemtemplate that contains a checkbox bound to the Discontinued property. For one or another reason, we want to programmatically enable or disable that checkbox. For demo purposes, let's implement a button that toggles the IsEnabled property of all checkboxes.
This is the XAML for the demo form:
<Window x:Class="DockOfTheBay.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DockOfTheBay"
Title="ItemTemplate Access"
Height="160" Width="200"
ResizeMode="NoResize">
<Window.Resources>
<DataTemplate x:Key="BookTemplate" DataType="{x:Type local:Book}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" SharedSizeGroup="Title" />
<ColumnDefinition Width="auto" SharedSizeGroup="Discontinued" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Title}"
Grid.Row="0" Grid.Column="0" Margin="2"/>
<CheckBox x:Name="DiscontinuedCheckBox"
IsChecked="{Binding Discontinued}"
IsEnabled="False"
Grid.Row="0" Grid.Column="1"
Margin="2"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="BookListBox"
ItemTemplate="{StaticResource BookTemplate}"
Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Center"
Margin="4" BorderThickness="0" />
<Button x:Name="ToggleButton"
Click="ToggleButton_Click"
Content="Toggle Checkbox Access"
Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Center"
Margin="4" />
</Grid>
</Window>
The form looks like this:

Let's implement the button's event handler. We have to iterate through the listbox's items. Thanks to data binding, these items are all Book -not ListBoxItem- instances. We gain access to the surrounding user interface element through a ItemContainerGenerator.ContainerFromItem call. The Content property of that listBoxItem refers back to the Book instance, so in order to access the child controls we have to follow another path: that of its ContentPresenter. We find it by walking through the VisualTree, looking for an instance of the appropriate type. The next extension method can be very useful here:
namespace DockOfTheBay{ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Media;/// <summary>
/// Extension methods to the DependencyObject class.
/// </summary>
public static class DependencyObjectExtensions
{/// <summary>
/// Find a child of a specific type in the Visual Tree.
/// </summary>
public static T FindVisualChild<T>(this DependencyObject obj) where T : DependencyObject
{for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
{ return (T)child;}
else {T grandChild = child.FindVisualChild<T>();
if (grandChild != null)
{ return grandChild;}
}
}
return null;
}
}
}
Eventually, the ContentTemplate of that presenter is the DataTemplate instance to which we'll send the FindName call. Here's the C# for the entire form:
namespace DockOfTheBay{ using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Documents;/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{/// <summary>
/// Initializes a new instance of the Window1 class.
/// </summary>
public Window1() {InitializeComponent();
this.FillListBox();}
/// <summary>
/// Fills the listbox with some sample books.
/// </summary>
private void FillListBox()
{List<Book> books = new List<Book>();
books.Add(new Book { Title = "WPF Unleashed ", Discontinued = false });
books.Add(new Book { Title = "WinForms in action", Discontinued = true });
books.Add(new Book { Title = "The iBook for Dummies", Discontinued = false });
books.Add(new Book { Title = "Essential WPF", Discontinued = false });
this.BookListBox.ItemsSource = books;}
/// <summary>
/// Toggle the access to the CheckBox.
/// </summary>
/// <param name="sender">Sender of the event (button).</param>
/// <param name="e">Event arguments.</param>
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{ // Iterate through Booksforeach (var item in this.BookListBox.Items)
{ // Get the ListBoxItem around the Book ListBoxItem listBoxItem =this.BookListBox.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
// Get the ContentPresenterContentPresenter presenter = listBoxItem.FindVisualChild<ContentPresenter>();
// Get the Template instance DataTemplate template = presenter.ContentTemplate; // Find the CheckBox within the TemplateCheckBox checkBox = template.FindName("DiscontinuedCheckBox", presenter) as CheckBox;
checkBox.IsEnabled = !checkBox.IsEnabled;
}
}
}
}

This comment has been removed by the author.
ReplyDeleteGreat! Love this solution, thank you very much.
ReplyDelete