Как обновлять данные в 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 шт):
Поясню о чем я говорил в комментариях.
У вас сейчас "стиль", который в себе содержит не вид одного конкретного контрола, а некую солянку из набора разных контролов. Такой подход не удобен, он плохо поддерживается, им трудно управлять. Вот для начала вам надо решить эту проблему. Ну а решается она просто, при помощи 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="Супер контрол с кнопкой"/>
Студия будет ворчать, пока не соберем успешно проект, что собственно и делаем, пересобераем и запускаем. На экране увидим что-то на подобии этого
Поздравляю с первым пользовательским элементом управления. Имея его, можно теперь приступать к 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.
Вот собственно максимально простой, базовый пример проекта по правилам MVVM, с привязками, да и со своим элементом управления.