Tuesday, April 15, 2008 4:01 PM
mtaulty
Silverlight 2 - Getting to the ListBoxItems in a ListBox
If you've got a Silverlight UI like this;
<Grid x:Name="LayoutRoot" Background="White">
<ListBox
ItemsSource="{Binding}"
x:Name="lstNumbers">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Rectangle
Width="100"
Height="50"
Fill="Red"></Rectangle>
<TextBlock
Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
and you set up the DataContext so that the binding works as in;
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = Enumerable.Range(1, 9);
}
}
then there might be circumstances where you want to get hold of some element of the UI for each item in the list like our ListBoxItem, StackPanel, Rectangle, TextBlock here.
You might write something like;
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = Enumerable.Range(1, 9);
lstNumbers.SelectionChanged += OnSelectionChanged;
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (object o in e.AddedItems)
{
// Do something
}
}
but you'll find in your SelectionChanged handler that what you get access to are the data bound object values and not the UI elements (i.e. you get access to the integers here and not the ListBoxItem->StackPanel).
Generally, that's good because that's usually what you want but sometimes you might have a reason to actually get to the StackPanel or similar that we see above.
What to do?
One approach might be to derive your own ListBox. Maybe something a little like;
public class MyListBox : ListBox
{
public MyListBox()
{
uiElements = new List<DependencyObject>();
}
public DependencyObject GetUIElementByItemIndex(int itemIndex)
{
return (uiElements[itemIndex]);
}
protected override DependencyObject GetContainerForItemOverride()
{
DependencyObject dObject = base.GetContainerForItemOverride();
uiElements.Add(dObject);
return (dObject);
}
List<DependencyObject> uiElements;
}
and then I can use that in my UI and do something like;
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = Enumerable.Range(1, 9);
lstNumbers.SelectionChanged += OnSelectionChanged;
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxItem lbi =
(ListBoxItem)lstNumbers.GetUIElementByItemIndex(lstNumbers.SelectedIndex);
lbi.Background = new SolidColorBrush(Colors.Green);
}
}
and that seems to work ok. Or, another approach that I used is to leave ListBox well alone and alter the UI definition and code to look something like this;
<Grid x:Name="LayoutRoot" Background="White">
<ListBox
ItemsSource="{Binding}"
x:Name="lstNumbers">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Rectangle
Width="100"
Height="50"
Fill="Red"></Rectangle>
<TextBlock
x:Name="myText"
Text="{Binding}"
Loaded="OnTextBlockItemLoaded"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
That is, add a Loaded event on to some aspect of the UI. Then, in the handler for that event, grab the object you've created and store it for later use. For example;
public partial class Page : UserControl
{
List<TextBlock> textBlocks;
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
textBlocks = new List<TextBlock>();
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = Enumerable.Range(1, 9);
}
void OnTextBlockItemLoaded(object sender, EventArgs args)
{
textBlocks.Add((TextBlock)sender);
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}