How to create a generic method to get selected value from WPF ListBox Control
Filed under: Blend, Programming, WPF, XAML | Tags: Blend, C#, listbox, WPF, XAML |
On a recent project, I had several listbox controls and needed to pass values to a web service. Since all my listboxs used the same template, I wanted a generic method to handle the events.
Start by creating a new project named ListBoxDemo in Blend or Visual Studio. I use V# 2008 Express for my projects and prefer starting my projects there.
Add a new class file to your project and name it Contact.cs
| using System; using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.Text; namespace ListBoxDemo { public class Contact : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void Notify(string propertyName) { if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } int pID; public int ID { get { return this.pID; } set { if (this.pID == value) return; this.pID = value; Notify(”ID”); } } string pFirstName; public string FirstName { get { return this.pFirstName; } set { if (this.pFirstName == value) return; this.pFirstName = value; Notify(”FirstName”); } } string pLastName; public string LastName { get { return this.pLastName; } set { if (this.pLastName == value) return; this.pLastName = value; Notify(”LastName”); } } public string Name { get { return this.pFirstName + ” ” + this.pLastName; } } public Contact() { } public Contact(int Id, string FirstName, string LastName) { this.ID = Id; this.FirstName = FirstName; this.LastName = LastName; } } public class Contacts : ObservableCollection { } public class ContactsKeeper { Contacts pContacts = new Contacts(); public Contacts Contacts { get { return pContacts; } } public ContactsKeeper() { // This will populate the control with data. You could use this // to load from a database or a webservice pContacts.Add(new Contact(1, “John”, “Smith”)); pContacts.Add(new Contact(2, “Mike”, “Wallace”)); pContacts.Add(new Contact(3, “Sally”, “Miller”)); pContacts.Add(new Contact(4, “Albert”, “Einstein”)); } } } |
On the main window that was generated when you created your project, add a reference to the namespace where the Contacts class is located. We will need to create our Data Template for our listbox. In our sample, we could add our event handler, but we will do that progammatically in our cs code file.
| <Window x:Class=”ListBoxDemo.Window1″ xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation” xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” xmlns:local=”clr-namespace:ListBoxDemo” Title=”Window1″ Height=”300″ Width=”300″> <Window.Resources> <local:ContactsKeeper x:Key=”ContactsKeeperDataSource”/> <DataTemplate x:Key=”ListItemTemplate”> <StackPanel x:Name=”RecordStack”> <TextBlock Text=”{Binding Path=ID}” x:Name=”RecordId” Visibility=”Collapsed”/> <TextBlock Text=”{Binding Path=Name}” x:Name=”RecordName”/> </StackPanel> </DataTemplate> </Window.Resources> <Grid x:Name=”MainLayout”> <Grid.RowDefinitions> <RowDefinition Height=”*”/> <RowDefinition Height=”Auto”/> </Grid.RowDefinitions> <ListBox x:Name=”MyListBox” ItemsSource=”{Binding Contacts}” BorderBrush=”Transparent” DataContext=”{StaticResource ContactsKeeperDataSource}” Width=”Auto” ItemTemplate=”{DynamicResource ListItemTemplate}” ScrollViewer.CanContentScroll=”True”/> </Grid> </Window> |
Finally we need to add our event handler and assign it to our ListBox control. Open up the Window1.xaml.cs file.
| using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace ListBoxDemo { /// /// Interaction logic for Window1.xaml /// public partial class Window1 : Window { public Window1() { InitializeComponent(); // find the listbox ListBox l = (ListBox)this.FindName(”MyListBox”); if (l == null) return; // add the event handler to the listbox l.SelectionChanged += MyListBox_SelectionChanged; } private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { // find our listbox control ListBox l = ((ListBox)sender); if (l == null) return; // find the selected listbox item if (l.SelectedItems.Count < = 0) return; ListBoxItem itm = (ListBoxItem)l.ItemContainerGenerator.ContainerFromIndex(l.SelectedIndex); if (itm == null) return; // reference the data template being used. DataTemplate dt = itm.ContentTemplate; // find the border around each listboxitem Border b = (Border)VisualTreeHelper.GetChild(itm, 0); // get the content presenter, this is the area within the control // that holds our content ContentPresenter cp = (ContentPresenter)b.Child; // Our datatemplate has a stackpanel with our textblocks. // we need to find the stackpanel StackPanel sp = (StackPanel)dt.FindName(”RecordStack”, cp); // textblock that contains our record id TextBlock txt = (TextBlock)sp.FindName(”RecordId”); // Display our record id. This is pretty worthless and // pretty annoying, but with this you could do something // a bit more useful MessageBox.Show(”Contact ID: ” + txt.Text); } } } |
I would love to know if anyone has an easier way to do this operation.
MyListBox.SelectedItem should return the Contact directly, so you shouldn’t have to dig into the visuals of the DataTemplate.
Try something like this:
Contact contact = MyListBox.SelectedItem as Contact;
if (contact != null)
MsgBox.Show(”Contact.ID:” + contact.ID);
Thanks, Rob
Rob Relyea | WPF & Xaml Language Team
robrelyea.com | /blog | /wpf | /xaml
Rob,
Thanks, I discovered that over the weekend and forgot to post it. With one small exception, which doesn’t really make any difference. Thanks for the input, I wish I had found out about this before spending so much time traversing the visuals.
Contact contact = (Contact)MyListBox.SelectedItem;