Using RelativeSource To Remove Items From a Collection Bound To A ListBox or ItemsControl

January 27 2009

Okay, I’m not sure this is the best way to do this, but I needed a way to remove an item from an ItemsControl within the UI context of the item itself. My problem was that the item itself, when in a datatemplate, doesn’t know its parent.  The RelativeSource property came to the rescue (tip o’ the hat to Jaime Rodriguez), although my use of the Tag property seems a little funky. Here’s my solution:

 

<DataTemplate x:Key="DataTemplate1">
    <StackPanel Orientation="Vertical" >
        <TextBox Text="{Binding Path=DisplayName}"/>
<TextBox Text="{Binding Path=NavigateURL}"/>
<Button Content="Remove" Click="Remove_Click" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"/> </StackPanel> </DataTemplate>

Then, in the code behind, here’s what I do:

 

void Remove_Click(object sender, RoutedEventArgs e)
{
    Button button = (Button) sender;
    ListBox listBox = (ListBox)button.Tag;
    MenuItemCollection menuItemCollection = (MenuItemCollection)listBox.ItemsSource;
    menuItemCollection.Remove((MenuItem)button.DataContext);
}

Basically, I get the listbox out of the Tag element and then call into its ItemsSource property, remove the object which is bound to the data template itself.  (Note that MenuItem and MenuItemCollection are objects I created.)

Maybe a little hacky, but it works!

Comments (4) -

1/24/2009 12:39:00 PM #

Dan Puzey

Hi,

If your ItemsControl is (or could be) bound to a source list, there's a cleaner way to do this with RoutedCommands.  The gist of it would be:

- the ItemsControl is bound to a collection implementing INotifyCollectionChanged
- create a RoutedCommand for your "Delete" functionality
- put a CommandBinding around the ItemsControl.  The Execute method for the command should take the CommandParameter and remove it from the source collection.
- your button then becomes <Button Content="Remove" Command="YourCommand" CommandParameter="{Binding}" />  (note that the "{Binding}" will pass the current list item as parameter to the command)

This removes the funky/hacky aspects, and separates the functionality from the UI (it'd be trivial to turn your button into a menu item or a keyboard shortcut or a mouse gesture).

In my experience, a general rule for WPF is that if you're using click event handlers (especially on buttons!), you're missing something Smile

I'd be happy to expand on this example if you'd like it fleshed out Smile

Regards,

Dan.
          

Dan Puzey

1/24/2009 1:11:00 PM #

Thanks for the tip, Dan. Much more elegant. And I grimaced when I put that click handler in XAML, but I just wanted to get things working! At this point in the app, I've held back from commands because it added complexity without a huge gain in functionality, even though I know it is the "right" way to do things.  Commands are on the the task list, but at this point there are other items of higher priority.  But if I get to it, I'll definitely implement your solution.  Thanks again for posting and I'd be curious if others have thoughts on commands vs. handlers...
          

Karsten Januszewski

1/25/2009 2:17:00 AM #

Another option is to change the Tag binding:

Tag="{Binding Path=ItemsSource,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"

Then in the codebehind:

Button button = (Button) sender;
MenuItemCollection listBox = (MenuItemCollection)button.Tag;
menuItemCollection.Remove((MenuItem)button.DataContext);



Another option that saves you having one click handler per item:

On the ListBox, put a handler for ButtonBase.Click:

<ListBox ButtonBase.Click="OnClick"...>...</ListBox>

void OnClick(object sender, RoutedEventArgs e)
{
    ListBox lb = (ListBox)sender;
    Button b = (Button)e.Source;
    if(b.Content == "Remove")
    {
        (lb.ItemsSource as MenuItemCollection).Remove(b.DataContext);
    }
}

          

eric burke

1/25/2009 11:09:00 AM #

Nice Eric! Thanks
          

Karsten Januszewski

Add comment

Enter your name, handle, alias, or email.

We'll incarnate your avatar from the services below.



biuquote
Loading