Double-click on Node/Link to begin text editing

Hi

This is for GoXam 2.1.1.5.

I’m trying to implement a behaviour on a node/link that initiates text editing when a double-click takes place. I’m doing this via an attached property that subscribes to the MouseLeftButtonDownEvent of the Part. When a double-click occurs, I call Diagram.CommandHandler.Edit(). However, text editing is not initiating. So I’m not sure what I’m doing wrong.

Here’s that code for the attached behaviour:
public class DoubleClickTextEditBehaviour
{
#region Fields
private static Part _currentPart;
#endregion
#region Dependency Properties
private static DependencyProperty PartProperty = DependencyProperty.RegisterAttached(
“Part”,
typeof(Part),
typeof(DoubleClickTextEditBehaviour),
new PropertyMetadata(PartPropertyChanged));
public static Part GetPart(DependencyObject target)
{
return target.GetValue(PartProperty) as Part;
}
public static void SetPart(DependencyObject target, Part value)
{
target.SetValue(PartProperty, value);
}
private static void PartPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if(_currentPart != null)
_currentPart.RemoveHandler(Part.MouseLeftButtonDownEvent, new MouseButtonEventHandler(CurrentPart_MouseLeftButtonDown));
if(e.NewValue is Part) {
_currentPart = (Part)e.NewValue;
_currentPart.AddHandler(Part.MouseLeftButtonDownEvent, new MouseButtonEventHandler(CurrentPart_MouseLeftButtonDown), true);

        }
    }
  
    #endregion
    #region Event Handlers
    private static void CurrentPart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if(e.ClickCount > 1 && _currentPart.Diagram != null){
            
            _currentPart.Diagram.CommandHandler.Edit();
        }
    }

    #endregion

}

And this is how I’m attaching the property:

<go:SpotPanel
            go:Part.LayerName="{Binding Path=Data.LayerID}"      
            go:Part.LayoutId="{Binding Data.LayoutID}"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
            go:Node.LocationSpot="Center"
            go:Part.SelectionAdorned="True"
            go:Part.SelectionElementName="Shape"
            go:Part.Rotatable="False"
            go:Part.Resizable="True"
            go:Node.LinkableFrom="False"
            go:Node.LinkableTo="False"
            go:Node.LinkableSelfNode="False"
            go:Node.LinkableDuplicates="False"
            go:Part.Text="{Binding Path=Data.Text}"
            go:Part.TextEditable="True"
            go:Node.PortId=""
            <strong>ivGoXam:DoubleClickTextEditBehaviour.Part="{Binding Part}"></strong>


Any suggestions?

Thanks
Justin

You need to set go:Part.TextEditable=“True” on the TextBlock that you want to be edited. This is required in order to identify the particular TextBlock to edit in case there are multiple TextBlocks.

The Edit command also requires Part.Editable to be true, but it is true by default, and presumably you haven’t set it to false. The requirements are described in the documentation for CommandHandler.CanEdit().

Hi Walter

I have Part.TextEditable=“True” on my TextBlock as well Part.Editable=“True” but I’m still not having any joy. Please note that the initial text value is null/empty. Below is my data template for the node:

<go:SpotPanel
            go:Part.LayerName="{Binding Path=Data.LayerID}"      
            go:Part.LayoutId="{Binding Data.LayoutID}"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
            go:Node.LocationSpot="Center"
            go:Part.SelectionAdorned="True"
            go:Part.SelectionElementName="Shape"
            go:Part.Rotatable="False"
            go:Part.Resizable="True"
            go:Node.LinkableFrom="False"
            go:Node.LinkableTo="False"
            go:Node.LinkableSelfNode="False"
            go:Node.LinkableDuplicates="False"
            go:Part.Editable="True"
            go:Node.PortId=""
            ivGoXam:DoubleClickTextEditBehaviour.Part="{Binding Part}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=leftPort}" PropertyName="Opacity" Value="1" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=topPort}" PropertyName="Opacity" Value="1" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=rightPort}" PropertyName="Opacity" Value="1" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=bottomPort}" PropertyName="Opacity" Value="1" />
                </i:EventTrigger>
                <i:EventTrigger EventName="MouseLeave">
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=leftPort}" PropertyName="Opacity" Value="0" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=topPort}" PropertyName="Opacity" Value="0" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=rightPort}" PropertyName="Opacity" Value="0" />
                    <ei:ChangePropertyAction TargetObject="{Binding ElementName=bottomPort}" PropertyName="Opacity" Value="0" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
            <go:NodePanel go:SpotPanel.Main="True">
                    <go:NodeShape x:Name="Shape" go:NodePanel.Figure="{Binding Path=Data.Shape}"
                        Stroke="Black" StrokeThickness="1" Fill="White"
                        Width="{Binding Data.Width, Mode=TwoWay, TargetNullValue=80}" Height="{Binding Data.Height, Mode=TwoWay, TargetNullValue=80}" MinWidth="20" MinHeight="20" />
                    <TextBlock Text="{Binding Path=Data.Text, Mode=TwoWay}" TextAlignment="Center" TextWrapping="Wrap" go:Part.TextEditable="True"
                        VerticalAlignment="Center" HorizontalAlignment="Center" />
            </go:NodePanel>
            <Rectangle x:Name="leftPort" Stroke="Black" StrokeThickness="1" Fill="White" Width="8" Height="8" Opacity="0"
             go:SpotPanel.Spot="MiddleLeft" go:SpotPanel.Alignment="MiddleLeft"
             go:Node.PortId="L" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
             go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft" go:Node.LinkableDuplicates="False" />
            <Rectangle x:Name="topPort" Stroke="Black" StrokeThickness="1" Fill="White" Width="8" Height="8"  Opacity="0"
             go:SpotPanel.Spot="MiddleTop" go:SpotPanel.Alignment="MiddleTop"
             go:Node.PortId="T" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand" 
             go:Node.FromSpot="MiddleTop" go:Node.ToSpot="MiddleTop" go:Node.LinkableDuplicates="False" />
            <Rectangle x:Name="rightPort" Stroke="Black" StrokeThickness="1" Fill="White" Width="8" Height="8"  Opacity="0"
             go:SpotPanel.Spot="MiddleRight" go:SpotPanel.Alignment="MiddleRight"
             go:Node.PortId="R" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
             go:Node.FromSpot="MiddleRight" go:Node.ToSpot="MiddleRight" go:Node.LinkableDuplicates="False" />
            <Rectangle x:Name="bottomPort" Stroke="Black" StrokeThickness="1" Fill="White" Width="8" Height="8"  Opacity="0"
             go:SpotPanel.Spot="MiddleBottom" go:SpotPanel.Alignment="MiddleBottom"
             go:Node.PortId="B" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"
             go:Node.FromSpot="MiddleBottom" go:Node.ToSpot="MiddleBottom" go:Node.LinkableDuplicates="False"  />
        </go:SpotPanel>
                               
    </DataTemplate>

Secondly, if you have a node/link that initially contains text and then edit the text by clearing it of all text, the only way to initiate text-editing afterwards is to press F2. Double-clicking does nothing. We find this behaviour non-intuitive.

Thanks
Justin

Have you set a breakpoint on your call to CommandHandler.Edit() to make sure it really is being called when you think it should be? All the F2 keyboard binding does is:
. . . else if (key == Key.F2) {
if (CanEdit()) Edit();
} . . .
So if F2 is working for you, just calling Edit() ought to also work for you.

MINOR EDIT: wait – you’re using WPF, aren’t you? In this case the Silverlight code above isn’t called, because there is a RoutedCommand that is defined as:
Commands.Edit = new RoutedCommand(“Edit”, typeof(Diagram), new InputGestureCollection() { new KeyGesture(Key.F2) })
And the command binding is:
diagram.CommandBindings.Add(new CommandBinding(Commands.Edit, (s, e) => Edit(), (s, e) => e.CanExecute=CanEdit()));

Hi Walter

No, I’m using Silverlight, not WPF.
I’ve verified via a breakpoint that CommandHandler.Edit() is being called.

private static void CurrentPart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if(e.ClickCount > 1 && _currentPart.Diagram != null){
            if(_currentPart.Diagram.CommandHandler.CanEdit()) {
                _currentPart.Diagram.CommandHandler.Edit();
            }
        }
    }

You can see the same behaviour on the “Dynamic Ports” sample. Clear the Text value on one of the nodes. Then try to initiate text editing by double-clicking on that node. You will notice that it’s not easy to do becasue the current dimesions of TextBlock are very small. However, pressing F2 works as expected.

Thanks

I suspect that the problem with your code is that you are not preventing the normal click behavior from causing the text editor to disappear immediately, unless the click point is on the TextBlock itself. Try setting e.Handled = true:

private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
  if (DiagramPanel.IsDoubleClick(e)) {
    e.Handled = true;
    myDiagram.CommandHandler.Edit();
  }
}

Thanks Walter.

Adding e.Handled = true does initiate text editing. However, lets say I have two nodes. I double-click on the first node to begin text-editing. With text-editing still active on the first node, I double-click on the second node. The second node does not get focus nor does it initiate text editing. Instead, the first node re-initiates text editing. Is there someway I can prevent this behaviour and give focus to the second node? I’ve tried Diagram.CommandHandler.StopCommand() but that didn’t help.

Secondly, using the same code on a Link produces mixed results. Double-clicking sometimes initiates text-editing and sometimes it doesn’t. Is there special handling that I need to do for a Link

Thanks
Justin

Is the first node still selected when the Edit command is invoked for the second double-click (i.e. on the second node)? If so, it is probably because the Edit command just works on the selection, looking for an eligible TextBlock, and it keeps finding the one in the first node.

You may need to replace the use of the Edit command with your own code to find the TextBlock that you want to edit, to make sure it is in the node that was double-clicked. You can then set myDiagram.TextEditingTool.TextBlock and then set myDiagram.CurrentTool to be the myDiagram.TextEditingTool.

Regarding your second issue, are you sure that the double click is really double-clicking on the link, and not partly on it or near it?

Thanks Walter, will try your suggestion when I get a chance.

Regarding the links, you’re right, the clickable area of the link was too narrow.

Regarding hard-to-click links – you can use a second LinkShape that is transparent and relatively wide. A number of the samples do this.

Thanks Walter, that helped quite a bit.