【问题标题】:using Composition API to draw objects on XAML Canvas with C++ UWP application使用 Composition API 在 XAML Canvas 上通过 C++ UWP 应用程序绘制对象
【发布时间】:2018-04-03 11:54:53
【问题描述】:

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

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

在本文中,我们将探讨 Windows.UI.Composition API。这 组合 API 是位于 Windows 10 之间的可视化层 XAML 框架和 DirectX。它提供了通用 Windows 平台应用程序 轻松访问较低级别的 Windows 绘图堆栈。 API 专注于绘制矩形和图像——有或没有 XAML 在这些表面上应用动画和效果。

我还发现了这篇博客文章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 我有以下代码。此代码在画布上绘制一个椭圆,然后绘制一条线,然后它启动一个周期性计时器,该计时器每 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);
}

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

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

【问题讨论】:

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


    【解决方案1】:

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

    有关向Canvas 添加 Web 控件并在其中显示 YouTube 视频的简要概述,请参见本文末尾。请参阅下面的附录 I。

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

    XAML 文件已更改,在 Canvas 中添加了 EllipseGrid 中。 heightwidth 是相同的,我们会在开始运行时以编程方式更改 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://docs.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://docs.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://docs.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();
    }
    

    此应用程序生成一个带有时钟的窗口,如下所示:

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

    在 Visual Studio 2017 的工具箱窗格中查看 XAML 设计器时,在“所有 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);
    

    【讨论】:

    • 一份来自微软的很好的文档,描述了 UWP 和 XAML 的组合系统A Guide to Windows.UI.Composition
    • 当前版本是通过 C++ 类中的 Composition API 访问 XAML 中的Canvas 来创建钟面装饰。我现在想知道是否应该进行更改,以便应该访问 XAML 中的 Ellipse 以进行修改。今晚可以尝试一下。
    • 我进行了更改,以便将 m_root = GetVisual(MyCanvas); 更改为 m_root = GetVisual(ClockFace);,这是 XAML 代码中 Ellipse 对象的 n:name,现在所有的钟面装饰都是实际的椭圆。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    • 1970-01-01
    • 2015-10-06
    • 1970-01-01
    相关资源
    最近更新 更多