uwpc++-cx

Can't create a CompositionTarget in UWP


I was using the CreateTargetForCurrentView API by invoking it on a UIElement's Compositor object extracted via

auto visual = ElementCompositionPreview::GetElementVisual(elem);
auto compositor = visual->Compositor;

After this, since I want to create a tree of Visuals with a sense of ordering amongst them so that I can position the visual one above the other as per my use case, I created a ContainerVisual to host this tree of visuals.

auto containerVisual = compositor->CreateContainerVisual();

Now since this ContainerVisual needs to be attached to the root of a CompositionTarget, I did so (taking the reference from here):

auto compositionTarget = compositor->CreateTargetForCurrentView();

but this leads to a DCOMPOSITION_ERROR_WINDOW_ALREADY_COMPOSED which as per the docs implies:

The IDCompositionDevice::CreateTargetForHwnd method was called with hwnd and topmost parameters for which a visual tree already exists.

If I understand correctly, this means IDCompositionDevice::CreateTargetForHwnd has been called somehow during the lifecycle of my UWP app. This API is a Win32 API that isn't being used directly in the app. Now my question is, in a UWP app, what API under Windows.UI.Composition namespace or any other, under the C++/Cx projection, should I be looking for that might be internally invoking IDCompositionDevice::CreateTargetForHwnd that's leading to this exception? Or better still, is there any API that can be used to extract a CompositionTarget from a View or Window? I see that there was a property CompositionRootVisual in the CoreApplicationView class that could be used to attach the ContainerVisual directly but it was removed in one of the API updates as per this.

| public class Windows.ApplicationModel.Core.CoreApplicationView {
-   public Visual CompositionRootVisual { get; set; }
| }

Strangely even the docs refer to this route of attaching a ContainerVisual to a View but the documentation is apparently outdated.


Solution

  • If you want to interop between XAML and the Visual Layer, we shouldn’t be entangled in CompositionTarget. Let’s back to how to use ContainerVisual to create a visual tree. Refer to the document, we could use ElementCompositionPreview.GetElementVisual(UIElement) method to grab the backing visual for any page element, and use ElementCompositionPreview.SetElementChildVisual(UIElement, Visual) method to take a created visual to a UIElement’s visual tree. There are some code in the above document and this document Using the Visual Layer with XAML, showing how to grab a visual from a UIElement and set a visual to a UIElement.

    Here is the sample code you could refer to:

    Add the namespaces and a header:

    #include "windowsnumerics.h"
    
    using namespace Windows::UI::Xaml::Hosting;
    using namespace Windows::UI::Composition;
    using namespace Windows::Foundation::Numerics;
    using namespace Windows::UI;
    

    Declare some members:

    private:
        Compositor^ _compositor;
        ContainerVisual^ _root;
        Visual^ CreateChildElement();
    

    The code to grab a visual and set a visual:

    void App2::MainPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        _compositor = ElementCompositionPreview::GetElementVisual(this)->Compositor;
        _root = _compositor->CreateContainerVisual();
        for (int i = 0; i < 5; i++)
        {
            _root->Children->InsertAtTop(CreateChildElement());
        }
        ElementCompositionPreview::SetElementChildVisual(this, _root);
    
    }
    
    Visual^ App2::MainPage::CreateChildElement()
    {
        auto element = _compositor->CreateContainerVisual();
        element->Size = float2(100.0f, 100.0f);
    
        //
        // Position this visual randomly within our window
        //
        element->Offset = float3(((float)rand() / RAND_MAX) * 400, ((float)rand() / RAND_MAX) * 400, 0.0f);
    
        //
        // The outer rectangle is always white
        //
        auto visual = _compositor->CreateSpriteVisual();
        element->Children->InsertAtTop(visual);
        visual->Brush = _compositor->CreateColorBrush(ColorHelper::FromArgb(0xFF, 0xFF, 0x11, 0xFF));
        visual->Size = float2(100.0f, 100.0f);
    
        //
        // The inner rectangle is inset from the outer by three pixels all around
        //
        auto child = _compositor->CreateSpriteVisual();
        visual->Children->InsertAtTop(child);
        child->Offset = float3(3.0f, 3.0f, 0.0f);
        child->Size = float2(94.0f, 94.0f);
    
        //
        // Pick a random color for every rectangle
        //
        byte red = (byte)(0xFF * (0.2f + (((float)rand()/RAND_MAX) / 0.8f)));
        byte green = (byte)(0xFF * (0.2f + (((float)rand() / RAND_MAX) / 0.8f)));
        byte blue = (byte)(0xFF * (0.2f + (((float)rand() / RAND_MAX) / 0.8f)));
        child->Brush = _compositor->CreateColorBrush(ColorHelper::FromArgb(0xFF, red, green, blue));
    
        //
        // Make the subtree root visual partially transparent. This will cause each visual in the subtree
        // to render partially transparent, since a visual's opacity is multiplied with its parent's
        // opacity
        //
        element->Opacity = 0.8f;
    
        return element;
    
    }