#24557·uno

[Regression][Skia] Image inside an open ToolTip stays blank when its source loads after the ToolTip opened

Author: mikernetCreated Sep 18, 2026Updated Sep 18, 2026

Current behavior

Regression. On Skia, an Image whose BitmapImage source is assigned (or finishes loading) after the ToolTip that contains it has opened stays blank for the whole time the tooltip is shown. The image only appears the next time the tooltip opens, once the bitmap is already loaded.

This worked in earlier 6.7 dev builds; it broke for us when moving from Uno.Sdk 6.7.0-dev.165 to 6.7.22. The tooltip/image code in our app has not changed since well before the last known-good build (the only nearby change was restyling the button that owns the tooltip, which does not touch the image).

We use the pattern to lazily load preview images for tooltips on tiles in a virtualized ItemsRepeater (thousands of items, so the previews can't be fetched eagerly):

csharp
var image = new Image { Width = 180, Height = 120, Stretch = Stretch.Uniform };
var tooltip = new ToolTip { Content = image };
tooltip.Opened += (_, _) => image.Source ??= new BitmapImage(new Uri(url));
ToolTipService.SetToolTip(tile, tooltip);

First hover: tooltip opens, blank 180×120 area. Move the pointer away and back: the tooltip opens with the image.

From reading Image.cs (Skia): the decoded data is stored in _pendingImageData and only applied during MeasureOverrideTryProcessPendingSource(). The InvalidateMeasure() the image issues when the data arrives does not lead to a measure pass while it is inside the tooltip's popup, so the pending data is never applied until the popup is opened again (which measures the image fresh with the data already present).

None of the following make the image appear on the first open once the data has arrived, which suggests the popup is not running layout for its content at all after it has opened:

  • image.InvalidateMeasure() / InvalidateArrange() / tooltip.InvalidateMeasure() from BitmapImage.ImageOpened, dispatched at Normal and Low priority (Low so it lands after the image's own subscription has received the data).
  • Re-presenting the content (tooltip.Content = null; tooltip.Content = image;) after the data arrived.
  • Assigning the source from image.Loaded instead of ToolTip.Opened.
  • Repeatedly calling image.InvalidateMeasure(); image.UpdateLayout(); on a 50 ms timer for 2 s after BitmapImage.ImageOpenedImage.ImageOpened (raised from TryProcessPendingSource) never fires during that time.

Expected behavior

An Image inside an open ToolTip should render its bitmap as soon as it has loaded, as it does when the same Image is anywhere else in the visual tree (and as it did before 6.7.22).

How to reproduce it (as minimally and precisely as possible)

  1. dotnet new unoapp (Skia renderer), add a Button to MainPage.
  2. In code-behind:
csharp
public MainPage()
{
    this.InitializeComponent();

    var image = new Image { Width = 180, Height = 120, Stretch = Stretch.Uniform };
    var tooltip = new ToolTip { Content = image };

    // Any http(s) image works; use something that takes a moment to download to make the timing obvious.
    tooltip.Opened += (_, _) => image.Source ??= new BitmapImage(new Uri("https://picsum.photos/360/240"));

    ToolTipService.SetToolTip(MyButton, tooltip);
}
  1. Run on Desktop (Windows), hover the button and wait for the tooltip.
  2. Observe: the tooltip opens but the image area is blank. Move the pointer off the button and back: the tooltip now shows the image.

Same result if step 2 uses image.Loaded += (_, _) => image.Source ??= ...; instead of tooltip.Opened.

Workaround ️

Don't attach the ToolTip until the bitmap is loaded, so its Image is measured for the first time with the data already available:

csharp
var bitmap = new BitmapImage(new Uri(url));
var loader = new Image { Source = bitmap }; // never enters the tree; just drives the load

bitmap.ImageOpened += (_, _) =>
{
    ToolTipService.SetToolTip(tile, new ToolTip { Content = new Image { Source = bitmap, Width = 180, Height = 120 } });
    GC.KeepAlive(loader);
};

Note that ToolTipService only starts its show timer from PointerEntered on an element that already has a tooltip, so a tooltip attached mid-hover must be opened manually (IsOpen = true after FeatureConfiguration.ToolTip.ShowDelay elapses) or it doesn't appear until the next hover.

Renderer

  • Skia
  • Native

Affected platforms ️

All

Uno.Sdk version (and other relevant versions)

  • Broken: Uno.Sdk 6.7.22
  • Last known good: Uno.Sdk 6.7.0-dev.165
  • .NET SDK 10.0.401, net10.0-desktop head

IDE version ‍

dotnet CLI (SDK 10.0.401); not IDE related

Anything else we need to know?

Relevant code: Image.cs (Skia) — InitializeImageSource stores _pendingImageData and calls InvalidateMeasure(); TryProcessPendingSource() runs only from MeasureOverride. The ToolTip popup does not seem to get a layout pass for that invalidation once it is open.