Ломаю голову над проблемой часов 6, надеюсь тут помогут. Имеется WPF приложение на .Net 8. В главном окне есть обычно текстовое поле с биндингом на нужно свойство в DataContext:
<Grid>
<TextBlock FontSize="22" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" Height="50" Width="100"/>
</Grid>
Сам дата контекст окна задается в code-behind:
using CryptoAnalys.ViewModels;
using Infrastructure;
using Infrastructure.Abstractions;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Windows;
using UseCases;
namespace CryptoAnalys
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var config = new MediatRServiceConfiguration()
{
AutoRegisterRequestProcessors = true,
};
config.RegisterServicesFromAssembly(typeof(GetIsApiExistsQuery).Assembly);
var services = new ServiceCollection()
.AddSingleton<IGrpcApiService, GrpcApiService>()
.AddMediatR(config)
.RegisterGrpc()
.BuildServiceProvider();
var sender = services.GetRequiredService<IMediator>();
DataContext = new MainViewModel(sender);
}
}
}
И, собственно, сам ViewModel:
namespace CryptoAnalys.ViewModels
{
public class MainViewModel : ViewModelBase
{
string _text;
public MainViewModel(IMediator sender) : base(sender)
{
Task.Run(async () =>
{
await Task.Delay(10);
Application.Current.Dispatcher.Invoke(() =>
{
Text = "123";
});
});
}
public string Text
{
get => _text;
set
{
if (_text == value)
return;
_text = value;
OnPropertyChanged();
}
}
}
}
В общем, всё по классике, но тут начинается сама настоящая магия. По абсолютно непонятной причине Application.Current.Dispatcher.Invoke не выводит на экран текст '123'. Притом если поменять код:
Task.Run(async () =>
{
await Task.Delay(10);
Application.Current.Dispatcher.Invoke(() =>
{
Text = "123";
});
});
на
Task.Run(async () =>
{
Text = "123";
});
Всё будет работать корректно. Если убрать
Task.Run(async () => ...
И оставить только конструктор с инициализацией:
public MainViewModel(IMediator sender) : base(sender)
{
Text = "123";
}
Всё также будет работать как следует. Самое интересное: если поместить код в диспетчер, но уже без таска, '123' снова не показывается:
public MainViewModel(IMediator sender) : base(sender)
{
Application.Current.Dispatcher.Invoke(() =>
{
Text = "123";
});
}
Кто-нибудь сталкивался с такой проблемой?
Upd 1: Код ViewModelBase:
using MediatR;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CryptoAnalys.ViewModels
{
public class ViewModelBase
{
public ViewModelBase(ISender sender)
{
Sender = sender;
}
public ViewModelBase()
{
}
public ISender Sender { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Что примечательно, создал на форме кнопку с командой в том же ViewModel:
<Grid>
<Button Command="{Binding LoadedCommand}" Height="50" Width="100" VerticalAlignment="Top"></Button>
<TextBlock FontSize="22" Text="{Binding Text}" Height="50" Width="100"/>
</Grid>
Код Vm поменялся на следующий:
public class MainViewModel : ViewModelBase
{
string _text;
public MainViewModel() : base()
{
LoadedCommand = new RelayCommand<object>(Loaded);
}
public RelayCommand<object> LoadedCommand { get; }
public string Text
{
get => _text;
set
{
if (_text == value)
return;
_text = value;
OnPropertyChanged();
}
}
private void Loaded(object obj)
{
Text = "123";
}
}
Loaded выполняется всё синхронно и даже в таком случае изменение текста не происходит( На всякий случай класс команды:
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Application.Current.Dispatcher.Invokeвам и не нужен для отображения данных во view так как вы работаете с vm. То есть вы не взаимодействуете с ui и выходить из потока в главный вам не зачем. Удалите эту чать кода, она вам не нужна. – xellan Jan 12 '24 at 15:09