DispatcherQueue null при попытке обновить свойство пользовательского интерфейса в ViewModel

#c# #xaml #data-binding #desktop #winui-3

Вопрос:

В WinUI 3 в настольном приложении у меня есть свойство для обновления, которое привязано к пользовательскому интерфейсу через x:Bind .

Я хочу использовать Dispatcher то, что я делаю в WPF, чтобы попасть в поток пользовательского интерфейса и избежать ошибки потока, которую я получаю при обновлении prop:

System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))'

Я просто не уверен, как это сделать в WinUI 3, когда я пытаюсь

 DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
{
    AeParty.OnSyncHub = false; // Prop bound in ui using x:Bind
});
 

Я получаю эту ошибку

введите описание изображения здесь

DispatcherQueue.GetForCurrentThread() равно нулю

Я также пытался:

 this.DispatcherQueue.TryEnqueue(() =>
{
    AeParty.OnSyncHub = false;
});
 

но он не будет компилироваться:

введите описание изображения здесь

Затем я обнаружил эту проблему с GitHub, поэтому я попытался:

 SynchronizationContext.Current.Post((o) =>
{
    AeParty.OnSyncHub = false;

}, null);
 

Это работает, но почему я не могу попасть в поток пользовательского интерфейса с помощью диспетчера в моей виртуальной машине?

Ответ №1:

DispatcherQueue.GetForCurrentThread() возвращает a только DispatcherQueue при вызове в потоке, который на самом деле имеет a DispatcherQueue . Если вы вызываете его в фоновом потоке, его действительно не DispatcherQueue нужно возвращать.

Итак, хитрость заключается в вызове метода в потоке пользовательского интерфейса и сохранении возвращаемого значения в переменной, которую вы затем используете из фонового потока, например:

 public sealed partial class MainWindow : YourBaseClass
{
    public MainWindow()
    {
        this.InitializeComponent();
    }

    public ViewModel ViewModel { get; } = new ViewModel();
}

public class ViewModel : INotifyPropertyChanged
{
    private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();

    public ViewModel()
    {
        Task.Run(() => 
        {
            for (int i = 0; i < 10; i  )
            {
                string val = i.ToString();
                _dispatcherQueue.TryEnqueue(() =>
                {
                    Text = val;
                });
                Thread.Sleep(2000);
            }
        });

    }
    private string _text;
    public string Text
    {
        get { return _text; }
        set { _text = value; NotifyPropertyChanged(nameof(Text)); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
 

Комментарии:

1. Если вы определяете string propertyName = null вместо этого, вы можете просто использовать NotifyPropertyChanged() без необходимости вручную указывать имя свойства. Также в общем случае лучше проверить, не равно ли _text значению, прежде чем вызывать событие.