Remove item from ItemsControl

3.6k views Asked by At

I have some UserControls inside of an ItemsControl.

Each UserControl (called JobView in my case) has a ContextMenu with 1 Item('Remove').

Now when the Item on the UserControls ContextMenu is Clicked I want to remove it from the ItemsControlItemCollection.

For that I need to get the Item, the ContextMenu is assigned to.

Currently I am using this:

private void Item_Click(object sender, RoutedEventArgs e)
{

    MenuItem item = (MenuItem)sender;
    JobView view = null;

    FrameworkElement currentObject = item;
    while(1 == 1)
    {
        currentObject = currentObject.Parent as FrameworkElement;
        if(currentObject.GetType() == typeof(System.Windows.Controls.Primitives.Popup))
        {
            view = (currentObject as System.Windows.Controls.Primitives.Popup).PlacementTarget as JobView;
            break;
        }
     }
     //Remove from ObservableCollection<JobView>:
     JobViews.Remove(view);
}

It is working fine but I am pretty sure that there must me some better solution.

Took me some time to figure that one out but I can't get to a different solution myself.

How can I get the JobView using the sender object or is using sendercompletely wrong in this case ?

1

There are 1 answers

0
mm8 On BEST ANSWER

Binding or setting the ItemsSource of an ItemsControl to an ObservableCollection of UIElements or views is wrong. At least if you care about the MVVM design pattern which is the recommended pattern to use for all XAML based applications.

You should create a class that represents the state of your JobView and bind to an ObservableCollection of such objects, e.g.:

public class Job
{
}

public class JobViewModel
{
    public ObservableCollection<Job> Jobs { get; } = new ObservableCollection<Job>()
    {
        new Job(),
        new Job(),
        new Job()
    };
}

You then use your UserControl (JobView) in the ItemTemplate of the ItemsControl:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new JobViewModel();
    }
}

<ItemsControl ItemsSource="{Binding Jobs}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:JobView />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

With this in place you could then add an ICommand property to the JobView class that binds to a command property of the view model that removes the Job class from the source collection. Please refer to the following sample code.

JobViewModel.cs:

public class JobViewModel
{
    public JobViewModel()
    {
        RemoveCommand = new DelegateCommand<object>(argument =>
        {
            Jobs.Remove(argument as Job);
        });
    }

    public ObservableCollection<Job> Jobs { get; } = new ObservableCollection<Job>()
    {
        new Job(),
        new Job(),
        new Job()
    };

    public DelegateCommand<object> RemoveCommand { get; }
}

JobView.xaml.cs:

public partial class JobView : UserControl
{
    public JobView()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(JobView));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }
}

JobView.xaml:

<UserControl x:Class="WpfApplication1.JobView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="uc">
    <UserControl.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Remove" Command="{Binding PlacementTarget.Command, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
                      CommandParameter="{Binding}"/>
        </ContextMenu>
    </UserControl.ContextMenu>
    <Grid>
        <TextBlock>job view...</TextBlock>
    </Grid>
</UserControl>

MainWindow.xaml:

<ItemsControl ItemsSource="{Binding Jobs}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <local:JobView Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

You will need to implement the DelegateCommand class yourself or you could use the one available in the Prism MVVM Library: https://github.com/PrismLibrary/Prism/blob/master/Source/Prism/Commands/DelegateCommand.cs

Prism can be installed using NuGet: https://www.nuget.org/packages/Prism.Wpf/.

You can read more about the MVVM pattern here: https://msdn.microsoft.com/en-us/library/hh848246.aspx. I really recommend you to learn it if you are developing XAML applications.