-
Notifications
You must be signed in to change notification settings - Fork 690
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
Loaded/Unloaded/IsLoaded APIs break down on quick swapping #1900
Comments
@MikeHillberg can provide more details on this. |
Hey @ranjeshj - thank you for your quick reply! I think I'll end up refactoring my own code in this scenario - I'm forced to put all my elements in the visual tree from the start because Anyway, I think this issue is still something that should be looked into, as the docs explicitly say that developers can (and should) rely on those events to track the lifetime of visual elements, so something like this is definitely not something one could or should expect to happen. Thanks again! 😊 |
Never mind, I was being too optimistic there. No matter what I try I still end up falling face first because of this issue. Not removing items from the visual tree after calling @ranjeshj You can see in the screen, for instance:
This is an output from an attached property in my control, which is set to
That's from a test I'll stick to my workaround for now hoping it'll hold, and won't touch anything there until this issue is fixed. But at this point I'm left wondering whether this might have been causing other involuntary issues somewhere else without me noticing, because of those events being raised so unreliably. |
We've done some work to correct these events, but ran into some compatibility issues and not shipped any fix yet. The Loaded event is raised during a layout/render tick. So if you add an element to the tree, Loaded should raise on the next tick. The Unloaded event is also async -- it also doesn't raise when you remove an element from the tree -- but in this case the notification is posted to the UI thread, which can get dispatched before the next tick. So if you move an element in and out of the tree quickly, the events can get out of order and raise in unmatching numbers. One workaround you can do is to look at an element's Parent property. When an element isn't in the live tree (the tree that's actually in the Window content), its Parent property is null, even if it's not the root of the tree. (This avoids some reference cycle leaks issues.) For example:
|
I raised directly the bug within my private channel. Let's see if it can get attention. |
Any updates @MikeHillberg? FYI @jonwis am curious if this bite us in Reunion areas such as app lifecycle. |
App lifecycle (and many WinRT objects) raise events just as soon as they're raised by the underlying system. For example, the "user has locked the screen" event is raised by the shell and detected by Project Reunion's infrastructure. Project Reunion raises the App runtimes and handlers have different ways of dealing with this incoming event, mostly related to their threading models. I don't forsee this being a problem with app lifecycle itself. XAML apps listening to an app lifecycle event would use the "dispatcher" mode of operation, to ensure the modifications to the XAML elements happen on the right thread during the next tick. (Which itself may be subject to the eventing issues in this thread, but not caused by Project Reunion or app lifecycle.) |
no fix yet? |
Is there any news about this issue? Was this fixed in WinUI 3? Is a fix still not considered for downlevel Windows Versions' Windows.UI.Xaml.dll (and not WinUI 3)? |
Think I'm hitting this with XAML Behaviors attach/detach with the loaded/unloaded events they rely on. In my scenario I have a behavior attached to an item with a DataTemplate containing a behavior. I drag and transfer the item between two different ItemsRepeater collections (which reference the same template for the items). I see the loaded event of the behavior fire again with the item created in the new collection (with the same element contained in the template), but then immediate see the detached event fire from the unloaded event of the behavior from the old collection... 😟 |
This is a real killer issue. There are a lot of cases where you need callbacks / events to be hooked up, or other state maintained, while the element is loaded. That requires the Loaded and Unloaded events to be fired correctly. Can we get this fixed please? |
Still hoping for a solution to this one! |
Is this issue being worked on still? |
? Any update on this? |
Bumping this (see #8638). |
I'm not 100% sure, but I've seemed to hit the same issue where a SwapChainPanel's unload event never or rarely hit when the Page containing it is navigated away from. Or when the application is exited. |
I've marked this as a feature proposal because we'll likely solve this by adding a new API. (it would likely cause compat issues if we changed the timing of the existing events). Thanks! |
I appreciate a way to fix this issue is in the works, but I personally have questions on how this could be done with a new api. Would this new api be available on currently supported windows versions where our apps run? (Windows 10 19045 as well as Windows 20348, Windows 11 22000 and Windows 11 22621). Will there be a way to dynamically detect this new api as well so our apps wouldn't crash when running on unpatched systems? Thanks for the update! |
Hi! Just to set expectations, we don't have any specific plans to address this soon, I'm mostly trying to categorize our issues. In WinAppSDK releases (especially minor ones) we try to avoid changing behavior apps may be depending on. If you want your app to sniff to see if an API is present, you can generally use the ApiInformation type for this -- for example, you can use ApiInformation.IsApiContractPresent() to determine if a contract is available. Thanks! |
The original API is behaving differently than documented, so realigning the API to the docs should be the priority here, not catering to some misbehaving apps right? Or is this more of an internal political issue because it breaks something big, like File Explorer? |
@Sergio0694 you mentioned in Discord:
You wouldn't happen to have a gist of an example of that somewhere or something to post here just so there's an kind of example of the exact pattern you describe for now? |
Meet the same bug here. |
Replaces PR #218 IconBox is a custom control that's a ContentControl, it's generic (toolkitable) and should be able to be styled and templated. It knows how to take an IconSource and create the underlying IconElement as its content. It can also take any general value as a `SourceKey` and via an implementation of the SourceRequested event, translate a bound general object into the `IconSource` required. This is how caching can be provided by an application as well, for instance (like we'll do here). This uses the deferred events pattern to await the call to the `SourceRequested` event which may need to load data asynchronously We create a static x:Bind helper `IconCacheProvider` to encapsulate our shared logic for our eventual Icon cache. Renamed IconCacheService.xaml.cs -> IconCacheService.cs Removed old broken behavior (believe ultimate issue was due to instability in loaded/unloaded events, i.e. issue microsoft/microsoft-ui-xaml#1900) XAML Styler also did its thing...
Replaces PR #218 IconBox is a custom control that's a ContentControl, it's generic (toolkitable) and should be able to be styled and templated. It knows how to take an IconSource and create the underlying IconElement as its content. It can also take any general value as a `SourceKey` and via an implementation of the SourceRequested event, translate a bound general object into the `IconSource` required. This is how caching can be provided by an application as well, for instance (like we'll do here). This uses the deferred events pattern to await the call to the `SourceRequested` event which may need to load data asynchronously We create a static x:Bind helper `IconCacheProvider` to encapsulate our shared logic for our eventual Icon cache. Renamed IconCacheService.xaml.cs -> IconCacheService.cs Removed old broken behavior (believe ultimate issue was due to instability in loaded/unloaded events, i.e. issue microsoft/microsoft-ui-xaml#1900) XAML Styler also did its thing...
Fixes #213 Replaces PR #218 FYI @Ryken100 (thanks for the info and assist in debugging the issue and discussing possible avenues of resolution) Thanks @zadjii-msft for validating the end path in #218 Before: ```xml <Border x:Name="IconBorder" Grid.Column="0" Width="16" Height="16" Margin="0,0,0,0"> <!-- LoadIconBehavior will magically fill this border up with an icon --> <Interactivity:Interaction.Behaviors> <cmdpalUI:LoadIconBehavior Source="{x:Bind Icon, Mode=OneWay}"/> </Interactivity:Interaction.Behaviors> </Border> ``` After: ```xml <cpcontrols:IconBox Grid.Column="0" Width="16" Height="16" Margin="0,0,0,0" SourceKey="{x:Bind Icon, Mode=OneWay}" SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" /> ``` The IconCacheProvider is the translation layer between having a light-weight control and our specific app's logic/desire for an icon cache, using the deferred event pattern: ```cs public static partial class IconCacheProvider { private static readonly IconCacheService IconService = new(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread()); public static async void SourceRequested(IconBox sender, SourceRequestedEventArgs args) { if (args.Key == null) { return; } if (args.Key is IconDataType iconData) { var deferral = args.GetDeferral(); args.Value = await IconService.GetIconSource(iconData); deferral.Complete(); } } } ``` ## Details `IconBox` is a custom control that's a ContentControl, its generic (toolkitable) and should be able to be styled and templated (haven't tested, but no reason it shouldn't as a XAML `ContentControl`, should help @niels9001 a ton). It knows how to take an `IconSource` and create the underlying `IconElement` as its content. It can also take any general value as a `SourceKey` and via an implementation of the `SourceRequested` event, translate a bound general object into the `IconSource` required. This is how caching can be provided by an application as well, for instance (like we'll do here). This uses the deferred events pattern to await the call to the `SourceRequested` event which may need to load data asynchronously We create a static x:Bind helper `IconCacheProvider` to encapsulate our shared logic for our eventual Icon cache. Also: - Renamed IconCacheService.xaml.cs -> IconCacheService.cs - Removed old broken behavior (believe ultimate issue was due to instability in loaded/unloaded events, i.e. issue microsoft/microsoft-ui-xaml#1900) - XAML Styler also did its thing... (some conflict here in config from PowerToys to resolve later, imagine this is also a consequence of us not having CI setup in fork...)
Describe the bug
As the title says, I've found a way to reproduce an annoying issue in the
FrameworkElement.Loaded
andFrameworkElement.Unloaded
events, and theFrameworkElement.IsLoaded
property. Basically, the two events will fire in a completely incorrect order or will not fire at all, and consequently theIsLoaded
property will report a value that does not reflect the state of a given element.In short:
Unloaded
raised lastIsLoaded
beingfalse
(!)This is especially an issue because devs are relying on those events to track the lifetime of visual items, eg. to subscribe/unsubscribe to external events, and whatnot. At the moment I've had to come up with specific workarounds in my app Legere to bypass this problem, but it's really not an ideal solution, plus this could be not doable at all in other scenarios where this issue is present.
Steps to reproduce the bug
Steps to reproduce the behavior:
Expected behavior
You should see the output window display the
Loaded
event last for the second button you clicked. Instead you'll seeUnloaded
being raised last for the control that you can clearly see being loaded.Additional steps
Loaded
andUnloaded
events are fired in a completely unreliable and incorrect manner.Screenshots
Version Info
NuGet package version:
Additional context
The reason why I have a setup like this is that in Legere I have a series of post views that I reuse, to avoid the overhead of creating a completely new instance every time a post of a given type is loaded. They're initially all stored inside an invisible and collapsed
Grid
, and lazily loaded. When one view is needed, I load it withFindName(string)
, then place it in a hostBorder
the user can interact with. When the user opens a post of another type, I remove that view and place it back in the original host (so thatFindName
will work again, as it needs the element to be in the visual tree for it to find it), then repeat the procedure to find and swap in the new view to use.The text was updated successfully, but these errors were encountered: