1

Автозаполнение элементов ввода и удаление/добавление строки с помощью MVVM

Kekisto
  • 47
  • 9
  • Здесь принято вставлять код текстом. И про удаление - это уже второй вопрос. – aepot Jun 02 '20 at 21:25
  • Обновил ответ, добавил про добавление и удаление элементов. – aepot Jun 02 '20 at 23:36

1 Answers1

3

Вот пример с использованием шаблона MVVM.

NotifyPropertyChanged.cs - вспомогательный класс

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

Member.cs - класс данных

public class Member : NotifyPropertyChanged
{
    private string _firstName;
    private string _middleName;
    private string _lastName;

    public string FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            OnPropertyChanged();
        }
    }
    public string MiddleName
    {
        get => _middleName;
        set
        {
            _middleName = value;
            OnPropertyChanged();
        }
    }
    public string LastName
    {
        get => _lastName;
        set
        {
            _lastName = value;
            OnPropertyChanged();
        }
    }
}

MainViewModel.cs - сюда указывает MainWindow.DataContext

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<Member> _members;

    public ObservableCollection<Member> Members
    {
        get => _members;
        set
        {
            _members = value;
            OnPropertyChanged();
        }
    }
    public MainViewModel()
    {
        // тестовые данные
        Members = new ObservableCollection<Member>
        {
            new Member { FirstName = "Иван", MiddleName = "Иванович", LastName = "Иванов"},
            new Member { FirstName = "Пётр", MiddleName = "Петрович", LastName = "Петров"}
        };
    }
}

MainWindow.xaml - полная разметка

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox Width="150" Margin="5" Text="{Binding Members/FirstName}"/>
            <TextBox Width="150" Margin="5" Text="{Binding Members/MiddleName}"/>
            <TextBox Width="150" Margin="5" Text="{Binding Members/LastName}"/>
        </StackPanel>
        <DataGrid Grid.Row="1" Margin="5" ItemsSource="{Binding Members}" IsSynchronizedWithCurrentItem="True" CanUserSortColumns="False"/>
    </Grid>
</Window>

Выглядит это так

IsSynchronizedWithCurrentItem

Вся магия заключается в IsSynchronizedWithCurrentItem, оно передает текущий выбранный элемент в CollectionView, через которую DataGrid взаимодействут с коллекцией Members. Затем нехитрым способом этом можно подхватить через Binding.Path в TextBox.


Дополнение

Вы отредактировали вопрос, чтобы узнать, как добавлять и удалять элементы, и как удалять сразу несколько элементов. Вообще, в таких случаях стоит создавтаь отдельный вопрос, но если очень хочется, то вот. Добавил немного кода в решение выше.

Потребуется еще один вспомогательный класс для работы с командами.

RelayCommand.cs

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

Далее, добавил код в следующие классы:

Member.cs - пробросил статус выделения строки сюда через Binding

private bool _selected;

public bool Selected
{
    get => _selected;
    set
    {
        _selected = value;
        OnPropertyChanged();
    }
}

MainViewModel.cs - добавил команды

private ICommand _addMemberCommand;
private ICommand _removeSelectedCommand;

public ICommand AddMemberCommand => _addMemberCommand ?? (_addMemberCommand = new RelayCommand(parameter =>
{
    foreach (Member m in Members)
    {
        m.Selected = false;
    }
    Members.Add(new Member() { Selected = true });
}));
public ICommand RemoveSelectedCommand => _removeSelectedCommand ?? (_removeSelectedCommand = new RelayCommand(parameter =>
{
    List<Member> members = Members.Where(x => x.Selected).ToList();
    if (members.Count > 0 && MessageBox.Show("Remove " + members.Count + " items?", "Delete", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
    {
        foreach (Member m in members)
        {
            Members.Remove(m);
        }
    }
}));

MainWindow.xaml - новая разметка

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal">
        <TextBox Width="150" Margin="5" Text="{Binding Members/FirstName}" IsEnabled="{Binding Members/Selected, FallbackValue=False}"/>
        <TextBox Width="150" Margin="5" Text="{Binding Members/MiddleName}" IsEnabled="{Binding Members/Selected, FallbackValue=False}"/>
        <TextBox Width="150" Margin="5" Text="{Binding Members/LastName}" IsEnabled="{Binding Members/Selected, FallbackValue=False}"/>
        <Button Margin="5" Padding="5,0" Content="Add" Command="{Binding AddMemberCommand}"/>
        <Button Margin="5" Padding="5,0" Content="Remove selected" Command="{Binding RemoveSelectedCommand}"/>
    </StackPanel>
    <DataGrid Grid.Row="1" Margin="5" ItemsSource="{Binding Members}" IsSynchronizedWithCurrentItem="True" AutoGenerateColumns="False" IsReadOnly="True" CanUserSortColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Имя" Binding="{Binding FirstName}"/>
            <DataGridTextColumn Header="Отчество" Binding="{Binding MiddleName}"/>
            <DataGridTextColumn Header="Фамилия" Binding="{Binding LastName}"/>
        </DataGrid.Columns>
        <DataGrid.ItemContainerStyle>
            <Style TargetType="{x:Type DataGridRow}">
                <Setter Property="IsSelected" Value="{Binding Selected}"/>
            </Style>
        </DataGrid.ItemContainerStyle>
        <DataGrid.InputBindings>
            <KeyBinding Key="Delete" Command="{Binding RemoveSelectedCommand}"/>
        </DataGrid.InputBindings>
    </DataGrid>
</Grid>

Так как клавиша Delete по умолчанию удаляет в DataGrid то что выделено без предупреждения, я ее тоже перенаправил на команду.

IsEnabled в текстбоксах прибиндил, чтобы они стали неактивными, если в таблице ничего не выделено, или вообще нет строк.

Колонки в DataGrid теперь формируются в разметке, а не автоматически. Сортировку при клике по заголовку отключил, потому что она не работает нормально с IsSynchronizedWithCurrentItem, начинается рассинхронизация после сортировки.

AddRemoveDataGrid

aepot
  • 49,560