0

I have a canvas with a rectangle box for marquee selecting and my uniformgrid which contains my buttons that are dynamically produced.

The marquee selection tool is now working i.e. i can see it being drawn over the uniform grid and i've got the mousedown position and mouseup position per the example code here:

Click and drag selection box in WPF

My XAML is this:

<Grid Name="mainGrid" DockPanel.Dock="Top"  Width="800" Height="400">
    <Rectangle x:Name="selectionBox" Visibility="Collapsed" Stroke="White" StrokeThickness="4" StrokeDashArray="2,1"/>
    <UniformGrid DockPanel.Dock="Top" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="uniformGrid" Grid.Row="1" Width="{Binding Width, ElementName=mainGrid}" Height="{Binding Height, ElementName=mainGrid}"
Rows="{Binding RowCount}"
Columns="{Binding ColumnCount}" MouseDown="UniformGrid_MouseDown" MouseUp="UniformGrid_MouseUp" MouseMove="UniformGrid_MouseMove" Background="Transparent">
    </UniformGrid>
    <Canvas Name="buttonCanvas">
    </Canvas>
</Grid>

Thing is, i'm not sure how to check if my buttons (which are children of the UniformGrid) are contained/partially contained in this rect.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
slickchick2
  • 137
  • 9
  • If the `UniformGrid` is inside of the `Rectangle`, and the grid contains the buttons, wouldn't the buttons automatically all be contained in the `Rectangle` anyway? – Sach Aug 12 '19 at 16:08
  • Oh never mind the grid is not in the rectangle. The formatting threw me off. – Sach Aug 12 '19 at 16:09
  • You can get all the controls inside of a control by using `Control.Childred` property, and you can use `uniformGrid.Children.OfType – Sach Aug 12 '19 at 16:19
  • Hey Sach. I've got the mousedown position coordinates and the mouseup position coordinates. I'm sure I can also get the coordinates of the buttons somehow but I'm not sure how this would be in code. If you look at the link in my question, the MouseUp event that is included in the popular answer for that question is where I'm stuck at in terms of what to write. Could you help with an example? – slickchick2 Aug 12 '19 at 16:27
  • Be aware that by putting the UniformGrid into the Canvas you'll loose your layout, if you ever intend to have it resizable. – Clemens Aug 12 '19 at 16:39
  • Not sure how else to do it Clemens – slickchick2 Aug 12 '19 at 16:48
  • On top of the UniformGrid, in a common parent Panel, e.g. a Grid. Just as shown in the answer to your previous question. However, the other way round, the Canvas over the UniformGrid, not under it. – Clemens Aug 12 '19 at 17:32
  • Hey @Clemens, I've edited my answer - does the XAML look any better? – slickchick2 Aug 12 '19 at 18:04
  • I don't get it, sorry. Now there are two Canvases, *both* under the UniformGrid. – Clemens Aug 12 '19 at 18:09
  • I thought they were above the uniformgrid... :\ *sighs* – slickchick2 Aug 12 '19 at 18:13
  • I've changed it again... – slickchick2 Aug 12 '19 at 18:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197835/discussion-between-slickchick2-and-clemens). – slickchick2 Aug 12 '19 at 18:22

1 Answers1

1

This is an expansion to the solution you've linked.

Following is a method that will decided a given button is inside the given MouseUp and MouseDown position. In that example, there's a member variable called mouseDownPos, and a local variable called mouseUpPos, which registers each of those. So, inside the Grid_MouseUp event handler, I'd add the following code to grab all the Button controls in your Canvas, iterate each of them and pass them to a method to see if it's inside the said area.

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!
    //
    var buttons = canvasButtons.Children.OfType<System.Windows.Controls.Button>();

    foreach (var button in buttons)
    {
        var isInSelection = IsInsideSelection(mouseDownPos, mouseUpPos, button);
    }
}

The IsInsideSelection() is a function that I wrote, which asks for the mouse up and down positions of the rectangle, and for the Button control.

private bool IsInsideSelection(Point mouseDown, Point mouseUp, System.Windows.Controls.Button button)
{
    // This grabs the coordinates of the button, relative to the main window. 
    // If you would like it relative to something else, like your canvas or the grid, you'd have to pass appropriate control to the `TransformToAncestor()` function.
    var buttonPos = button.TransformToAncestor(mainWin).Transform(new Point(0, 0));

    // Bottom right corner coordinates of the button control.
    var btnBottomRight = new Point(buttonPos.X + button.Width, buttonPos.Y + button.Height);

    // If button X and Y (which is the top left corner of the button) 
    // are outside the mouse down position, it's not inside the rectangle 
    if (buttonPos.X < mouseDown.X || buttonPos.Y < mouseDown.Y)
        return false;

    // If X and Y of button bottom right corner is outside mouse up coordinates,
    // then the control is again outside the rectangle
    if (btnBottomRight.X > mouseUp.X || btnBottomRight.Y > mouseUp.Y)
        return false;

    // Everything else, control is inside
    return true;
}

NOTE:

  • This grabs the coordinates of the button, relative to the main window. If you would like it relative to something else, like your canvas or the grid, you'd have to pass appropriate control to the TransformToAncestor() function.

EDIT

The above function only works if the rectangle was drawn left-to-right. To handle the right-to-left scenario, you can switch the mouse up and down positions, like so:

private bool IsInsideSelection(Point mouseDown, Point mouseUp, System.Windows.Controls.Button button)
{
    if (mouseUp.X < mouseDown.X)
    {
        var temp = mouseUp;
        mouseUp = mouseDown;
        mouseDown = temp;
    }

    var buttonPos = button.TransformToAncestor(mainWin).Transform(new Point(0, 0));
    var btnBottomRight = new Point(buttonPos.X + button.Width, buttonPos.Y + button.Height);

    if (buttonPos.X < mouseDown.X || buttonPos.Y < mouseDown.Y)
        return false;

    if (btnBottomRight.X > mouseUp.X || btnBottomRight.Y > mouseUp.Y)
        return false;

    return true;
}

EDIT 2:

Following is the XAML of my test app, and I'm using a UniformGrid to hold controls here. Note that when retrieving buttons, you must use the name of the UniformGrid to do so, in this case, 'unfGrid`.

<Window ...
        Name="mainWin"
        WindowStartupLocation="CenterScreen"
        Title="MainWindow" Height="600" Width="600">
    <Grid x:Name="theGrid"
          MouseDown="Grid_MouseDown"
          MouseUp="Grid_MouseUp"
          MouseMove="Grid_MouseMove"
          Background="Transparent">
        <UniformGrid Name="unfGrid" Grid.Row="0">
            <Button Name="Btn1" Content="Button1" Grid.Row="0"
                        Width="100" Height="24"/>
            <Button Name="Btn2" Content="Button2" Grid.Row="1"
                        Width="100" Height="24"/>
            <Button Name="Btn3" Content="Button3" Grid.Row="2"
                        Width="100" Height="24"/>
            <!-- This canvas contains elements that are to be selected -->
        </UniformGrid>

        <Canvas Grid.Row="0">
            <!-- 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>
Sach
  • 10,091
  • 8
  • 47
  • 84
  • Hey Sach, the line ` var buttons = canvasButtons.Children.OfType();` is returning nothing. This is based on the XAML I have in my original post. I'm adding buttons to the uniformGrid like uniformGrid.Children.Add. What am I doing wrong? – slickchick2 Aug 12 '19 at 17:54
  • 'canvasButtons' is the name I used for the canvas in my test code. In your case you must use the name of the canvas you're adding the buttons to, whatever that may be. Instead of simply copy pasting code, try to understand. I've already explained what the code does; you add buttons to a canvas, then in code behind, using the canvas name you get hold of those same buttons and evaluate their locations. – Sach Aug 12 '19 at 18:32
  • I understood the code and I said it was returning nothing, not that it wasn't compiling. I changed it to suit my canvas button name but I'm not adding to the canvas, i'm adding to the uniform grid because this is what I want - i don't want to be adding buttons to the canvas - only to the uniform grid. That's why I'm trying to understand if I can pull the buttons from the uniform grid instead of the canvas – slickchick2 Aug 12 '19 at 18:37
  • 1
    Then simply add buttons to a `UniformGrid` instead of a canvas, and use the name of that grid to retrieve buttons. I just replaced the `Canvas` with a `UniformGrid` in my code, and it works just fine. I'll edit my answer and post my `XAML`. – Sach Aug 12 '19 at 18:52