Saturday, April 11, 2009

Replicating a Visio Page Using a Silverlight Panel

The shape model in Visio simplifies the location of the shape into 3 ShapeSheet cells: PinX, PinY and Angle. This is all you need to drop a shape on a page and rotate it.

It is possible to create a Silverlight custom panel, using attached properties that position XAML elements using the Visio PinX, PinY analogy. Sometimes this is easier than using the Top Left position commands associated with the Silverlight canvas:


#region Attached Properties PinX PinY Angle
public static double GetPinX(DependencyObject obj)
{
return (double)obj.GetValue(PinXProperty);
}

public static void SetPinX(DependencyObject obj, double value)
{
obj.SetValue(PinXProperty, value);
}

// Using a DependencyProperty as the backing store for PinX. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PinXProperty =
DependencyProperty.RegisterAttached("PinX",
typeof(double),
typeof(SketchPanel),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnPinXChanged)));

protected static void OnPinXChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SketchPanel oPanel = obj as SketchPanel;
if (oPanel != null)
oPanel.InvalidateArrange();
}


public static double GetPinY(DependencyObject obj)
{
return (double)obj.GetValue(PinYProperty);
}

public static void SetPinY(DependencyObject obj, double value)
{
obj.SetValue(PinYProperty, value);
}

// Using a DependencyProperty as the backing store for PinY. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PinYProperty =
DependencyProperty.RegisterAttached("PinY",
typeof(double),
typeof(SketchPanel),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnPinYChanged)));

protected static void OnPinYChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SketchPanel oPanel = obj as SketchPanel;
if (oPanel != null)
oPanel.InvalidateArrange();
}


public static double GetAngle(DependencyObject obj)
{
return (double)obj.GetValue(AngleProperty);
}

public static void SetAngle(DependencyObject obj, double value)
{
obj.SetValue(AngleProperty, value);
}

// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty =
DependencyProperty.RegisterAttached("Angle",
typeof(double),
typeof(SketchPanel),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnAngleChanged)));


protected static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
SketchPanel oPanel = obj as SketchPanel;
if (oPanel != null)
oPanel.InvalidateArrange();
}
#endregion

Once you have defined the attachment properties on the panel, you can use these to lay things out in the arrange method:

protected override Size ArrangeOverride(Size finalSize)
{
DoArrange(finalSize);
return base.ArrangeOverride(finalSize);
}


public virtual void DoArrange(Size finalSize)
{
foreach (UIElement oChild in Children)
{
double dX = (double)oChild.GetValue(SketchPanel.PinXProperty);
double dY = (double)oChild.GetValue(SketchPanel.PinYProperty);
double dAngle = (double)oChild.GetValue(SketchPanel.AngleProperty);

Point oPoint = new Point(dX, dY);
SetRenderTransform(oChild, dAngle, oPoint);

oChild.Arrange(new Rect(0, 0, oChild.DesiredSize.Width, oChild.DesiredSize.Height));
}
}



internal TransformGroup SetRenderTransform(UIElement oControl, double dAng, Point pTrans)
{
if (!(oControl.RenderTransform is TransformGroup))
oControl.RenderTransform = new TransformGroup();

TransformGroup oGroup = oControl.RenderTransform as TransformGroup;
if (oGroup.Children.Count == 0)
{
oGroup.Children.Add(new RotateTransform());
oGroup.Children.Add(new TranslateTransform());
}

RotateTransform oRotate = oGroup.Children[0] as RotateTransform;
oRotate.Angle = dAng;

TranslateTransform oTranslate = oGroup.Children[1] as TranslateTransform;
oTranslate.X = pTrans.X;
oTranslate.Y = pTrans.Y;

return oGroup;
}

In the next post we will explore extending the model to be even more Visio-like, using LocPinX and LocPinY, height and width.

No comments: