Graphics and Multimedia - Multimedia, Visual Layer 프로그래밍/WPF2017. 1. 14. 16:51
Multimedia Overview
https://msdn.microsoft.com/en-us/library/aa970915(v=vs.110).aspx
The multimedia features in Windows Presentation Foundation (WPF) enable you to integrate audio and video into your applications to enhance the user experience. This topic introduces the multimedia features of WPF.
Media API
The MediaElement and MediaPlayer classes are used to present audio or video content. These classes can be controlled interactively or by a clock. These classes can use on the Microsoft Windows Media Player 10 control for media playback. Which class you use, depends on the scenario.
MediaElement is a UIElement that is supported by the Layout and can be consumed as the content of many controls. It is also usable in Extensible Application Markup Language (XAML) as well as code. MediaPlayer, on the other hand, is designed for Drawing objects and lacks layout support. Media loaded using a MediaPlayer can only be presented using a VideoDrawing or by directly interacting with a DrawingContext. MediaPlayer cannot be used in XAML.
For more information about drawing objects and drawing context, see Drawing Objects Overview.
![]() |
---|
When distributing media with your application, you cannot use a media file as a project resource. In your project file, you must instead set the media type to Content and set CopyToOutputDirectory to PreserveNewest or Always. |
Media Playback Modes
![]() |
---|
Both MediaElement and MediaPlayer have similar members. The links in this section refer to the MediaElement class members. Unless specifically noted, members linked to in the MediaElement class can also be found in the MediaPlayer class. |
To understand media playback in Windows Presentation Foundation (WPF), an understanding of the different modes in which media can be played is required. Both MediaElement and MediaPlayer can be used in two different media modes, independent mode and clock mode. The media mode is determined by the Clock property. When Clock is null, the media object is in independent mode. When the Clock is non-null, the media object is in clock mode. By default, media objects are in independent mode.
Independent Mode
In independent mode, the media content drives media playback. Independent mode enables the following options:
Media's Uri can be directly specified.
Media playback can be directly controlled.
Media's Position and SpeedRatio properties can be modified.
Media is loaded by either setting the MediaElement object's Source property or by calling the MediaPlayer object's Open method.
To control media playback in independent mode, the media object's control methods can be used. The control methods available are Play, Pause, Close, and Stop. For MediaElement, interactive control using these methods is only available when the LoadedBehavior is set to Manual. These methods are unavailable when the media object is in clock mode.
See How to: Control a MediaElement (Play, Pause, Stop, Volume, and Speed) for an example of independent mode.
Clock Mode
In clock mode, a MediaTimeline drives media playback. Clock mode has the following characteristics:
Media's Uri is indirectly set through a MediaTimeline.
Media playback can be controlled by the clock. The media object's control methods cannot be used.
Media is loaded by setting a MediaTimeline object's Source property, creating the clock from the timeline, and assigning the clock to the media object. Media is also loaded this way when a MediaTimeline inside a Storyboard targets a MediaElement.
To control media playback in clock mode, the ClockController control methods must be used. A ClockController is obtained from the ClockController property of the MediaClock. If you attempt to use the control methods of either a MediaElement or MediaPlayer object while in clock mode, an InvalidOperationException will be thrown.
See the Animation Overview for more information about clocks and timelines.
See How to: Control a MediaElement by Using a Storyboard for an example of clock mode.
MediaElement Class
Adding media to an application is as simple as adding a MediaElement control to the user interface (UI) of the application and providing a Uri to the media you wish to include. All media types supported by Microsoft Windows Media Player 10 are supported in Windows Presentation Foundation (WPF). The following example shows a simple usage of the MediaElement in Extensible Application Markup Language (XAML).
<!-- This page shows a simple usage of MediaElement --> <Page x:Class="MediaElementExample.SimpleUsage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="SimpleUsage" > <StackPanel Margin="20"> <MediaElement Source="media/numbers-aud.wmv" /> </StackPanel> </Page>
In this sample, media is played automatically as soon as it is loaded. Once the media has finished playing, the media is closed and all media resources are release (including video memory). This is the default behavior of the MediaElement object and is controlled by the LoadedBehavior and UnloadedBehavior properties.
Controlling a MediaElement
The LoadedBehavior and UnloadedBehavior properties control the behavior of the MediaElement when IsLoaded is true or false, respectively. The MediaState the properties are set to affect the media playback behavior. For example, the default LoadedBehavior is Play and the default UnloadedBehavior is Close. This means that as soon as the MediaElement is loaded and the preroll is complete, the media begins to play. Once playback is complete, media is closed and all media resources are released.
The LoadedBehavior and UnloadedBehavior properties are not the only way to control media playback. In clock mode, the clock can control the MediaElement and the interactive control methods have control when the LoadedBehavior is Manual. MediaElement handles this competition for control by evaluating the following priorities.
UnloadedBehavior. In place when media is unloaded. This ensures that all media resources are released by default, even when a MediaClock is associated with the MediaElement.
MediaClock. In place when media has a Clock. If media is unloaded, the MediaClock will take effect as long as the UnloadedBehavior is Manual. Clock mode always overrides the loaded behavior of the MediaElement.
LoadedBehavior. In place when media is loaded.
Interactive control methods. In place when LoadedBehavior is Manual. The control methods available are Play, Pause, Close, and Stop.
Displaying a MediaElement
To display a MediaElement it must have content to render and it will have its ActualWidth and ActualHeight properties set to zero until content is loaded. For audio only content, these properties are always zero. For video content, once the MediaOpened event has been raised the ActualWidth and ActualHeight will report the size of the loaded media. This means that until media is loaded, the MediaElement will not take up any physical space in the user interface (UI) unless the Width or Height properties are set.
Setting both the Width and Height properties will cause the media to stretch to fill the area provided for the MediaElement. To preserve the media's original aspect ratio, either the Width or Height property should be set but not both. Setting both the Width and Height properties will cause the media to present in a fixed element size that may not be desirable.
To avoid having a fixed size element which, Windows Presentation Foundation (WPF) can preroll the media. This is done by setting the LoadedBehavior to either Play or Pause. In a Pause state, the media will preroll and will present the first frame. In a Play state, the media will preroll and begin to play.
MediaPlayer Class
Where as the MediaElement class is a framework element, the MediaPlayer class is designed to be used in Drawing objects. Drawing objects are used when you can sacrifice framework level features to gain performance benefits or when you need Freezable features. MediaPlayer enables you to take advantage of these features while providing media content in your applications. Like MediaElement, MediaPlayer can be used in independent or clock mode but does not have the MediaElement object's unloaded and loaded states. This reduces the playback control complexity of the MediaPlayer.
Controlling MediaPlayer
Because MediaPlayer is stateless, there are only two ways to control media playback.
Interactive control methods. In place when in independent mode (null Clock property).
MediaClock. In place when media has a Clock.
Displaying a MediaPlayer
Technically, a MediaPlayer cannot be displayed since it has no physical representation. However, it can be used to present media in a Drawing using the VideoDrawing class. The following example demonstrates the use of a VideoDrawing to display media.
// // Create a VideoDrawing. // MediaPlayer player = new MediaPlayer(); player.Open(new Uri(@"sampleMedia\xbox.wmv", UriKind.Relative)); VideoDrawing aVideoDrawing = new VideoDrawing(); aVideoDrawing.Rect = new Rect(0, 0, 100, 100); aVideoDrawing.Player = player; // Play the video once. player.Play();
See the Drawing Objects Overview for more information about Drawing objects.
Audio and Video How-to Topics
How to: Control a MediaElement (Play, Pause, Stop, Volume, and Speed)
The following example shows how to control playback of media using a MediaElement. The example creates a simple media player that allows you to play, pause, stop, and skip back and forth in the media as well as adjust the volume and speed ratio.
Example
The code below creates the UI.
![]() |
---|
The LoadedBehavior property of MediaElement must be set to Manual in order to be able to interactively stop, pause, and play the media. |
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.MediaElementExample" > <StackPanel Background="Black"> <!-- To interactively stop, pause, and play the media, the LoadedBehavior property of the MediaElement must be set to "Manual". --> <MediaElement Source="media\numbers.wmv" Name="myMediaElement" Width="450" Height="250" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="Fill" MediaOpened="Element_MediaOpened" MediaEnded="Element_MediaEnded"/> <StackPanel HorizontalAlignment="Center" Width="450" Orientation="Horizontal"> <!-- Play button. --> <Image Source="images\UI_play.gif" MouseDown="OnMouseDownPlayMedia" Margin="5" /> <!-- Pause button. --> <Image Source="images\UI_pause.gif" MouseDown="OnMouseDownPauseMedia" Margin="5" /> <!-- Stop button. --> <Image Source="images\UI_stop.gif" MouseDown="OnMouseDownStopMedia" Margin="5" /> <!-- Volume slider. This slider allows a Volume range between 0 and 1. --> <TextBlock Foreground="White" VerticalAlignment="Center" Margin="5" >Volume</TextBlock> <Slider Name="volumeSlider" VerticalAlignment="Center" ValueChanged="ChangeMediaVolume" Minimum="0" Maximum="1" Value="0.5" Width="70"/> <!-- Volume slider. This slider allows you to change the speed of the media playback. --> <TextBlock Foreground="White" Margin="5" VerticalAlignment="Center">Speed</TextBlock> <Slider Name="speedRatioSlider" VerticalAlignment="Center" ValueChanged="ChangeMediaSpeedRatio" Value="1" Width="70" /> <!-- Seek to slider. Ths slider allows you to jump to different parts of the media playback. --> <TextBlock Foreground="White" Margin="5" VerticalAlignment="Center">Seek To</TextBlock> <Slider Name="timelineSlider" Margin="5" ValueChanged="SeekToMediaPosition" Width="70"/> </StackPanel> </StackPanel> </Page>
Example
The code below implements the functionality of the sample UI controls. The Play, Pause, and Stop methods are used to respectively play, pause and stop the media. Changing the Position property of the MediaElement allows you to skip around in the media. Finally, the Volume and SpeedRatio properties are used to adjust the volume and playback speed of the media.
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Data; using System.Windows.Media; using System.Windows.Input; namespace SDKSample { public partial class MediaElementExample : Page { // Play the media. void OnMouseDownPlayMedia(object sender, MouseButtonEventArgs args) { // The Play method will begin the media if it is not currently active or // resume media if it is paused. This has no effect if the media is // already running. myMediaElement.Play(); // Initialize the MediaElement property values. InitializePropertyValues(); } // Pause the media. void OnMouseDownPauseMedia(object sender, MouseButtonEventArgs args) { // The Pause method pauses the media if it is currently running. // The Play method can be used to resume. myMediaElement.Pause(); } // Stop the media. void OnMouseDownStopMedia(object sender, MouseButtonEventArgs args) { // The Stop method stops and resets the media to be played from // the beginning. myMediaElement.Stop(); } // Change the volume of the media. private void ChangeMediaVolume(object sender, RoutedPropertyChangedEventArgs<double> args) { myMediaElement.Volume = (double)volumeSlider.Value; } // Change the speed of the media. private void ChangeMediaSpeedRatio(object sender, RoutedPropertyChangedEventArgs<double> args) { myMediaElement.SpeedRatio = (double)speedRatioSlider.Value; } // When the media opens, initialize the "Seek To" slider maximum value // to the total number of miliseconds in the length of the media clip. private void Element_MediaOpened(object sender, EventArgs e) { timelineSlider.Maximum = myMediaElement.NaturalDuration.TimeSpan.TotalMilliseconds; } // When the media playback is finished. Stop() the media to seek to media start. private void Element_MediaEnded(object sender, EventArgs e) { myMediaElement.Stop(); } // Jump to different parts of the media (seek to). private void SeekToMediaPosition(object sender, RoutedPropertyChangedEventArgs<double> args) { int SliderValue = (int)timelineSlider.Value; // Overloaded constructor takes the arguments days, hours, minutes, seconds, miniseconds. // Create a TimeSpan with miliseconds equal to the slider value. TimeSpan ts = new TimeSpan(0, 0, 0, 0, SliderValue); myMediaElement.Position = ts; } void InitializePropertyValues() { // Set the media's starting Volume and SpeedRatio to the current value of the // their respective slider controls. myMediaElement.Volume = (double)volumeSlider.Value; myMediaElement.SpeedRatio = (double)speedRatioSlider.Value; } } }
How to: Control a MediaElement by Using a Storyboard
This example shows how to control a MediaElement by using a MediaTimeline in a Storyboard.
Example
When you use a MediaTimeline in a Storyboard to control the timing of a MediaElement, the functionality is identical to the functionality of other Timeline objects, such as animations. For example, a MediaTimeline uses Timeline properties like the BeginTime property to specify when to start a MediaElement (start media playback). It also uses the Duration property to specify how long the MediaElement is active (duration of media playback). For more information about using Timeline objects with a Storyboard, see Storyboards Overview.
This example shows how to create a simple media player that uses a MediaTimeline to control playback. The media player includes play, pause, resume, and stop buttons. The player also has a Slider control that acts as a progress bar.
The following example creates the user interface (UI) for the media player.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SDKSample.MediaTimelineExample" > <StackPanel Background="Black"> <MediaElement Name="myMediaElement" MediaOpened="Element_MediaOpened" Width="260" Height="150" Stretch="Fill" /> <!-- Button controls for play, pause, resume, and stop. --> <StackPanel HorizontalAlignment="Center" Width="260" Orientation="Horizontal"> <Image Name="PlayButton" Source="images\UI_play.gif" Margin="30,10,10,10" /> <Image Name="PauseButton" Source="images\UI_pause.gif" Margin="10" /> <Image Name="ResumeButton" Source="images\UI_resume.gif" Margin="10" /> <Image Name="StopButton" Source="images\UI_stop.gif" Margin="10" /> </StackPanel> <!-- Ths slider shows the progress of the media. --> <Slider Name="timelineSlider" Margin="5" Width="250" HorizontalAlignment="Center"/> <StackPanel.Triggers> <EventTrigger RoutedEvent="Image.MouseDown" SourceName="PlayButton"> <EventTrigger.Actions> <BeginStoryboard Name= "myBegin"> <Storyboard SlipBehavior="Slip"> <!-- The MediaTimeline controls the timing of the video and acts like other Timeline objects. For example, although the video clip (numbers.wmv) lasts longer, playback ends after six seconds because that is the duration of the MediaTimeline (Duration="0:0:6"). --> <MediaTimeline Source="media\numbers.wmv" Storyboard.TargetName="myMediaElement" BeginTime="0:0:0" Duration="0:0:6" CurrentTimeInvalidated="MediaTimeChanged" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <!-- These triggers impliment the functionality of the Pause, Resume and Stop buttons.--> <EventTrigger RoutedEvent="Image.MouseDown" SourceName="PauseButton"> <EventTrigger.Actions> <PauseStoryboard BeginStoryboardName="myBegin" /> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Image.MouseDown" SourceName="ResumeButton"> <EventTrigger.Actions> <ResumeStoryboard BeginStoryboardName="myBegin" /> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Image.MouseDown" SourceName="StopButton"> <EventTrigger.Actions> <StopStoryboard BeginStoryboardName="myBegin" /> </EventTrigger.Actions> </EventTrigger> </StackPanel.Triggers> </StackPanel> </Page>
The following example creates the functionality for the progress bar.
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace SDKSample { public partial class MediaTimelineExample : Page { // When the media opens, initialize the "Seek To" slider maximum value // to the total number of miliseconds in the length of the media clip. private void Element_MediaOpened(object sender, EventArgs e) { timelineSlider.Maximum = myMediaElement.NaturalDuration.TimeSpan.TotalMilliseconds; } private void MediaTimeChanged(object sender, EventArgs e) { timelineSlider.Value = myMediaElement.Position.TotalMilliseconds; } } }
How to: Trigger Media Playback with a User Event
This example shows how to synchronize media playback with an event.
Example
The following example uses the MediaElement control and the MediaTimeline class to play a sound that occurs when the user clicks a Button.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <StackPanel> <!-- The MediaElement control plays the sound. --> <MediaElement Name="myMediaElement" /> <Button>Click to Hear a Sound! <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <!-- Sound wave from this source is played when the button is clicked.--> <MediaTimeline Source="C:\WINDOWS\Media\ringin.wav" Storyboard.TargetName="myMediaElement" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Button.Triggers> </Button> </StackPanel> </Page>
How to: Repeat Media Playback
This example shows how to playback media indefinitely, that is, to set media so that it plays in an infinite loop.
Example
The following example uses MediaElement and MediaTimeline in a Storyboard to play a media clip in an infinite loop.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <StackPanel> <!-- The MediaElement control plays the sound. --> <MediaElement Name="myMediaElement" > <MediaElement.Triggers> <EventTrigger RoutedEvent="MediaElement.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <!-- The MediaTimeline has a RepeatBehavior="Forever" which makes the media play over and over indefinitely.--> <MediaTimeline Source="media\tada.wav" Storyboard.TargetName="myMediaElement" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </MediaElement.Triggers> </MediaElement> </StackPanel> </Page>
How to: Play Media with Animations
This example shows how to play media and animations at the same time by using the MediaTimeline and DoubleAnimationUsingKeyFrames classes in the same Storyboard.
Example
You can use one or more MediaTimeline objects in a Storyboard together with other Timeline objects, such as animations.
The following example sets the SlipBehavior property of the Storyboard to a value of Slip, which specifies that the animation does not progress until the media (video in this example) progresses. This functionality might be needed if media playback is delayed because of loading time.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Canvas> <!-- Upper right hand Canvas contains the animations. --> <Border BorderBrush="Black" BorderThickness="1" Canvas.Left="250"> <Canvas Width="250" Height="250" Background="White" > <!-- The two Path elements below create the purple and gold rings which are animated while the media is played. --> <Path Stroke="Purple" StrokeThickness="5"> <Path.Data> <EllipseGeometry x:Name="MyEllipseGeometry" Center="125,125" RadiusX="15" RadiusY="10" /> </Path.Data> </Path> <Path Stroke="Gold" StrokeThickness="5"> <Path.Data> <EllipseGeometry x:Name="MyEllipseGeometry2" Center="125,125" RadiusX="10" RadiusY="15" /> </Path.Data> </Path> </Canvas> </Border> <!-- Upper left hand Canvas contains the video. --> <Canvas Width="250" Height="250" Background="Green"> <MediaElement Name="myvideo" Width="250" Height="250" Canvas.Left="0" Canvas.Top="0"> <MediaElement.Triggers> <EventTrigger RoutedEvent="MediaElement.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <!-- This Storyboard contains both media (video in this example) and animations. Note the SlipBehavior value of "Slip" specifies that the animation does not progress until the media progresses. This might be desirable if media playback is delayed because of loading time. --> <Storyboard SlipBehavior="Slip"> <!-- The MediaTimeline controls the timing of the video and acts like other Timeline objects. For example, although the video clip (numbers.wmv) lasts longer, playback ends after six seconds because that is the duration of the MediaTimeline (Duration="0:0:6"). --> <MediaTimeline Source="media\numbers.wmv" BeginTime="0:0:0" Duration="0:0:10"/> <!-- The animations below animate the ellipses in the right hand pane. These animations are timed to correspond to the counting in the video. --> <!-- Animate the RadiusY property of the purple ellipse. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyEllipseGeometry" Storyboard.TargetProperty="RadiusY" RepeatBehavior="10x"> <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="80" KeyTime="0:0:0.4" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:1" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> <!-- Animate the RadiusX property of the gold ellipse. --> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyEllipseGeometry2" Storyboard.TargetProperty="RadiusX" RepeatBehavior="10x"> <DoubleAnimationUsingKeyFrames.KeyFrames> <LinearDoubleKeyFrame Value="80" KeyTime="0:0:0.4" /> <SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:1" /> </DoubleAnimationUsingKeyFrames.KeyFrames> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </MediaElement.Triggers> </MediaElement> </Canvas> </Canvas> </Page>
How to: Use Transforms on a MediaElement
This example shows how to use a RotateTransform on a MediaElement.
Example
In the following markup, the MediaElement is rotated using a RotateTransform.
<MediaElement Source="media/numbers-aud.wmv"> <MediaElement.LayoutTransform> <TransformGroup> <RotateTransform Angle="305" /> </TransformGroup> </MediaElement.LayoutTransform> </MediaElement>
Visual Layer Programming
Hit Testing in the Visual Layer
https://msdn.microsoft.com/en-us/library/ms752097(v=vs.110).aspx
This topic provides an overview of hit testing functionality provided by the visual layer. Hit testing support allows you to determine whether a geometry or point value falls within the rendered content of a Visual, allowing you to implement user interface behavior such as a selection rectangle to select multiple objects.
Hit Testing Scenarios
The UIElement class provides the InputHitTest method, which allows you to hit test against an element using a given coordinate value. In many cases, the InputHitTest method provides the desired functionality for implementing hit testing of elements. However, there are several scenarios in which you may need to implement hit testing at the visual layer.
Hit testing against non-UIElement objects: This applies if you are hit testing non-UIElement objects, such as DrawingVisual or graphics objects.
Hit testing using a geometry: This applies if you need to hit test using a geometry object rather than the coordinate value of a point.
Hit testing against multiple objects: This applies when you need to hit test against multiple objects, such as overlapping objects. You can get results for all visuals intersecting a geometry or point, not just the first one.
Ignoring UIElement hit testing policy: This applies when you need to ignore the UIElement hit testing policy, which takes into consideration such factors as whether an element is disabled or invisible.
![]() |
---|
For a complete code sample illustrating hit testing at the visual layer, see Hit Test Using DrawingVisuals Sample and Hit Test with Win32 Interoperation Sample. |
Hit Testing Support
The purpose of the HitTest methods in the VisualTreeHelper class is to determine whether a geometry or point coordinate value is within the rendered content of a given object, such as a control or graphic element. For example, you could use hit testing to determine whether a mouse click within the bounding rectangle of an object falls within the geometry of a circle. You can also choose to override the default implementation of hit testing to perform your own custom hit test calculations.
The following illustration shows the relationship between a non-rectangular object's region and its bounding rectangle.

Diagram of valid hit test region
Hit Testing and Z-Order
The Windows Presentation Foundation (WPF) visual layer supports hit testing against all objects under a point or geometry, not just the top-most object. Results are returned in z-order. However, the visual object that you pass as the parameter to the HitTest method determines which portion of the visual tree that will be hit test. You can hit test against the entire visual tree, or any portion of it.
In the following illustration, the circle object is on top of both the square and triangle objects. If you are only interested in hit testing the visual object whose z-order value is top-most, you can set the visual hit test enumeration to return Stop from the HitTestResultCallback to stop the hit test traversal after the first item.

Diagram of the z-order of a visual tree
If you want to enumerate all visual objects under a specific point or geometry, return Continue from the HitTestResultCallback. This means you can hit test for visual objects that are beneath other objects, even if they are wholly obscured. See the sample code in the section "Using a Hit Test Results Callback" for more information.
![]() |
---|
A visual object that is transparent can also be hit test. |
Using Default Hit Testing
You can identify whether a point is within the geometry of a visual object, by using the HitTest method to specify a visual object and a point coordinate value to test against. The visual object parameter identifies the starting point in the visual tree for the hit test search. If a visual object is found in the visual tree whose geometry contains the coordinate, it is set to the VisualHit property of a HitTestResult object. The HitTestResult is then returned from the HitTest method. If the point is not contained with the visual sub-tree you are hit testing, HitTest returns null.
![]() |
---|
Default hit testing always returns the top-most object in the z-order. In order to identify all visual objects, even those that may be partly or wholly obscured, use a hit test result callback. |
The coordinate value you pass as the point parameter for the HitTest method has to be relative to the coordinate space of the visual object you are hit testing against. For example, if you have nested visual objects defined at (100, 100) in the parent's coordinate space, then hit testing a child visual at (0, 0) is equivalent to hit testing at (100, 100) in the parent's coordinate space.
The following code shows how to set up mouse event handlers for a UIElement object that is used to capture events used for hit testing.
// Respond to the left mouse button down event by initiating the hit test. private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // Retrieve the coordinate of the mouse position. Point pt = e.GetPosition((UIElement)sender); // Perform the hit test against a given portion of the visual object tree. HitTestResult result = VisualTreeHelper.HitTest(myCanvas, pt); if (result != null) { // Perform action on hit visual object. } }
How the Visual Tree Affects Hit Testing
The starting point in the visual tree determines which objects are returned during the hit test enumeration of objects. If you have multiple objects you want to hit test, the visual object used as the starting point in the visual tree must be the common ancestor of all objects of interest. For example, if you were interested in hit testing both the button element and drawing visual in the following diagram, you would have to set the starting point in the visual tree to the common ancestor of both. In this case, the canvas element is the common ancestor of both the button element and the drawing visual.

Diagram of a visual tree hierarchy
![]() |
---|
The IsHitTestVisible property gets or sets a value that declares whether a UIElement-derived object can possibly be returned as a hit test result from some portion of its rendered content. This allows you to selectively alter the visual tree to determine which visual objects are involved in a hit test. |
Using a Hit Test Result Callback
You can enumerate all visual objects in a visual tree whose geometry contains a specified coordinate value. This allows you to identify all visual objects, even those that may be partly or wholly obscured by other visual objects. To enumerate visual objects in a visual tree use the HitTest method with a hit test callback function. The hit test callback function is called by the system when the coordinate value you specify is contained in a visual object.
During the hit test results enumeration, you should not perform any operation that modifies the visual tree. Adding or removing an object from the visual tree while it is being traversed can result in unpredictable behavior. You can safely modify the visual tree after the HitTest method returns. You may want to provide a data structure, such as an ArrayList, to store values during the hit test results enumeration.
// Respond to the right mouse button down event by setting up a hit test results callback. private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e) { // Retrieve the coordinate of the mouse position. Point pt = e.GetPosition((UIElement)sender); // Clear the contents of the list used for hit test results. hitResultsList.Clear(); // Set up a callback to receive the hit test result enumeration. VisualTreeHelper.HitTest(myCanvas, null, new HitTestResultCallback(MyHitTestResult), new PointHitTestParameters(pt)); // Perform actions on the hit test results list. if (hitResultsList.Count > 0) { Console.WriteLine("Number of Visuals Hit: " + hitResultsList.Count); } }
The hit test callback method defines the actions you perform when a hit test is identified on a particular visual object in the visual tree. After you perform the actions, you return a HitTestResultBehavior value that determines whether to continue the enumeration of any other visual objects or not.
// Return the result of the hit test to the callback. public HitTestResultBehavior MyHitTestResult(HitTestResult result) { // Add the hit test result to the list that will be processed after the enumeration. hitResultsList.Add(result.VisualHit); // Set the behavior to return visuals at all z-order levels. return HitTestResultBehavior.Continue; }
You can stop the enumeration of visual objects at any time in the hit test callback function by returning Stop.
Using a Hit Test Filter Callback
You can use an optional hit test filter to restrict the objects that are passed on to the hit test results. This allows you to ignore parts of the visual tree that you are not interested in processing in your hit test results. To implement a hit test filter, you define a hit test filter callback function and pass it as a parameter value when you call the HitTest method.
// Respond to the mouse wheel event by setting up a hit test filter and results enumeration. private void OnMouseWheel(object sender, MouseWheelEventArgs e) { // Retrieve the coordinate of the mouse position. Point pt = e.GetPosition((UIElement)sender); // Clear the contents of the list used for hit test results. hitResultsList.Clear(); // Set up a callback to receive the hit test result enumeration. VisualTreeHelper.HitTest(myCanvas, new HitTestFilterCallback(MyHitTestFilter), new HitTestResultCallback(MyHitTestResult), new PointHitTestParameters(pt)); // Perform actions on the hit test results list. if (hitResultsList.Count > 0) { ProcessHitTestResultsList(); } }
If you do not want to supply the optional hit test filter callback function, pass a null value as its parameter for the HitTest method.
// Set up a callback to receive the hit test result enumeration, // but no hit test filter enumeration. VisualTreeHelper.HitTest(myCanvas, null, // No hit test filtering. new HitTestResultCallback(MyHitTestResult), new PointHitTestParameters(pt));

Pruning a visual tree
The hit test filter callback function allows you to enumerate through all the visuals whose rendered content contains the coordinates you specify. However, you may want to ignore certain branches of the visual tree that you are not interested in processing in your hit test results callback function. The return value of the hit test filter callback function determines what type of action the enumeration of the visual objects should take. For example, if you return the value, ContinueSkipSelfAndChildren, you can remove the current visual object and its children from the hit test results enumeration. This means that the hit test results callback function will not see these objects in its enumeration. Pruning the visual tree of objects decreases the amount of processing during the hit test results enumeration pass. In the following code example, the filter skips labels and their descendants and hit tests everything else.
// Filter the hit test values for each object in the enumeration. public HitTestFilterBehavior MyHitTestFilter(DependencyObject o) { // Test for the object value you want to filter. if (o.GetType() == typeof(Label)) { // Visual object and descendants are NOT part of hit test results enumeration. return HitTestFilterBehavior.ContinueSkipSelfAndChildren; } else { // Visual object is part of hit test results enumeration. return HitTestFilterBehavior.Continue; } }
![]() |
---|
The hit test filter callback will sometimes be called in cases where the hit test results callback is not called. |
Overriding Default Hit Testing
You can override a visual object’s default hit testing support by overriding the HitTestCore method. This means that when you invoke the HitTest method, your overridden implementation of HitTestCore is called. Your overridden method is called when a hit test falls within the bounding rectangle of the visual object, even if the coordinate falls outside the rendered content of the visual object.
// Override default hit test support in visual object. protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { Point pt = hitTestParameters.HitPoint; // Perform custom actions during the hit test processing, // which may include verifying that the point actually // falls within the rendered content of the visual. // Return hit on bounding rectangle of visual object. return new PointHitTestResult(this, pt); }
There may be times when you want to hit test against both the bounding rectangle and the rendered content of a visual object. By using the PointHitTestParameters parameter value in your overridden HitTestCore method as the parameter to the base method HitTestCore, you can perform actions based on a hit of the bounding rectangle of a visual object, and then perform a second hit test against the rendered content of the visual object.
// Override default hit test support in visual object. protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { // Perform actions based on hit test of bounding rectangle. // ... // Return results of base class hit testing, // which only returns hit on the geometry of visual objects. return base.HitTestCore(hitTestParameters); }
Using DrawingVisual Objects
This topic provides an overview of how to use DrawingVisual objects in the WPF visual layer.
This topic contains the following sections.
DrawingVisual Object
The DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art.
DrawingVisual Host Container
In order to use DrawingVisual objects, you need to create a host container for the objects. The host container object must derive from the FrameworkElement class, which provides the layout and event handling support that the DrawingVisual class lacks. The host container object does not display any visible properties, since its main purpose is to contain child objects. However, the Visibility property of the host container must be set to Visible; otherwise, none of its child elements will be visible.
When you create a host container object for visual objects, you need to store the visual object references in a VisualCollection. Use the Add method to add a visual object to the host container. In the following example, a host container object is created, and three visual objects are added to its VisualCollection.
// Create a host visual derived from the FrameworkElement class. // This class provides layout, event handling, and container support for // the child visual objects. public class MyVisualHost : FrameworkElement { // Create a collection of child visual objects. private VisualCollection _children; public MyVisualHost() { _children = new VisualCollection(this); _children.Add(CreateDrawingVisualRectangle()); _children.Add(CreateDrawingVisualText()); _children.Add(CreateDrawingVisualEllipses()); // Add the event handler for MouseLeftButtonUp. this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp); }
![]() |
---|
For the complete code sample from which the preceding code example was extracted, see Hit Test Using DrawingVisuals Sample. |
Creating DrawingVisual Objects
When you create a DrawingVisual object, it has no drawing content. You can add text, graphics, or image content by retrieving the object's DrawingContext and drawing into it. A DrawingContext is returned by calling the RenderOpen method of a DrawingVisual object.
To draw a rectangle into the DrawingContext, use the DrawRectangle method of the DrawingContext object. Similar methods exist for drawing other types of content. When you are finished drawing content into the DrawingContext, call the Close method to close the DrawingContext and persist the content.
In the following example, a DrawingVisual object is created, and a rectangle is drawn into its DrawingContext.
// Create a DrawingVisual that contains a rectangle. private DrawingVisual CreateDrawingVisualRectangle() { DrawingVisual drawingVisual = new DrawingVisual(); // Retrieve the DrawingContext in order to create new drawing content. DrawingContext drawingContext = drawingVisual.RenderOpen(); // Create a rectangle and draw it in the DrawingContext. Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80)); drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect); // Persist the drawing content. drawingContext.Close(); return drawingVisual; }
Creating Overrides for FrameworkElement Members
The host container object is responsible for managing its collection of visual objects. This requires that the host container implement member overrides for the derived FrameworkElement class.
The following list describes the two members you must override:
GetVisualChild: Returns a child at the specified index from the collection of child elements.
VisualChildrenCount: Gets the number of visual child elements within this element.
In the following example, overrides for the two FrameworkElement members are implemented.
// Provide a required override for the VisualChildrenCount property. protected override int VisualChildrenCount { get { return _children.Count; } } // Provide a required override for the GetVisualChild method. protected override Visual GetVisualChild(int index) { if (index < 0 || index >= _children.Count) { throw new ArgumentOutOfRangeException(); } return _children[index]; }
Providing Hit Testing Support
The host container object can provide event handling even if it does not display any visible properties—however, its Visibility property must be set to Visible. This allows you to create an event handling routine for the host container that can trap mouse events, such as the release of the left mouse button. The event handling routine can then implement hit testing by invoking the HitTest method. The method's HitTestResultCallback parameter refers to a user-defined procedure that you can use to determine the resulting action of a hit test.
In the following example, hit testing support is implemented for the host container object and its children.
// Capture the mouse event and hit test the coordinate point value against // the child visual objects. void MyVisualHost_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { // Retreive the coordinates of the mouse button event. System.Windows.Point pt = e.GetPosition((UIElement)sender); // Initiate the hit test by setting up a hit test result callback method. VisualTreeHelper.HitTest(this, null, new HitTestResultCallback(myCallback), new PointHitTestParameters(pt)); } // If a child visual object is hit, toggle its opacity to visually indicate a hit. public HitTestResultBehavior myCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { if (((DrawingVisual)result.VisualHit).Opacity == 1.0) { ((DrawingVisual)result.VisualHit).Opacity = 0.4; } else { ((DrawingVisual)result.VisualHit).Opacity = 1.0; } } // Stop the hit test enumeration of objects in the visual tree. return HitTestResultBehavior.Stop; }
Tutorial: Hosting Visual Objects in a Win32 Application
Windows Presentation Foundation (WPF) provides a rich environment for creating applications. However, when you have a substantial investment in Win32 code, it might be more effective to add WPF functionality to your application rather than rewrite your code. To provide support for Win32 and WPF graphics subsystems used concurrently in an application, WPF provides a mechanism for hosting objects in a Win32 window.
This tutorial describes how to write a sample application, Hit Test with Win32 Interoperation Sample, that hosts WPF visual objects in a Win32 window.
Requirements
This tutorial assumes a basic familiarity with both WPF and Win32 programming. For a basic introduction to WPF programming, see Walkthrough: My First WPF Desktop Application1. For an introduction to Win32 programming, see any of the numerous books on the subject, in particularProgramming Windows by Charles Petzold.
![]() |
---|
This tutorial includes a number of code examples from the associated sample. However, for readability, it does not include the complete sample code. For the complete sample code, see Hit Test with Win32 Interoperation Sample. |
Creating the Host Win32 Window
The key to hosting WPF objects in a Win32 window is the HwndSource class. This class wraps the WPF objects in a Win32 window, allowing them to be incorporated into your user interface (UI) as a child window.
The following example shows the code for creating the HwndSource object as the Win32 container window for the visual objects. To set the window style, position, and other parameters for the Win32 window, use the HwndSourceParameters object.
// Constant values from the "winuser.h" header file. internal const int WS_CHILD = 0x40000000, WS_VISIBLE = 0x10000000; internal static void CreateHostHwnd(IntPtr parentHwnd) { // Set up the parameters for the host hwnd. HwndSourceParameters parameters = new HwndSourceParameters("Visual Hit Test", _width, _height); parameters.WindowStyle = WS_VISIBLE | WS_CHILD; parameters.SetPosition(0, 24); parameters.ParentWindow = parentHwnd; parameters.HwndSourceHook = new HwndSourceHook(ApplicationMessageFilter); // Create the host hwnd for the visuals. myHwndSource = new HwndSource(parameters); // Set the hwnd background color to the form's background color. myHwndSource.CompositionTarget.BackgroundColor = System.Windows.Media.Brushes.OldLace.Color; }
![]() |
---|
The value of the ExtendedWindowStyle property cannot be set to WS_EX_TRANSPARENT. This means that the host Win32 window cannot be transparent. For this reason, the background color of the host Win32 window is set to the same background color as its parent window. |
Adding Visual Objects to the Host Win32 Window
Once you have created a host Win32 container window for the visual objects, you can add visual objects to it. You will want to ensure that any transformations of the visual objects, such as animations, do not extend beyond the bounds of the host Win32 window's bounding rectangle.
The following example shows the code for creating the HwndSource object and adding visual objects to it.
![]() |
---|
The RootVisual property of the HwndSource object is set to the first visual object added to the host Win32 window. The root visual object defines the top-most node of the visual object tree. Any subsequent visual objects added to the host Win32 window are added as child objects. |
public static void CreateShape(IntPtr parentHwnd) { // Create an instance of the shape. MyShape myShape = new MyShape(); // Determine whether the host container window has been created. if (myHwndSource == null) { // Create the host container window for the visual objects. CreateHostHwnd(parentHwnd); // Associate the shape with the host container window. myHwndSource.RootVisual = myShape; } else { // Assign the shape as a child of the root visual. ((ContainerVisual)myHwndSource.RootVisual).Children.Add(myShape); } }
Implementing the Win32 Message Filter
The host Win32 window for the visual objects requires a window message filter procedure to handle messages that are sent to the window from the application queue. The window procedure receives messages from the Win32 system. These may be input messages or window-management messages. You can optionally handle a message in your window procedure or pass the message to the system for default processing.
The HwndSource object that you defined as the parent for the visual objects must reference the window message filter procedure you provide. When you create the HwndSource object, set the HwndSourceHook property to reference the window procedure.
The following example shows the code for handling the left and right mouse button up messages. The coordinate value of the mouse hit position is contained in the value of the lParam parameter.
// Constant values from the "winuser.h" header file. internal const int WM_LBUTTONUP = 0x0202, WM_RBUTTONUP = 0x0205; internal static IntPtr ApplicationMessageFilter( IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) { // Handle messages passed to the visual. switch (message) { // Handle the left and right mouse button up messages. case WM_LBUTTONUP: case WM_RBUTTONUP: System.Windows.Point pt = new System.Windows.Point(); pt.X = (uint)lParam & (uint)0x0000ffff; // LOWORD = x pt.Y = (uint)lParam >> 16; // HIWORD = y MyShape.OnHitTest(pt, message); break; } return IntPtr.Zero; }
Processing the Win32 Messages
The code in the following example shows how a hit test is performed against the hierarchy of visual objects contained in the host Win32 window. You can identify whether a point is within the geometry of a visual object, by using the HitTest method to specify the root visual object and the coordinate value to hit test against. In this case, the root visual object is the value of the RootVisual property of the HwndSource object.
// Constant values from the "winuser.h" header file. public const int WM_LBUTTONUP = 0x0202, WM_RBUTTONUP = 0x0205; // Respond to WM_LBUTTONUP or WM_RBUTTONUP messages by determining which visual object was clicked. public static void OnHitTest(System.Windows.Point pt, int msg) { // Clear the contents of the list used for hit test results. hitResultsList.Clear(); // Determine whether to change the color of the circle or to delete the shape. if (msg == WM_LBUTTONUP) { MyWindow.changeColor = true; } if (msg == WM_RBUTTONUP) { MyWindow.changeColor = false; } // Set up a callback to receive the hit test results enumeration. VisualTreeHelper.HitTest(MyWindow.myHwndSource.RootVisual, null, new HitTestResultCallback(CircleHitTestResult), new PointHitTestParameters(pt)); // Perform actions on the hit test results list. if (hitResultsList.Count > 0) { ProcessHitTestResultsList(); } }
For more information on hit testing against visual objects, see Hit Testing in the Visual Layer.
How-to Topics
How to: Get the Offset of a Visual
These examples show how to retrieve the offset value of a visual object that is relative to its parent, or any ancestor or descendant.
How to: Enumerate Drawing Content of a Visual
The Drawing object provide an object model for enumerating the contents of a Visual.
Example
The following example uses the GetDrawing method to retrieve the DrawingGroup value of a Visual and enumerate it.
![]() |
---|
When you are enumerating the contents of the visual, you are retrieving Drawing objects, and not the underlying representation of the render data as a vector graphics instruction list. For more information, see WPF Graphics Rendering Overview. |
public void RetrieveDrawing(Visual v) { DrawingGroup dGroup = VisualTreeHelper.GetDrawing(v); EnumDrawingGroup(dGroup); } // Enumerate the drawings in the DrawingGroup. public void EnumDrawingGroup(DrawingGroup drawingGroup) { DrawingCollection dc = drawingGroup.Children; // Enumerate the drawings in the DrawingCollection. foreach (Drawing drawing in dc) { // If the drawing is a DrawingGroup, call the function recursively. if (drawing.GetType() == typeof(DrawingGroup)) { EnumDrawingGroup((DrawingGroup)drawing); } else if (drawing.GetType() == typeof(GeometryDrawing)) { // Perform action based on drawing type. } else if (drawing.GetType() == typeof(ImageDrawing)) { // Perform action based on drawing type. } else if (drawing.GetType() == typeof(GlyphRunDrawing)) { // Perform action based on drawing type. } else if (drawing.GetType() == typeof(VideoDrawing)) { // Perform action based on drawing type. } } }
How to: Hit Test Geometry in a Visual
This example shows how to perform a hit test on a visual object that is composed of one or more Geometry objects.
Example
The following example shows how to retrieve the DrawingGroup from a visual object that uses the GetDrawing method. A hit test is then performed on the rendered content of each drawing in the DrawingGroup to determine which geometry was hit.
![]() |
---|
In most cases, you would use the HitTest method to determine whether a point intersects any of the rendered content of a visual. |
// Determine if a geometry within the visual was hit. static public void HitTestGeometryInVisual(Visual visual, Point pt) { // Retrieve the group of drawings for the visual. DrawingGroup drawingGroup = VisualTreeHelper.GetDrawing(visual); EnumDrawingGroup(drawingGroup, pt); } // Enumerate the drawings in the DrawingGroup. static public void EnumDrawingGroup(DrawingGroup drawingGroup, Point pt) { DrawingCollection drawingCollection = drawingGroup.Children; // Enumerate the drawings in the DrawingCollection. foreach (Drawing drawing in drawingCollection) { // If the drawing is a DrawingGroup, call the function recursively. if (drawing.GetType() == typeof(DrawingGroup)) { EnumDrawingGroup((DrawingGroup)drawing, pt); } else if (drawing.GetType() == typeof(GeometryDrawing)) { // Determine whether the hit test point falls within the geometry. if (((GeometryDrawing)drawing).Geometry.FillContains(pt)) { // Perform action based on hit test on geometry. } } } }
The FillContains method is an overloaded method that allows you to hit test by using a specified Point or Geometry. If a geometry is stroked, the stroke can extend outside the fill bounds. In which case, you may want to call StrokeContains in addition to FillContains.
You can also provide a ToleranceType that is used for the purposes of Bezier flattening.
![]() |
---|
This sample does not take into account any transforms or clipping that may be applied to the geometry. In addition, this sample will not work with a styled control, since it does not have any drawings directly associated with it. |
How to: Hit Test Using Geometry as a Parameter
This example shows how to perform a hit test on a visual object using a Geometry as a hit test parameter.
Example
The following example shows how to set up a hit test using GeometryHitTestParameters for the HitTest method. The Point value that is passed to the OnMouseDown method is used to create a Geometry object in order to expand the range of the hit test.
// Respond to the mouse button down event by setting up a hit test results callback. private void OnMouseDown(object sender, MouseButtonEventArgs e) { // Retrieve the coordinate of the mouse position. Point pt = e.GetPosition((UIElement)sender); // Expand the hit test area by creating a geometry centered on the hit test point. EllipseGeometry expandedHitTestArea = new EllipseGeometry(pt, 10.0, 10.0); // Clear the contents of the list used for hit test results. hitResultsList.Clear(); // Set up a callback to receive the hit test result enumeration. VisualTreeHelper.HitTest(myControl, null, new HitTestResultCallback(MyHitTestResultCallback), new GeometryHitTestParameters(expandedHitTestArea)); // Perform actions on the hit test results list. if (hitResultsList.Count > 0) { ProcessHitTestResultsList(); } }
The IntersectionDetail property of GeometryHitTestResult provides information about the results of a hit test that uses a Geometry as a hit test parameter. The following illustration shows the relationship between the hit test geometry (the blue circle) and the rendered content of the target visual object (the red square).

Intersection between hit test geometry and target visual object
The following example shows how to implement a hit test callback when a Geometry is used as a hit test parameter. The result parameter is cast to a GeometryHitTestResult in order to retrieve the value of the IntersectionDetail property. The property value allows you to determine if the Geometry hit test parameter is fully or partially contained within the rendered content of the hit test target. In this case, the sample code is only adding hit test results to the list for visuals that are fully contained within the target boundary.
// Return the result of the hit test to the callback. public HitTestResultBehavior MyHitTestResultCallback(HitTestResult result) { // Retrieve the results of the hit test. IntersectionDetail intersectionDetail = ((GeometryHitTestResult)result).IntersectionDetail; switch (intersectionDetail) { case IntersectionDetail.FullyContains: // Add the hit test result to the list that will be processed after the enumeration. hitResultsList.Add(result.VisualHit); return HitTestResultBehavior.Continue; case IntersectionDetail.Intersects: // Set the behavior to return visuals at all z-order levels. return HitTestResultBehavior.Continue; case IntersectionDetail.FullyInside: // Set the behavior to return visuals at all z-order levels. return HitTestResultBehavior.Continue; default: return HitTestResultBehavior.Stop; } }
![]() |
---|
The HitTestResult callback should not be called when the intersection detail is Empty. |
How to: Hit Test Using a Win32 Host Container
You can create visual objects within a Win32 window by providing a host window container for the visual objects. To provide event handling for the contained visual objects you process the messages passed to the host window container’s message filter loop. Refer to Tutorial: Hosting Visual Objects in a Win32 Application for more information on how to host visual objects in a Win32 window.
Example
The following code shows how to set up mouse event handlers for a Win32 window that is used as a host container for visual objects.
// Constant values from the "winuser.h" header file. internal const int WM_LBUTTONUP = 0x0202, WM_RBUTTONUP = 0x0205; internal static IntPtr ApplicationMessageFilter( IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) { // Handle messages passed to the visual. switch (message) { // Handle the left and right mouse button up messages. case WM_LBUTTONUP: case WM_RBUTTONUP: System.Windows.Point pt = new System.Windows.Point(); pt.X = (uint)lParam & (uint)0x0000ffff; // LOWORD = x pt.Y = (uint)lParam >> 16; // HIWORD = y MyShape.OnHitTest(pt, message); break; } return IntPtr.Zero; }
The following example shows how to set up a hit test in response to trapping specific mouse events.
// Constant values from the "winuser.h" header file. public const int WM_LBUTTONUP = 0x0202, WM_RBUTTONUP = 0x0205; // Respond to WM_LBUTTONUP or WM_RBUTTONUP messages by determining which visual object was clicked. public static void OnHitTest(System.Windows.Point pt, int msg) { // Clear the contents of the list used for hit test results. hitResultsList.Clear(); // Determine whether to change the color of the circle or to delete the shape. if (msg == WM_LBUTTONUP) { MyWindow.changeColor = true; } if (msg == WM_RBUTTONUP) { MyWindow.changeColor = false; } // Set up a callback to receive the hit test results enumeration. VisualTreeHelper.HitTest(MyWindow.myHwndSource.RootVisual, null, new HitTestResultCallback(CircleHitTestResult), new PointHitTestParameters(pt)); // Perform actions on the hit test results list. if (hitResultsList.Count > 0) { ProcessHitTestResultsList(); } }
The HwndSource object presents Windows Presentation Foundation (WPF) content within a Win32 window. The value of the RootVisual property of theHwndSource object represents the top-most node in the visual tree hierarchy.
For the complete sample on hit testing objects using a Win32 host container, see Hit Test with Win32 Interoperation Sample.
How to: Render on a Per Frame Interval Using CompositionTarget
The WPF animation engine provides many features for creating frame-based animation. However, there are application scenarios in which you need finer-grained control over rendering on a per frame basis. The CompositionTarget object provides the ability to create custom animations based on a per-frame callback.
CompositionTarget is a static class which represents the display surface on which your application is being drawn. The Rendering event is raised each time the application's scene is drawn. The rendering frame rate is the number of times the scene is drawn per second.
![]() |
---|
For a complete code sample using CompositionTarget, see Using the CompositionTarget Sample. |
Example
The Rendering event fires during the WPF rendering process. The following example shows how you register an EventHandler delegate to the static Rendering method on CompositionTarget.
// Add an event handler to update canvas background color just before it is rendered.
CompositionTarget.Rendering += UpdateColor;
You can use your rendering event handler method to create custom drawing content. This event handler method gets called once per frame. Each time that WPF marshals the persisted rendering data in the visual tree across to the composition scene graph, your event handler method is called. In addition, if changes to the visual tree force updates to the composition scene graph, your event handler method is also called. Note that your event handler method is called after layout has been computed. However, you can modify layout in your event handler method, which means that layout will be computed once more before rendering.
The following example shows how you can provide custom drawing in a CompositionTarget event handler method. In this case, the background color of the Canvas is drawn with a color value based on the coordinate position of the mouse. If you move the mouse inside the Canvas, its background color changes. In addition, the average frame rate is calculated, based on the current elapsed time and the total number of rendered frames.
// Called just before frame is rendered to allow custom drawing. protected void UpdateColor(object sender, EventArgs e) { if (_frameCounter++ == 0) { // Starting timing. _stopwatch.Start(); } // Determine frame rate in fps (frames per second). long frameRate = (long)(_frameCounter / this._stopwatch.Elapsed.TotalSeconds); if (frameRate > 0) { // Update elapsed time, number of frames, and frame rate. myStopwatchLabel.Content = _stopwatch.Elapsed.ToString(); myFrameCounterLabel.Content = _frameCounter.ToString(); myFrameRateLabel.Content = frameRate.ToString(); } // Update the background of the canvas by converting MouseMove info to RGB info. byte redColor = (byte)(_pt.X / 3.0); byte blueColor = (byte)(_pt.Y / 2.0); myCanvas.Background = new SolidColorBrush(Color.FromRgb(redColor, 0x0, blueColor)); }
You may discover that your custom drawing runs at different speeds on different computers. This is because your custom drawing is not frame-rate independent. Depending on the system you are running and the workload of that system, the Rendering event may be called a different number of times per second. For information on determining the graphics hardware capability and performance for a device that runs a WPF application, see Graphics Rendering Tiers.
Adding or removing a rendering EventHandler delegate while the event is firing will be delayed until after the event is finished firing. This is consistent with how MulticastDelegate-based events are handled in the Common Language Runtime (CLR). Also note that rendering events are not guaranteed to be called in any particular order. If you have multiple EventHandler delegates that rely on a particular order, you should register a single Rendering event and multiplex the delegates in the correct order yourself.
'프로그래밍 > WPF' 카테고리의 다른 글
Commanding Overview (0) | 2017.02.08 |
---|---|
Properties (0) | 2017.01.24 |
Data Binding (0) | 2017.01.10 |
Graphics and Multimedia - Animation Overview (0) | 2017.01.07 |
Graphics and Multimedia (0) | 2016.11.24 |
The following code example shows how to use the GetOffset method to retrieve the offset of the TextBlock. The offset values are contained within the returned Vector value.
The offset takes into account the Margin value. In this case, X is 4, and Y is 4.
The returned offset value is relative to the parent of the Visual. If you want to return an offset value that is not relative to the parent of a Visual, use the TransformToAncestor method.
Getting the Offset Relative to an Ancestor
The following markup example shows a TextBlock that is nested within two StackPanel objects.
The following illustration shows the results of the markup.
TextBlock nested within two StackPanels
The following code example shows how to use the TransformToAncestor method to retrieve the offset of the TextBlock relative to the containing Window. The offset values are contained within the returned GeneralTransform value.
The offset takes into account the Margin values for all objects within the containing Window. In this case, X is 28 (16 + 8 + 4), and Y is 28.
The returned offset value is relative to the ancestor of the Visual. If you want to return an offset value that is relative to the descendant of a Visual, use the TransformToDescendant method.
Getting the Offset Relative to a Descendant
The following markup example shows a TextBlock that is contained within a StackPanel object.
The following code example shows how to use the TransformToDescendant method to retrieve the offset of the StackPanel relative to its child TextBlock. The offset values are contained within the returned GeneralTransform value.
The offset takes into account the Margin values for all objects. In this case, X is -4, and Y is -4. The offset values are negative values, since the parent object is negatively offset relative to its child object.