DragSelection of Nodes

Hi,
I have a template for my node with a Label-Control in it.
Now the text in the label is very long. But the size of the node is much samler than needed to fully display the text. If I now want to drag select this node it only get selected if the select rectangle is covering the whole (invisible) text.
Is there a way for the node to get selected when the select rectangle is cover only the dimensions of the node?

I hope you understand what I try to discribe.

 The node
       |
       ˅
+--------------+
| This is the intern Label with long text
+--------------+

And this node get selected by the dragselectingtool only if the selecting rectangle covers the whole text although the text (ntern Label with long text) is not visible cause the node dimension are smaller.

First, have you considered:

<go:Diagram ...>
    <go:Diagram.DragSelectingTool>
        <go:DragSelectingTool Include="Intersects" />
    </go:Diagram.DragSelectingTool>
</go:Diagram> 

Second, what are the actual Node.Bounds for that Node? Does the Width include the clipped text? If so, why and how?

Hi Walter,
at the moment I don’t want to use intersects cause I don’t want to select any underlying nodes.
The Node bounds are ok, but the contentPresenter of the inner button has a textblock and this textblock has a much larger width than the node.
I have no idea what to do - I thought the nodebounds are responsible for the selection.

How is the node template defined? Does clicking in the area where the text is clipped cause the node to be selected?

No, clicking in this area doesn’t select the node.

<Style x:Key="ElementGridStyle" TargetType="{x:Type Grid}">
    <Setter Property="go:Part.Resizable" Value="{Binding Path=Part.Diagram.DataContext.EditMode}"/>
    <Setter Property="go:Part.SelectionAdorned" Value="False" />
    <Setter Property="go:Node.ZOrder" Value="{Binding Path=Data.Z}" />
    <Setter Property="go:Part.Movable" Value="{Binding Path=Data.IsMovable}" />
    <Setter Property="go:Part.Selectable" Value="{Binding Path=Data.IsSelectable}" />
    <Setter Property="go:Part.Deletable" Value="{Binding Path=Data.IsDeletable}" />
    <Setter Property="go:Part.Rotatable" Value="{Binding Path=Data.IsRotatable}" />
    <Setter Property="Width" Value="{Binding Path=Data.Width, Mode=TwoWay}" />
    <Setter Property="Height" Value="{Binding Path=Data.Height, Mode=TwoWay}" />
    <Setter Property="Opacity" Value="{Binding Path=Data.Opacity}" />
    <Setter Property="Visibility" Value="{Binding Path=Data.Visibility}" />
    <Setter Property="Background" Value="{Binding Path=Part.Diagram.DataContext.EditMode, Converter={StaticResource IsEditModeBackgroundConverter}}" />
    <Setter Property="Tag" Value="{Binding Part.Diagram}" />
    <Setter Property="go:Node.LocationSpot" Value="TopLeft" />
    <!--<Setter Property="go:Node.RotationSpot" Value="Center" />-->
    <Setter Property="go:Node.Location">
        <Setter.Value>
            <MultiBinding Converter="{StaticResource PosToPointConverter}" Mode="TwoWay">
                <Binding Path="Data.X" Mode="TwoWay" />
                <Binding Path="Data.Y" Mode="TwoWay" />
            </MultiBinding>
        </Setter.Value>
    </Setter>
    <Setter Property="go:Node.RotationAngle" Value="{Binding Path=Data.Angle, Mode=TwoWay}" />
    <Setter Property="ContextMenu" Value="{StaticResource ElementContextMenu}" />
    <Setter Property="ToolTip" Value="{x:Null}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=Part.Diagram.DataContext.EditMode}" Value="True">
            <Setter Property="ToolTip" Value="{Binding Path=Data.Description}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=Part.Diagram.DataContext.EditMode}" Value="False">
            <Setter Property="ContextMenu" Value="{x:Null}" />
        </DataTrigger>
        <Trigger Property="ToolTip" Value="{x:Static system:String.Empty}">
            <Setter Property="ToolTipService.IsEnabled" Value="False" />
        </Trigger>
    </Style.Triggers>
</Style>


    <DataTemplate x:Key="SimpleText" DataType="{x:Type classes:Element}">
        <Grid Style="{StaticResource ElementGridStyle}">
            <!--<Path Style="{StaticResource IsPinnedAndEditmodeStyle}" />-->
            <Label x:Name="textBlock"
                   IsHitTestVisible="{Binding Path=Part.Diagram.DataContext.EditMode, Converter={StaticResource BooleanNegationConverter}}"
                   BorderThickness="2"
                   Background="{Binding Path=Data.BackgroundBrush}" 
                   BorderBrush="{Binding Path=Data.BorderColor, Converter={StaticResource ColorToBrushConverter}}"
                   Foreground="{Binding Path=Data.ForegroundColor, Converter={StaticResource ColorToBrushConverter}}"
                   HorizontalContentAlignment="{Binding Path=Data.Horizontal}"
                   VerticalContentAlignment="{Binding Path=Data.Vertical}"
                   Padding="2"
                   FontSize="{Binding Path=Data.FontSize}"
                   FontFamily="{Binding Path=Data.FontFamily, TargetNullValue='Segoe UI'}" 
                    >
                <TextBlock Text="{Binding Path=Data.Text}" TextWrapping="{Binding Data.Wrap, Converter={StaticResource WrapConverter}}" />
            </Label>
            <ToggleButton x:Name="PinnedButton" Style="{StaticResource PinnedToggleButton}" Opacity="0"/>
            <ToggleButton x:Name="QuickZoomButton" Style="{StaticResource QuickZoomToggleButton}" Opacity="0" />
            <Button x:Name="SettingButton" Style="{StaticResource SettingButton}" Opacity="0" />
            <Border Style="{StaticResource MarkerBorderStyle}" Visibility="{Binding Path=Data.IsMarked, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Path=Data.IsSelected}" Value="True">
                <Setter TargetName="textBlock" Property="Background" Value="{Binding Path=Data.BackgroundBrushSelected}" />
                <Setter TargetName="textBlock" Property="BorderBrush" Value="{Binding Path=Data.BorderColorSelected, Converter={StaticResource ColorToBrushConverter}}" />
                <Setter TargetName="textBlock" Property="Foreground" Value="{Binding Path=Data.ForegroundColorSelected, Converter={StaticResource ColorToBrushConverter}}" />
            </DataTrigger>
            <Trigger Property="Button.IsMouseOver" Value="True">
                <Setter TargetName="PinnedButton" Property="Opacity" Value="1"></Setter>
                <Setter TargetName="QuickZoomButton" Property="Opacity" Value="1"></Setter>
                <Setter TargetName="SettingButton" Property="Opacity" Value="1"></Setter>
            </Trigger>
        </DataTemplate.Triggers>
    </DataTemplate>

If I override the SelectInRect-Method it is working:

    public override void SelectInRect(Rect r)
    {
        base.SelectInRect(r);

        Diagram diagram = Diagram;
        if (diagram == null) return;
        
        var nodes = diagram.PartManager.Nodes.Where(p => r.Contains(p.Bounds.TopLeft) && r.Contains(p.Bounds.BottomRight));

        foreach (Node part in nodes)
        {
            part.IsSelected = true;
        }
    }

But I think it must work without that.

DragSelectingTool.SelectInRect calls DiagramPanel.FindPartsIn which calls VisualTreeHelper.HitTest. I’m surprised this isn’t working for you.

I was going to suggest overriding SelectInRect next, which apparently you have already done. However, what you have implemented will leave previously selected Parts still selected, which might not be your intent.

I call the base method and this handles the previously selected nodes.
I think it’s working - tell me if I made a mistake.

Ah, right – I missed the base call, which I think might not be necessary if you cleared out the selection beforehand. However in either case it doesn’t handle modifiers. Here’s the complete definition:

    public virtual void SelectInRect(Rect r) {
      Diagram diagram = this.Diagram;
      if (diagram == null) return;
      
      // choose the selectable Parts within the rectangle
      HashSet<Part> parts = new HashSet<Part>(diagram.Panel.FindPartsIn<Part>(r, SearchFlags.SelectableParts, this.Include, SearchLayers.Parts));

      if (IsControlKeyDown()) {  // toggle or deselect
        if (IsShiftKeyDown()) {  // deselect only
          foreach (Part p in parts) {
            if (p.IsSelected) p.IsSelected = false;
          }
        } else {  // toggle selectedness of parts
          foreach (Part p in parts) {
            p.IsSelected = !p.IsSelected;
          }
        }
      } else if (IsShiftKeyDown()) {  // extend selection only
        foreach (Part p in parts) {
          if (!p.IsSelected) p.IsSelected = true;
        }
      } else {  // select parts, and unselect all other previously selected parts
        // this tries to avoid deselecting and then reselecting any Part
        List<Part> tounselect = new List<Part>();
        foreach (Part p in diagram.SelectedParts) {
          if (!parts.Contains(p)) tounselect.Add(p);
        }
        foreach (Part p in tounselect) {
          p.IsSelected = false;
        }
        foreach (Part p in parts) {
          if (!p.IsSelected) p.IsSelected = true;
        }
      }
    }

Yes, that’s better - thank you for the code.

Nevertheless I try to figure out what’s the problem with VisualTreeHelper.HitTest.
If I find something I will tell you.

I looked at the source code and for me it looks absolute correct.

So I search on StackOverflow and found this article:
Maybe this could be an issue and some Parts didn’t come in the result of FindPartsWithin.

http://stackoverflow.com/questions/3411910/problem-with-visualtreehelper-hittest-in-wpf

Hmmm, I did not know about that. However it is not clear to me that that is the problem here – are there any UserControls involved in your case?

50% of our templates include a UserControl, but the other 50% there are “standard” controls.
And the problem occurs with the standard controls also.

What’s the value of Diagram.DataContext.EditMode ?

It’s a boolean to show that the application is in editmode (true) so the nodes are movable, sizeable and so on.

I’ll assume BooleanNegationConverter negates a boolean value.

So the Label will not be “hittable”, which will cause any hit-testing on the text to fail, even though the Label still takes up most of the space of the Node.

Even the Hittest is set to false the node only get selected if the selection rect of dragselectiontool covers the whole bounds of the partly not visible text.

ClickSelecting is no problem! The node only get selected if someone is clicking inside the node bound (textbounds does not matter).

I think the problem is that the Label is really long, so as far as hit-testing goes, the node is effectively very big, even if the top-level visual element is relatively small because it clips the long Label.

What you have done to override SelectInRect is the right solution.

   public override void SelectInRect(Rect r)
    {
        Diagram diagram = this.Diagram;
        if (diagram == null) return;

        // choose the selectable Parts within the rectangle
        HashSet<Part> parts = new HashSet<Part>(Diagram.PartManager.Nodes.Where(p => r.Contains(p.Bounds.TopLeft) && r.Contains(p.Bounds.BottomRight) && p.Selectable));

        if (IsControlKeyDown())
        {  // toggle or deselect
            if (IsShiftKeyDown())
            {  // deselect only
                foreach (Part p in parts)
                {
                    if (p.IsSelected) p.IsSelected = false;
                }
            }
            else
            {  // toggle selectedness of parts
                foreach (Part p in parts)
                {
                    p.IsSelected = !p.IsSelected;
                }
            }
        }
        else if (IsShiftKeyDown())
        {  // extend selection only
            foreach (Part p in parts)
            {
                if (!p.IsSelected) p.IsSelected = true;
            }
        }
        else
        {  // select parts, and unselect all other previously selected parts
           // this tries to avoid deselecting and then reselecting any Part
            List<Part> tounselect = new List<Part>();
            foreach (Part p in diagram.SelectedParts)
            {
                if (!parts.Contains(p)) tounselect.Add(p);
            }
            foreach (Part p in tounselect)
            {
                p.IsSelected = false;
            }
            foreach (Part p in parts)
            {
                if (!p.IsSelected) p.IsSelected = true;
            }
        }
    }

Just the line with filling the hashset is different from the original.