Highlight ListView Item From Code - WPF Data Binding

你离开我真会死。 提交于 2021-02-11 07:01:52

问题


Previous Sources I Visited (and did not find an answer):

  1. Highlight Row In WPF
  2. Selected Item Color
  3. Highlighting Items In WPF ListView
  4. Triggers For Styling
  5. Styles For Styling

And more closely-related yet too complicated/not exactly what I need sources.

General Information:

As tagged, this code is in c#, using WPF, with target framework .NET Framework 4.5.

Note: This is my first try at implementing MVVM, so comments about best-practices i'm missing will be appreciated (although this is not the main subject of this question).

Question:

WPF with a ListView and a Button. The Button removes items from the ListView.

ListView<String> (View) ---> RemoveStringFromList() (ViewModel)

The above works. My problem is with highlighting.

I want to be able to remove a string from the ListView, and after removal highlight a different Item.

My initial thought was that by using a Property (SelectedItemProperty) that binds with the ListView's SelectedItem property - the highlighting will be automatic.

But in practice, the SelectedItem property binding works - as i can keep pressing the Button and remove items that became the SelectedItem per the logic implemented in the SelectedItemProperty setter - but although they are selected code-wise, they are not highlighted.

Code:

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="213.06">
    <Grid>
        <ListView ItemsSource="{Binding ItemsProperty}" SelectedItem="{Binding SelectedItemProperty}" HorizontalAlignment="Left" Height="214" Margin="35,74,0,0" VerticalAlignment="Top" Width="142">
            <ListView.View>
                <GridView>
                    <GridViewColumn/>
                </GridView>
            </ListView.View>
        </ListView>
        <Button Command="{Binding RemoveString}" Content="Remove From List!" HorizontalAlignment="Left" Margin="35,10,0,0" VerticalAlignment="Top" Width="142" Height="46"/>

    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private readonly MainWindowViewModel _viewModel;

        public MainWindow()
        {
            InitializeComponent();
            _viewModel = new MainWindowViewModel();
            DataContext = _viewModel;
            Show();
        }
    }
}

MainWindowViewModel.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

namespace WpfApplication1
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<String> _list;
        private String _selectedItem;

        public MainWindowViewModel()
        {
            _list = new ObservableCollection<String> {"1", "2", "3", "4"};
            RemoveString = new RemoveStringCommand(this);
        }

        public ObservableCollection<String> ItemsProperty
        {
            get { return _list; }
        }

        public String SelectedItemProperty
        {
            get { return _selectedItem; }
            set
            {
                if (value != null)
                {
                    _selectedItem = value;
                }
                else
                {
                    if (_list.Count > 0)
                    {
                        _selectedItem = _list[0];
                    }
                }
            }
        }

        public ICommand RemoveString
        {
            get;
            private set;
        }

        public bool CanRemoveString
        {
            get { return _list.Count > 0; }
        }

        public void RemoveStringFromList()
        {
            if (SelectedItemProperty != null)
            {
                _list.Remove(SelectedItemProperty);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }


    }
}

RemoveStringCommand.cs

using System.Windows.Input;
using WpfApplication1;

namespace WpfApplication1
{
    class RemoveStringCommand : ICommand
    {
        private MainWindowViewModel _viewModel;

        public RemoveStringCommand(MainWindowViewModel viewModel)
        {
            _viewModel = viewModel;
        }

        public event System.EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return _viewModel.CanRemoveString;
        }

        public void Execute(object parameter)
        {
            _viewModel.RemoveStringFromList();
        }
    }
}

App Image - Before First Click

App Image - After 1 Click (Notice - No Highlight!)

App Image - After 2 Clicks (Still No Highlight...)


回答1:


First of all remove a mistake

public MainWindow()
{
    InitializeComponent();
    _viewModel = new MainWindowViewModel();
    DataContext = _viewModel;
    // Show(); remove this, it's not needed
}

I made an example with two reusable helper classes.

1) First common class implements INotifyPropertyChanged. It may help not to repeat INPC implementing in each ViewModel class.

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

[CallerMemberName] here allows not to include Property name to each OnPropertyChanged() call. Compiler will do it automatically.

2) Class for easy Commands use. (grabbed here)

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

3) In the following example i changed names for properties as you asked for suggestions. Don't name properties such as SomethingProperty to avoid conflicts with Depency Properties, this naming pattern would be useful with DP only.

Markup:

<Grid>
    <ListView ItemsSource="{Binding ItemsList}" SelectedIndex="{Binding SelectedItemIndex}" HorizontalAlignment="Left" Height="214" Margin="35,74,0,0" VerticalAlignment="Top" Width="142">
        <ListView.View>
            <GridView>
                <GridViewColumn/>
            </GridView>
        </ListView.View>
    </ListView>
    <Button Command="{Binding RemoveItem}" Content="Remove From List!" HorizontalAlignment="Left" Margin="35,10,0,0" VerticalAlignment="Top" Width="142" Height="46"/>
</Grid>

4) ViewModel:

public class MainWindowViewModel : NotifyPropertyChanged
{
    private ObservableCollection<string> _itemsList;
    private int _selectedItemIndex;
    private ICommand _removeItem;

    public MainWindowViewModel()
    {
        // never interact with fields outside of the property 'set' clause
        // use property name instead of back-end field
        ItemsList = new ObservableCollection<string> { "1", "2", "3", "4" };
    }

    public ObservableCollection<string> ItemsList
    {
        get => _itemsList;
        set
        {
            _itemsList = value;
            OnPropertyChanged(); // Notify UI that property was changed

            //other ways doing the same call
            // OnPropertyChanged("ItemsList");
            // OnPropertyChanged(nameof(ItemsList));
        }
    }

    public int SelectedItemIndex
    {
        get => _selectedItemIndex;
        set
        {
            _selectedItemIndex = value;
            OnPropertyChanged();
        }
    }

    // command will be initialized in "lazy" mode, at a first call.
    public ICommand RemoveItem => _removeItem ?? (_removeItem = new RelayCommand(parameter =>
    {
        ItemsList.RemoveAt(SelectedItemIndex);
    }, 
    // SelectedItemIndex -1 means nothing is selected
    parameter => SelectedItemIndex >=0 && ItemsList.Count > 0));
}

As a bonus you may programmatically change SelectedIndex of ListView simply setting any value to SelectedItemIndex.

Edit:

Sorry, I've forgot to keep selection after deletion. Modify the command:

public ICommand RemoveItem => _removeItem ?? (_removeItem = new RelayCommand(parameter =>
{
    int index = SelectedItemIndex;
    ItemsList.RemoveAt(index);
    if (ItemsList.Count > 0)
        SelectedItemIndex = (index == ItemsList.Count) ? index - 1 : index;
}, parameter => SelectedItemIndex >= 0 && ItemsList.Count > 0));


来源:https://stackoverflow.com/questions/61538222/highlight-listview-item-from-code-wpf-data-binding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!