Click and drag selection box in WPF
Asked Answered
E

4

26

Is it possible to implement mouse click and drag selection box in WPF. Should it be done through simply drawing a rectangle, calculating coordinates of its points and evaluating position of other objects inside this box? Or are there some other ways?

Could you give a bit of sample code or a link?

Eurydice answered 3/12, 2009 at 6:55 Comment(2)
For drawing specifically, it's not so simple, since you'll probably want your selection box to be drawn on top of everything, and your objects are likely UIElements themselves. You'll need to use an adorner.Qoph
Pavel, thank you for your tip. I will dig into the adorner topic. If you could give me yet another bit of info (just study direction) on using adorner for this purpose I would be greatful. In anyway, thank you.Eurydice
R
48

Here is sample code for a simple technique that I have used in the past to draw a drag selection box.

XAML:

<Window x:Class="DragSelectionBox.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    >
    <Grid
        x:Name="theGrid"
        MouseDown="Grid_MouseDown"
        MouseUp="Grid_MouseUp"
        MouseMove="Grid_MouseMove"
        Background="Transparent"
        >
        <Canvas>
            <!-- This canvas contains elements that are to be selected -->
        </Canvas>
        
        <Canvas>
            <!-- This canvas is overlaid over the previous canvas and is used to 
                place the rectangle that implements the drag selection box. -->
            <Rectangle
                x:Name="selectionBox"
                Visibility="Collapsed"
                Stroke="Black"
                StrokeThickness="1"
                />
        </Canvas>
    </Grid>
</Window>

C#:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
    }

    bool mouseDown = false; // Set to 'true' when mouse is held down.
    Point mouseDownPos; // The point where the mouse button was clicked down.

    private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
    {
        // Capture and track the mouse.
        mouseDown = true;
        mouseDownPos = e.GetPosition(theGrid);
        theGrid.CaptureMouse();

        // Initial placement of the drag selection box.         
        Canvas.SetLeft(selectionBox, mouseDownPos.X);
        Canvas.SetTop(selectionBox, mouseDownPos.Y);
        selectionBox.Width = 0;
        selectionBox.Height = 0;
        
        // Make the drag selection box visible.
        selectionBox.Visibility = Visibility.Visible;
    }

    private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
    {
        // Release the mouse capture and stop tracking it.
        mouseDown = false;
        theGrid.ReleaseMouseCapture();
        
        // Hide the drag selection box.
        selectionBox.Visibility = Visibility.Collapsed;
        
        Point mouseUpPos = e.GetPosition(theGrid);
        
        // TODO: 
        //
        // The mouse has been released, check to see if any of the items 
        // in the other canvas are contained within mouseDownPos and 
        // mouseUpPos, for any that are, select them!
        //
    }

    private void Grid_MouseMove(object sender, MouseEventArgs e)
    {
        if (mouseDown)
        {
            // When the mouse is held down, reposition the drag selection box.
            
            Point mousePos = e.GetPosition(theGrid);

            if (mouseDownPos.X < mousePos.X)
            {
                Canvas.SetLeft(selectionBox, mouseDownPos.X);
                selectionBox.Width = mousePos.X - mouseDownPos.X;
            }
            else
            {
                Canvas.SetLeft(selectionBox, mousePos.X);
                selectionBox.Width = mouseDownPos.X - mousePos.X;
            }

            if (mouseDownPos.Y < mousePos.Y)
            {
                Canvas.SetTop(selectionBox, mouseDownPos.Y);
                selectionBox.Height = mousePos.Y - mouseDownPos.Y;
            }
            else
            {
                Canvas.SetTop(selectionBox, mousePos.Y);
                selectionBox.Height = mouseDownPos.Y - mousePos.Y;
            }
        }
    }
}

I wrote an article about this:

https://www.codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPF

Riant answered 7/1, 2010 at 10:45 Comment(5)
Genius! Add a StrokeDashArray="2,1" to your rectangle to get a dotted line selector.Zachery
Great solution. One improvement I did is to add following code after Point mousePos = e.GetPosition(theGrid); in Grid_MouseMove() to constraint selection rectangle to the parent Grid: if (mousePos.X < 0) mousePos.X = 0; if (mousePos.X > theGrid.ActualWidth) mousePos.X = theGrid.ActualWidth; if (mousePos.Y < 0) mousePos.Y = 0; if (mousePos.Y > theGrid.ActualHeight) mousePos.Y = theGrid.ActualHeight;Brittnybritton
Its easier to use VisualTreeHelper.HitTest(theGrid, mousePos) for that case.Hege
Works great! I had a hiccup when selecting and "cropping" images though, this is because of the DPI of the image not necessarily match the DPI of the display, giving an "offset" or a "shift". I simply added: Width="{Binding Source.PixelWidth,RelativeSource={RelativeSource Self}}" Height="{Binding Source.PixelHeight,RelativeSource={RelativeSource Self}}" to the Image element to automatically normalise the DPI.Variolite
People are still using WPF?! Wow, it's been 10 years since I answered this question. You should checkout the article I wrote on this: codeproject.com/Articles/148503/Simple-Drag-Selection-in-WPFRiant
R
7

You can get this functionality pretty easily by adding an InkCanvas and set its EditingMode to Select. Although it's primarily intended for Tablet PC ink collection and rendering, it's very easy to use it as a basic designer surface.

<Window Width="640" Height="480" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    <InkCanvas EditingMode="Select">
        <Button Content="Button" Width="75" Height="25"/>
        <Button Content="Button" Width="75" Height="25"/>
    </InkCanvas>
</Window>
Reiterant answered 3/12, 2009 at 17:27 Comment(4)
Hi Josh, Thank you. I will certainly study the InkCanvas functionality. Just tell me please, do you mean that drawing a rectangle on InkCanvas will match automatically all objects beneath it and will allow to turn them selected?Eurydice
Unfortunately I can't seem to find an easy way to get InkCanvas to use a rectangular selection. It uses a lasso selection. But yes, you put elements in it and you can select them with a lasso and drag, resize, etc. You can disable the drag/resize functionality by setting properties on the InkCanvas.Reiterant
I put your code in a test WPF project and played with it for a while. And yes, it appeared to have a lot of interesting features including what you said - a lasso, drag, resize. I didn't know about it. Thank you. But, you know, I didn't expect that it would be so hard to find information about selection box. To be honest, I expected it to be among standard predefined functionality, say, like putting button on the canvas.. :)Eurydice
The InkCanvas is described in the book "Pro WPF in C# 2010" by M. MacDonald, p. 96. FYI.Confraternity
N
0

This project created a custom MultiSelector which supports several selection methods including a rectangular "lasso" style:

Developing a MultiSelector by Teofil Cobzaru

It is far too long to reproduce here. The key elements of the design, IIRC, were to create a custom ItemContainer which knows how to interact with its MultiSelector parent. This is analagous to ListBoxItem / ListBox.

This is probably not the simplest possible approach, however if you are already using some type of ItemsControl to host the items which may need to be selected, it could fit into that design pretty easily.

Nary answered 31/8, 2020 at 15:11 Comment(0)
S
-2

MouseDown logic:

MouseRect.X = mousePos.X >= MouseStart.X ? MouseStart.X : mousePos.X;
MouseRect.Y = mousePos.Y >= MouseStart.Y ? MouseStart.Y : mousePos.Y;
MouseRect.Width = Math.Abs(mousePos.X - MouseStart.X);
MouseRect.Height = Math.Abs(mousePos.Y - MouseStart.Y);
Sharpnosed answered 7/11, 2016 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.