|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article shows how to create text annotations over an image, in a WPF application. The technique involves rendering a custom control in the adorner layer of an BackgroundWhen you read the newspaper and scribble a thought on the page, you are creating an annotation. The term "annotation" refers to a note which describes or explains part of another document. The Windows Presentation Foundation has built-in support for document annotations, as described here. It does not, however, provide out-of-the-box support for annotating images. A while back I wrote a blog post about how to annotate an The demo appThis article is accompanied by a demo application, available for download at the top of this page. The demo app allows you to create annotations on two images. It contains explanatory text about how to create, modify, and delete annotations. Here is a screenshot of the demo application, after a few annotations have been created:
Notice the location of the various annotations, relative to entities in the picture. After the Window is made smaller, you will see that the annotations remain "pinned" to those entities:
Even though the dimensions of the The demo app lets the user delete annotations in several ways. If an annotation loses input focus and has no text, it is automatically deleted. Also, aside from the glaringly obvious 'Delete Annotations' button seen above, you can also delete an annotation by right-clicking on it, to pull up a context menu. For example:
LimitationsThe demo app is not a "complete" solution. It does not provide any means of persisting annotations across runs of the application. I did not write annotation persistence code because there are so many different ways that this functionality might be used, that writing my own implementation seemed like a shot in the dark. I did, however, try to write the classes in such a way that it will be straightforward to implement saving and loading of annotations. The demo app also does not provide any fancy UI features like drag-drop of annotations. That might be a useful feature, but I wanted to keep this simple. Drag-drop in WPF is pretty well documented on the Web, so if you need to add that feature you should be able to find some good reference material out there. How it worksThere are four main participants involved, as seen below:
The <ContentControl
x:Class="ImageAnnotationDemo.ImageAnnotationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ImageAnnotationDemo"
x:Name="mainControl"
>
<ContentControl.Resources>
<!-- The template used to create a TextBox
for the user to edit an annotation. -->
<DataTemplate x:Key="EditModeTemplate">
<TextBox
KeyDown="OnTextBoxKeyDown"
Loaded="OnTextBoxLoaded"
LostFocus="OnTextBoxLostFocus"
Style="{DynamicResource STYLE_AnnotationEditor}"
Text="{Binding
ElementName=mainControl,
Path=Content,
UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
<!-- The template used to create a TextBlock
for the user to read an annotation. -->
<DataTemplate x:Key="DisplayModeTemplate">
<Border>
<TextBlock
MouseLeftButtonDown="OnTextBlockMouseLeftButtonDown"
Style="{DynamicResource STYLE_Annotation}"
Text="{Binding ElementName=mainControl, Path=Content}"
>
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem
Header="Delete"
Click="OnDeleteAnnotation"
>
<MenuItem.Icon>
<Image Source="delete.ico" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Border>
</DataTemplate>
<Style TargetType="{x:Type local:ImageAnnotationControl}">
<Style.Triggers>
<!-- Applies the 'edit mode' template
to the Content property. -->
<Trigger Property="IsInEditMode" Value="True">
<Setter
Property="ContentTemplate"
Value="{StaticResource EditModeTemplate}"
/>
</Trigger>
<!-- Applies the 'display mode' template
to the Content property. -->
<Trigger Property="IsInEditMode" Value="False">
<Setter
Property="ContentTemplate"
Value="{StaticResource DisplayModeTemplate}"
/>
</Trigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
When an void InstallAdorner()
{
if (_isDeleted)
return;
_adornerLayer = AdornerLayer.GetAdornerLayer(_image);
_adornerLayer.Add(_adorner);
}
When the void OnImageSizeChanged(object sender, SizeChangedEventArgs e)
{
Point newLocation = this.CalculateEquivalentTextLocation();
_adorner.UpdateTextLocation(newLocation);
}
Point CalculateEquivalentTextLocation()
{
double x = _image.RenderSize.Width * _horizPercent;
double y = _image.RenderSize.Height * _vertPercent;
return new Point(x, y);
}
The private ImageAnnotation(
Point textLocation, Image image,
Style annontationStyle, Style annotationEditorStyle)
{
if (image == null)
throw new ArgumentNullException("image");
_image = image;
this.HookImageEvents(true);
Size imageSize = _image.RenderSize;
if (imageSize.Height == 0 || imageSize.Width == 0)
throw new ArgumentException("image has invalid dimensions");
// Determine the relative location of the TextBlock.
_horizPercent = textLocation.X / imageSize.Width;
_vertPercent = textLocation.Y / imageSize.Height;
// Create the adorner which displays the annotation.
_adorner = new ImageAnnotationAdorner(
this,
_image,
annontationStyle,
annotationEditorStyle,
textLocation);
this.InstallAdorner();
}
The void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image image = sender as Image;
// Get the location of the mouse cursor relative to the Image. Offset the
// location a bit so that the annotation placement feels more natural.
Point textLocation = e.GetPosition(image);
textLocation.Offset(-4, -4);
// Get the Style applied to the annotation's TextBlock.
Style annotationStyle = base.FindResource("AnnotationStyle") as Style;
// Get the Style applied to the annotations's TextBox.
Style annotationEdtiorStyle =
base.FindResource("AnnotationEditorStyle") as Style;
// Create an annotationwhere the mouse cursor is located.
ImageAnnotation imgAnn = ImageAnnotation.Create(
image,
textLocation,
annotationStyle,
annotationEdtiorStyle);
this.CurrentAnnotations.Add(imgAnn);
}
Those two <!-- This is the Style applied to the TextBlock within
an ImageAnnotationControl. -->
<Style x:Key="AnnotationStyle" TargetType="TextBlock">
<Setter Property="Background" Value="#AAFFFFFF" />
<Setter Property="FontWeight" Value="Bold" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#CCFFFFFF" />
</Trigger>
</Style.Triggers>
</Style>
<!-- This is the Style applied to the TextBox within
an ImageAnnotationControl. -->
<Style x:Key="AnnotationEditorStyle" TargetType="TextBox">
<Setter Property="Background" Value="#FFFFFFFF" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Padding" Value="-2,0,-1,0" />
</Style>
Revision history
| ||||||||||||||||||||