Setting Pos of a Group via binding

Hi,
if I bind the location of a group and set this location from code, the children didn’t move!
If I move the group with the mouse all works well.

Any idea?

That’s because one might want the freedom to move a group without moving its members.

Call Node.Move instead.

Ok, that a good point.

But I don’t get it.

I have my GroupStyle:

<Style x:Key="GroupStackPanelStyle" TargetType="{x:Type StackPanel}">
    <Setter Property="go:Node.LocationElementName" Value="GroupPanel" />
    <Setter Property="go:Part.SelectionAdorned" Value="True" />
    <Setter Property="Tag" Value="{Binding Part.Diagram}" />
    <Setter Property="ContextMenu" Value="{StaticResource ElementContextMenu}" />
    <Setter Property="go:Part.Selectable" Value="{Binding Path=Part.Diagram.DataContext.EditMode}" />
    <Setter Property="go:Node.LocationSpot" Value="TopLeft" />
    <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>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=Part.Diagram.DataContext.EditMode}" Value="False">
            <Setter Property="ContextMenu" Value="{x:Null}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

and my Template:

<go:DataTemplateDictionary x:Key="NodeTemplates">
    <DataTemplate x:Key="Group" DataType="{x:Type classes:Element}">
        <StackPanel Style="{StaticResource GroupStackPanelStyle}">
            <Border BorderBrush="{Binding Path=Part.Diagram.DataContext.EditMode, Converter={StaticResource IsEditmodeGroupOuterBorderConverter}}" 
                    BorderThickness="3" Background="Transparent">
                <Border BorderBrush="{Binding Path=Part.Diagram.DataContext.EditMode, Converter={StaticResource IsEditmodeGroupInnerBorderConverter}}" 
                        BorderThickness="1" Margin="-2">
                    <go:GroupPanel x:Name="GroupPanel" />
                </Border>
            </Border>
        </StackPanel>
    </DataTemplate>
...

I’m showing the properties of my nodes in a propertygrid, so the X and Y values.
Now the user changes the X value and a setter was called:

public class Element : GraphLinksModelNodeData<String>, IDisplay, ILayerKey
{
    [Category("Layout")]
    public int X
    {
        get { return x; }
        set
        {
            if (!Equals(x, value))
            {
                var old = x;
                x = value;
                RaisePropertyChanged("X", old, value);
            }
        }
    }
...

Where should I call Node.Move?

I suppose you could define your own Location-like property on Group.

But you’ll need to be careful with nested groups!

Hi Walter,
I’m still working on this.
In the setter of Property “X” I can check if the category is “Group”, but on which element I have to call the move-method?
I didn’t found it.

I tried the following, but I get an StackOverflow

    [Category("Layout")]
    public int X
    {
        get { return x; }
        set
        {
            if (!Equals(x, value))
            {
                var old = x;
                x = value;
                if (Category == "Group" && model != null)
                {
                    var nodeData = model.FindNodeByKey(Key);
                    var node = model.Diagram.PartManager.FindNodeForData(nodeData, Model);
                    if (node != null)
                        model.Diagram.LayoutManager.MoveAnimated(node, new Point(X, Y));
                }
                RaisePropertyChanged("X", old, value);
            }
        }
    }

Node.Move is a method on Node, not on some model data.

Hi Walter,

I have no idea why I didn’t see Move on the Node - sorry for that.

private bool isInSetter;

[Category("Layout")]
public int X
{
    get { return x; }
    set
    {
        if (!Equals(x, value) && !isInSetter)
        {
            var old = x;
            x = value;
            MoveGroup();
            RaisePropertyChanged("X", old, value);
        }
    }
}

... same for Y here ...

private void MoveGroup()
{
    isInSetter = true;
    try
    {
        if (Category == "Group" && model != null)
        {
            var nodeData = model.FindNodeByKey(Key);
            var node = model.Diagram.PartManager.FindNodeForData(nodeData, Model);
            if (string.IsNullOrEmpty(nodeData.SubGraphKey))
                node?.Move(new Point(X, Y), false);
        }
    }
    finally
    {
        isInSetter = false;
    }
}

This is working if I set the new values for X and Y of the group in my propertygrid for existing groups.
But if I create a new group the group move to a nonvisible area in my diagram and the content of the group also moves away of the group (it looks like it’s moving by the same value as the group is moving from the 0,0 location of the diagramm).

I have the feeling this is not the right way to do this.

Yes, you need to be careful to design the code and templates so that there are no “feedback loops”.

Your previous tips were ever better! :-(

I have no ideas where I have to expect feedback loops and where not! I don’t want to write lots of code and get the result of a new feedback.

I just want to set new location to a group via a textbox/propertygrid and want the children to follow - which, in my opinion, is the only result the user expected.

BTW: Moving the group with Animation set to true has also a nice effect.
The childnodes are moving direct to their new position (one move) and the group is slowly following.
I don’t want to use animation - just as a remark for you.

Well, you could try what I suggested earlier (6 days ago): adding your own property that is like Node.Location but is only on your subclass of Group. Its setter would call Group.move().

I just call the GroupCommand and the UngroupCommand of the CommandManager.
I think now I must implement a CustomPartManager to create MyGroup instead of the standard Group (I was so lucky the time I could delete this PartManager and all was working by category).

Ok, I try, but that’s not that simple to get the simple behavior that I want.

Well, if you want a GroupPanel to control where the Group is and if you want to set the Group.Location, that means you have two independent positioning sources, which can conflict. Should the group’s location be the Group.Location or should it be at some spot in the bounds computed from the group’s member nodes?

Note that there’s no problem in the Planogram sample, which does not use a GroupPanel in the group template. It has the usual TwoWay Binding of Location.

I made a little video:

https://cloud.bfe.tv/index.php/s/r6fraDXF0an0M2F

You can see two nodes in a group.
First I select one of the nodes and move it by mouse - you see the group changes it’s bounds.
Than I select the group and move the group by mouse - you the the properties in the propertygrid changing.
Than I set a new X Position for the Group - the Group moves - but the childs not.

What template should I use to achieve this little part? That’s all what’s missing.
I understand the problem of Group and GroupPanel but I thought you told me, long time ago, todo so to get the surrounding border or something.

OK, so if you don’t want to do what I suggested (defining your own Group.Location2 property), another approach would be to change the propertygrid so that when the user modifies an X or Y value for a Group.Location, it calls Group.move().

Is the most recent code that you posted supposed to be property definitions in the model data? If so, what I’m suggesting is similar but only applicable to the propertygrid, not to all group data objects.

Yes, it’s the one and only setter for the X-Property.

I will keep trying…

Can I get the Name of the actual Transaction? Cause the propertygrid is starting a transaction if changing something.
If I could so I can call move only when this transaction is active.

http://goxam.com/2.2/helpWPF/webframe.html#Northwoods.GoWPF~Northwoods.GoXam.Model.UndoManager~CurrentTransactionName.html

Perfect! It’s working.
Thank you.