Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / WPF

Marching Ants Selection

Rate me:
Please Sign up or sign in to vote.
4.74/5 (18 votes)
15 Jul 2008CPOL3 min read 57.1K   2K   29   7
A WPF implementation of a rectangular marquee selection with marching ants.

Image 1

Introduction

This is a WPF implementation of the famous marching ants selection paradigm which you see almost in every selection-enabled software, like the one in Adobe Photoshop Marquee Selection Tool. I created the interface and animations in Expression Blend 2.0, and used Visual Studio 2008 to code the behavior. Through this simple example, you'll learn some basic concepts about how WPF works and how you manipulate objects created with Blend in code-behind.

Background

Recently, while working with WPF for a software project, I had to implement some kind of selection tool, and no wonder, the first thing came to my mind was to use the famous marching ants, the rectangle with animated dashed strokes. I had done this before with GDI+, but this time, using Blend and VS, I created the same cool effect. Note that using WPF has many advantages over traditional methods, and you get a much richer user interface. For the sake of simplicity, I removed many selection styles like rounded rectangles, blinking ones, color fading ones, and ... originally used in my application, so it's much easier to follow the sample code. But once you get the idea, you can do whatever you want with it.

Using the code

The code consists of some XAML markup and a few lines of C# code that you can use according to your needs. Note that the logic for actually selecting the objects are not implemented as it must be defined in the context of the application you are developing. Probably, the most common way of detecting which objects are selected is the HitTest concept, which in WPF you can find in VisualTreeHelper.HitTest. I'll talk about it later in detail, but for now, my main purpose in this article is to show you how to draw a perfectly animated selection rectangle without any complexity, and note that in real world projects, you should consider turning this code to something much more reusable, like a custom component or something.

XAML

XML
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="MarchingAntsSelection.Window1"
    x:Name="Window"
      Title="Marching Ants Selection"
      Width="563" Height="447" 
      Background="#FF353535" ResizeMode="NoResize">
    <Window.Resources>
        <Storyboard x:Key="MarchingAnts">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                               Storyboard.TargetName="rectangle" 
                               Storyboard.TargetProperty="(Shape.StrokeDashOffset)" 
                               RepeatBehavior="Forever">
                <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                <SplineDoubleKeyFrame KeyTime="00:00:00.5000000"
                               Value="10"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
        </EventTrigger>
    </Window.Triggers>

    <Grid x:Name="LayoutRoot">
        <Canvas x:Name="canvas" Background="#FF262626">
            <Rectangle Fill="#14FFFFFF" StrokeDashArray="5" 
                Stroke="#FFFFFFFF" Visibility="Hidden" 
                x:Name="rectangle" Width="50" Height="50" 
                StrokeDashOffset="0" StrokeThickness="1" 
                RadiusX="0" RadiusY="0"
                Canvas.Left="0" Canvas.Top="0"/>
            <TextBlock Width="Auto" Height="Auto" 
                FontFamily="Century Gothic" 
                FontSize="48" Foreground="#FF5B5B5B" 
                Text="MARCHING ANTS" TextWrapping="Wrap" 
                Canvas.Top="182" Canvas.Left="79"/>
        </Canvas>
    </Grid>
</Window>

As you can see in the markup above, I have a Canvas object which includes a Rectangle. This is the actual selection rectangle. Note that I've set the StrokeDashArray property to 5, which converts the solid stroke to dashed. You can use any thickness, color, corner radius, or background color for different look and feel of the selection rectangle. Then, I created a Storyborad which animates the StrokeDashOffset property of the rectangle. The first keyframe is at 00:00:00, and the second at 00:00:00.5000000. If you want faster ants, then reduce the second KeyTime. The key trick for a smooth animation without jumpy ants is to set the second SplineDoubleKeyFrame value equal to 2*StrokeDashArray, which in our case would be 2*5=10. Any multiple of 2 instead of the 2 itself should work fine, but they will make the animation faster. Initially, I set the Rectangle position to (0,0) through the Canvas.Left and Canvas.Top properties, which is also important because I'm using the RenderTransform property in my C# code to translate the rectangle to the proper place. You can use Canvase.SetLeft and Canvas.SetTop instead. Initially, I set the Visibility of the selection rectangle to Hidden too. Later, through our code, when the user actually drags the mouse on the surface, we make it Visible.

C#

C#
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Input;

namespace MarchingAntsSelection
{
    public partial class Window1
    {
        private Point startDrag;

        public Window1()
        {
            this.InitializeComponent();

            canvas.MouseDown += new MouseButtonEventHandler(canvas_MouseDown);
            canvas.MouseUp += new MouseButtonEventHandler(canvas_MouseUp);
            canvas.MouseMove += new MouseEventHandler(canvas_MouseMove);
        }

        private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            //Set the start point
            startDrag = e.GetPosition(canvas);
            //Move the selection marquee on top of all other objects in canvas
            Canvas.SetZIndex(rectangle, canvas.Children.Count);
            //Capture the mouse
            if (!canvas.IsMouseCaptured)
                canvas.CaptureMouse();
            canvas.Cursor = Cursors.Cross;
        }

        private void canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            //Release the mouse
            if (canvas.IsMouseCaptured)
                canvas.ReleaseMouseCapture();
            canvas.Cursor = Cursors.Arrow;
        }

        private void canvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (canvas.IsMouseCaptured)
            {
                Point currentPoint = e.GetPosition(canvas);

                //Calculate the top left corner of the rectangle 
                //regardless of drag direction
                double x = startDrag.X < currentPoint.X ? startDrag.X : currentPoint.X;
                double y = startDrag.Y < currentPoint.Y ? startDrag.Y : currentPoint.Y;

                if (rectangle.Visibility == Visibility.Hidden)
                    rectangle.Visibility = Visibility.Visible;

                //Move the rectangle to proper place
                rectangle.RenderTransform = new TranslateTransform(x, y);
                //Set its size
                rectangle.Width = Math.Abs(e.GetPosition(canvas).X - startDrag.X);
                rectangle.Height = Math.Abs(e.GetPosition(canvas).Y - startDrag.Y);
            }
        }
    }
}

This is the whole C# code which is absolutely simple and self explanatory. Whenever the user clicks the mouse on canvas, we set the start point, capture the mouse within the canvas, and we make sure that the selection rectangle is the topmost object in canvas using the Canvas.SetZIndex method. As my friend Josh suggested, you can use an adorner layer too. The only thing which remains is, OnMouseMove you calculate the rectangle again and set its position and size accordingly.

Footnote

If you used this code as is or as a base for more advanced selection styles in your application, I'd be grateful if you mention this article with a link; otherwise, it's absolutely free for any kind of commercial or personal use.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questioncan we draw circle? Pin
anjanf217-May-13 4:52
anjanf217-May-13 4:52 
GeneralMy vote of 5 Pin
chinloon20-Dec-12 21:54
chinloon20-Dec-12 21:54 
GeneralGreat article Pin
rickengle17-Oct-08 3:53
rickengle17-Oct-08 3:53 
GeneralI gave this a 3 Pin
Josh Smith16-Jul-08 2:15
Josh Smith16-Jul-08 2:15 
I don't normally vote an article down, but I feel this article is missing a lot. Here are the things I am talking about:

First, the demo project does not show how to determine what elements to select after resizing the selection rectangle. There should at least be a paragraph explaining how one might do that.

Second, the article is incorrect in stating that you cannot get the mouse coordinates via MouseEventArgs. All you have to do is call e.GetPosition().

Third, selection decorators are usually placed into the adorner layer, not directly into a layout panel's Children. This ensures that the selection decorator is always above all selectable elements, with respect to the Z order.

Fourth, your example puts the mouse interaction logic of the selection rectangle into the Window's code-behind. It does not belong there. That code should be in the code-behind of a selection decorator class, which can then be reused in as many UIs as necessary.

Fifth, your mouse interaction logic is not capturing the mouse. If I am dragging and move the mouse very fast, the selection rectangle will not "keep up" with me and end being a different size than I want. Capturing the mouse during a drag would solve this issue.

:josh:
My WPF Blog[^]
All of life is just a big rambling blog post.

GeneralRe: I gave this a 3 Pin
Meysam Mousavi16-Jul-08 6:01
Meysam Mousavi16-Jul-08 6:01 
GeneralRe: I gave this a 4 Pin
Josh Smith16-Jul-08 6:10
Josh Smith16-Jul-08 6:10 
GeneralRe: I gave this a 4 Pin
Meysam Mousavi16-Jul-08 6:15
Meysam Mousavi16-Jul-08 6:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.