Выделите элемент ListView из кода - привязка данных WPF - PullRequest
1 голос
/ 01 мая 2020

Предыдущие источники, которые я посетил (и не нашел ответа):

  1. Выделить строку в WPF
  2. Цвет выделенного элемента
  3. Подсветка элементов в WPF ListView
  4. Триггеры для стилей
  5. Стили для стилей

И более тесно связанные, но слишком сложные / не совсем те, которые мне нужны источники.

Общая информация:

Как отмеченный тегом, этот код находится в c#, используется WPF, с целевой структурой .NET Framework 4.5.

Примечание: Это моя первая попытка реализации MVVM, поэтому комментарии о лучших практиках, которые мне не хватает, будут оценены (хотя это * 1043) * не основной предмет этого вопроса).

Вопрос:

WPF с ListView и Button. Button удаляет элементы из ListView.

ListView<String> (Просмотр) ---> RemoveStringFromList() (ViewModel)

Вышеуказанное работает. Моя проблема с выделением .

Я хочу иметь возможность удалить строку из ListView, а после удаления выделить другой элемент .

Первоначально я думал, что при использовании свойства (SelectedItemProperty), которое связывается со свойством ListView SelectedItem - подсветка будет automati c.

Но на практике привязка свойства SelectedItem работает - я могу продолжать нажимать Button и удалять элементы, которые стали SelectedItem в логах c, реализованных в установщике SelectedItemProperty - но хотя они выбраны по коду, они не выделены.

Код:

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

Изображение приложения - до первого нажатия

enter image description here

Изображение приложения - после 1 клика (обратите внимание - Без выделения!)

enter image description here

Изображение приложения - после 2 щелчков (все еще без выделения ...)

enter image description here

1 Ответ

2 голосов
/ 01 мая 2020

Прежде всего удалите ошибку

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

Я сделал пример с двумя повторно используемыми вспомогательными классами.

1) Первый распространенный класс реализует INotifyPropertyChanged. Это может помочь не повторять реализацию INP C в каждом классе ViewModel.

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

[CallerMemberName] здесь позволяет не включать имя свойства в каждый вызов OnPropertyChanged(). Компилятор сделает это автоматически.

2) Класс для удобного использования команд. (схватил здесь )

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) В следующем примере я изменил имена свойств, как вы просили для предложений. Не называйте свойства, такие как SomethingProperty, чтобы избежать конфликтов со свойствами зависимости, этот шаблон именования будет полезен только для DP.

Разметка:

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

В качестве бонуса вы можете программно изменить SelectedIndex из ListView, просто установив любое значение на SelectedItemIndex.

Редактировать:

Извините, я забыл сохранить выбор после удаления. Изменить команду:

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));
...