Tuesday, April 28, 2009

Element Binding That Acts Like Visio Glue

In previous posts I discussed drag and drop techniques, inspired by my years of work with Visio. Now that Silverlight 3 has element to element binding, I was inspired to investigate ways to implement Visio glue behaviors in Silverlight.




The following is the XAML shown in the video:


<UserControl x:Class="Sketch.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:ApprenticeView;assembly=ApprenticeView"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="gray">
<v:SketchPage x:Name="MyPage">
<v:OneDLink
X1="{Binding PinX, ElementName=RedSquare}"
Y1="{Binding PinY, ElementName=RedSquare}"
X2="{Binding PinX, ElementName=BlueSquare}"
Y2="{Binding PinY, ElementName=BlueSquare}"
>
</v:OneDLink>
<v:SketchShape x:Name="RedSquare"
v:SketchPanel.PinX="100"
v:SketchPanel.PinY="100"
Background="Red"
Width="40"
Height="30" >

</v:SketchShape>
<v:SketchShape x:Name="BlueSquare"
v:SketchPanel.PinX="300"
v:SketchPanel.PinY="140"
Background="Blue"
Width="40"
Height="30" >

</v:SketchShape>
</v:SketchPage>
</Grid>
</UserControl>

Dependency properties do all the heavy lifting in this example. This is because they send and receive notifications when their values change. To take advantage of this you need to program your custom XAML controls in a very declarative way, reacting to the events that occur naturally as Silverlight renders the screen.


PinX and PinY are attached properties of a SketchPage, and are used to position SketchShapes on the page (see the entry: Replicating a Visio Page Using a Silverlight Panel).

The OneDLink shape has four dependency properties -- X1,Y1,X2,Y2 -- that represent the start and finish of a line. These properties are bound to the PinX and PinY of the shape using the element binding syntax:


<v:OneDLink
X1="{Binding PinX, ElementName=RedSquare}"
Y1="{Binding PinY, ElementName=RedSquare}"
X2="{Binding PinX, ElementName=BlueSquare}"
Y2="{Binding PinY, ElementName=BlueSquare}"
>
</v:OneDLink>

The last bit of magic is in the code of the OneDLink class, because X1,Y1,X2,Y2 call InvalidateRouting(); when their value changes.


        public double Y2
{
get { return (double)GetValue(Y2Property); }
set { SetValue(Y2Property, value); }
}

public static readonly DependencyProperty Y2Property =
DependencyProperty.Register("Y2",
typeof(double),
typeof(OneDLink),
new PropertyMetadata(0.0, new PropertyChangedCallback(OnRouteChanged)));


protected static void OnRouteChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
OneDLink oPanel = obj as OneDLink;
if (oPanel != null)
oPanel.InvalidateRouting();
}

public override void InvalidateRouting()
{
CreateRouting();
InvalidateArrange();
}

CreateRouting() does the math that generates a line between the two end points.



public virtual void CreateRouting()
{
Children.Clear();
AddChild(SmartRouting(X1, Y1, X2, Y2));
}

protected Polyline SmartRouting(double dX1, double dY1, double dX2, double dY2)
{
Point oUpperLeft = new Point(Math.Min(dX1, dX2), Math.Min(dY1, dY2));
Point oLowerRight = new Point(Math.Max(dX1, dX2), Math.Max(dY1, dY2));

//Make sure these is at least 3.0 to render the line, just in case
//it is vertical or horizontal
Width = Math.Abs(oLowerRight.X - oUpperLeft.X) + 3.0;
Height = Math.Abs(oLowerRight.Y - oUpperLeft.Y) + 3.0;

PointCollection oColl = new PointCollection();
if (dX1 <= dX2 && dY1 <= dY2)
{
oColl.Add(new Point(0, 0));
oColl.Add(new Point(Width, Height));
}
else if (dX1 > dX2 && dY1 > dY2)
{
oColl.Add(new Point(0, 0));
oColl.Add(new Point(Width, Height));
}
else
{
oColl.Add(new Point(0, Height));
oColl.Add(new Point(Width, 0));
}

Polyline oPoly = new Polyline();
oPoly.SetValue(Polyline.PointsProperty, oColl);
oPoly.StrokeThickness = 2.0;
oPoly.Stroke = new SolidColorBrush(Colors.Blue);
return oPoly;
}
}

I know that the code samples I posted are not complete enough to reproduce the example shown in the video simply by cutting and pasting the code into a Silverlight 3 project. I decided to remove the complex issues associated with building a full-featured 2D drawing system, and focus on what is needed to make element binding work automatically in custom user controls.

In the future there will be more posts on creating a full-featured 2D drawing system in Silverlight.

Beat Box Application - Part 1 overview

I was searching the web for Silverlight applications the used the WPF MediaElement, and I came across these applications / demos:

John Papa has a nice chord finder application:
http://johnpapa.net/silverlight/silverlight-chord-finder/

Pete Brown has built a virtual synthesizer keyboard:
http://community.irritatedvowel.com/blogs/pete_browns_blog/archive/2009/03/23/Creating-Sound-using-MediaStreamSource-in-Silverlight-3-Beta.aspx

During the search I found eight mp3 files that play drum sounds "crash; hhclosed; hhopen; kick; snare; tom1; tom2; tom3". This and my recent knowledge of creating custom layout panels inspired me to create a beat box application. This is what I was able to build in about 3 hours.




I have always been interested in the relationship between math and music, so I constructed the API so the user could fill the beat box using lambda expressions. But before I go into that I need to explain the math used to control the box.





















In the picture, there are 2 custom panels, the BeatPanel is horizontal. It is composed on many KitPanel's. Each KitPanel is composed of 8 Ellipses, one for each sound.

All 8 sounds in the drum kit can be played at the same time. Each beat is played if the opacity of the drum element is .6 or greater. Here is the code:


public void Play()

{

Background = new SolidColorBrush(Colors.Black);

for (int i = 0; i < 8; i++)

{

Ellipse oEllipse = Children[i] as Ellipse;

if (oEllipse.Opacity < .6)

continue;

StreamResourceInfo oInfo = Sounds[i];

MediaElement oElement = Elements[i];

oElement.SetSource(oInfo.Stream);

}

}



I used the threshold value of .6 on opacity because I wanted to make it possible to write multiple math expressions that, if applied to the same section of the beat, could cancel or enhance the pattern already in place.

To do this I used 2 techniques. The first was a lambda expression:

KitAction(0, xx => (xx % 16) == 1); //Crash every 16 beat

KitAction(4, xx => (xx % 4) == 1); //snare every 4 beats

public void KitAction(int iIndex, Func<int,bool> oOnFunction)

{

for (int i = 0; i < MaxCount; i++)

{

double dValue = oOnFunction(i) ? 1.0 : 0.1;

BlendBeat(iIndex, i, dValue);

}

}



Using the lambda expression lets you describe a beat pattern with a simple rule; it lays down the basic beat for a complete song.

Fill patterns are not as regular, so I created an API to describe them with a string of X's and Spaces:


BlendBeat(6, 0, "XXX XX XXX XXXXXXXX XX"); //Tom2 fill from opening

BlendBeat(7, 0, "XXX X XXX XX XXXXX X"); //Tom3 fill from opening


public int BlendBeat(int iIndex, int i, string sMask)

{

for (int j = 0; j <= sMask.Length-1; j++)

{

double dValue = sMask[j] == ' ' ? 0.0 : 1.0;

try

{

KitPanel oKit = Children[i + j] as KitPanel;

oKit.BlendBeat(iIndex, dValue);

}

catch

{

return -1;

}

}

return i + sMask.Length;

}


In the process of building this example, I documented four "A-Ha" moments about custom panels and media elements. These are things that took longer then they should have until I learned the tricks and tips of Silverlight development. I will explain them in parts 2 and 3.

The complete source code for beat box can be downloaded here:

http://drumpanel.codeplex.com/

Friday, April 24, 2009

Looping Over Lists

Here is a very common way to define a list of instances in .NET (in this example, a list of particles):


List<Particle> Particles = new List<Particle>();


There is a really good video on animation that includes the creation and application of particle emitters at this link:

http://videos.visitmix.com/MIX09/T12F

The demo on Particles is about halfway through.

Also you can try an example created by Robby at:

http://labs.nerdplusart.com/particles/fullscreen.php

There are at least two ways you can loop through all the elements in a list -- a For loop and a ForEach loop. A For loop would look like this:



for (int i=0; i<Particles.count; i++)
{
var oParticle=Particles[i];
if ( oParticle.Age > oParticle.LifeTime)
{
LayoutRoot.Children.Remove(oParticle);
Particles.Remove(oParticle);
}
}

When I first did my implementation I used a ForEach loop, because I always use a ForEach -- the coding is simpler:



foreach (Particle oParticle in Particles)
if ( oParticle.Age > oParticle.LifeTime)
{
LayoutRoot.Children.Remove(oParticle);
Particles.Remove(oParticle);
}


In both cases the loop is (sooner or later) going to try to remove a particle object from the collection you are looping over. Removing it from the LayoutRoot.Children is never a problem because this connection is not currently locked by the loop, but calling Particles.Remove(oParticle) is a problem in the ForEach loop because the Particles collection is locked from editing (adds and removes) during the ForEach loop.

My first fix was to make a copy of the collection I was looping over. This is a bad idea because it really slows things down, and in a graphics animation, you need all the speed you can get.

So I went back and tried the For loop and realized why the experts selected that implementation. Even though the ith item is removed from the list, the collection does not automatically close up ranks and reindex all the items. Being aware of this distinction is extremely useful in certain applications.

Saturday, April 11, 2009

Binding a State Change to a Property Change

The video below describes the application in which we applied the approach described in this blog post:



VisualStateManager is a great way to change a lot of properties at once when the state of the model that is bound to the control changes. It allows you to separate design from implementation and gives your designers freedom to build some really interesting visual applications.
The code for forcing a Silverlight control to change state is:

VisualStateManager.GoToState(oControl, "NEWSTATE", true);

So you need to call this code to force a state transition. But what if you want to notify VisualStateManager based on the change of a value in your domain model? You can exploit the IValueConverter interface and the normal binding mechanism to do this.



void OnLoaded(object sender, RoutedEventArgs e)
{
Binding oBinding = new Binding("DisplayState");
oBinding.Converter = StateConverter;
oBinding.ConverterParameter = ValueIcon;
//the control that is the target of the state change
LayoutRoot.SetBinding(FrameworkElement.VisibilityProperty, oBinding);
}


When the control is loaded I use standard binding to associate the VisibilityProperty of the control to the property in my domain model named "DisplayState". The binding instructions include the StateConverter (defined next), and the value of ConverterParameter is the XAML element that is the target of the GoToState Command. As you can see, the StateConverter ignores the conversion completely and always returns 'visible'. However, during the update process, the control passed in the parameter argument is now the target of the GoToState command. Furthermore, the new state is set to the value calculated by the property bound to this StateConverter. Whenever the domain model changes state, the GoToState will be targeted at the associated control and passed the correct value.

public class StateConverter : ValueConverter
{
#region IValueConverter Members

public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Control oControl = parameter as Control;
sState = value.ToString();
VisualStateManager.GoToState(oControl, sState, true);
return Visibility.Visible;
}

#endregion
}


I like to have all my converter classes inherit from a common converter class, mainly because in most cases the conversions do not require a ConvertBack method -- this way I only need to implement the functionality the converter truly requires.

public class ValueConverter : IValueConverter
{
#region IValueConverter Members

public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}

public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}

#endregion
}

Replicating a Visio Page Using a Silverlight Panel - Part II

The arrange method can be modified to account for the object being positioned so that the center of rotation is exactly at the pin location.

Under the covers, the Visio shape sheet uses four other cells to render the shape's positioning transform -- Width, Height, LocPinX and LocPinY. This can be managed in the following function LocalPin():

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 oLocal = (Point)LocalPin(oChild);

dX -= oLocal.X;
dY -= oLocal.Y;
Point oPoint = new Point(dX, dY);
SetRenderTransform(oChild, dAngle, oPoint);

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



public Point LocalPin(UIElement oChild)
{
double dWidth = (double)oChild.GetValue(SketchPanel.WidthProperty);
double dHeight = (double)oChild.GetValue(SketchPanel.HeightProperty);
Point oOrigin = (Point)oChild.GetValue(SketchPanel.RenderTransformOriginProperty);

double dLocPinX = double.NaN.Equals(dWidth) ? 0.0 : dWidth * oOrigin.X;
double dLocPinY = double.NaN.Equals(dHeight) ? 0.0 : dHeight * oOrigin.Y;
return new Point(dLocPinX, dLocPinY);
}

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.

Monday, April 6, 2009

Using the Mouse to Rotate a Silverlight Element

Steve: I was asked on two separate occasions, during interviews, programming problems that involved geometry -- in other words, could I work with sin, cos and tan to calculate geometry. This is something I learned in 8th grade and I have used this knowledge in many applications, but this demo is probably the most fun.

The goal of this demo, as explained in the video below, is to build a targeting system that uses mouse movement to direct the projectiles.



The XAML in the tank control needs to represent the turret separate from the body of the tank; Deb has used Expression Blend to create a transform group that lets the turret rotate separately from the tank.



Now it is time to overlay the math on the geometry:



Call the code below every time the mouse moves; the variables synchronize with the picture:

e.GetPosition(oElement).X and e.GetPosition(oElement).Y get the current position
of the mouse in the coordinate system of the canvas.

oControl is the user control that is the tank on the screen, so oSize is the height and width of the tank, and oPoint is where the center of the tank is relative to the top-left corner and is used to position it on the canvas.

TranslationX and TranslationY is current location of the tank's top-left corner on the canvas, and TranslationAngle is the direction it is heading.

Now that math. Dx an Dy represent the distance along the X and Y axes from the center of the tank to the current mouse location. The calculations are done in the coordinate system of the canvas.

The radius or hypotenuse of the triangle is calculated and then divided into Dx and Dy to normalize the values. Then the ArcTan is calculated to determine the angle.

One final trick. Remember from the first diagram that the rotation of the turret is relative to the body of the tank. To orient the gun so it points at the mouse, you must subtract the calculated angle from the direction the tank is traveling.

Call the code below when the mouse is moved:


////virtual method that lets any game element fire a weapon
public virtual bool TrackMouse(Canvas oElement, Control oControl, MouseEventArgs e)
{
Size oSize = oControl.RenderSize;
Point oPoint = oControl.RenderTransformOrigin;

//some basic trig
double dX = e.GetPosition(oElement).X - TranslationX + oPoint.X * oSize.Width;
double dY = TranslationY + oPoint.Y * oSize.Height - e.GetPosition(oElement).Y;
double dRadius = Math.Sqrt(dX * dX + dY * dY);

//remember WPF wants angles in degrees
double dAngle = System.Math.Atan2(dY / dRadius, dX / dRadius) * 180.0 / System.Math.PI;
dAngle %= 360.0;

//the virtual SetFireAngle method is used to control the turret
//this code is here to account for the direction the tank is heading (i.e. Player.TranslationAngle)
//allowing the tank to move in one direction and shoot in another
SetFireAngle(TranslationAngle - dAngle);
return false;
}

Friday, April 3, 2009

How To Do Drag & Drop Using the Visual Tree

First, a shout out to Jesse Liberty who has a great post on the basic Drag & Drop pattern:

http://www.silverlight.net/blogs/jesseliberty/archive/2009/01/13/drag-and-drop-with-managed-code.aspx

When creating a complex application, I have found that attaching a MouseLeftButtonDown, MouseMove and MouseLeftButtonUp to every object that is dragable is problematic. As an alternative, I use the function VisualTreeHelper.FindElementsInHostCoordinates and manage the process from a parent UI element.

This code sets up mouse up and mouse down events for the parent UI object:


public DrawingSurface() : base()
{
MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
MouseLeftButtonUp += new MouseButtonEventHandler(OnMouseLeftButtonUp);
}


The following code lets me hit test against the children objects in the entire VisualTree, looking for an element of type "T", which are UIElements. During the search I am also looking for a panel of type "C", just in case the user is not over an object (for example, selecting nothing). A list of select objects is returned :


public List<T> FindElement<T,C>(Point oPoint, ref Panel oPanel) where T : UIElement
{
List<T> oList = new List<T>();
foreach (UIElement oElement in VisualTreeHelper.FindElementsInHostCoordinate(oPoint, this))
{
T oShape = oElement as T;
if (oShape != null)
oList.Add(oShape);

if (oElement is C && oPanel == null)
oPanel = oElement as Panel;
}
return oList;
}


Here is just one example of some of the hit test logic you can implement. Based on the type of SketchObject that is found, I can infer the user's drawing need -- is the user planning to drag the shape, stretch the shape, connect the shape, or create the shape -- and auto-select the right drawing tool. It is a lot easier to implement this logic in the parent UI Element:


void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Panel oPanel = null;
Point oPoint = e.GetPosition(null);

CurrentTool = DrawingTool.Ignore;

ActiveHandle = FindElement(oPoint, ref oPanel);
if (ActiveHandle.Count != 0)
{
if (ActiveHandle[0] is RotateHandle)
CurrentTool = DrawingTool.Rotate;
}

ActivePort = FindElement(oPoint,ref oPanel);
if (ActivePort.Count != 0)
{
if (CurrentTool == DrawingTool.Ignore)
CurrentTool = DrawingTool.Connect;

OpenDragConnect(ActivePort[0], oPoint);
return;
}

ActiveGroup = FindElement(oPoint, ref oPanel);
if(ActiveGroup.Count != 0)
{
if ( CurrentTool == DrawingTool.Ignore )
CurrentTool = DrawingTool.Move;

OpenDragDropShape(ActiveGroup[0],oPoint);
return;
}

ActiveMaster = FindElement(oPoint, ref oPanel);
if (ActiveMaster.Count != 0)
{
CurrentTool = DrawingTool.Create;
OpenDragDropShape(ActiveMaster[0], oPoint);
return;
}

}


By using this approach you can consolidate all of drag & drop code in one place, making it easier to manage and maintain.

How to Zoom In and Out with a Mouse Wheel in Silverlight

A known fact about Silverlight is that it does not currently listen to the mouse wheel; there are no events in XAML that return its status. The following code will allow you to monitor the mouse wheel by using events passed by the browser, and zoom in and out when the mouse wheel is turned.

The first section of code sets up Silverlight to listen to the browser for the OnMouseWheel event:


private void OnMouseWheel(object sender, HtmlEventArgs args)
{
HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
}


This next piece of code gets the delta for the mouse wheel, positive or negative. We don't care how much the wheel has turned, only if it was up/forward/positive or down/back/negative, translating this into a +1 or -1:


private void OnMouseWheel(object sender, HtmlEventArgs args)
{
double mouseDelta = 0;
ScriptObject e = args.EventObject;

// Mozilla and Safari
if (e.GetProperty("detail") != null)
{
mouseDelta = ((double)e.GetProperty("detail"));
}
// IE and Opera
else if (e.GetProperty("wheelDelta") != null)
mouseDelta = ((double)e.GetProperty("wheelDelta"));

double dSign = Math.Sign(mouseDelta);
//now change the scale of the activePage xaml element
//this is typically a canvas this is a child of the local root
ChangeScale(ActivePage, dSign);
}


The last piece of code uses this number to calculate and apply a scaling factor to the target XAML element.


private void ChangeScale(FrameworkElement oTarget, double dSign)

{
if(!(oTarget.RenderTransform is TransformGroup))
oTarget.RenderTransform = new TransformGroup();

TransformGroup oGroup = oTarget.RenderTransform as TransformGroup;
if (oGroup.Children.Count == 0)
{
oGroup.Children.Add(new ScaleTransform());
oTarget.RenderTransformOrigin = new Point(.5, .5);
}

m_dCurrentZoom += 0.1 * dSign;

ScaleTransform oScale = oGroup.Children[0] as ScaleTransform;
oScale.ScaleX = m_dCurrentZoom;
oScale.ScaleY = m_dCurrentZoom;
}


This code sets the focal point of the zoom to the center (.5, .5) of the XAML element that is passed in the function, typically a canvas. If you wanted to zoom in on something that is drawn on the canvas, like a shape or a button, you would need to calculate where that element is relative to the total width and height of the canvas (e.g. 30% of x, 70% of y = .3, .7).

Wednesday, April 1, 2009

Behaviors and Uses of Collapsed vs. Zero Opacity

While visually, setting a XAML element's opacity to zero is the same as setting visibility to collapsed, the two have different behaviors when it comes to detecting interaction with the mouse. An element whose visibility is set to 'collapsed' is undetectable by a mouse; it is as if the element has been removed from the visual tree. Setting an element's opacity to zero, however, makes it invisible while it is still detectable by the mouse, e.g. a button with zero opacity is still clickable.

This distinction is useful in a number of ways. One application is creating an invisible button on a page that the knowledgeable user can click to trigger a desired action such as debugging or a special report. To do this, you would create a button with opacity equal to zero. Conversely, you may have a button you wish to deactivate as well as hide, in which case you would set visibility to collapsed.

Another application of zero opacity is to create a shield which allows lower level elements to display but renders them inactive - for example, you may want to do this during an asynchronous operation like retrieving an RSS feed, during which you want to prevent user interaction. In this case, you could create a large rectangle covering the page but set its opacity to zero -- this would allow the user to see what is happening underneath but prevents them from doing anything. When the asynchronous event is complete, the rectangle could then be collapsed to allow user interaction.

Opacity and visibility are also be different in their use for visualization, especially during animations. Opacity is a continuous value that ranges from 0 to 100% in Blend or 0 to 1 in XAML, making it a good candidate for animations, where an element can move from one value to another during a given timeframe. Visibility is an on/off value, either visible or collapsed, and changes at one point in time. These differences in visualization, combined with differences in mouse interaction, should be considered by the designer/developer when choosing between the two properties.

The most important thing to remember is just because you can't see it, doesn't mean you can't click it! An element whose opacity = 0 can still be an active control on your Silverlight application. If you want something to be really 'gone', set its visiblity to collapsed.

Sharing What We've Learned about Silverlight

DEB:
We’ve always been very big on visualization. The complex applications we’ve built and deployed as Apprentice Systems made extensive use of Microsoft Visio for knowledge capture, diagramming and drawing generation; we’ve even used interactive 3D successfully. But nothing has excited us as much as Silverlight.

With Silverlight we get two things our customers have consistently asked for: compelling visuals and web-based deployment. Our use of Visio required that we stay on the desktop, but increasingly customers have been demanding solutions that run inside a browser. Also, big upfront licensing fees are getting increasingly difficult to sell; software as a service is the business model that fits our tough economic times. Silverlight is a perfect fit for the direction we wanted to take our business.

But it’s also a great fit for the way we work, because I do most of the design work, and Steve is the developer. I handle everything Adobe and FrontPage, do most of the Visio shape work, and know next to nothing about .NET and C#. We used to pass things back and forth A LOT, interrupting the workflow, waiting for a project to return for my input, then handing it off again. The move to Silverlight and Expression Blend has made me more self sufficient, and from what I’ve seen of Blend 3, I’ll be even more so in the future. Also, the integration of Photoshop and Illustrator will really unleash creativity as control skinning gets much, much easier. I can’t wait to get my hands on a version of Blend that has all the new features, including SketchFlow!

STEVE:
I have learned Silverlight by reading a lot of blogs and watching hours of video. I feel like I have attended Mix07, Mix08, Mix09 and PCD08 from the web coverage. I have been learning from the bloggers and tweeting with the Twitters: Jesse Liberty, Tim Heuer, Shawn Wildermuth, Mike Snow, Brad Abrams, Nikhil Kothari, Jeff Wilcox, John Papa, Tim Sneath, Scorbs (Karen Corby), Shawn Oster, Erik Mork, John Stockton, Joe Stegman, Laurence Moroney, & Scott Gu.

Silverlight.net is my home page, and I bought an iPod to listen to shows like Sparkling Client, .NET Rocks, Herding Code, and Stack Overflow.

The writing is on the wall and you can see it in the Silverlight 3 and Blend 3 Betas -- this is a platform that is going to change how many people will think about software and the web.

In the past year we have completed five significant Silverlight projects: Buzzoggi (a web mashup), Robot Arena (a game), an association list box control, a product configurator for CinemaTech (home theater furniture), and a narrative storytelling device currently being tested at Silverlit Art. In the process of building these I have built over a hundred test projects to explore the patterns used to develop Silverlight applications.

This blog is about sharing what we have learned and sharing the journey.