首页 文章

使用Composition API在具有C UWP应用程序的XAML Canvas上绘制对象

提问于
浏览
1

我正在从使用Blank模板开始的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 . Composition 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);
}

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

image of screen shot of application window showing graphics

如何在椭圆形顶部绘制线条,这样的方式使得每次定时器触发功能 App2_ArmTest::MainPage::OnTick() 时,我可以将时钟指针或旋转到钟面上的正确位置?

1 回答

  • 1

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

    另请参阅本文末尾,以获取有关将控件添加到 Canvas 并在其中显示YouTube视频的简要概述 . 见下面的附录I.

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

    XAML文件已更改,在 Grid 中的 Canvas 中添加了 Ellipse . 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();
    }
    

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

    Appendix I (Oct-25-2017): Adding WebView control

    在查看XAML设计器的Visual Studio 2017的“工具箱”窗格中,在"All XAML Controls"部分中有一个控件 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);
    

相关问题