0

Не могу обновить изображение через MVVM.

WPF изучаю от силы 4 дня.

Часть кода формы:

<Ellipse Height="200" Width="200" Margin="8,10,8,349">
<Ellipse.Fill>
    <ImageBrush x:Name="usprof" Stretch="UniformToFill" ImageSource="{Binding CurrentAvatar}"/>
</Ellipse.Fill>

MVVM:

public RelayCommand ChangeAvatarCommand { get; set; }

private object? _currentAvatar; public object? CurrentAvatar { get { return _currentAvatar; } set { _currentAvatar = value; OnPropertyChanged(); } }

public MainViewModel() { ChangeAvatarCommand = new RelayCommand(o => { CurrentAvatar = UserData.AvatarPath; // Код срабатывает, но изображение не меняется }); }

UPD:

Если необходимо, то вот код кнопки из другой панели:

<Button x:Name="selectBtn" Command="{Binding ChangeAvatarCommand}" Click="selectBtn_Click" Content="Select" VerticalAlignment="Center" Foreground="White" FontSize="15" Margin="10 0 0 0" Width="100" BorderThickness="0" Style="{StaticResource stdButton}"/> 

Также в отдельном StackPanel

Выбор изображения по кнопке:

OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "PNG files(*.png)|*.png|JPG files(*.jpg)|*.jpg|All files(*.*)|*.*";
if (ofd.ShowDialog().Value)
{
    avatarPath.Text = ofd.FileName;
    UserData.AvatarPath = ofd.FileName;
}

OnPropertyChanged:

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string? name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }

RelayCommand:

class RelayCommand : ICommand
{
    private Action<object> _execute;
    private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}

public RelayCommand(Action&lt;object&gt; execute, Func&lt;object, bool&gt; canExecute = null)
{
    _execute = execute;
    _canExecute = canExecute;
}

public bool CanExecute(object parameter) 
{
    return _canExecute == null || _canExecute(parameter);
}

public void Execute(object parameter) { _execute(parameter); }

}

  • "Обновляется", то есть сначала она стоит и все ок? И что значит "из дочерней панели"? Ну а так, если выполняется и там все ок, то занимайтесь отладкой XAML, смотрите что ему конкретно не нравиться (если такое есть), ну а если там все ок, значит всеж вы ошибаетесь, и утверждение "код срабатывает" ошибочно. По тому, что показано в вопросе, все хорошо, а это значит, что вы нам что-то не договариваете (например Ellipse может лежит внутри ListBox). – EvgeniyZ Aug 25 '23 at 05:36
  • В начале всё ок, да. У меня стоит свап панелей через mvvm, и на одной из них- кнопка выбора изображения. P.S. Ellipse в StackPanel – Yato Simidzu Aug 25 '23 at 05:55
  • В XAML тоже всё отлично. – Yato Simidzu Aug 25 '23 at 06:08
  • OnPropertyChanged покажите, что за обработчик события на кнопке, что он делает? – aepot Aug 25 '23 at 06:24
  • https://imgur.com/a/mWWavwP В обработчике- смена UserData.AvatarPath P.S. В теме код – Yato Simidzu Aug 25 '23 at 06:27
  • Делайте тогда самодостаточный пример, который мы можем запустить у себя и выяснить в чем проблема. Пока я лишь могу заметить 2 вещи: 1. У вас кнопка делает и клик, и команду, что не правильно. 2. У вас нарушение MVVM, ибо ваш View слой вдруг знает про ViewModel, ну или наоборот (я не знаю где этот код OpenFileDialog ofd = new OpenFileDialog();), если наоборот, то ViewModel не должна открывать окна, даже диалоговые, ибо окно это View. – EvgeniyZ Aug 25 '23 at 13:50

1 Answers1

0

Оказывается, мое приложение просто не вызывало событие обновления изображения (решил костылём). Спасибо большое Евгению за информацию.

Костыль:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    username.Text = UserData.Login;
    Task.Run(() => 
    {
        string pathNow = string.Empty;
        while (true)
        {
            if (pathNow != UserData.AvatarPath)
            {
                pathNow = UserData.AvatarPath;
                ChangePic();
            }
        Thread.Sleep(150);
    }
});

}

private void ChangePic() { if (UserData.AvatarPath != string.Empty) { Dispatcher.Invoke(new Action(() => usprof.ImageSource = new BitmapImage(new Uri(UserData.AvatarPath, UriKind.Relative)))); } else { Dispatcher.Invoke(new Action(() => usprof.ImageSource = new BitmapImage(new Uri("pack://application:,,,/Resources/avatar.png")))); } }

В итоге MVVM можно отложить... То еще Г, но всё же.

Ссылка на пример: https://mega.nz/file/l6oizZTJ#26vag4RcjkuYbFuOQDw85q5d9m3PXyrPKOTkddRYJ5w

  • Еще раз повторю, сделайте самодостаточный пример, вот возьмите чистый WPF проект, напишите там то, что у вас, без лишних классов, методов и прочего, одно свойство и его изменение, и прикрепите к вопросу этот чистый код, мы посмотрим и скажем как правильно. В ответе вы написали действительно непонятную кашу, когда как у вас должна успешно работать привязка, но раз не работает, вы ее где-то ломаете, но мы не можем сказать где, ибо вы нам это место не показываете. Так что, пример, мы у себя воспроизводим - даем рекомендации, готово. – EvgeniyZ Aug 27 '23 at 10:20
  • Ссылка на проект: https://mega.nz/file/l6oizZTJ#26vag4RcjkuYbFuOQDw85q5d9m3PXyrPKOTkddRYJ5w – Yato Simidzu Aug 27 '23 at 11:21
  • Когда пишете человеку, указывайте его ник через @, иначе он не увидит уведомление. Я сейчас ваш вопрос увидел лишь благодаря правки, когда вы мне 3 дня назад дали ссылку... Посмотрел я ваш проект, там многое не так, если брать именно обновления изображения, то ваша проблема в постоянном <viewmodel:MainViewModel/>, что является нарушением MVVM. Смотрите, когда вы пишете <ЧтоТо.DataContext><...></..>, то это аналогично написанию this.DataContext = new ...();, ключевое тут new, то есть вы каждый раз создаете новый MainViewModel – EvgeniyZ Aug 30 '23 at 17:42
  • А теперь простая логика, вот у вас две корзинки на столе, обе одинакового размера, одинаковой формы, цвета, и так далее. В одну вы положили яблоко, в другой это яблоко появится? Нет. А если вы положите в обе корзинки по яблоку, а после откусите у одного яблока, это изменения будет в другой? Нет. Ок, если вы напишете var c1 = new Class(); var c2 = new Class(); а потом сделаете c1.Value = 123;, в классе 2 будет Value == 123? Нет, ибо это два разных экземпляра класса. Я надеюсь вы поняли к чему я клоню? У вас вообще не должно быть <ЧтоТо.DataContext>, он должен задаваться автоматически. – EvgeniyZ Aug 30 '23 at 17:46
  • @EvgeniyZ да, я вас понял. Большое спасибо. Иными словами, мне необходимо работать в c1. – Yato Simidzu Sep 01 '23 at 07:55
  • Не совсем так, как вы это себе понимаете) Ваша задача сейчас, не создавать повторно MainViewModel, а использовать на нее ссылку, либо перейти в XAML на уровень выше и взять DataContext родителя, от которого получать уже команду. То есть, у вас должно быть либо слой ConfigViewModel, в котором есть ссылка на MainViewModel и в XAML вы делаете {Binding MainVM.ChangeAvatarCommand}, либо переписывает XAML, и ищете там предка, например, так Command="{Binding DataContext.ChangeAvatarCommand, RelativeSource={RelativeSource AncestorType=Window }}". – EvgeniyZ Sep 01 '23 at 10:38