CanvasAnimatedControl Class |
Namespace: Microsoft.Graphics.Canvas.UI.Xaml
public sealed class CanvasAnimatedControl : UserControl, IAnimationObject, IVisualElement, ICanvasAnimatedControl, ICanvasResourceCreatorWithDpi, ICanvasResourceCreator
The CanvasAnimatedControl type exposes the following members.
Name | Description | |
---|---|---|
CanvasAnimatedControl | Initializes a new instance of the CanvasAnimatedControl class. |
Name | Description | |
---|---|---|
ClearColor | The color that the control is cleared to before the Draw event is raised. | |
CustomDevice | Gets or sets an application-chosen device for this control. | |
Device | Gets the underlying device used by this control. | |
Dpi | Gets the current dots-per-inch (DPI) of this control. | |
DpiScale | Gets or sets a scaling factor applied to this control's Dpi. | |
ForceSoftwareRenderer | Gets or sets the whether the devices that this control creates will be forced to software rendering. | |
HasGameLoopThreadAccess | Gets whether the current thread is the game loop thread. | |
IsFixedTimeStep | Indicates whether the game loop is running in fixed or variable timing mode. | |
Paused | Indicates whether the control's game loop is paused. | |
ReadyToDraw | Gets whether the control is in a state where it is ready to draw. | |
Size | Gets the current size of the control, in device independent pixels (DIPs). | |
TargetElapsedTime | Gets or sets the time between Update events | |
UseSharedDevice | Gets or sets whether this control should create a new device each time, or use a device which may common between other controls. |
Name | Description | |
---|---|---|
ConvertDipsToPixels | Converts units from device independent pixels (DIPs) to physical pixels based on the current DPI of this control. | |
ConvertPixelsToDips | Converts units from physical pixels to device independent pixels (DIPs) based on the current DPI of this control. | |
CreateCoreIndependentInputSource | Creates an input source that can process input on a non-UI thread (such as the game loop thread). | |
Invalidate | Marks this control as requiring redrawing. | |
RemoveFromVisualTree | Removes the control from the last FrameworkElement it was parented to. | |
ResetElapsedTime | Ensures that only a single Update event will be raised on the next iteration of the game loop. | |
RunOnGameLoopThreadAsync | Schedules the provided callback to run asynchronously on the game loop thread. |
Name | Description | |
---|---|---|
CreateResources | Hook this event to create any resources needed for your drawing. | |
Draw | Hook this event to draw the contents of the control. | |
GameLoopStarting | Occurs on the game loop thread just before the game loop starts. | |
GameLoopStopped | Occurs on the game loop thread just after the game loop stops. | |
Update | Hook this event to update any data, as necessary, for your app's animation. |
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:
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.
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.
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.
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.
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.
Each iteration of the game loop:
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".
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:
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.
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.
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); }); }
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.
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.
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.