Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: WinUI3 dispatchers (And why is Window.Current null in Desktop?) #2609

Closed
tomasfabian opened this issue Jun 5, 2020 · 27 comments
Closed
Labels

Comments

@tomasfabian
Copy link

I wanted to create a Rx DispatcherScheduler for WinUI 3 Preview 1 based on UWP implementation, but the static readonly property Window.Current is null. How should I or the Rx team get the corresponding CoreDispatcher?

Steps to reproduce:

Steps to reproduce the behavior:

  1. Create new Blank App, Packaged (WinUI in Desktop) app

  2. Open MainWindow.xaml.cs and try get Window.Current

Expected behavior:

Window.Current in Blank App, Packaged (WinUI in UWP) returns a window instance

@msft-github-bot msft-github-bot added the needs-triage Issue needs to be triaged by the area owners label Jun 5, 2020
@Felix-Dev
Copy link
Contributor

Felix-Dev commented Jun 5, 2020

Your stated expected behavior (WinUI UWP) does not match your bug description (WinUI Desktop). Is that intended?

Window.Current only returns a window instance when called from a UI thread of a UWP app. In all other cases it returns null, including WinUI Desktop apps.

The CoreDispatcher is only available for UWP apps currently. Can you use the Dispatcher class from .NET instead?

@tomasfabian
Copy link
Author

@Felix-Dev sorry I probably confused you, I didn't state that it's a bug, it was just my expectation.

I tried to get the dispatcher from .NET as you kindly suggested me. This returned null:

var dispatcher = Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread);

but I was able to get the dispatcher like this:

var dispatcher = Windows.Threading.Dispatcher.CurrentDispatcher;

I have to admit that I'm confused, because I checked that for example a text box returns a CoreDispatcher from it's Dispatcher property:

Windows.UI.Core.CoreDispatcher dispatcher = MyTextBox.Dispatcher

I'm used from WPF that this is true:

System.Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread) == MyTextBox.Dispatcher

Isn't that CoreDispatcher confusing in WinUI Desktop apps, if it's only used in UWP apps currently?

Thank you, Tomas

@weltkante
Copy link

weltkante commented Jun 5, 2020

Your steps to reproduce are

Packaged (WinUI in Desktop) app

But you say your expectation is

Packaged (WinUI in UWP) returns a window instance

As such you are mixing up UWP/Desktop packaging in your issue description and were asked to clarify which of them is actually your problem.

@tomasfabian
Copy link
Author

Packaged (WinUI in Desktop) app is my actual problem.
Packaged (WinUI in UWP) was an example how it could behave in Packaged (WinUI in Desktop), too.

@tomasfabian
Copy link
Author

@Felix-Dev

Your stated expected behavior (WinUI UWP) does not match your bug description (WinUI Desktop). Is that intended?

Yes it was intended, but it is obviously confusing, sorry again.

So my main issue was to get the correct current dispatcher for switching from a background thread to the main UI thread. I wanted to use Window.Current.Dispatcher for that purpose in a Packaged (WinUI in Desktop) app, but it was null.

@Felix-Dev
Copy link
Contributor

@tomasfabian Interesting that you get a valid CoreDispatcher reference when accessing MyTextBox.Dispatcher in a WinUI Desktop app! I honestly thought a CoreDispatcher was related to a CoreWindow and as WinUI Desktop apps don't have a CoreWindow (they directly use the classic HWND) I was under the impression that there won't be a CoreDispatcher then for WinUI Desktop apps. If I'm wrong here and this caused you additional confusion I would like to apologize!

@MikeHillberg MikeHillberg added the product-winui3 WinUI 3 issues label Jun 5, 2020
@MikeHillberg
Copy link
Contributor

In a UWP WinUI app there's guaranteed to be one Window on the UI thread, because multiple windows aren't supported. To create a second Window you need to create a new thread. So Window.Current returns the Window on the calling thread.

In a Desktop WinUI app, in the current preview only one Window can be created, but the plan is to allow multiple Windows to be created on the same thread. So Window.Current doesn't make sense any longer, and now just returns null. (It's behavior is unchanged in a UWP app.)

About dispatchers, CoreDispatcher only works in a UWP app, but DispatcherQueue always works. DispatcherQueue isn't new to the WinUI preview, it's been around for a while, but what is new in the preview is that Xaml elements (and Window) have a DispatcherQueue property so that you can get one for any element.

@tomasfabian
Copy link
Author

tomasfabian commented Jun 5, 2020

@Felix-Dev that's alright. Your constructive explanation was actually very helpful for me to learn that there is another (not used?) core dispatcher in the Desktop version which misled me.

For all I'm going to change the title to WinUI 3 dispatchers (Why is Window.Current null?) if it's not a problem for anyone.

I realize now, based on the information gathered by you, that my original question should be split to two issues (these issues are coupled via Dispatcher property):

  1. Window.Current is not self explanatory in my opinion. @MikeHillberg explained to us that it produces different results in both platforms and the behavior in Desktop WinUI will probably change. What kind of behavior should we expect from MAUI integration (Android/iOS etc)?

Originally I used Window.Current only to get the dispatcher, but it was null (and of type CoreDispatcher).

  1. It would be great to have a unified (cross-platform) dispatcher provider.
    I learnt that MyTextBox.Dispatcher is a CoreDispatcher used only in UWP, but the DispatchedHandler agileCallback passed to RunAsync was "suprisingly" called in the main thread.
    public async void Init()
    {
      await MyTextBox.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
       {
       });
    }

@MikeHillberg DispatcherQueue is not new in WinUI, but it is new for new/adapting developers. Is this the way how to call DispatcherQueue?

          DispatcherQueueSyncContext.Current.Post(c =>
          {
          }, null);

How is it possible to get the "current" Dispatcher for ticks for example?

          var timer = DispatcherQueue.CreateTimer();

I wasn't able to find this in the documentation.

@MikeHillberg
Copy link
Contributor

You're right that the DispatcherQueue isn't new, what's new to WinUI3 is that Xaml elements have a DispatcherQueue property. This works whether in a UWP or Desktop app, whether C++ or C#.

.Net has the similar but abstract concept of a SynchronizationContext, and in WinUI3 an implementation is provided that wraps the current thread's DispatcherQueue. As a result, in a c# app there's two ways to post:

        public MainWindow()
        {
            this.InitializeComponent();

            SynchronizationContext.Current.Post(
                (o) => Debug.WriteLine("Sync Context"),
                null);

            this.DispatcherQueue.TryEnqueue(() => Debug.WriteLine("Dispatcher Queue"));
        }

@tomasfabian tomasfabian changed the title Question: Why is Window.Current null? Question: WinUI3 dispatchers (And why is Window.Current null in Desktop?) Jun 8, 2020
@tomasfabian
Copy link
Author

tomasfabian commented Jun 8, 2020

I found DispatcherQueue.GetForCurrentThread, this works in Desktop:

      var dispatcherQueue = DispatcherQueue.GetForCurrentThread();

      dispatcherQueue.TryEnqueue(() => Debug.WriteLine("Dispatcher Queue"));

      var timer = dispatcherQueue.CreateTimer();
      timer.Interval = TimeSpan.FromSeconds(2);
      timer.Tick += Timer_Tick;
      timer.Start();

@MikeHillberg you wrote that DispatcherQueue works also in UWP. I tried it and the methods are marked as Experimental. I would usually do this with MVVM, but I tried the code bellow to verify where will be the continuation executed and in UWP it was on a worker thread, not on main thread:

      await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
          {
            myButton.Content = "Updated content";
          });

In spite of that it was successfully updated. Interesting... I'm used to that the main thread is the main UI thread. Thank you

@sjb-sjb
Copy link

sjb-sjb commented Dec 19, 2020

@MikeHillberg , you commented that "CoreDispatcher only works in a UWP app". However, it is available on a WinUI 3 desktop app that is in an MSIX package. This is consistent with the WinUI documentation, which says that the CoreDispatcher (presumably one that works) can be obtained from a ui element (https://docs.microsoft.com/en-us/windows/winui/api/microsoft.ui.xaml.dependencyobject.dispatcher?view=winui-3.0-preview)

To access the CoreDispatcher, create a Window in OnLaunched, assign the Content of the window to be a UI element ("rootElement"), and subscribe to the Activated event of the Window. Then in the Activated event, test that the WindowActivationState is CodeActivated or PointerActivated and access rootElement.Dispatcher (not Window.Dispatcher).

Another way to test this is to compile the Xaml Controls Gallery branch for WinUI 3 - Desktop. In ButtonPage.xaml.cs, add the following lines to the Button_Click method (and make the method async):

        System.Diagnostics.Debug.WriteLine("Dispatching....");
        await b.Dispatcher.RunAsync( Windows.UI.Core.CoreDispatcherPriority.Normal, () => { System.Diagnostics.Debug.WriteLine("Dispatched!"); }).AsTask();
        System.Diagnostics.Debug.WriteLine("Awaited.");

It works ... NOT! When you click the button it prints "Dispatching..." and "Dispatched!" but not "Awaited".

@Gavin-Williams
Copy link

Seems like it might be mighty sensible to make UWP and desktop behave the same way and have the same capabilities with regard to Windows. UWP is just a security model after all, not a model for application design.

@sjb-sjb
Copy link

sjb-sjb commented Dec 31, 2020

@MikeHillberg what I don’t understand about using DispatcherQueue instead of CoreDispatcher is that DispatcherQueue.TryEnqueue returns synchronously but CoreDispatcher.RunAsync returns an awaitable. How should I achieve the same effect as RunAsync using DispatcherQueue?

@MikeHillberg
Copy link
Contributor

I would use a Semaphore and wait on it from the non-UI thread. CoreDispatcher.RunAsync can be a bit difficult to use, because it returns/completes early if the code itself makes an async call.

@scott-moore-ms FYI

@sjb-sjb
Copy link

sjb-sjb commented Jan 27, 2021

@MikeHillberg I did it with a TaskCompletionSource, which is a bit lighter-weight than Semaphore.

As a philosophical comment, it seems funny to have the Windows.UI.Core namespace mixed up in the signatures in WinUI.

@MikeHillberg
Copy link
Contributor

@sjb-sjb, about WinUI referencing types in Windows.UI.Core: WinUI isn't meant to be a complete stack; it's a layer. So it references types in Windows.UI.Core, Windows.Storage, Windows.Media, etc.

@sigmarsson
Copy link

I witness too Window.Current is null even on the UI thread :

unoplatform/uno#5642

Win UI desktop is launched as a Uno Head.

@sigmarsson
Copy link

As for this null reference matter, I found this. They differentiate for net5 and Windows :

image

@tinmac
Copy link

tinmac commented Nov 8, 2021

@MikeHillberg Can I ask, in WPF all controls and window inherit DispatcherObject so we have a reference & can call it quite simply from anywhere. How do I get a dispatcher for WinUI 3 desktop, at the moment DIspatcherQueue in my VM is always null?

@MikeHillberg
Copy link
Contributor

In WinUI2 most UI objects have a CoreDispatcher, like DependencyObject.Dispatcher.

In WinUI3 we've moved to CoreDispatcher's successor: DispatcherQueue. So you should be able to get that for WinUI3?

@tinmac
Copy link

tinmac commented Nov 10, 2021

@MikeHillberg yeah thanks appreciate that, ive managed to sus it out. I added the DispatcherQueue as a property on my VM. Was a little to used to 'the WPF way'

@MikeHillberg
Copy link
Contributor

Is the difference that in WPF your ViewModel was deriving from DispatcherObject? (WinUI doesn't have DispatcherObject, instead essentially DispatcherObject got merged onto DependencyObject.)

@tinmac
Copy link

tinmac commented Nov 10, 2021

@MikeHillberg I was generally using MVVM LIght's DispatcherHelper & forgot just how much it was doing & how helpful it was 😉

@eduardobragaxz
Copy link

Is there an alternative for Window.Current.Content for WinUI 3?

@KangUnia
Copy link

https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-supported-api#core-unsupported-classes

I don't understand.

    protected InputCursor ProtectedCursor
    {
        get
        {
            return IUIElementProtectedMethods.get_ProtectedCursor(_objRef_global__Microsoft_UI_Xaml_IUIElementProtected);
        }
        set
        {              IUIElementProtectedMethods.set_ProtectedCursor(_objRef_global__Microsoft_UI_Xaml_IUIElementProtected, value);
        }
    }

this is protected
not public
cannot be used

How do I use it?
Cursor should change when using this 'PointerPressed' function

Why did you make this?

@castorix
Copy link

protected InputCursor ProtectedCursor
{
    get
    {
        return IUIElementProtectedMethods.get_ProtectedCursor(_objRef_global__Microsoft_UI_Xaml_IUIElementProtected);
    }
    set
    {              IUIElementProtectedMethods.set_ProtectedCursor(_objRef_global__Microsoft_UI_Xaml_IUIElementProtected, value);
    }
}

this is protected not public cannot be used

How do I use it? Cursor should change when using this 'PointerPressed' function

With Reflection

@eduardobragaxz
Copy link

Is there an alternative for Window.Current.Content for WinUI 3?

I found my way around my issue by getting the XamlRoot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests