Thursday, September 24, 2009

Building a Dial Control

A friend posed a question a few weeks back about implementing a dial control. I found a few but they seemed problematic so I decided it can't possibly be that hard. So the idea is a control with a custom event for dial position change that I can add templates etc to change the look and feel of the control to any kind of dial I want and bind an event handler to so that in can control say volumn. I posted how to implement a custom event last week so I'll focus specifically on the rest of the dial control. Lets start with the template in Generic.xaml.

<Style TargetType="DialTest:Dial">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DialTest:Dial">
<Grid x:Name="Knob" Background="Transparent" >
<ContentPresenter x:Name="Background" Content="{TemplateBinding KnobBackground}"/>
<ContentPresenter x:Name="DialKnob" Content="{TemplateBinding KnobFace}" RenderTransformOrigin="0.5,0.5" >
<ContentPresenter.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="DialAngle" Angle="0" />
</TransformGroup>
</ContentPresenter.RenderTransform>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

So from this template we can see that the control will have a wrapper grid to force content to be resized as set and two content presenters one for the background and one for the dial and further a rotate transform on the dial so we can make it turn. As noted these content presenters imply that we have two dependency properties for the dial face and background but we will also have DP's for setting the start or minimum or max angle setting of the dial (0 to 360 degrees).

In our constructor then we set our template and then OnApplyTemplate() we wire things together like so:

Knob = ((Grid)GetTemplateChild("Knob"));
DialAngle = ((RotateTransform)GetTemplateChild("DialAngle"));

Knob.MouseLeftButtonUp += new MouseButtonEventHandler(Knob_MouseLeftButtonUp);
Knob.MouseLeave += new MouseEventHandler(Knob_MouseLeave);
Knob.MouseLeftButtonDown += new MouseButtonEventHandler(Knob_MouseLeftButtonDown);
Knob.MouseMove += new MouseEventHandler(Knob_MouseMove);

base.OnApplyTemplate();

if (Minimum > 0 && Minimum < 360)
{
SetPosition(Minimum);
}

this wires our internal events we use to make the wiring work. What we are doing todo is on mouse down we will calculate the angle of that related to the control and then turn the dial to that position. we then fire off the angle changed event or custom event. The hard part turned out to the just figuring out the math and it turns out that this not the best method 'mathmatically' speaking but this works well. Once get a point from the mouse down we do this to get the relative angle:

double TheDiameter = Knob.ActualWidth;
double CurrentHeight = Knob.ActualHeight;

double Radius = TheDiameter / 2;

double AngleOfRotation = AngleOfRotationQuadrant(TheDiameter, CurrentHeight, NewPoint);

//int DialQuadrant = 1;
if ((NewPoint.X > Radius) && (NewPoint.Y <= Radius))
{
//DialQuadrant = 2;
AngleOfRotation = 90.0 + (90.0 - AngleOfRotation);
}
else if ((NewPoint.X > Radius) && (NewPoint.Y > Radius))
{
//DialQuadrant = 4;
AngleOfRotation = 180.0 + AngleOfRotation;
}
else if ((NewPoint.X < Radius) && (NewPoint.Y > Radius))
{
//DialQuadrant = 3;
AngleOfRotation = 270.0 + (90.0 - AngleOfRotation);
}
return AngleOfRotation;

Basically we calculate 90 degress of position for he dial face quadrant that the event was in and then add the relative additional quadrants as needed to get the correct angle. getting the quadrant goes like this:

double DialRadius = CurrentWidth / 2;

Point CenterPoint = new Point(DialRadius, CurrentHeight / 2);
Point StartPoint = new Point(0, CurrentHeight / 2);

double TriangleTop = Math.Sqrt(ToThePowerOfTwo(NewPoint.X - CenterPoint.X) + ToThePowerOfTwo(CenterPoint.Y - NewPoint.Y));

double TriangleHeight = (NewPoint.Y > CenterPoint.Y) ? NewPoint.Y - CenterPoint.Y : CenterPoint.Y - NewPoint.Y;

return ((TriangleHeight * Math.Sin(90.0)) / TriangleTop) * 100;

now we can set the angle in 'SetPosition(); like this:
if (Minimum > 0 && Max > 0 && Minimum < 360 && Max <= 360 )
{
if (AngleOfRotation < Minimum)
{
AngleOfRotation = Minimum;
}
if (AngleOfRotation > Max)
{
AngleOfRotation = Max;
}
}

DialAngle.Angle = AngleOfRotation;

OnPositionChanged(new DialEventArgs(AngleOfRotation));

the last line being our custom event. and poof it works... So in xaml using the control might look like this:

<cc:Dial x:Name="NewKnobControl" Height="100" Width="100" PositionChangedEvent="NewKnobControl_PositionChangedEvent" Minimum="45.0" Max="135" >
<cc:Dial.KnobFace>
<Grid >
<Ellipse Fill="Aquamarine" />
<Rectangle x:Name="Indicator" Height="10" Width="49" Fill="Blue" Margin="1,45,50,45" />
</Grid>
</cc:Dial.KnobFace>
</cc:Dial>

nice and simple, needs some design love but you get the point. The dial control code will be up on HackingSilverlight.codeplex.com some time this week.

Monday, September 21, 2009

Blend/Catalyst Smack Down?

Designer and Developer Workflow

Is it a myth? Marketing lingo? Or could it be real with the help of powerful tools?

Listen in as Ryan Stewart from Adobe and Adam Kinney from Microsoft discuss the workflow concept from their respective point of views. Ryan will demonstrate how Flash Catalyst works within the Flash Platform development cycle. Adam will show how Expression Blend fits into the Silverlight development workflow.

Come join the fun with two of the best speakers in the world on Adobe/Blend at Interact

http://www.seattled2ig.org/?p=257

Thursday, September 17, 2009

The Gu a Silveright firestarter

if your paying attention and want to learn some Silverlight they are streaming the gu and more all star speakers free from the Silverlight firestarter at:

http://www.msdnevents.com/firestarter/online/index.html

Saturday, September 12, 2009

Don't Forget about Silverlight Week

So this coming week is really really exciting and I wanted to make sure no one forgot. The stars have aligned and 4 big events are all happening in Seattle:

Monday:
.NET Developer Association User Group Meeting - Featuring Jesse Liberty
http://www.dotnetda.com/Events/EventNewsletter.aspx?EventDate=9/14/2009

Tuesday
Silverlight Geek Dinner
http://adamkinney.com/Blog/silverlight-geek-dinner-sep-15th-seattleredmondbellevue

Wed
Interact Seattle - Featuring Tim Hueur
http://www.InteractSeattle.org/

Thur
Silverlight 3 Firestarter Event
http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032422412&Culture=en-US

Wednesday, September 9, 2009

64bit Surface Hack

Not strickly speaking Silverlight but in setting up my new laptop to run the Surface SDK in 64bits I had to get special mojo to get it working so I can run my Simon demo (plus some work related stuff) from Silverlight to WPF to Surface. I did a bit of googling er I mean 'binging' and found some of the posts lacking mostly the orca link. Anyway Ariel sent me the link to this post: that had all the magic juice to make it work. if your trying to get this running on your box in 64bits these steps working for me.

http://www.theruntime.com/blogs/brianpeek/archive/2009/03/10/install-the-surface-sdk-on-windows-7-andor-x64.aspx

Tuesday, September 8, 2009

Custom Control Development: Simple code guidelines

Jeff Wilcox did a great post on custom control conventions that is cool, if for nothing else that I feel vindicated with regard to the use of regions. Some time ago I got into a heated discussion on this topic and I feel as much as it is esoteric regions help break things down into logical understandable chunks. The rest of the article is great too as to best practices which seem to be still evolving around Silverlight

http://www.jeff.wilcox.name/2009/08/custom-controls-simple-code-guidelines/

Monday, September 7, 2009

Custom Events on Silverlight Controls

So this weekend my project of choice was to build a dial control. I'll post more on that later. But one cool thing I ended up doing was building a custom event on the dial control called OnPosition Changed. Typically when you use or build controls the events you use are more straight forward but this case, we have a dial so the 'mouseover' or click isn't really want you want. So what we want is when the dial moves to a position we want the 'position changed' event called.

To start with you need some custom event args as we want to pass the 'angle' of the dial to the event handler in the consuming application. So the custom event args looks like this:

public class DialEventArgs : EventArgs
{
private double angle;

public DialEventArgs(double _Angle)
{
this.angle = _Angle;
}

public double Angle
{
get
{
return angle;
}
}
}

In this case its a pretty straight forward class that drives from eventargs and we add a constructor that lets us set the angle property easily. Next we need in our control class to define the event like this:

public delegate void PositionChangeHandler (Object Sender, DialEventArgs e);

public event PositionChangeHandler PositionChangedEvent;

protected virtual void OnPositionChanged(DialEventArgs e)
{
PositionChangedEvent(this, e);
}

With this in place a consuming xaml page if they use the control will be able to set an event handler for this event. But first we need to actually call the event when the angle of the dial changes: In the method that sets the angle we have this code:


OnPositionChanged(new DialEventArgs(AngleOfRotation));

Now when you have an event handler set in xaml you get the event called at the correct time. In xaml this might look like this:

<cc:Dial x:Name="NewKnobControl" Height="100" Width="100" PositionChangedEvent="NewKnobControl_PositionChangedEvent" Minimum="45.0" Max="135" >
<cc:Dial.KnobFace>
<Grid >
<Ellipse Fill="Aquamarine" />
<Rectangle x:Name="Indicator" Height="10" Width="49" Fill="Blue" Margin="1,45,50,45" />
</Grid>
</cc:Dial.KnobFace>
</cc:Dial>

Now in the client code you need an event handler and in this case in my demo app it looks like this:

private void NewKnobControl_PositionChangedEvent(Object sender, DialEventArgs e)
{
// applicable values
double Angle = e.Angle;
}