Как обновлять данные в WPF MVVM через view?

У меня есть textbox

            <TextBox Text="{Binding DisplayNickName}"
                     Style="{StaticResource enternickname}"
                     Margin="10,10,0,0"/>

Внутри его стиля есть кнопка. Я хочу чтобы при нажатии на нее обновлялся ник юзера в файле и в model, но не знаю как это сделать. Пытаясь решить проблему я добавил в Model метод

public void update_nick(string nickname)
        {
            // обновляем в файле
            JsonData.set_name(nickname);
            // устанавливаем ник в model
            this.nickname = nickname;
        }

После чего хотел сделать команду в VM, но к сожалению не знаю что именно там брать за входящие данные(к чему обращаться чтобы получить их) и застрял пока что на этом моменте, поэтому надеюсь на выручку от более опытных людей к чему именно нужно обратиться в VM чтобы получить вводимые в textbox данные(DisplayNickName был в мыслях) а также как внутри стиля указать команду? Кусок из стиля

<!--Добавим текст бокс-->
<TextBox 
    Text="{TemplateBinding Text}"
    Background="Transparent"
    BorderThickness="0"
    Foreground="#deddd0"
    FontSize="20"
    Margin="1,0,0,0"
    x:Name="NameBox"
    MaxLength="15"
    Width="auto"/>
<!--Кнопка подтвеждения никнейма-->
<Button VerticalAlignment="Center"
        HorizontalAlignment="Right"
        Background="Transparent"
        BorderThickness="0"
        Cursor="Hand"
        Command="{Binding DataContext.UpdateNicknameCommand, RelativeSource={RelativeSource AncestorType=Window}}"
        CommandParameter="{Binding Text, ElementName=NameBox}">
    <Image Source="/images/check_mark.png" Width="25" Height="25"/>
    <!--Добавил в стиль кнопки триггеры для того чтобы она выводилась только при вводе и чтобы фон не менялся-->
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="Visibility" Value="Collapsed"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border x:Name="Border" Background="{TemplateBinding Background}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                        </Border>
                        <ControlTemplate.Triggers>
                            <DataTrigger Binding="{Binding IsKeyboardFocused, ElementName=NameBox}" Value="True">
                                <Setter Property="Visibility" Value="Visible"/>
                            </DataTrigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Transparent" TargetName="Border" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Style>
</Button>

Команда и метод команды для смены никнейма

public ICommand ChangeNickNameCommand { get; set; }
private void ChangeNickName(object obj)
{
    if (obj is string nickname)
    {
        _accountModel.update_nick(nickname);
    }
}

кусок кода из конструктора с привязкой

    // привязываем команду
    ChangeNickNameCommand = new RelayCommand(ChangeNickName);

Код ICommand

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

Ответы (1 шт):

Автор решения: EvgeniyZ

Поясню о чем я говорил в комментариях.

У вас сейчас "стиль", который в себе содержит не вид одного конкретного контрола, а некую солянку из набора разных контролов. Такой подход не удобен, он плохо поддерживается, им трудно управлять. Вот для начала вам надо решить эту проблему. Ну а решается она просто, при помощи UserControl.

Простой пример

  • Жмем ПКМ по проекту/папке в проекте и выбираем "Добавить" > "Пользовательский элемент управления" > Указываем название > "Добавить". Я лично назову его просто TestControl.

  • Нам студия сгенерирует 2 файла, XAML и CS (все как у окна).

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

  • Собственно, пишем propdp после конструктора и жмем TAB, студия любезно создаст за нас код, подправляем его под себя, задаем нужные типы и названия. В итоге получаем примерно такое:

    public partial class TestControl : UserControl
    {
        public TestControl()
        {
            InitializeComponent();
        }
    
        public ICommand Command
        {
            get => (ICommand)GetValue(CommandProperty);
            set => SetValue(CommandProperty, value);
        }
        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set => SetValue(TitleProperty, value);
        }
    
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(TestControl), new PropertyMetadata(default));
    
        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(TestControl), new PropertyMetadata(default));
    
    }
    
  • Далее открываем XAML и пишем там нужный дизайн. Тут важный момент с привязками, у контролов не задается DataContext, все привязки делаются обычно через имена или аналогичные механизмы, поэтому в самом начале зададим контролу x:Name (я пишу обычно просто uc). В итоге получаем следующую разметку:

    <UserControl
        x:Class="WPF_net.TestControl"
        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:local="clr-namespace:WPF_net"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Name="uc"
        d:DesignHeight="450"
        d:DesignWidth="800"
        mc:Ignorable="d">
        <StackPanel Margin="8">
            <TextBlock
                Margin="0,5"
                FontWeight="Medium"
                Text="{Binding Title, ElementName=uc}" />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <TextBox x:Name="textBox" Grid.Column="0" />
                <Button
                    Grid.Column="1"
                    Command="{Binding Command, ElementName=uc}"
                    CommandParameter="{Binding Text, ElementName=textBox}"
                    Content="Отправить" />
            </Grid>
        </StackPanel>
    </UserControl>
    
    • Ключевое тут, как и говорил, это привязка через имя самого контрола. Также внимание на CommandParameter, по вашей задумке (как я понял), вы хотите введенный текст из TextBox передать в команду, собственно подобной привязкой (опять, по имени ищем нужный контрол и берем его свойство), мы передадим введенный текст в команду при клике на кнопку.
  • Можно использовать теперь его. Открываем окно и пишем там <namespace:ИмяКонтрола Свойства = "Значения" />, в моем случае это будет выглядеть так: <local:TestControl Title="Супер контрол с кнопкой"/>

  • Студия будет ворчать, пока не соберем успешно проект, что собственно и делаем, пересобераем и запускаем. На экране увидим что-то на подобии этого

User Control Result

Поздравляю с первым пользовательским элементом управления. Имея его, можно теперь приступать к MVVM...

MVVM, передача данных, привязки

Собственно, наша теперь задача, по клику кнопки получить введенные данные в VM, от куда затем отправить их в Model, которая в свою очередь должна что-то там сделать.

  • Пишем Model, пусть это будет простой класс, который будет добавлять введенную строку в файл. У меня он будет максимально упрощенный, но суть думаю вы поймете...

    public class FileModel
    {
        public string Path { get; } = "TestFile.txt";
    
        public Task AddToFileAsync(string text)
            => File.AppendAllTextAsync(Path, text);
    }
    
  • Теперь пишем ViewModel, ее задача проста, получить текст с кнопки при помощи команды и отправить это в Model. Пусть это будет что-то такое:

    public partial class MainViewModel : ObservableObject
    {
        private readonly FileModel _fileModel;
    
        public MainViewModel()
        {
            _fileModel = new();
        }
    
        [RelayCommand]
        private async Task Send(string text)
        {
            await _fileModel.AddToFileAsync(text);
        }
    }
    
    • Тут можете удивится, что за странный код, что за RelayCommand, что за partial, ObservableObject и др.? Я лентяй (и вам советую быть таким), все реализации этих ICommand, INotifyPropertChanged и др., я отдаю "генератору кода". Если интересно, изучите CommunityToolkit.Mvvm, если нет, пишите все это вручную, примеров реализации подобного в интернете полно.
  • Осталось привязать команду в XAML и задать DataContext окну. Пишем в XAML нашему контролу привязку к команде, получаем <local:TestControl Title="Супер контрол с кнопкой" Command="{Binding SendCommand}"/>. Ну и задаем окну DataContext на MainViewModel (я напишу просто в конструкторе DataContext = new MainViewModel();, но так делать не стоит).

  • Запускаем, пробуем. При клике создался файл, в него добавилась новая строка, которая была написала в текстовом поле UserControl'a.

Result

Вот собственно максимально простой, базовый пример проекта по правилам MVVM, с привязками, да и со своим элементом управления.

→ Ссылка