Как "поймать" маршрутизированное событие в UserControl?
Ситуация такая.
У меня есть UserControl(называется NamePanel), состоящий из одного TextBox и одной Button(кнопка удалить). Здесь у меня определено RoutedEvent DeleteClick.
Дизайн:
x:Name="Root">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyName}"/>
<Button x:Name="buttonDelete" Content="Delete" Click="buttonDelete_Click"/>
</Grid>
CodeBehind:
public partial class NamePanel : UserControl
{
public static readonly DependencyProperty MyNameProperty = DependencyProperty.Register("MyName", typeof(string), typeof(NamePanel), new PropertyMetadata(""));
public string MyName
{
get { return (string)GetValue(MyNameProperty); }
set { SetValue(MyNameProperty, value); }
}
public NamePanel()
{
InitializeComponent();
Root.DataContext = this;
}
public event RoutedEventHandler DeleteClick;
private void buttonDelete_Click(object sender, RoutedEventArgs e)
{
DeleteClick?.Invoke(sender, e);
}
}
Также есть другой UserControl(называется NameStack), состоящий из одного свойства IEnumerable ItemSource и методов для его обновления. В дизайне добавлены лишь ScrollViewer, а внутри него одна StackPanel. Эта StackPanel заполняется дочерними элементами из коллекции ItemSource при инициализации контрола NameStack.
Дизайн:
<Grid>
<ScrollViewer>
<StackPanel Orientation="Vertical" x:Name="MainStackPanel">
</StackPanel>
</ScrollViewer>
</Grid>
CodeBehind:
public partial class NameStack : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSourcee", typeof(IEnumerable), typeof(NameStack), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public NameStack()
{
InitializeComponent();
}
private void Refresh()
{
if (ItemsSource != null)
{
MainStackPanel.Children.Clear();
foreach (var name in ItemsSource)
{
MainStackPanel.Children.Add(new NamePanel() { MyName = name.ToString() });
}
}
}
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var namestack = sender as NameStack;
if (namestack != null)
{
namestack.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
namestack.Refresh();
}
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (oldValueINotifyCollectionChanged != null)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (newValueINotifyCollectionChanged != null)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
private void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Refresh();
}
}
Вопрос - как мне можно перехватить и обработать событие DeleteClick, которое может быть вызвано любым из дочерних NamePanel, внутри NameStack?
Ответы (1 шт):
Вы не используйете привязки данных и команды, поэтому делаете много работы вручную.
Вот простой пример юзерконтрола с кнопками удаления.
Для начала, добавьте класс команды в проект
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
Он позволит удобно работать с командами
Сам юзерконтрол
<UserControl x:Class="WpfAppDeleteableItemsControl.EditableItemsControl"
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:WpfAppDeleteableItemsControl"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Template>
<ControlTemplate>
<ItemsControl ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding}"/>
<Button Content="X" Grid.Column="1" Margin="1" Command="{Binding DeleteCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</UserControl.Template>
</UserControl>
public partial class EditableItemsControl : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IList), typeof(EditableItemsControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
private ICommand _deleteCommand;
private ICollectionView CollectionView
=> CollectionViewSource.GetDefaultView(ItemsSource);
public IList ItemsSource
{
get => (IList)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public ICommand DeleteCommand => _deleteCommand ??= new RelayCommand(parameter =>
{
ItemsSource.Remove(parameter);
// если коллекция сама умеет обновляться, тогда не нужно делать это принудительно
if (ItemsSource is not INotifyCollectionChanged)
CollectionView.Refresh();
});
public EditableItemsControl()
{
InitializeComponent();
}
}
Проверяем
<Window x:Class="WpfAppDeleteableItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppDeleteableItemsControl"
mc:Ignorable="d" Loaded="Window_Loaded"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:EditableItemsControl ItemsSource="{Binding Items}" HorizontalAlignment="Left"/>
</Grid>
</Window>
ObervableCollection<T>, реализует INotifyCollectionChanged
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<int> _items;
public ObservableCollection<int> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Items = new ObservableCollection<int>(Enumerable.Range(0, 20));
}
}
List<T>, не реализует INotifyCollectionChanged
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<int> _items;
public List<int> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Items = Enumerable.Range(0, 20).ToList();
}
}
Оба варианта работают.
Я поудалял часть элементов кнопками на картинке.

Из команды можно сделать DependencyProperty, тогда можно будет ее переопределить в окне и например выдавать запрос юзеру для подтверждения удаления. Можно и в сам контрол эту логику запихнуть, решать вам.