DispatcherQueue null when trying to update Ui property in ViewModel
Asked Answered
N

1

2

In a WinUI 3 in Desktop app I have a property to update which is bound to the ui via x:Bind.

I want to use the Dispatcher like I do in WPF to get on the UI thread and avoid the thread error im getting when I update the prop:

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

Im just not sure how to do it in WinUI 3, when I try

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

I get this error

enter image description here

DispatcherQueue.GetForCurrentThread() is null

I also tried:

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

but it wont compile:

enter image description here

I then found this GitHub issue, so I tried:

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

}, null);

This works but why can't I get onto the UI thread with the Dispatcher in my VM?

Nikaniki answered 7/11, 2021 at 14:28 Comment(0)
M
7

DispatcherQueue.GetForCurrentThread() only returns a DispatcherQueue when being called on a thread that actually has a DispatcherQueue. If you call it on a background thread there is indeed no DispatcherQueue to be returned.

So the trick is to call the method on the UI thread and store the return value in a variable that you then use from the background thread, e.g.:

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));
    }
}
Munger answered 10/11, 2021 at 16:2 Comment(3)
If you define string propertyName = null instead, you can just use NotifyPropertyChanged() w/o having to manually specify the property name. Also in the general case, it's better to test if _text is not equal to value before raising the event.Balanchine
@SimonMourier: What do you do in a Unit Test when there is no Program.cs file that has called Application.Start()? When constructing the same ViewModel inside of a Unit Test, DispatcherQueue.GetForCurrentThread() throws an exception because you're no longer running inside of a XAML App.Tiler
@Tiler - you should ask another questionBalanchine

© 2022 - 2024 — McMap. All rights reserved.