Hi,
You’ll want to either allow the DiagramPanel.Scale to change normally for a zoom and compensate in the size of your nodes, or you’ll want to not have the DiagramPanel.Scale change at all but explicitly have your nodes move closer/farther apart and change the scale of your background.
Are you using Silverlight or WPF?
Hi,
Well, that way should work. Furthermore I believe you can literally “reverse scale” the contents of the Nodes by applying a ScaleTransform to the root element of the node (not to the Node itself), instead of setting the Width and Height of the node.
However, I haven’t tried this to see if it works, particularly if you allow nodes to be resized or rotated interactively.
I’m working on a sample that demonstrates one way of doing what you want. It replaces the standard zooming mechanism of modifying the DiagramPanel.Scale with a separate mechanism that effectively changes the Node.Location of all of the nodes.
Maybe later today…
OK, here is a new sample in the next couple of replies. The two files are Zoom.xaml and Zoom.xaml.cs.
The basic idea is that the “nominal” location for each node is in the node data, as the Location property. Normally there is a data binding to get the location information from the data to the Node:
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
However, in this sample, we have added a Binding data Converter:
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay, Converter={StaticResource myDiagramSpacedLocationConverter}}"
This SpacedLocationConverter has a Space dependency property that scales up the Node.Location value. Each Diagram has to have its own SpacedLocationConverter. That also means that you won’t be able to share the same node template between different diagrams.
There is also a custom CommandHandler that reimplements the control-mouse-wheel and zoom keyboard commands to change the SpacedLocationConverter.Space property instead of changing the DiagramPanel.Scale property.
The XAML is for WPF, but naturally there aren’t many differences for Silverlight.
The CS code is precisely the same for both WPF and for Silverlight.
This was developed using version 1.1. I don’t know if this will work with 1.0, but off hand I don’t know of any reasons why it wouldn’t.
<UserControl.Resources>
<local:SpacedLocationConverter x:Key=“myDiagramSpacedLocationConverter” />
<DataTemplate x:Key="StateTemplate">
<go:NodePanel Sizing="Auto"
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay,
Converter={StaticResource myDiagramSpacedLocationConverter}}"
go:Part.SelectionAdorned="True">
<go:NodeShape go:NodePanel.Figure="RoundedRectangle"
Fill="LightBlue" Stroke="Black" StrokeThickness="1"
go:Node.PortId=""
go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
<TextBlock Text="{Binding Path=Data.Key}" Margin="10"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</go:NodePanel>
</DataTemplate>
</UserControl.Resources>
/* Copyright © Northwoods Software Corporation, 2008-2010. All Rights Reserved. */
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
namespace Zoom {
public partial class Zoom : UserControl {
public Zoom() {
InitializeComponent();
// create the diagram's data model
var model = new GraphLinksModel<State, String, String, Transition>();
model.NodesSource = new ObservableCollection<State>() {
new State() { Key="S1" },
new State() { Key="S2" },
new State() { Key="S3" },
};
model.LinksSource = new ObservableCollection<Transition>() {
new Transition() { From="S1", To="S2" },
new Transition() { From="S1", To="S3" },
};
model.Modifiable = true; // let the user modify the graph
model.HasUndoManager = true; // support undo/redo
myDiagram.Model = model;
// initialize the converter to know about the Diagram
var conv = Diagram.FindResource<SpacedLocationConverter>(this, "myDiagramSpacedLocationConverter");
if (conv != null) conv.Diagram = myDiagram;
}
}
// This redefines mouse wheel and keyboard zoom commands to change
// the SpacedLocationConverter.Space, rather than the DiagramPanel.Scale.
public class SpaceZoomCommandHandler : CommandHandler {
// This needs to be initialized!
public SpacedLocationConverter Conv { get; set; }
public override void DecreaseZoom(object param) {
this.Conv.Space = Math.Max(0.1, this.Conv.Space / 1.05);
}
public override void IncreaseZoom(object param) {
this.Conv.Space = Math.Min(10.0, this.Conv.Space * 1.05);
}
public override void Zoom(object param) {
this.Conv.Space = (param is double ? (double)param : 1.0);
}
}
// This is used in the Node.Location binding in the NodeTemplate.
// There has to be one of these for each Diagram –
// thus the NodeTemplate cannot be shared by different Diagrams.
#if !SILVERLIGHT
[ValueConversion(typeof(Point), typeof(Point))]
#endif
public class SpacedLocationConverter : DependencyObject, IValueConverter {
// This needs to be initialized!
public Diagram Diagram { get; set; }
static SpacedLocationConverter() {
SpaceProperty = DependencyProperty.Register("Space", typeof(double), typeof(SpacedLocationConverter),
new PropertyMetadata(1.0, OnSpaceChanged));
}
private static readonly DependencyProperty SpaceProperty;
public double Space {
get { return (double)GetValue(SpaceProperty); }
set { SetValue(SpaceProperty, value); }
}
private static void OnSpaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var conv = (SpacedLocationConverter)d;
if ((double)e.NewValue <= 0.01) { // don't allow negative or very small value
conv.Space = 1;
} else if (conv.Diagram != null) {
foreach (Node n in conv.Diagram.Nodes) {
// re-evaluate the binding to get the updated Location for the Node
FrameworkElement root = n.VisualElement;
if (root != null) {
BindingExpression expr = root.GetBindingExpression(Node.LocationProperty);
if (expr != null) root.SetBinding(Node.LocationProperty, expr.ParentBinding);
}
}
conv.Diagram.Panel.UpdateDiagramBounds();
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
Point p = (Point)value;
return new Point(p.X*this.Space, -p.Y*this.Space);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
Point p = (Point)value;
return new Point(p.X/this.Space, -p.Y/this.Space);
}
}
// the data for each node; the predefined data class is enough for this sample
public class State : GraphLinksModelNodeData {
public State() {
this.Key = “State”;
}
}
// the data for each link
public class Transition : GraphLinksModelLinkData<String, String> { }
}
Hi,
Are you using GoXam 1.0? The change in 1.1 is that the control-mouse-wheel events go through the CommandHandler commands for zooming.
Sorry, I forgot about that. If you want this code to work as-is in 1.0, you’ll need to define a replacement ToolManager that overrides StandardMouseWheel to do what you want.