c++ - 使用 Composition API 通过 C++ UWP 应用程序在 XAML Canvas 上绘制对象

标签 c++ xaml canvas uwp visual-studio-2017

我正在使用 Visual Studio 2017 开发一个示例通用 Windows 程序,从空白模板开始。我的目标是编写一个简单的 UWP 应用程序,它将显示一个模拟钟面,显示当前时间以及时间更新和指针移动。在我的台式 PC 上使用 x86 后,我会将构建更改为 ARM 并部署到运行 Windows 10 IoT 的 Raspberry Pi 3 Model B。

我找到了这篇博文,Using the Composition API in UWP apps ,但是它似乎使用的是 C# 而不是 C++。它有以下说法,表明有一种使用组合接口(interface)的方法:

In this article we’ll explore the Windows.UI.Composition API. The Composition API is a visual layer that sits between the Windows 10 XAML framework and DirectX. It gives Universal Windows Platform apps an easy access to the lower level Windows drawing stacks. The API focuses on drawing rectangles and images –with or without a XAML surface- and applying animations and effects on these.

我还找到了这篇博客文章,Introduction to Composition ,但它似乎也是 C# 而不是 C++。我找到了这篇文章,Interop between XAML and the Visual Layer .

我在阅读这些文章时遇到的问题是,C# 和 C++ 之间的各种 XAML 对象和类并不相同,而且我是新手,这一点也无济于事。

本文,Using the Visual Layer with XAML ,似乎有一些 C# 源代码的 C++ 版本,但是我不确定这是否是我实际需要的。

想法是使用 Ellipse() 函数绘制一个圆,然后为指针绘制两条线,一条(时针)较短且较粗,第二条(分针在时针之后绘制,以便它在顶部)比第一个更长更薄,以便在双手重叠时可以看到它。

在我的 MainPage.xaml 文件中,我有以下测试代码:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="60,4,1348,712" RenderTransformOrigin="0.5,0.5">
    <Canvas x:Name="MyCanvas" HorizontalAlignment="Left" Height="300" Margin="61,27,0,0" VerticalAlignment="Top" Width="424"/>
</Grid>

在 MainPage.xaml.cpp 中,我有以下代码。此代码先绘制一个椭圆,然后在 Canvas 上绘制一条线,然后启动一个周期性计时器,每 2 秒更改一次椭圆的颜色。

MainPage::MainPage()
{
    InitializeComponent();

    // See https://xamlbrewer.wordpress.com/2016/01/04/using-the-composition-api-in-uwp-apps/
//  m_root = MyCanvas->GetVisual();
//  m_compositor = m_root->Compositor;

    m_BrushList[0] = ref new SolidColorBrush(Windows::UI::Colors::Red);
    m_BrushList[1] = ref new SolidColorBrush(Windows::UI::Colors::Purple);
    m_BrushList[2] = ref new SolidColorBrush(Windows::UI::Colors::Blue);
    m_BrushList[3] = ref new SolidColorBrush(Windows::UI::Colors::Green);
    m_BrushList[4] = ref new SolidColorBrush(Windows::UI::Colors::Yellow);
    m_BrushList[5] = ref new SolidColorBrush(Windows::UI::Colors::Orange);
    m_icount = 0;

    m_r = ref new Windows::UI::Xaml::Shapes::Ellipse();
    m_r->Width = 200;
    m_r->Height = 200;
    m_r->Stroke = m_BrushList[m_icount];
    //  r->Fill = ref new SolidColorBrush(Windows::UI::Colors::Blue);
    m_r->StrokeThickness = 4;
    m_r->Margin = Thickness(20, 20, 0, 0);
    MyCanvas->Children->Append(m_r);

    m_line1 = ref new Windows::UI::Xaml::Shapes::Line();
    m_line1->Stroke = ref new SolidColorBrush(Windows::UI::Colors::Red);
    m_line1->StrokeThickness = 6;
    m_line1->Y1 = 30;
    m_line1->X1 = 100;
    m_line1->X2 = 400;

    MyCanvas->Children->Append(m_line1);
    StartTimerAndRegisterHandler();
}

void App2_ArmTest::MainPage::StartTimerAndRegisterHandler()
{
    // create our time task so that we can change the clock periodically.
    auto timer = ref new Windows::UI::Xaml::DispatcherTimer();
    TimeSpan ts;
    // right now we are using a 2 second timer as part of prototyping this out.
    // this allows us to check that the timer is in fact working.
    // this needs to be changed from every two seconds to every minute once we
    // have the hands of the clock displaying properly.
    ts.Duration = 2 * 10000000;  // 10,000,000 ticks per second as value units is 100 nanoseconds
    timer->Interval = ts;
    timer->Start();
    auto registrationtoken = timer->Tick += ref new EventHandler<Object^>(this, &MainPage::OnTick);
}

void App2_ArmTest::MainPage::OnTick(Object^ sender, Object^ e)
{
    // change the color of our clock.
    m_icount = (m_icount + 1) % 6;
    m_r->Stroke = m_BrushList[m_icount];

    // get the current local time which will be used for positioning the
    // clock hands once we have that figured out.
    std::time_t result = std::time(nullptr);
    std::tm localTime;
    localtime_s (&localTime, &result);
}

几秒钟后我当前显示的窗口看起来像这个图像:

image of screen shot of application window showing graphics

我如何在钟面的椭圆顶部画线,每次函数 App2_ArmTest::MainPage::OnTick() 被计时器触发时,我可以将时钟指针绘制或旋转到钟面上的正确位置吗?

最佳答案

经过一些工作并深入研究各种集中在 C# 上的不足的 Microsoft 文档之后,我有一个工作的初始应用程序,它显示一个模拟钟面,两只手显示小时和分钟并 self 更新。

另请参阅本文末尾的简要概述,了解如何将 Web 控件添加到 Canvas 并在其中显示 YouTube 视频。请参阅下面的附录 I。

我花了很多时间阅读,然后使用 Visual Studio 2017 IDE 探索各种组件。在某些情况下,C# 源代码使用与 C++ 不同的对象(例如,C# 使用 Vector2Vector3 类,而 C++ 使用 float2Windowsnumerics.h 中的 float3)。在某些情况下,需要更改 C# 语法,涉及使用指针引用 C++ 语法。

XAML 文件已更改,在 Grid 中的 Canvas 中添加了一个 Ellipseheightwidth 是相同的来制作一个圆圈,我们会以编程方式更改 heightwidth 一旦我们开始运行。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="60,4,1348,712" RenderTransformOrigin="0.5,0.5">
    <Canvas x:Name="MyCanvas" HorizontalAlignment="Left" Height="300" Margin="61,27,0,0" VerticalAlignment="Top" Width="424">
        <Ellipse x:Name="ClockFace" Fill="AliceBlue" Height="100" Width="100" Canvas.Left="0" Canvas.Top="0" />
    </Canvas>
</Grid>

文件 MainPage.xaml.h 有类成员更改。

//
// MainPage.xaml.h
// Declaration of the MainPage class.
//

#pragma once

#include "MainPage.g.h"

namespace App2_ArmTest
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public ref class MainPage sealed
    {
    public:
        MainPage();

    private:
        Windows::UI::Composition::Compositor       ^m_compositor;
        Windows::UI::Composition::ContainerVisual  ^m_root;
        Windows::UI::Composition::SpriteVisual     ^m_hourhand;
        Windows::UI::Composition::SpriteVisual     ^m_minutehand;

        Windows::UI::Composition::ContainerVisual ^GetVisual(Windows::UI::Xaml::UIElement ^element);
        void StartTimerAndRegisterHandler();
        void SetHandsCurrentTime(void);
        void OnTick(Object^ sender, Object^ e);
    };
}

MainPage.xaml.cpp 文件具有最彻底的更改。

//
// MainPage.xaml.cpp
//
// Using the Canvas in the Grid as specified in MainPage.xaml we
// are going to draw and animate an analogue clock with two hands,
// hour and minute, to show the current local time.
//

#include "pch.h"
#include "MainPage.xaml.h"

// include for the system time and conversion functions from C++ run time.
#include <ctime>

// see Windows Numerics and DirectXMath Interop APIs at URL
// https://msdn.microsoft.com/en-us/library/windows/desktop/mt759298(v=vs.85).aspx
// see also https://blogs.msdn.microsoft.com/win2d/2015/06/02/winrt-vector-and-matrix-types-in-windows-10/
// following header provides for  Windows::Foundation::Numerics needed for vectors
#include <Windowsnumerics.h>

using namespace App2_ArmTest;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;

using namespace Windows::UI;

// See https://learn.microsoft.com/en-us/uwp/api/windows.ui.composition.compositionobject
using namespace Windows::UI::Composition;

using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;
using namespace Windows::UI::Xaml::Hosting;

// See the UWP for Windows 10 and Fluent at https://developer.microsoft.com/en-us/windows/apps

// tick->Offset - The offset of the visual relative to its parent or for a root visual the offset
//                relative to the upper-left corner of the windows that hosts the visual.

const float clockCenterPoint = 200.0f;       // center of the clock face, a circle, is from left margin and down from top.
const float tickHeight = 20.0f;              // the height of tick marks drawn to indicate hours of day.
const float handCenterOffset = 20.0f;        // number of units of stub of the hand for center of rotation.
const float hourHandDifference = 40.0f;      // number of units difference in length between hour hand and minute hand.
const float degreesInClockFace = 360.0f;     // number of degrees in a circle. clock face is a circle.
const float hoursOnClock = 12.0f;            // number of hours on a clock face, 12 hours counted 1 through 12.

Windows::UI::Composition::ContainerVisual ^MainPage::GetVisual(Windows::UI::Xaml::UIElement ^element)
{
    // Given a UI element from the XAML as specified by the x:Name="" assigned to the
    // UI element, lets get a Visual Container so that we can start placing stuff into
    // this UI element.
    // For this application the UI element will be a Canvas that we are adorning.
    auto hostVisual = ElementCompositionPreview::GetElementVisual(element);
    auto root = hostVisual->Compositor->CreateContainerVisual();
    ElementCompositionPreview::SetElementChildVisual(element, root);
    return root;
}

MainPage::MainPage()
{
    InitializeComponent();

    // See https://xamlbrewer.wordpress.com/2016/01/04/using-the-composition-api-in-uwp-apps/
    // See https://blogs.windows.com/buildingapps/2015/12/08/awaken-your-creativity-with-the-new-windows-ui-composition/
    // See https://learn.microsoft.com/en-us/windows/uwp/composition/visual-layer
    // See Graphics and Animation - Windows Composition Turns 10 https://msdn.microsoft.com/magazine/mt590968
    // See https://learn.microsoft.com/en-us/windows/uwp/graphics/drawing-shapes
    // See https://blogs.windows.com/buildingapps/2016/09/12/creating-beautiful-effects-for-uwp/

    m_root = GetVisual(MyCanvas);
    m_compositor = m_root->Compositor;

    // set the size of the clock face, an ellipse defined in the XAML
    // so that it is the proper size for the adornment we draw on the clock face.
    ClockFace->Height = clockCenterPoint * 2.0f;
    ClockFace->Width = clockCenterPoint * 2.0f;

    // Create the tick marks for the 12 hours around the face of the clock.
    // The clock face is a circle which is 360 degrees. Since we have 12 tick marks
    // we create each tick mark as a small rectangle at the 12 O'Clock or noon position
    // and then we rotate it around the face of the clock by a number of degrees until
    // we position it where it needs to go.

    // Windows::Foundation::Numerics::float2() is the C++ version of the C# Vector2()
    // Windows::Foundation::Numerics::float3() is the C++ version of the C# Vector3()

    SpriteVisual ^tick;

    for (int i = 0; i < 12; i++)
    {
        tick = m_compositor->CreateSpriteVisual();
        if (i % 3 != 0) {
            // for tick marks other than 3, 6, 9, and 12 make them less prominent.
            tick->Brush = m_compositor->CreateColorBrush(Windows::UI::Colors::Silver);
            tick->Size = Windows::Foundation::Numerics::float2(4.0f, tickHeight);                      // width and height of sprite
        }
        else {
            // for tick marks for 3, 6, 9, and 12 make them more prominent.
            tick->Brush = m_compositor->CreateColorBrush(Windows::UI::Colors::Black);
            tick->Size = Windows::Foundation::Numerics::float2(6.0f, tickHeight);                      // width and height of sprite
        }
        tick->CenterPoint = Windows::Foundation::Numerics::float3(tick->Size.x / 2.0f, clockCenterPoint, 0.0f);   // center point for rotations
        tick->Offset = Windows::Foundation::Numerics::float3(clockCenterPoint, 0.0f, 0.0f);                       // offset from the left only.
        tick->RotationAngleInDegrees = i * (degreesInClockFace / hoursOnClock);  // degrees divided by number of hour ticks on clock face.
        m_root->Children->InsertAtTop(tick);
    }

    // Draw the clock hands at the initial point of noon, both hands straight up. The hour hand is
    // not as tall as the minute hand and the hour hand is a bit wider than the minute hand.
    // Differences in size are to allow for visibility when they hands overlap.
    //
    // We have an hour hand and a minute hand to show the current hour and current minute.
    // The hour is from 0 to 11 though the clock face shows 1 to 12. The hour hand sweeps
    // around the clock face in 12 hours. The minute hand sweeps around the clock face in
    // one hour or 60 minutes. So each tick mark is 5 minutes for the minute hand and one
    // hour for the hour hand.
    //
    // The center point for the hand rotation is half the width of the hand and the height of a
    // tick mark from the bottom of the hand. This will put the center of rotation so that
    // a bit of the hand will extend past the center of rotation and look more realistic.
    // This axis of rotation should be where a line drawn from noon to 6 and a line from 9 to 3
    // cross in the center of the clock face.

    // Create the sprite for the minute hand of the clock.
    // The minute hand is a green rectangle 2.0 wide by 100.0 high
    m_minutehand = m_compositor->CreateSpriteVisual();
    m_minutehand->Brush = m_compositor->CreateColorBrush(Windows::UI::Colors::Green);
    m_minutehand->Size = Windows::Foundation::Numerics::float2(2.0f, clockCenterPoint - handCenterOffset);
    m_minutehand->CenterPoint = Windows::Foundation::Numerics::float3(m_minutehand->Size.x / 2.0f, m_minutehand->Size.y - handCenterOffset, 0.0f);
    m_minutehand->Offset = Windows::Foundation::Numerics::float3(clockCenterPoint, clockCenterPoint - m_minutehand->CenterPoint.y, 0.0f);

    // Create the sprite for the hour hand of the clock.
    // The hour hand is a gray rectangle 4.0 wide. It is shorter and wider than the minute hand.
    m_hourhand = m_compositor->CreateSpriteVisual();
    m_hourhand->Brush = m_compositor->CreateColorBrush(Windows::UI::Colors::Gray);
    m_hourhand->Size = Windows::Foundation::Numerics::float2(4.0f, m_minutehand->Size.y - hourHandDifference);
    m_hourhand->CenterPoint = Windows::Foundation::Numerics::float3(m_hourhand->Size.x / 2.0f, m_hourhand->Size.y - handCenterOffset, 0.0f);
    m_hourhand->Offset = Windows::Foundation::Numerics::float3(clockCenterPoint, clockCenterPoint - m_hourhand->CenterPoint.y, 0.0f);

    m_root->Children->InsertAtTop(m_hourhand);      // add hour hand first so that it is beneath the minute hand
    m_root->Children->InsertAtTop(m_minutehand);    // add the minute hand after the hour hand so it is on top.

    // Set the hands of the clock to the current time and then start our timer.
    // The timer will update the position of the clock hands once a minute.
    SetHandsCurrentTime();
    StartTimerAndRegisterHandler();
}

void App2_ArmTest::MainPage::StartTimerAndRegisterHandler()
{
    // create our time task so that we can change the clock periodically.
    auto timer = ref new Windows::UI::Xaml::DispatcherTimer();
    TimeSpan ts;
    // right now we are using a 2 second timer as part of prototyping this out.
    // this allows us to check that the timer is in fact working.
    // this needs to be changed from every two seconds to every minute once we
    // have the hands of the clock displaying properly.
    ts.Duration = 2 * 10000000;  // 10,000,000 ticks per second as value units is 100 nanoseconds
    timer->Interval = ts;
    timer->Start();
    auto registrationtoken = timer->Tick += ref new EventHandler<Object^>(this, &MainPage::OnTick);
}

void App2_ArmTest::MainPage::SetHandsCurrentTime(void)
{
    // get the current local time which will be used for positioning the
    // clock hands. We then use the local time to rotate the hands to the
    // correct position on the clock face.
    std::time_t result = std::time(nullptr);
    std::tm localTime;
    localtime_s(&localTime, &result);

    m_hourhand->RotationAngleInDegrees = (float)localTime.tm_hour * (degreesInClockFace / hoursOnClock);  // degrees divided by number of hour ticks on clock face.
    m_minutehand->RotationAngleInDegrees = (float)localTime.tm_min * (degreesInClockFace / 60.0f); // degrees divided by minutes in an hour.
}

void App2_ArmTest::MainPage::OnTick(Object^ sender, Object^ e)
{
    // A timer tick is received so lets position the clock hands
    // on the clock face to reflect the current time.
    SetHandsCurrentTime();
}

此应用程序生成一个带有时钟的窗口,如下所示: image of the displayed clock face with hour and minute hands

附录 I(2017 年 10 月 25 日):添加 WebView 控件

在查看 XAML 设计器时,在 Visual Studio 2017 的“工具箱” Pane 中,“所有 XAML 控件”部分中有一个控件 WebView,可以将其插入到 XAML 页面中。

添加它并稍微调整大小后,我得到了以下 XAML 代码:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="60,4,966,410" RenderTransformOrigin="0.5,0.5">
    <Canvas x:Name="MyCanvas" HorizontalAlignment="Left" Height="482" Margin="10,43,0,0" VerticalAlignment="Top" Width="735">
        <Ellipse x:Name="ClockFace" Fill="AliceBlue" Height="200" Width="200" Canvas.Left="0" Canvas.Top="0" />
        <WebView x:Name="WebDisplay" Height="462" Canvas.Left="205" Canvas.Top="10" Width="520"/>
    </Canvas>
</Grid>

通过添加 WebView,我们现在有了一个控件,我们可以在 C++ 代码中访问它来设置 URI 并在 Web 上显示网页或资源。

在我的初始化代码中添加了以下两行以将 YouTube 视频嵌入到 WebView 控件中:

Uri ^targetUri = ref new Uri(L"https://www.youtube.com/embed/21JhWTIPQSw");
WebDisplay->Navigate(targetUri);

关于c++ - 使用 Composition API 通过 C++ UWP 应用程序在 XAML Canvas 上绘制对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46880801/

相关文章:

c# - WPF DataGrid显示有限的行数并在该数后滚动

javascript - 使用外部文件的文件夹制作网页

javascript - 在 HTML canvas 上绘制的视频只能在 chrome 中播放,即使使用 webm

javascript - html5音频GUI开发

c++ - 通过异常处理错误

c++ - Mergecom 标签不按顺序 (MC_OUT_OF_ORDER_TAG) 问题

c# - Click 事件的 XAML 参数

c++ - 嵌套 c++11 范围循环以查找组合

c++ - 当我的相机远离物体时,为什么 OpenGL 会剔除我的多边形?

xaml - 如何将 View 与ViewModel或ViewModel的多个DataTemplates关联?