问题
Previous Sources I Visited (and did not find an answer):
- Highlight Row In WPF
- Selected Item Color
- Highlighting Items In WPF ListView
- Triggers For Styling
- 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