Sub Group shown when root parent is collapsed

Hi Walter,
I’ve got a strange phenomena.
I’m using group in group (more than once).
Regarding to selecting process it’s possible to hide some group according to some properties.

Under certain condition I’ve got a node inside a collapsed group to be displayed (outside it’s parent), even if subgraphKey is correct.

Here an example.

a) group 1 is created
group 7 is created with group 1 as subgraphKey

b) group 1 is collapsed

c) group 7 is removed and group 6 is added with group 1 as subgraphKey.

d) group 7 is added with group 1 as subgraphKey
group 6 subgraphkey is changed to group 7

e) group 8 is added with group 7 as subgraphKey
group 6 subgraphKey is changed to group 8

Error: Unexpected group 6 is now displayed.

If I switch step b) and c) showing group 6 before collapsing group 1, group 6 is NOT shown after step e)

The only difference is that Node 6 has been once displayed on the diagram.

PS: On the right of the picture group 6 is a the right position if after my error group 1 is expanded.

It’s look like I’ve got this problem as soon as a group contains more than 2 sub groups.

PS 2: All subgroup are expanded

So the problem only happens when adding a group to a collapsed group?
And all of your adding and removing of groups is done through the GraphLinksModel?
And each step is done within a transaction?

I’ll have to do some experimentation.

Hi Walter,
I’ve got a question.

What’s the different between properties:

  • IsSubGraphExp
  • IsExpandedSubGraph.

I’ve linked the first one to a NodeKey property as following :
Group.IsSubGraphExpanded=_{Binding Data.IsExpanded, Mode=TwoWay}

IsExpanded is my property delcared in my GraphLinksModelNodeData.

Doing like this, ad soon as I expand a group all sub group are automatically expanded too. They do not keep their status. I don’t understand why subgraph item are updated. Do I do something wrong?

To avoid this problem I currently change Group porperty IsExpandedSubGraph directly (as you do in your “Grouping” example), but this not the way I would like to implement it.

PS: No results for my non-expected node problem displayed.

Maybe a useful remark
I’ve just remark that overview display node that are not displayed in the diagram.


May be it help to understand what’s happening.

Typically if you want to save the expanded/collapsed state of a Group, you just add something like this Binding:

go:Group.IsSubGraphExpanded="{Binding Path=Data.IsSubGraphExpanded, Mode=TwoWay}"

So I think you’re doing the right thing.

When a Group is expanded, whether or not nested Groups are expanded depends on the WasSubGraphExpanded property of each nested Group. Since you care about which nested subgraphs are expanded or not, maybe you want a TwoWay Binding on that attached property too.

The IsExpandedSubGraph attached property is used for commands and the Expander control, which is why it is separate but very much related. Perhaps we should have named it slightly differently to avoid confusion.

If the Overview is not showing what you are expecting, it’s because the model does not faithfully contain everything that you want to show. In this case maybe it’s because the Group shown in the Overview doesn’t know that it should be collapsed or expanded. (I can’t tell what you think should be drawn.)

Hi Walter,

I’m not able to maintain expanded status under 2 sub element with a binding on Group if I bind it with a NodeData property. As soon as I expand a collapsed group

Here is my DataTemplate

<DataTemplate x:Key="GroupTemplate2" DataType="{x:Type local:NodeData}">
      <Border x:Name="myBorder"
              CornerRadius="0" BorderThickness="2" BorderBrush="Black" Background="Orange"
              gowpf:Part.SelectionAdorned="True"
              gowpf:Group.LocationElementName="myGroupPanel"
              gowpf:Group.IsSubGraphExpanded="{Binding Path=Data.IsExpanded, Mode=TwoWay}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>

          <StackPanel Grid.Row="0">
            <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
              <TextBlock Text="{Binding Path=Data.Key.Name}" FontWeight="Bold" />
              
              <WrapPanel>
                <TextBlock Text="IsExpanded : "/>
                <TextBlock Text="{Binding Path=Data.IsExpanded}"/>
              </WrapPanel>

              <Button x:Name="myCollapseExpandButton" 
                      Command="{Binding Data.CommandExpandCollapse}"
                      Content="+"
                      Width="20" Margin="0 0 5 0" />
            </StackPanel>

          </StackPanel>
          <gowpf:GroupPanel x:Name="myGroupPanel" 
                            Grid.Row="1"
                            Padding="5" 
                            MinWidth="20" 
                            MinHeight="20" 
                            Background="LightGray" >
          </gowpf:GroupPanel>

        </Grid>
        <gowpf:Group.Layout >
          <gowpf:GridLayout WrappingColumn="4" />
        </gowpf:Group.Layout>
      </Border>
    </DataTemplate>

Here is my Fiagram definition

    <gowpf:Diagram Grid.Row="1"
                   x:Name="myDiagram" 
                   Padding="10"                  
                   GroupTemplate="{StaticResource GroupTemplate2}"
                   AllowMove="True"
                   Background="WhiteSmoke">
      <gowpf:Diagram.Layout>
       <gowpf:GridLayout WrappingColumn="4"/>
      </gowpf:Diagram.Layout>
    </gowpf:Diagram>

Here is my GraphModelNodeData


  public class NodeData : GraphModelNodeData<MyNodeKey>
  {
    public ICommand CommandExpandCollapse { get; set; }
    
    private bool _isExpanded = false;
    public bool IsExpanded
    {
      get => _isExpanded;
      set
      {
        var old = _isExpanded;
        _isExpanded = value;
        RaisePropertyChanged(nameof(IsExpanded), old, value);
      }
    }


    public bool HasChildren => Key.HasChild;

    public List<NodeData> Children { get; set; } = new List<NodeData>();
    
    public NodeData(MyNodeKey key)
    {
      Key = key;
      IsExpanded = true;
      CommandExpandCollapse = new CommandBase(CanExecuteCommandExpandCollapse, ExecuteCommandExpandCollapse);
    }

    public bool CanExecuteCommandExpandCollapse(object parameter)
    {
      return true;
    }

    public void ExecuteCommandExpandCollapse(object parameter)
    {
      IsExpanded = !IsExpanded;
    }

  }

I wonder why sub_Group_23 and Sub_Group_25 are expanded too.
Interessant is that sub_Group_27 stay collapsed, as expected (at least for me).

Any Idea what’s I’m doing wrong?

I’ve modified my test program to set binding directly in NodeKey as follow:

public class NodeData : GraphModelNodeData<MyNodeKey>
  {
    //   ************* NEW **********
    private Group _Node = null;
    public Group Node
    {
      get => _Node;
      set
      {
        if (value != null)
        {
          _Node = value;

          var binding = new Binding("IsExpanded") { Source = this, Mode = BindingMode.TwoWay};
          var res = Node.SetBinding(Group.IsExpandedSubGraphProperty, binding);
        }
      }
    }

    public ICommand CommandExpandCollapse { get; set; }
    
    private bool _isExpanded = false;
    public bool IsExpanded
    {
      get => _isExpanded;
      set
      {
        var old = _isExpanded;
        _isExpanded = value;

        RaisePropertyChanged(nameof(IsExpanded), old, value);
      }
    }
    
    public bool HasChildren => Key.HasChild;
...

Group is referenced to it’s NodeKey after being added to the diagram.
In this case expand/collapse status is properly memorized.

Ok my Problem with unexpected Group displayed was due to IsExpanded/IsExpandedSubGraph Status.

I’ve fixed it by synchronizing IsExpanded/IsExpandedSubGraph status with it’s parent.

Are you sure? This is why I talked above about WasSubGraphExpanded.

Hi Walter,

My solution is not the good one, regarding to Overview (as you told me).

Can you explain me why IsSubGraphExpanded change the expand status over 2 deeper groups when I use following binding:

gowpf:Group.IsSubGraphExpanded="{Binding Path=Data.IsExpanded, Mode=TwoWay}"> 

You said that have to use WasSubGraphExpanded. But I found no explanation about this property on how to use it .
The only references that I’ve, are

  • API Reference or
  • GoXamIntro.xaml

Thanks

The point of WasSubGraphExpanded is to record the collapsed/expanded state of a Group at the time the containing group was collapsed. That is just to implement the policy that when a group is expanded, that property on each nested group controls whether it is expanded or not.

Binding WasSubGraphExpanded has no impact, I still have the same problem with Group in group.

Could you please describe the simplest model and minimal change that exhibits the problem? And then show or tell me what the problem is, precisely?

<Window x:Class="TestGoWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestGoWpf"
        xmlns:gowpf="http://schemas.nwoods.com/GoXam"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1200">

  <Window.Resources>

    <DataTemplate x:Key="GroupTemplate" DataType="{x:Type local:NodeData}">
      <Border x:Name="myBorder"
              CornerRadius="0" BorderThickness="2" BorderBrush="Black" Background="Orange"
              gowpf:Part.SelectionAdorned="True"
              gowpf:Group.LocationElementName="myGroupPanel"
              gowpf:Group.IsSubGraphExpanded="{Binding Path=Data.IsExpanded, Mode=TwoWay}"
              gowpf:Group.WasSubGraphExpanded="{Binding Path=Data.WasExpanded, Mode=TwoWay}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>

          <StackPanel Grid.Row="0">
            <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
              <TextBlock Text="{Binding Path=Data.Key.Name}" FontWeight="Bold" />
              
              <WrapPanel>
                <TextBlock Text="IsExpanded : "/>
                <TextBlock Text="{Binding Path=Data.IsExpanded}"/>
              </WrapPanel>
              <WrapPanel>
                <TextBlock Text="WasExpanded : "/>
                <TextBlock Text="{Binding Path=Data.WasExpanded}"/>
              </WrapPanel>
              <WrapPanel>
                <Button x:Name="myCollapseExpandButtonCommand"
                        Command="{Binding Data.CommandExpandCollapse}"
                        Content="Expand/Collapse"
                        Margin="0 0 5 0" />
                </WrapPanel>
            </StackPanel>

          </StackPanel>
          <gowpf:GroupPanel x:Name="myGroupPanel" 
                            Grid.Row="1"
                            Padding="5" 
                            MinWidth="20" 
                            MinHeight="20" 
                            Background="LightGray" >
          </gowpf:GroupPanel>
        </Grid>
        <gowpf:Group.Layout >
          <gowpf:GridLayout WrappingColumn="4" />
        </gowpf:Group.Layout>
      </Border>
    </DataTemplate>

  </Window.Resources>

  <Grid Margin="0 10">
    <Grid.RowDefinitions>
      <RowDefinition Height="auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel>
      <WrapPanel>
        <Button Click="AddRootToModel"  Content="Add Root to Model"/>
        <Button Click="AddSubGroup"  Content="Add to Selected"/>
        <Button Click="DoLayout"  Content="Do Layout"/>
        <WrapPanel>
          <TextBlock x:Name="Width" Text="{Binding }" />
        </WrapPanel>
      </WrapPanel>
    </StackPanel>
    <gowpf:Diagram Grid.Row="1"
                   x:Name="myDiagram" 
                   Padding="10"                  
                   GroupTemplate="{StaticResource GroupTemplate}"
                   AllowMove="True"
                   Background="WhiteSmoke">
      <gowpf:Diagram.Layout>
       <gowpf:GridLayout WrappingColumn="4" ></gowpf:GridLayout>
      </gowpf:Diagram.Layout>
    </gowpf:Diagram>
  </Grid>
</Window>
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;

namespace TestGoWpf
{
  internal class CommandBase : ICommand
  {
    #region properties

    public event EventHandler CanExecuteChanged
    {
      add
      {
        CommandManager.RequerySuggested += value;
      }
      remove
      {
        CommandManager.RequerySuggested -= value;
      }
    }
    
    private Func<object, bool> _CanExecute;
    private Action<object> _Execute;

    #endregion properties

    #region ctor

    public CommandBase(Func<object, bool> canExecute, Action<object> execute)
    {
      _CanExecute = canExecute;
      _Execute = execute;
    }
    #endregion ctor

    #region ICommand

    public bool CanExecute(object parameter)
    {
      return _CanExecute(parameter);
    }

    public void Execute(object parameter)
    {
      _Execute(parameter);
    }
    #endregion ICommand
  }

  public class MyNodeKey : DependencyObject
  {
    public string Name
    {
      get { return (string)GetValue(NameProperty); }
      set { SetValue(NameProperty, value); }
    }
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register("Name", typeof(string), typeof(MyNodeKey), new PropertyMetadata(""));

    public MyNodeKey(string name)
    {
      Name = name;
    }

    public override string ToString()
    {
      return Name;
    }
  }

  public class NodeData : GraphModelNodeData<MyNodeKey>
  {
    public ICommand CommandExpandCollapse { get; set; }
    
    private bool _isExpanded = false;
    public bool IsExpanded
    {
      get => _isExpanded;
      set
      {
        var old = _isExpanded;
        _isExpanded = value;
        RaisePropertyChanged(nameof(IsExpanded), old, value);
      }
    }
    private bool _wasExpanded = false;
    public bool WasExpanded
    {
      get => _wasExpanded;
      set
      {
        var old = _wasExpanded;
        _wasExpanded = value;
        RaisePropertyChanged(nameof(WasExpanded), old, value);
      }
    }
    
    public NodeData(MyNodeKey key)
    {
      Key = key;
      IsExpanded = true;
      CommandExpandCollapse = new CommandBase(CanExecuteCommandExpandCollapse, ExecuteCommandExpandCollapse);
    }
    public bool CanExecuteCommandExpandCollapse(object parameter)
    {
      return true;
    }
    public void ExecuteCommandExpandCollapse(object parameter)
    {
      IsExpanded = !IsExpanded;
    }
  }
  
  public partial class MainWindow : Window
  {

    ObservableCollection<NodeData> NodesSource = new ObservableCollection<NodeData>();
    private int i = 0;
    

    public MainWindow()
    {
      InitializeComponent();
      var model = new GraphModel<NodeData, MyNodeKey>();
      model.NodesSource = NodesSource;
      myDiagram.Model = model;
      model.Modifiable = true;  
    }

    private void AddSubGroup(object sender, RoutedEventArgs e)
    {
      NodeData Root = null;
      if (myDiagram.SelectedPart is Group)
      {
        Root = myDiagram.SelectedPart.Data as NodeData;
      }
      if (Root is null)
      {
        return;
      }
      var subGroup = new NodeData(new MyNodeKey("SubGroup_" + (++i)))
      {
        IsSubGraph = true,
        SubGraphKey = Root.Key,
      };
      NodesSource.Add(subGroup);
      myDiagram.LayoutDiagram();
    }
    
    private void AddRootToModel(object sender, RoutedEventArgs e)
    {
      var rootNode = new NodeData(new MyNodeKey("Group_" + i))
      {
        IsSubGraph = true,
      };

      NodesSource.Add(rootNode);
      myDiagram.LayoutDiagram();
    }
    
    private void DoLayout(object sender, RoutedEventArgs e)
    {
      myDiagram.LayoutDiagram();
    }
  }
}

So You have a functionnal Programm.

1/ Click on “Add root to Model”
2/ Select group added
3/ Click on “Add to Selected”
4/ Select sub group added
5/ Repeat steps 3/ and 4/ at least twice
6/ Form the deeper group Click on “Expand/Collpase” to collapse all group one after the other one
7/ Expand last visible group
→ 3 groups are now visible instead of 2

Status of linked property WasExpandedGroup is never set in the class NodeData

So my questions are

gowpf:Group.WasSubGraphExpanded="{Binding Path=Data.WasExpanded, Mode=TwoWay}"

works?

I see that you have defined your own data properties: IsExpanded and WasExpanded. That’s OK, but you don’t need to since IsSubGraphExpanded and WasSubGraphExpanded are already defined on the GraphLinksModel.NodeData class.

But if you do implement such properties, the setter needs to check that the value has actually changed. It must not RaisePropertyChanged unless the property has changed. This is necessary to avoid some potential undesirable recursion. i.e. infinite loops.

Furthermore the data should be simple – there shouldn’t be visual objects or commands as property values. The whole point of having models and views is to keep them separate – models cannot know about views.

But again, if you really want that anyway, you have to implement the commands to execute a transaction, because all model changes (once the model has been assigned to a diagram) must be performed in a transaction.

That also applies to your AddSubGroup and AddRootToModel button event handlers.

Once all of those things are cleaned up, I do see the problem that you are pointing out. I suspect that the order in which the bindings are being evaluated cause the state to be wrong at the time a change is made. I’ll look into this further when I have more time later.

Hi Walter,

In the official project changes are notify only if properties have been changed, so this point is already managed (I’ve done it quick and dirty).

If I’ve implemented a IsExpanded property, it’s to be able to “synchronized” group expanded status for the same data on different diagram.
From now I’ve never used StartTransaction/CommitTransaction (what’s bad), so regarding to your remarks this point, what could be the consequences, if I do not use it ? In any case I have fix it.

I’m modifying the demo to solve all your remarks (I hope), but (if I’m not wrong) except by setting the command in the main view (in this case class MainWindow) I do not see how I should use the Start/Commit transaction principle as Property is on the model and Diagram on the view.

Here are the modified class:

<Window x:Class="TestGoWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestGoWpf"
        xmlns:gowpf="http://schemas.nwoods.com/GoXam"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1200">

  <Window.Resources>

    <DataTemplate x:Key="GroupTemplate" DataType="{x:Type local:NodeData}">
      <Border x:Name="myBorder"
              CornerRadius="0" BorderThickness="2" BorderBrush="Black" Background="Orange"
              gowpf:Part.SelectionAdorned="True"
              gowpf:Group.LocationElementName="myGroupPanel"
              gowpf:Group.IsSubGraphExpanded="{Binding Path=Data.Key.IsExpanded, Mode=TwoWay}"
              gowpf:Group.WasSubGraphExpanded="{Binding Path=Data.Key.WasExpanded, Mode=TwoWay}">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>

          <StackPanel Grid.Row="0">
            <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
              <TextBlock Text="{Binding Path=Data.Key.Name}" FontWeight="Bold" />
              
              <WrapPanel>
                <TextBlock Text="IsExpanded : "/>
                <TextBlock Text="{Binding Path=Data.Key.IsExpanded}"/>
              </WrapPanel>
              <WrapPanel>
                <TextBlock Text="WasExpanded : "/>
                <TextBlock Text="{Binding Path=Data.Key.WasExpanded}"/>
              </WrapPanel>
              <WrapPanel>
                <!-- This is the only way I see to be able to use command and Start/Commit -->
                <Button x:Name="myCollapseExpandButtonCommand"
                        Command="{Binding CommandExpandCollapse, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}"
                        CommandParameter="{Binding}"
                        Content="Expand/Collapse"
                        Margin="0 0 5 0" />
                <!-- Version with button (same as your Grouping.xaml example)-->
                <Button x:Name="myCollapseExpandButtonCommand2"
                        Click="ButtonBase_OnClick"
                        Content="Expand/Collapse"
                        Margin="0 0 5 0" />

              </WrapPanel>
            </StackPanel>

          </StackPanel>
          <gowpf:GroupPanel x:Name="myGroupPanel" 
                            Grid.Row="1"
                            Padding="5" 
                            MinWidth="20" 
                            MinHeight="20" 
                            Background="LightGray" >
          </gowpf:GroupPanel>
        </Grid>
        <gowpf:Group.Layout >
          <gowpf:GridLayout WrappingColumn="4" />
        </gowpf:Group.Layout>
      </Border>
    </DataTemplate>

  </Window.Resources>

  <Grid Margin="0 10">
    <Grid.RowDefinitions>
      <RowDefinition Height="auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel>
      <WrapPanel>
        <Button Click="AddRootToModel"  Content="Add Root to Model"/>
        <Button Click="AddSubGroup"  Content="Add to Selected"/>
        <Button Click="DoLayout"  Content="Do Layout"/>
        <WrapPanel>
          <TextBlock x:Name="Width" Text="{Binding }" />
        </WrapPanel>
      </WrapPanel>
    </StackPanel>
    <gowpf:Diagram Grid.Row="1"
                   x:Name="myDiagram" 
                   Padding="10"                  
                   GroupTemplate="{StaticResource GroupTemplate}"
                   AllowMove="True"
                   Background="WhiteSmoke">
      <gowpf:Diagram.Layout>
       <gowpf:GridLayout WrappingColumn="4" ></gowpf:GridLayout>
      </gowpf:Diagram.Layout>
    </gowpf:Diagram>
  </Grid>
</Window>

using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using System;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TestGoWpf
{
  internal class CommandBase : ICommand
  {
    #region properties

    public event EventHandler CanExecuteChanged
    {
      add
      {
        CommandManager.RequerySuggested += value;
      }
      remove
      {
        CommandManager.RequerySuggested -= value;
      }
    }
    
    private Func<object, bool> _CanExecute;
    private Action<object> _Execute;

    #endregion properties

    #region ctor

    public CommandBase(Func<object, bool> canExecute, Action<object> execute)
    {
      _CanExecute = canExecute;
      _Execute = execute;
    }
    #endregion ctor

    #region ICommand

    public bool CanExecute(object parameter)
    {
      return _CanExecute(parameter);
    }

    public void Execute(object parameter)
    {
      _Execute(parameter);
    }
    #endregion ICommand
  }

  public class MyNodeKey : DependencyObject
  {
    public string Name
    {
      get { return (string)GetValue(NameProperty); }
      set { SetValue(NameProperty, value); }
    }
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register("Name", typeof(string), typeof(MyNodeKey), new PropertyMetadata(""));
    
    public bool IsExpanded
    {
      get { return (bool) GetValue(IsExpandedProperty); }
      set { SetValue(IsExpandedProperty, value); }
    }
    public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
      "IsExpanded", typeof(bool), typeof(MyNodeKey), new PropertyMetadata(true));
    
    public bool WasExpanded
    {
      get { return (bool) GetValue(WasExpandedProperty); }
      set { SetValue(WasExpandedProperty, value); }
    }
    public static readonly DependencyProperty WasExpandedProperty = DependencyProperty.Register(
      "WasExpanded", typeof(bool), typeof(MyNodeKey), new PropertyMetadata(true));

    public MyNodeKey(string name)
    {
      Name = name;
    }

    public override string ToString()
    {
      return Name;
    }

  }

  public class NodeData : GraphModelNodeData<MyNodeKey>
  {
    public NodeData(MyNodeKey key, NodeData parent = null)
    {
      Key = key;
    }
  }

  public partial class MainWindow : Window
  {

    public ICommand CommandExpandCollapse { get; set; }

    ObservableCollection<NodeData> NodesSource = new ObservableCollection<NodeData>();
    private int i = 0;

    public MainWindow()
    {
      InitializeComponent();
      var model = new GraphModel<NodeData, MyNodeKey>();
      model.NodesSource = NodesSource;
      myDiagram.Model = model;
      model.Modifiable = true;
      CommandExpandCollapse = new CommandBase(CanExecuteCommandExpandCollapse, ExecuteCommandExpandCollapse);
    }

    private void AddSubGroup(object sender, RoutedEventArgs e)
    {
      NodeData Root = null;
      if (myDiagram.SelectedPart is Group)
      {
        Root = myDiagram.SelectedPart.Data as NodeData;
      }
      if (Root is null)
      {
        return;
      }
      var subGroup = new NodeData(new MyNodeKey("SubGroup_" + (++i)), Root)
      {
        IsSubGraph = true,
        SubGraphKey = Root.Key,
      };
      myDiagram.StartTransaction("Add SubGroup");
      NodesSource.Add(subGroup);
      myDiagram.CommitTransaction("Add SubGroup");
      myDiagram.LayoutDiagram();
    }
    
    private void AddRootToModel(object sender, RoutedEventArgs e)
    {
      var rootNode = new NodeData(new MyNodeKey("Group_" + i))
      {
        IsSubGraph = true,
      };

      NodesSource.Add(rootNode);
      myDiagram.LayoutDiagram();
    }
    
    private void DoLayout(object sender, RoutedEventArgs e)
    {
      myDiagram.LayoutDiagram();
    }


    public bool CanExecuteCommandExpandCollapse(object parameter)
    {
      return true;
    }
    public void ExecuteCommandExpandCollapse(object parameter)
    {
      // For me here I loose the benefit of using a Command 
      if (parameter is NodeData nodeData)
      {
        myDiagram.StartTransaction("ExpandCoLlapse");
        nodeData.Key.IsExpanded = !nodeData.Key.IsExpanded;
        myDiagram.CommitTransaction("ExpandCoLlapse"); 
      }
    }
    // version with click
    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
      Button button = (Button) sender;
      Group sg = Part.FindAncestor<Group>(button);
      if (sg != null)
      {
        NodeData nodeData = (NodeData) sg.Data;
        
        myDiagram.StartTransaction("CollapseExpand");
        nodeData.Key.IsExpanded = !nodeData.Key.IsExpanded;
        myDiagram.CommitTransaction("CollapseExpand");
      }
    }
  }
}

One potential problem is that property setters might be called for different reasons, especially if they are the source of a TwoWay Binding. That’s why it’s important to make sure that the property setter never raises a property changed event when it hasn’t changed value.

Your commands could use the main window’s Diagram to perform the transaction.

I’m still working on this.

This will be fixed in the 3.0.3 release.

Hi Walter,

I’ve another problem regarding to insert node between Nodes

Step to reproduce

  1. click “Add Root to Model”
  2. Select “Group_0”
  3. click “Add to Selected”
  4. Select “SubGroup_1”
  5. Click “Expand/Collapse” on “Group_0”
    SubGroup_1 is still selected, Group_0 is collapsed
  6. click “Insert before Selected”
  7. Click “Expand/Collapse” on “Group_0”
    SubGroup_1 is still selected, new group “SubGroup_2” has been added between Group_0 and SubGroup_1 - OK
    SubGroup1 has a IsExpanded displayed status = False (?) Not changed from me, no notification received on liked property
  8. Click “Expand/Collapse” on “Group_0”
    SubGroup_1 is still selected, Group_0 is collapsed
  9. click “Insert before Selected”
    Sub_group1 is now displayed - NOK
    It’s SubGraphKey is new Group added, “SubGroup_3” has expected
    Its status Is Expanded is False (not change from me)

image

Remark 1: If Group_0 is expanded then all node are well done placed.
Result after clicking on “Expand/Collapse” on “Group_0”
image

Remark 2: If you ignore all intermediate steps “Expand/Collapse”, “SubGroup_1” is not displayed
Result without intermediate steps “Expand/Collapse”
image

Any Idea?

PS I’m using the new 304 alpha dll

using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using System;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TestGoWpf
{
  internal class CommandBase : ICommand
  {
    #region properties

    public event EventHandler CanExecuteChanged
    {
      add
      {
        CommandManager.RequerySuggested += value;
      }
      remove
      {
        CommandManager.RequerySuggested -= value;
      }
    }
    
    private Func<object, bool> _CanExecute;
    private Action<object> _Execute;

    #endregion properties

    #region ctor

    public CommandBase(Func<object, bool> canExecute, Action<object> execute)
    {
      _CanExecute = canExecute;
      _Execute = execute;
    }
    #endregion ctor

    #region ICommand

    public bool CanExecute(object parameter)
    {
      return _CanExecute(parameter);
    }

    public void Execute(object parameter)
    {
      _Execute(parameter);
    }
    #endregion ICommand
  }

  public class MyNodeKey : DependencyObject
  {
    public ICommand CommandExpandCollapse { get; set; }

    public string Name
    {
      get { return (string)GetValue(NameProperty); }
      set { SetValue(NameProperty, value); }
    }
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register("Name", typeof(string), typeof(MyNodeKey), new PropertyMetadata(""));
    
    public bool IsExpanded
    {
      get { return (bool) GetValue(IsExpandedProperty); }
      set { SetValue(IsExpandedProperty, value); }
    }
    public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
      "IsExpanded", typeof(bool), typeof(MyNodeKey), new PropertyMetadata(true));
    
    public MyNodeKey(string name)
    {
      Name = name;
      CommandExpandCollapse = new CommandBase(CanExecuteCommandExpandCollapse, ExecuteCommandExpandCollapse);
    }

    public override string ToString()
    {
      return Name;
    }

    public bool CanExecuteCommandExpandCollapse(object parameter)
    {
      return true;
    }
    public void ExecuteCommandExpandCollapse(object parameter)
    {
      IsExpanded = !IsExpanded;
    }

  }

  public class NodeData : GraphModelNodeData<MyNodeKey>
  {
    public NodeData(MyNodeKey key)
    {
      Key = key;
    }
  }

  public partial class MainWindow : Window
  {
    ObservableCollection<NodeData> NodesSource = new ObservableCollection<NodeData>();
    private int i = 0;

    public MainWindow()
    {
      InitializeComponent();
      var model = new GraphModel<NodeData, MyNodeKey>();
      model.NodesSource = NodesSource;
      myDiagram.Model = model;
      model.Modifiable = true;
    }

    private void AddSubGroup(object sender, RoutedEventArgs e)
    {
      NodeData Root = null;
      if (myDiagram.SelectedPart is Group)
      {
        Root = myDiagram.SelectedPart.Data as NodeData;
      }

      if (Root is null)
      {
        return;
      }

      var subGroup = new NodeData(new MyNodeKey("SubGroup_" + (++i)))
      {
        IsSubGraph = true,
        SubGraphKey = Root.Key,
      };
      myDiagram.StartTransaction("Add SubGroup");
      NodesSource.Add(subGroup);
      myDiagram.CommitTransaction("Add SubGroup");
      myDiagram.LayoutDiagram();
    }



    private void InsertBerforeSubGroup(object sender, RoutedEventArgs e)
    {
      NodeData node1 = null;
      if (myDiagram.SelectedPart is Group)
      {
        node1 = myDiagram.SelectedPart.Data as NodeData;
      }

      if (node1 is null)
      {
        return;
      }
      
      var newGroup = new NodeData(new MyNodeKey("SubGroup_" + (++i)))
      {
        IsSubGraph = true,
        SubGraphKey = node1.SubGraphKey,
        IsSubGraphExpanded = true,
      };
      
      NodesSource.Add(newGroup);
      myDiagram.LayoutDiagram();
      myDiagram.StartTransaction("SetSubGraphKey");
      node1.SubGraphKey =  newGroup.Key;
      myDiagram.CommitTransaction("SetSubGraphKey");
    }

    private void AddRootToModel(object sender, RoutedEventArgs e)
    {
      var rootNode = new NodeData(new MyNodeKey("Group_" + i))
      {
        IsSubGraph = true,
      };

      NodesSource.Add(rootNode);
      myDiagram.LayoutDiagram();
    }

    private void DoLayout(object sender, RoutedEventArgs e)
    {
      myDiagram.LayoutDiagram();
    }
    
    // version with click
    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
      Button button = (Button) sender;
      Group sg = Part.FindAncestor<Group>(button);
      if (sg != null)
      {
        NodeData nodeData = (NodeData) sg.Data;

        myDiagram.StartTransaction("CollapseExpand");
        nodeData.Key.IsExpanded = !nodeData.Key.IsExpanded;
        myDiagram.CommitTransaction("CollapseExpand");
      }
    }

    private void Clear(object sender, RoutedEventArgs e)
    {
      NodesSource.Clear();
    }
  }
}
<Window x:Class="TestGoWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestGoWpf"
        xmlns:gowpf="http://schemas.nwoods.com/GoXam"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1200">

  <Window.Resources>

    <DataTemplate x:Key="GroupTemplate" DataType="{x:Type local:NodeData}">
      <Border x:Name="myBorder"
              CornerRadius="0" BorderThickness="2" BorderBrush="Black" Background="Orange"
              gowpf:Part.SelectionAdorned="True"
              gowpf:Group.LocationElementName="myGroupPanel"
              gowpf:Group.IsSubGraphExpanded="{Binding Path=Data.Key.IsExpanded, Mode=TwoWay}"
              >
        <Grid><!--gowpf:Group.WasSubGraphExpanded="{Binding Path=Data.Key.WasExpanded, Mode=TwoWay}"-->
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
          </Grid.RowDefinitions>

          <StackPanel Grid.Row="0">
            <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
              <TextBlock Text="{Binding Path=Data.Key.Name}" FontWeight="Bold" />

              <TextBlock Text="{Binding Path=Data.SubGraphKey}" FontWeight="Bold" />
              
              <WrapPanel>
                <TextBlock Text="IsExpanded : "/>
                <TextBlock Text="{Binding Path=Data.Key.IsExpanded}"/>
              </WrapPanel>
              <!--<WrapPanel>
                <TextBlock Text="WasExpanded : "/>
                <TextBlock Text="{Binding Path=Data.Key.WasExpanded}"/>
              </WrapPanel>-->
              <WrapPanel>
                <!-- This is the only way I see to be able to use command and Start/Commit -->
                <Button x:Name="myCollapseExpandButtonCommand"
                        Command="{Binding Data.Key.CommandExpandCollapse}"
                        CommandParameter="{Binding}"
                        Content="Expand/Collapse"
                        Margin="0 0 5 0" />
                <!-- Version with button (same as your Grouping.xaml example)-->
                <Button x:Name="myCollapseExpandButtonCommand2"
                        Click="ButtonBase_OnClick"
                        Content="Expand/Collapse"
                        Margin="0 0 5 0" />

              </WrapPanel>
            </StackPanel>

          </StackPanel>
          <gowpf:GroupPanel x:Name="myGroupPanel" 
                            Grid.Row="1"
                            Padding="5" 
                            MinWidth="20" 
                            MinHeight="20" 
                            Background="LightGray" >
          </gowpf:GroupPanel>
        </Grid>
        <gowpf:Group.Layout >
          <gowpf:GridLayout WrappingColumn="4" />
        </gowpf:Group.Layout>
      </Border>
    </DataTemplate>

  </Window.Resources>

  <Grid Margin="0 10">
    <Grid.RowDefinitions>
      <RowDefinition Height="auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel>
      <WrapPanel>
        <Button Click="AddRootToModel"  Content="Add Root to Model"/>
        <Button Click="AddSubGroup"  Content="Add to Selected"/>
        <Button Click="InsertBerforeSubGroup"  Content="Insert before Selected"/>
        <Button Click="DoLayout"  Content="Do Layout"/>
        <Button Click="Clear"  Content="Clear"/>
        <WrapPanel>
          <TextBlock x:Name="Width" Text="{Binding }" />
        </WrapPanel>
      </WrapPanel>
    </StackPanel>
    <gowpf:Diagram Grid.Row="1"
                   x:Name="myDiagram" 
                   Padding="10"                  
                   GroupTemplate="{StaticResource GroupTemplate}"
                   AllowMove="True"
                   Background="WhiteSmoke">
      <gowpf:Diagram.Layout>
       <gowpf:GridLayout WrappingColumn="4" ></gowpf:GridLayout>
      </gowpf:Diagram.Layout>
    </gowpf:Diagram>
  </Grid>
</Window>

I’m concerned that your key type is mutable. That’s not right, even if that has nothing to do with the expansion/collapse of groups and the visibility of their members.

The IsExpanded state really belongs on the NodeData class.

I’m still looking at the behavior. Ah, yes, there is a bug there. I’ll build an alpha kit for you again, but with the correct version number.