Reputation: 17593
I am working on a sample Universal Windows Program with Visual Studio 2017 starting with the Blank template. My goal is to write a simple UWP app that will display an analogue clock face showing the current time with the time updating and the hands moving. Once I have this working with x86 on my desktop PC, I am going to change the build to ARM and deploy to my Raspberry Pi 3 Model B running Windows 10 IoT.
I found this blog posting, Using the Composition API in UWP apps, however it appears to be using C# rather than C++. It has the following to say which indicates there is a way using a kind of composition 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.
I also found this blog article, Introduction to Composition, but it also appears to be C# and not C++. And I found this article, Interop between XAML and the Visual Layer.
The problem I am having with these articles is that the various XAML objects and classes are not the same between C# and C++ and the fact that I am new to this doesn't help.
This article, Using the Visual Layer with XAML, seems to have a C++ version of some of the C# source however I am not sure if this is what I actually require.
The idea is to draw a circle using the Ellipse() function then draw two lines for the hands, one (the hour hand) being shorter and thicker and the second (the minute hand which is drawn after the hour hand so that it is on top) being longer and thinner than the first in order to allow it to be seen when the hands overlap.
In my MainPage.xaml file I have the following test code:
<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>
And in MainPage.xaml.cpp I have the following code. This code draws an ellipse and then a line on the canvas then it starts a periodic timer which every 2 seconds changes the color of the ellipse.
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);
}
And my current displayed window looks like this image after a few seconds:
How do I draw lines on top of the ellipse which is the clock face in such a fashion that every time the function App2_ArmTest::MainPage::OnTick()
is triggered by the timer, I can draw or rotate the clock hands to the correct position on the clock face?
Upvotes: 1
Views: 2965
Reputation: 17593
After a bit of work and a deep and dirty dive into various pieces of insufficient Microsoft documentation which concentrated on C# I have a working initial application which displays an analogue clock face with two hands showing the hour and minute and updating itself.
Also see the end of this post for a brief overview for adding a web control to the Canvas
and displaying a YouTube video in it. See Appendix I below.
I spent a lot of time reading and then using the Visual Studio 2017 IDE to explore the various components. In some cases the C# source code used different objects than the C++ (for example the C# used a Vector2
and a Vector3
class while C++ used a float2
and float3
from the Windowsnumerics.h
). In some cases there was a need to change C# syntax involving references to C++ syntax using pointers.
The XAML file has changed with the addition of an Ellipse
in the Canvas
which is in the Grid
. The height
and width
are the same to make a circle and we do programatically change the height
and width
once we start running.
<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>
The file MainPage.xaml.h has class member changes.
//
// 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);
};
}
The file MainPage.xaml.cpp has the most sweeping changes.
//
// 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();
}
This application generates a window with a clock that looks like:
Appendix I (Oct-25-2017): Adding WebView control
In the Toolbox pane of Visual Studio 2017 when viewing the XAML designer, in the section "All XAML Controls" there is a control WebView
which can be inserted into the XAML page.
After adding it and resizing things a bit I ended up with the following XAML code:
<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>
With the addition of the WebView
, we now have a control that we can access within the C++ code to set a URI and display a web page or resource on the web.
In the initialization code of I added the following two lines to embed a YouTube video into the WebView
control:
Uri ^targetUri = ref new Uri(L"https://www.youtube.com/embed/21JhWTIPQSw");
WebDisplay->Navigate(targetUri);
Upvotes: 2