XAML Composition Interop Behavior Changes

Fela Ameghino edited this page Apr 21, 2017 · 3 revisions

XAML-Composition Interop Behavior Changes

Starting with build 14332, there is a behavior change with visuals returned by ElementCompositionPreview.GetElementVisual. The visual returned by the API now is used by both XAML and the caller, and as such there are new behaviors surrounding how these properties get set. This page serves to explain the differences between build 10586 (the November Update) and 14322+ (the Anniversary Update). These changes only take place if you mark the "TargetVersion" in your manifest to a build that is 14332 or later. Apps that target build 10586 will still behave the way they do on older builds even if they are running on a machine with the Anniversary Update.

It is important to note that this behavior change is only for visuals returned by ElementCompositionPreview.GetElementVisual and NOT for visuals created by the developer.

Build 10586 (November Update)

In build 10586 the visual returned by ElementCompositionPreview.GetElementVisual is solely controlled by the caller. Manipulations to a UIElement through its corresponding visual were purely additive to XAML's layout. This was done by returning a Visual that is the child (under the covers) of the root visual for a given UIElement. To illustrate this, bellow is a simplified sub-tree of a UIElement:

XAML tree in the November Update

In this case XAML layout controls the root visual for its own layout and a developer would control a separate child visual.

Build 14332+ (Anniversary Update)

In builds 14332 and later, the visual returned by ElementCompositionPreview.GetElementVisual is the same visual that is manipulated by XAML for layout purposes. This means that unlike the November Update, manipulations to a UIElement through its corresponding visual are absolute changes to XAML's layout. To illustrate this, bellow is a simplified sub-tree of a UIElement in the Anniversary Update:

XAML tree in the Anniversary Update

Because both the caller and XAML are manipulating the same Visual, it is possible that XAML may override values set by the caller. The following are properties that may be set by XAML:

  • Offset
  • Size
  • Opacity
  • TransformMatrix
  • Clip
  • CompositeMode

The policies for XAML updates to an interop visual are as follows:

  1. XAML will set these properties on the interop visual during "Tick" processing

  2. XAML will not read back the values set by app code directly on the interop visual

  3. XAML will only set the properties when new value is different than the last value set by XAML

    • The last-set value defaults are the same as the Visual default property values
    • The above means XAML will not set the visual property if the XAML property stays set to its default (eg the XAML layout offset, which maps to Visual.Offset, has a default of [0,0])

The consequences of this are:

  1. XAML won't set the property on the Visual if the property wasn't different from XAML's perspective.

For example:

Visual myButtonVisual = ElementCompositionPreview.GetElementVisual(myButton);
myButton.Opacity = 1.0;
myButtonVisual.Opacity = 0.5;
myButtonVisual.StartAnimation("Opacity", …);

In this case, XAML wouldn't know that the app set 0.5 or that it started the animation, so XAML doesn't change anything on the Visual. This is because XAML believes the last-set value is the default of 1.0. (even though XAML goes and sets the Visual opacity asynchronously later during Tick processing).

The same applies to layout results, so for example:

Canvas.SetLeft(myButton, 5);
Canvas.SetTop(myButton, 5);

In this case XAML would later set:

myButtonVisual.Offset = new Vector3(5,5,0);

If the app code later does:

myButtonVisual.Offset = new Vector3(20,20,0);

Then the 20,20 value will win no matter how many times layout is run in XAML because XAML always believes the layout output value is 5,5.

HOWEVER one very important caveat for non-zero layout positions is that because XAML is detecting its last-set visual value and the layout positions are subject to layout-rounding, we need to take the above example again but consider DPI scaling.

Start with DPI scaling of 1.0.

Canvas.SetLeft(myButton, 5);
Canvas.SetTop(myButton, 5);

In this example with DPI scale = 1.0, XAML would set

LayoutRound(5, 1.0) = floor(5 * 1.0 + 0.5) / 1.0 = 5.0
myButtonVisual.Offset = new Vector3(5.0,5.0,0);

If the app code later does:

myButtonVisual.Offset = new Vector3(20,20,0);

Then the Visual value is 20,20.

But now suppose the DPI scale changes dynamically to 2.5. Now, XAML layout kicks in again but this time with DPI scale = 2.5. This means that XAML would set

LayoutRound(5, 2.5) = floor(5 * 2.5 + 0.5) / 2.5 = 5.2 
myButtonVisual.Offset = new Vector3(5.2,5.2,0);

And now because newXAMLValue (5.2, 5.2, 0) != oldXAMLValue (5.0,5.0,0.0), XAML will set the Visual Offset value and override the 20,20.

XAML also does layout-rounding on Size, so that property is similarly affected for non-zero size values.

This same behavior can also happen with Size even without DPI scaling. If the app code is setting Visual.Size directly but the element's size is a function of the size of the Window, then the Size property will be re-set by XAML after layout.

The safest way to handle this for Offset and Size is to use a layout in XAML which positions the element at 0,0 or sizes to 0,0 (e.g. MaxWidth = 0, MaxHeight = 0) if you want to interact directly with the Offset and Size on the interop visual.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.