简体   繁体   中英

Getting 'IsMouseOver' to work on a ListView when ItemsSource is set to a list

I have a WPF project where I have created a UserControl for the purpose of making a custom ListViewItem which includes a close button inside. Here is the code for that:

<ListViewItem x:Name="lviTab" Height="36" Background="#232323" 
MouseUp="LviTab_MouseUp" MouseEnter="LviTab_MouseEnter">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding TabText}" FontSize="15" />
        <ListViewItem x:Name="lviTabClose" Margin="5, 0, 0, 0" 
Padding="0, 0, 0, 0" MouseUp="LviTabClose_MouseUp">
            <materialDesign:PackIcon Kind="Close" Foreground="White" 
VerticalAlignment="Center"

HorizontalAlignment="Center" Width="20" Height="20" />
        </ListViewItem>
    </StackPanel>
</ListViewItem>

I am adjusting the text inside of each item by binding the text of the TextBlock inside of the UserControl to another class TabData.cs

public class TabData : UIElement
{
    public TabData() : base()
    {
    }

    public string TabText { get; set; }
}

In my ListView, I have set the DataTemplate to my UserControl. I have set the ListView ItemSource to a list of TabData objects.

tabs = new List<TabData>()
        {
            new TabData{TabText = "Tab 1"},
            new TabData{TabText = "Tab 2"},
            new TabData{TabText = "Tab 3"},
            new TabData{TabText = "Tab 4"},
            new TabData{TabText = "Tab 5"}
        };

        lvTabs.ItemsSource = tabs;

When moving the mouse over a ListViewItem, I need IsMouseOver to be true. I've tried inheriting UIElement to TabData but it hasn't worked. I'm quite new to WPF, I would appreciate any help with figuring out how I can retain IsMouseOver property when using a UserControl and setting the ItemSource to items which arn't ListViewItems.

Here's the simplest way to do what you're trying to do. When you populate a ListView with items from a collection, it creates its own ListViewItems. You don't need to create another ListViewItem inside each of its ListViewItems, much less a third one inside your own.

I'm going to dispense with the UserControl and put that XAML straight in a template. The reason for that is that we want the click handler to be in MainWindow.xaml.cs, where it can interact with the main viewmodel. We could fairly easily make that work with the UserControl in a couple of different ways, but I'm keeping this as simple as I can. Ideally you would use a Command for that, but that's one particular case where a little impurity in your MVVM won't ruin you. And for a case where the item UI is as simple as one textblock and one button, a UserControl is more than you need.

First, 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}");
    }
}

The TabData class. Don't inherit from UIElement. It's not an element in the user interface, it's just a plain C# class. If you were going to let the user edit TabText, you would make it a viewmodel that implements INotifyPropertyChanged.

    public class TabData
    {
        public string TabText { get; set; }
    }

The main viewmodel. Nothing we're doing here actually requires INotifyPropertyChanged on any of your classes, but you'll need it if you turn this into anything useful, so we'll include it.

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>();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM