无法在 UWP 中创建 CompositionTarget

问题描述

我通过在 CreateTargetForCurrentView UIElement's 对象上调用 Compositor API 来使用它

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

在此之后,由于我想创建一棵 Visual 树,它们之间具有排序感,以便我可以根据我的用例将视觉放在另一个之上,因此我创建了一个 {{3 }} 来托管这个视觉树。

auto containerVisual = compositor->CreateContainerVisual();

现在,由于此 ContainerVisual 需要附加到 CompositionTarget 的 root,因此我这样做了(从 ContainerVisual 获取参考):

auto compositionTarget = compositor->CreateTargetForCurrentView();

但这会导致 DCOMPOSITION_ERROR_WINDOW_ALREADY_COMPOSED,根据 here 暗示:

IDCompositionDevice::CreateTargetForHwnd 方法调用 已存在可视化树的 hwnd 和最顶层参数。

如果我理解正确,这意味着 docs 在我的 UWP 应用的生命周期中以某种方式被调用。此 API 是未直接在应用中使用的 Win32 API。现在我的问题是,在 UWP 应用程序中,Windows.UI.Composition 命名空间或任何其他命名空间下的什么 API,在 C++/Cx 投影下,我应该寻找可能在内部调用 IDCompositionDevice::CreateTargetForHwnd 导致这个例外?或者更好的是,是否有任何 API 可用于从 CompositionTargetView提取 Window?我看到 CompositionRootVisual 类中有一个属性 CoreApplicationView 可用于直接附加 ContainerVisual,但根据 IDCompositionDevice::CreateTargetForHwnd 在其中一个 API 更新中将其删除.

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

奇怪的是,即使 this 指的是这种将 ContainerVisual 附加到 View方法,但文档显然已经过时了。

解决方法

如果要在 XAML 和可视层之间进行互操作,我们不应该纠结于 CompositionTarget。让我们回到如何使用 ContainerVisual 创建可视化树。 参考 document,我们可以使用 ElementCompositionPreview.GetElementVisual(UIElement) 方法获取任何页面元素的背景视觉,并使用 ElementCompositionPreview.SetElementChildVisual(UIElement,Visual) 方法将创建的视觉效果带到 UIElement 的视觉树中。上述文档和本文档 Using the Visual Layer with XAML 中有一些代码,展示了如何从 UIElement 中获取视觉对象并将视觉对象设置为 UIElement

以下是您可以参考的示例代码:

添加命名空间和标题:

#include "windowsnumerics.h"

using namespace Windows::UI::Xaml::Hosting;
using namespace Windows::UI::Composition;
using namespace Windows::Foundation::Numerics;
using namespace Windows::UI;

声明一些成员:

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

抓取视觉对象和设置视觉对象的代码:

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;

}