Click or drag to resize
CanvasAnimatedControl Class
XAML control intended for displaying animating content.
Inheritance Hierarchy
SystemObject
  Windows.UI.Xaml.ControlsUserControl
    Microsoft.Graphics.Canvas.UI.XamlCanvasAnimatedControl

Namespace:  Microsoft.Graphics.Canvas.UI.Xaml
Assembly:  Microsoft.Graphics.Canvas (in Microsoft.Graphics.Canvas.dll) Version: 0.0.0.0
Syntax
C#
public sealed class CanvasAnimatedControl : UserControl, 
	IAnimationObject, IVisualElement, ICanvasAnimatedControl, ICanvasResourceCreatorWithDpi, ICanvasResourceCreator

The CanvasAnimatedControl type exposes the following members.

Constructors
  NameDescription
Public methodCanvasAnimatedControl
Initializes a new instance of the CanvasAnimatedControl class.
Top
Properties
  NameDescription
Public propertyClearColor
The color that the control is cleared to before the Draw event is raised.
Public propertyCustomDevice
Gets or sets an application-chosen device for this control.
Public propertyDevice
Gets the underlying device used by this control.
Public propertyDpi
Gets the current dots-per-inch (DPI) of this control.
Public propertyDpiScale
Gets or sets a scaling factor applied to this control's Dpi.
Public propertyForceSoftwareRenderer
Gets or sets the whether the devices that this control creates will be forced to software rendering.
Public propertyHasGameLoopThreadAccess
Gets whether the current thread is the game loop thread.
Public propertyIsFixedTimeStep
Indicates whether the game loop is running in fixed or variable timing mode.
Public propertyPaused
Indicates whether the control's game loop is paused.
Public propertyReadyToDraw
Gets whether the control is in a state where it is ready to draw.
Public propertySize
Gets the current size of the control, in device independent pixels (DIPs).
Public propertyTargetElapsedTime
Gets or sets the time between Update events
Public propertyUseSharedDevice
Gets or sets whether this control should create a new device each time, or use a device which may common between other controls.
Top
Methods
  NameDescription
Public methodConvertDipsToPixels
Converts units from device independent pixels (DIPs) to physical pixels based on the current DPI of this control.
Public methodConvertPixelsToDips
Converts units from physical pixels to device independent pixels (DIPs) based on the current DPI of this control.
Public methodCreateCoreIndependentInputSource
Creates an input source that can process input on a non-UI thread (such as the game loop thread).
Public methodInvalidate
Marks this control as requiring redrawing.
Public methodRemoveFromVisualTree
Removes the control from the last FrameworkElement it was parented to.
Public methodResetElapsedTime
Ensures that only a single Update event will be raised on the next iteration of the game loop.
Public methodRunOnGameLoopThreadAsync
Schedules the provided callback to run asynchronously on the game loop thread.
Top
Events
  NameDescription
Public eventCreateResources
Hook this event to create any resources needed for your drawing.
Public eventDraw
Hook this event to draw the contents of the control.
Public eventGameLoopStarting
Occurs on the game loop thread just before the game loop starts.
Public eventGameLoopStopped
Occurs on the game loop thread just after the game loop stops.
Public eventUpdate
Hook this event to update any data, as necessary, for your app's animation.
Top
Remarks

CanvasAnimatedControl uses a CanvasSwapChain and CanvasSwapChainPanel for displaying graphics. The swap chain is automatically resized and recreated as necessary.

The control provides a game-style update and draw loop. This game loop runs on a dedicated thread. This means that the game loop and XAML's UI thread cannot block each other, ensuring that XAML UI elements remain responsive regardless of what the game loop is doing (and vice versa). However, it does require that care is taken when accessing data shared between the UI thread and the game loop thread.

When using CanvasAnimatedControl from managed code, care must be taken to avoid memory leaks due to reference count cycles. See Avoiding memory leaks for more information.

The control has five events that can be subscribed to:

CreateResources

This is raised when a new device is created and is the appropriate place to start loading bitmaps from disk and so on.

This event is raised on the UI thread.

GameLoopStarting

This is raised just before the game loop starts running. Applications can hook this to perform any initialization that must happen on the game loop thread. For example, CreateCoreIndependentInputSource(CoreInputDeviceTypes) must be called from the thread that will process the input events raised by the CoreIndependentInputSource.

This event is raised on the game loop thread.

Update

This tells the app to update animations, game logic, simulations etc. Update will only ever be raised after CreateResources, including any tracked asynchronous actions, has completed. See this article for more advanced asynchronous loading scenarios.

Depending on the value of IsFixedTimeStep, Update may be raised multiple times between each Draw event. See below for more details.

This event is raised on the game loop thread.

Draw

This tells the app to draw the contents of the control, using CanvasDrawEventArgs.DrawingSession. The control is been cleared to ClearColor before the Draw event is raised.

This event will only ever be raised after at least one Update has been completed. However, it may be raised multiple times in succession without an Update if the control is resized or the ClearColor property is modified.

This event is raised on the game loop thread.

GameLoopStopped

This is raised just after the game loop has stopped, and just before the game loop thread exits. Applications can hook this to perform any deinitialization that must happen on the game loop thread, such as unregistering event handlers on a CoreIndependentInputSource.

This event is raised on the game loop thread.

Game Loop

Each iteration of the game loop:

  • raises some number of Update events
  • raises one Draw event

The IsFixedTimeStep property controls how often the Update event is raised. This is a boolean value, and when set to true we say that the control is running with "fixed timing". When set to false it is running with "variable timing".

Defaults

IsFixedTimeStep
true
TargetElapsedTime
16.6ms (60 fps)
Paused
false

Fixed Timing

When IsFixedTimeStep is set to true, the control is running in fixed timing mode. This mode is particularly useful for games since it provides a deterministic elapsed time between updates, providing consistency in game simulation regardless of actual display framerate.

When running in fixed timing mode mode, the Update event is raised at a fixed rate, controled by the TargetElapsedTime property. For example, if TargetElapsedTime is set to its default value of 16.6ms, Update will be raised 60 times a second. There are several different scenarios depending on how long the Update and Draw handlers take to complete:

The simplest situation is that the total time spent in Update + Draw is exactly 1/60 of a second. In this case the control will raise Update, then raise Draw, then look at the clock and notice it is time for another Update, then Draw, and so on. Simple!

What if Update + Draw takes less than 1/60 of a second? Also simple. Here the control raises Update, then Draw, then looks at the clock, notices there is some time left over, so waits until it is time to raise Update again.

What if Update + Draw takes longer than 1/60 of a second? This is where things get complicated. There are many reasons why this could happen:

  1. The computer might be slightly too slow to run the at the desired speed.
  2. Or the computer might be way too slow to run the game at the desired speed!
  3. The computer might be basically fast enough, but this particular frame might have taken an unusually long time for some reason. Perhaps there were too many explosions on screen, or there was a garbage collection.
  4. The app could have been paused in the debugger.
The control does the same thing in response to all four causes of slowness:
  • CanvasTimingInformation.IsRunningSlowly to true.
  • Raises Update extra times (without calling Draw) until it catches up.
  • If things are getting ridiculous and too many Updates are required to catch up, it just gives up.
More information on this algorithm can be found in this blog post.

Variable Timing

When IsFixedTimeStep is set to false, the control is running in variable timing mode. In this mode one, and only one, Update event is always raised before each Draw event. The app can determine how much time has passed between Update events by examining the CanvasTimingInformation available through the event args.

Paused behavior

When the Paused property is set to true, the control will stop raising Update events. However, when the control is resized or the clear color has changed, the Draw event will be raised to ensure that the display has been updated accordingly.

Pointer Input

The recommended way to receive pointer input events for a CanvasAnimatedControl is to use the standard XAML events, such as UIElement.PointerPressed.

These events are raised on the UI thread, so they may be raised concurrently with any of the game loop thread events (such as Update, Draw and so on). Care must be taken to ensure that access to any shared data structures is synchronized.

One strategy for handling this is to use RunOnGameLoopThreadAsync(DispatchedHandler) to execute code on the game loop thread. For example:

private void animatedControl_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    var position = e.GetCurrentPoint(animatedControl).Position;
    var action = animatedControl.RunOnGameLoopThreadAsync(() =>
    {
        HandlePointerPressed(position);
    });
}
More detailed examples can be seen in these ExampleGallery samples:

For more advanced input handling, CanvasAnimatedControl provides CreateCoreIndependentInputSource(CoreInputDeviceTypes). This allows input to be processed on any thread (including the game loop thread). This gives the app finer control over input processing.

Keyboard Input

The simplest way to receive keyboard input events is to subscribe to CoreWindow's KeyDown and KeyUp events. The handlers for these events will be called on the UI thread, so if these need to access data that is also accessed by the game loop thread some kind of synchronization is required.

One possible approach here is to use RunOnGameLoopThreadAsync(DispatchedHandler) to execute code on the game loop thread. The gotcha here is that the KeyEventArgs is not agile and so should not be accessed from the game loop thead. Although this will work, it may have surprising performance effects since method calls / property accesses on the KeyEventArgs object could involving dispatching back to the UI thread!

Instead, process or extract the data from KeyEventArgs that needs to be accessed on the UI thread. Example:

// Register for keyboard events in the loaded handler...
private void PageLoaded(object sender, RoutedEventArgs e)
{
    Window.Current.CoreWindow.KeyDown += KeyDown_UIThread;
}

// Don't forget to unregister in unloaded!
private void PageUnloaded(object sender, RoutedEventArgs e)
{
    Window.Current.CoreWindow.KeyDown -= KeyDown_UIThread;
}

// The KeyDown handler runs on the UI thread...
private async void KeyDown_UIThread(CoreWindow sender, KeyEventArgs args)
{
    args.Handled = true;

    // extract the data from the args before marshaling it to the
    // game loop thread
    var virtualKey = args.VirtualKey;

    var action = animatedControl.RunOnGameLoopThreadAsync(() => KeyDown_GameLoopThread(virtualKey));
}

private void KeyDown_GameLoopThread(VirtualKey virtualKey)
{
    // ...
}

A more complete example can be found in the KeyboardInputExample in ExampleGallery.

Threading Considerations

CanvasAnimatedControl runs its game loop on a dedicated thread. This thread is started in response to the control's Loaded event, and stopped in response to the control's Unloaded event.

The CreateResources event runs on the UI thread. The other events - GameLoopStarting, Update, Draw and GameLoopStopped all run on the game loop thread. The control ensures that it does not raise any events on the game loop thread while CreateResources is running on the UI thread.

Arbitrary code can be scheduled to run on the game loop thread using RunOnGameLoopThreadAsync(DispatchedHandler). Work scheduled with this method will only run after CreateResources has completed.

The following diagram shows how the UI thread and game loop thread are synchronized:

When the control is first loaded the game loop thread is created. The control will not raise its own Loaded event until the game loop thread has finished raising the GameLoopStarting event. The app's Loaded event handlers will never run at the same time as the GameLoopStarting event.

The game loop thread waits until the CreateResources process has completed (included any asynchronous resource creation tasks) before starting the dispatcher.

While the dispatcher is running, it raises Update / Draw events as appropriate and runs RunOnGameLoopThread actions. If a CoreIndependentInputSource has been created on this thread then its events are dispatched through the dispatcher.

When the control is unloaded the game loop thread is shutdown. The game loop thread raises the GameLoopStopped event after the dispatcher has been stopped. The control will not raise its own Unloaded event until after the GameLoopStopped event has completed. The app's Unloaded event handlers will never run at the same time as the GameLoopStopped event.

See Also