Вот самый простой способ сделать то, что вы пытаетесь сделать. Когда вы заполняете ListView элементами из коллекции, он создает свои собственные ListViewItems. Вам не нужно создавать другой ListViewItem внутри каждого из его ListViewItems, а тем более третий внутри вашего собственного.
Я собираюсь обойтись без UserControl и поместить этот XAML прямо в шаблон. Причина в том, что мы хотим, чтобы обработчик кликов находился в MainWindow.xaml.cs, где он может взаимодействовать с основной моделью представления. Мы могли бы довольно легко заставить это работать с UserControl несколькими различными способами, но я держу это настолько простым, насколько я могу. В идеале вы бы использовали Command
для этого, но это один конкретный случай, когда небольшая примесь в вашем MVVM не испортит вас. А для случая, когда пользовательский интерфейс элемента так же прост, как один текстовый блок и одна кнопка, UserControl - это больше, чем вам нужно.
Сначала MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="TabListViewItemTemplate">
<DockPanel LastChildFill="False">
<TextBlock Text="{Binding TabText}" DockPanel.Dock="Left" />
<Button x:Name="TabCloseButton" Click="TabCloseButton_Click" DockPanel.Dock="Right" >
<Button.Template>
<ControlTemplate TargetType="Button">
<materialDesign:PackIcon
Kind="Close" Foreground="White"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="20" Height="20" />
</ControlTemplate>
</Button.Template>
</Button>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListView
ItemsSource="{Binding TabItems}"
ItemTemplate="{StaticResource TabListViewItemTemplate}"
HorizontalContentAlignment="Stretch"
/>
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel
{
TabItems = {
new TabData { TabText = "Fred" },
new TabData { TabText = "Ginger" },
new TabData { TabText = "Herman" },
}
};
}
private void TabCloseButton_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement).DataContext is TabData tabData)
{
MessageBox.Show($"TabCloseButton_Click() {tabData.TabText}");
}
}
Класс TabData. Не наследуй от UIElement. Это не элемент в пользовательском интерфейсе, это просто класс C #. Если вы собираетесь позволить пользователю редактировать TabText, вы сделаете его моделью представления, которая реализует INotifyPropertyChanged.
public class TabData
{
public string TabText { get; set; }
}
Основной вид модели. Ничто из того, что мы делаем здесь, на самом деле не требует INotifyPropertyChanged для любого из ваших классов, но вам это понадобится, если вы превратите это во что-нибудь полезное, поэтому мы включим его.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public class MainViewModel : ViewModelBase
{
public ObservableCollection<TabData> TabItems { get; } = new ObservableCollection<TabData>();
}