Overview empty after diagram bounds increase

I’m seeing something strange with GoXam for WPF 1.3.5. I have a Diagram that is observed by an Overview. Whenever the Diagram is resized, the Overview stops displaying data. The purple indicator box is gone and no matter how much I scroll around, I can’t get it or any of my nodes back. I get into this state by starting with an empty diagram, adding one node at the top, adding a second node at the bottom, and then dragging the second node further south so a scroll bar appears.

I noticed that the only sample which uses the Overview control is one with a static diagram. How can I get my Overview to behave with an editable diagram?

Here’s a few code snippets:
Diagram control template which places an Overview control in the lower right corner without obscuring the scroll bars:


<ControlTemplate TargetType="go:Diagram"
  x:Key="DiagramTemplate">
  <Border Background="{TemplateBinding Background}"
    BorderBrush="{TemplateBinding BorderBrush}"
    BorderThickness="{TemplateBinding BorderThickness}">
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="3*" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="3*" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
     <ScrollViewer HorizontalScrollBarVisibility="Auto"
       VerticalScrollBarVisibility="Auto"
       CanContentScroll="True"
       Grid.RowSpan="2"
       Grid.ColumnSpan="2">
      <i:Interaction.Behaviors>
        <behaviors:ScrollBarVisibilityBehavior TargetObject="{Binding ElementName=overviewContainer}" />
      </i:Interaction.Behaviors>
      <go:DiagramPanel x:Name="Panel"
        Stretch="{TemplateBinding Stretch}"
        Padding="{TemplateBinding Padding}"
        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
        DiagramBounds="{Binding DiagramBounds, Mode=TwoWay}"
        MinimumScale="0.2"
        MaximumScale="4.0">
        <i:Interaction.Behaviors>
          <behaviors:ObserveDiagramBehavior Observer="{Binding ElementName=overview}" />
        </i:Interaction.Behaviors>
      </go:DiagramPanel>
      </ScrollViewer>
      <StackPanel x:Name="overviewContainer"
        Grid.Row="1"
        Grid.Column="1">
        <Button Content="Close" />
        <Border BorderThickness="1"
          BorderBrush="Silver">
          <go:Overview x:Name="overview" />
        </Border>
      </StackPanel>
    </Grid>
  </Border>
</ControlTemplate>

Here’s the behavior that hooks up the Overview to the Diagram via the DiagramPanel:

[code]
using System.Windows;
using System.Windows.Interactivity;
using Northwoods.GoXam;

namespace Behaviors
{
internal class ObserveDiagramBehavior : Behavior
{
public static readonly DependencyProperty ObserverProperty =
DependencyProperty.Register(“Observer”, typeof (Overview), typeof (ObserveDiagramBehavior), new PropertyMetadata(default(Overview)));

public Overview Observer
{
  get { return (Overview) GetValue(ObserverProperty); }
  set { SetValue(ObserverProperty, value); }
}

protected override void OnAttached()
{
  base.OnAttached();
  AssociatedObject.Loaded += OnLoaded;
}

protected override void OnDetaching()
{
  base.OnDetaching();
  AssociatedObject.Loaded -= OnLoaded;

  if (Observer != null)
    Observer.Observed = null;
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
  if (AssociatedObject.Diagram == null || Observer == null)
    return;

  Observer.Observed = AssociatedObject.Diagram;
}

}
}[/code]

Here’s the ScrollBarVisibilityBehavior for completeness, but I’m fairly certain this isn’t playing into my problem:


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Behaviors
{
  internal class ScrollBarVisibilityBehavior : Behavior<ScrollViewer>
  {
    public static readonly DependencyProperty TargetObjectProperty =
      DependencyProperty.Register("TargetObject", typeof (FrameworkElement), typeof (ScrollBarVisibilityBehavior), new PropertyMetadata(default(FrameworkElement)));

    public FrameworkElement TargetObject
    {
      get { return (FrameworkElement) GetValue(TargetObjectProperty); }
      set { SetValue(TargetObjectProperty, value); }
    }

    private const double Epsilon = 0.0001;

    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += OnLoaded;
    }

    protected override void OnDetaching()
    {
      base.OnDetaching();
      AssociatedObject.Loaded -= OnLoaded;
      AssociatedObject.ScrollChanged -= OnFirstScrollChanged;
      AssociatedObject.ScrollChanged -= OnScrollChanged;
    }

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
      AssociatedObject.ScrollChanged += OnFirstScrollChanged;
    }

    private void OnFirstScrollChanged(object sender, ScrollChangedEventArgs e)
    {
      // The first ScrollChanged event always seems to be layout related, so skip it.
      AssociatedObject.ScrollChanged -= OnFirstScrollChanged;
      AssociatedObject.ScrollChanged += OnScrollChanged;
    }

    private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
    {
      if (Math.Abs(e.ViewportWidthChange) < Epsilon && Math.Abs(e.ViewportHeightChange) < Epsilon)
        return;

      var margin = TargetObject.Margin;

      if (Math.Abs(e.ViewportWidthChange) > Epsilon)
        margin.Right = e.ViewportWidthChange < 0 ? Math.Abs(e.ViewportWidthChange) : 0;

      if (Math.Abs(e.ViewportHeightChange) > Epsilon)
        margin.Bottom = e.ViewportHeightChange < 0 ? Math.Abs(e.ViewportHeightChange) : 0;

      TargetObject.Margin = margin;
    }
  }
}

Furthermore, I tried to initialize the Overview.Observed property in a different way. My hope was that the Overview would be actively observing only when it was visible, but any attempt to set the Overview.Observed property significantly after the diagram was loaded resulted in no Overview at all. (I saw only the button and a silver border with no content – the Overview control did not display.)

I replaced the previous Overview portion of my template with the following:


<go:Overview x:Name="overview">
  <i:Interaction.Triggers>
    <ei:PropertyChangedTrigger Binding="{Binding Visibility, ElementName=overviewContainer}">
      <behaviors:ObserveDiagramBehavior2 Observed="{Binding Diagram, ElementName=Panel}" />
    </ei:PropertyChangedTrigger>
  </i:Interaction.Triggers>
</go:Overview>

Where ObserveDiagramBehavior2 is defined as:


using System.Windows;
using System.Windows.Interactivity;
using Northwoods.GoXam;

namespace Behaviors
{
  internal class ObserveDiagramBehavior2 : TargetedTriggerAction<Overview>
  {
    public static readonly DependencyProperty ObservedProperty =
      DependencyProperty.Register("Observed", typeof(Diagram), typeof(ObserveDiagramBehavior2), new PropertyMetadata(default(Diagram)));

    public Diagram Observed
    {
      get { return (Diagram)GetValue(ObservedProperty); }
      set { SetValue(ObservedProperty, value); }
    }

    protected override void Invoke(object parameter)
    {
      if (Target == null)
        return;

      if (!(parameter is DependencyPropertyChangedEventArgs))
        return;

      var eventArgs = (DependencyPropertyChangedEventArgs)parameter;
      if (!(eventArgs.NewValue is Visibility))
        return;

      Target.Observed = ((Visibility)eventArgs.NewValue) == Visibility.Visible ? Observed : null;
    }
  }
}

I verified under a debugger that the expected Diagram object was assigned to the Overview.Observed property when the containing StackPanel became visible. I’m quite surprised that no overview appeared in my display in this circumstance.

I haven’t gone through your code yet, but Overview controls do work with Observed Diagrams whose content (i.e. model) changes size. You can verify this by restoring the Diagram.DraggingTool in the Org Chart (static) sample – just remove the line that sets it to “{x:Null}”.

I do have a custom DraggingTool so I thought that might be a problem. I removed it and used the built-in dragging tool, but the problem is unchanged. I look forward to hearing more once you’ve had time to digest my code sample.

No, I would not expect any tool to be the problem in this situation.

How is the Overview being shown or hidden? Is it being Unloaded and Loaded? I could imagine that that could cause a problem. You can attach event handlers for those events to see if they are being called, and if so, when they are called relative to the setting of Overview.Observed.

To show or hide the Overview, I’m toggling the Visibility of the StackPanel that contains it. I’ve got just a simple ToggleButton that is two-way bound to a bool? property on my view model. I then bound the Visibility of my overviewContainer (from my previously posted control template) to that property and am running it through the built-in BooleanToVisibilityConverter.

I attached Loaded and Unloaded handlers as you requested. The Overview (which is in a container with Visibility.Collapsed) is being loaded and unloaded with the Diagram. That’s all working as I would expect.

To confirm my understanding of what you said: no Unloaded event occurs until both the Overview and the main Diagram “go away forever”, yes?

OK, I can see if I can reproduce the problem. I’ll try first with a minimal example.

Yes, your understanding is correct.

I did some further exploration and came up short. I modified the OrgChart sample so the data is loaded from the click event of a button. I also added a toggle button that toggles the visibility of the Border that holds the Overview. Finally I modified the Overview so it sets Overview.Observed to the Diagram when the Overview becomes visible and sets it to null when it is collapsed. I tried loading the diagram and then the overview as well as loading the reverse (overview then diagram). Both worked just fine.

The only difference I can see is that I’m using a ControlTemplate while the Org Chart sample uses just controls.

edit: Here’s the modified OrgChart code for reference. This simply changes the target of Overview.Observed based on the visibility changes of its container. You’ll need to add references to Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll from the Blend SDK to make it work. Simply add the following namespaces:


xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

then give the main Border (just below the “an overview of the main org chart” comment) the name “overviewContainer” and do the following to the overview:


<go:Overview x:Name="myOverview" Width="200" Height="100">
  <!--Observed="{Binding ElementName=myDiagram}">-->
  <i:Interaction.Triggers>
    <ei:PropertyChangedTrigger Binding="{Binding Visibility, ElementName=overviewContainer}">
      <i:Interaction.Behaviors>
        <ei:ConditionBehavior>
          <ei:ConditionalExpression>
            <ei:ComparisonCondition LeftOperand="{Binding Visibility, ElementName=overviewContainer}" RightOperand="Visible" />
          </ei:ConditionalExpression>
        </ei:ConditionBehavior>
      </i:Interaction.Behaviors>
      <ei:ChangePropertyAction PropertyName="Observed" Value="{Binding ElementName=myDiagram}" />
    </ei:PropertyChangedTrigger>
    <ei:PropertyChangedTrigger Binding="{Binding Visibility, ElementName=overviewContainer}">
      <i:Interaction.Behaviors>
        <ei:ConditionBehavior>
          <ei:ConditionalExpression>
            <ei:ComparisonCondition LeftOperand="{Binding Visibility, ElementName=overviewContainer}" Operator="NotEqual" RightOperand="Visible" />
          </ei:ConditionalExpression>
        </ei:ConditionBehavior>
      </i:Interaction.Behaviors>
      <ei:ChangePropertyAction PropertyName="Observed" Value="{x:Null}" />
    </ei:PropertyChangedTrigger>
  </i:Interaction.Triggers>
  <go:Overview.BoxTemplate> ...

Thanks for the additional information – I hadn’t had any success in reproducing the problem yet.

Could you check some state when the Overview is not displaying anything?
You have already checked that overview.Observed == diagram.

Is the value of overview.Box a Node? What are the values of overview.Box.Visible and overview.Box.Bounds?

What is the value of overview.Panel.FixedBounds? It should be about the same as the diagram.Panel.DiagramBounds, although it could be bigger if it is taking the size of the Overview.Box into account. You can remove the Box from the Overview’s diagram bounds by setting InDiagramBounds=“False” on the Overview.Box. That wouldn’t explain the problem, but might make it easier to track the size of the Overview.Panel.DiagramBounds as you are dragging a Node in the main Diagram.

I have to admit I’m a little confused. I returned to my overview code today (after making changes in other unrelated areas) and I no longer see the issue with the overview not appearing at all. However, I’m definitely still have the issue where the overview does not display anything. So I performed the following steps: opened a new diagram, added a single node, pressed the toggle button that made the overview both become visible and observe the diagram, and then collected the information you requested.

The value of overview.Box is a Node. The other values are:
overview.Box.Visible = true
overview.Box.Bounds is {0,0,681, 851.55}
overview.Panel.FixedBounds is {-10,-10, 701, 871.55}
diagram.Panel.DiagramBounds is {125,117,60,81}
Setting overview.Box.InDiagramBounds to false did make the overview’s box visible, but it seemed to have a height of zero and width equal to the overview. (It was a short, wide purple box.) Toggling InDiagramBounds back to true makes the box disappear from view.

Actually, I should have also asked about the overview.Panel.Scale and overview.Panel.ViewportBounds.

I’m wondering if the Overview is sometimes zoomed in too far or out too far, causing you not to see the single node in the observed Diagram, or not to see the whole Box.

What happens when there are several nodes in the observed diagram, spread out from each other?

Following the same test procedure as above, the overview.Panel.Scale is ~0.24001 and the overview.Panel.ViewportBounds is {-10, -1389.85, 701, 3631.24}.

I then placed an additional 2 nodes: one far to the right of the original and one far below the original, in each case forcing a decent length scrollbar to appear. The overview was unchanged. I also increased and decreased the diagram’s scale just for kicks, again with no visible change to the overview.

The Overview establishes several event handlers on the Observed Diagram. And of course it removes them on the old Diagram when the value of Observed changes.

It’s as if some (all?) of those event handlers have been removed. I wonder if any changes to the observed diagram will affect the overview. Is there anything you can then do to see any change in the Overview, including the values you have seen in the debugger? If you move around the original node in the observed diagram, do you see it move in the overview?

I’m sorry to keep asking questions, but because I cannot reproduce the problem, it’s the best I can do. Unfortunately this kind of investigation is not as likely to be successful.