In Part 1, we’ve discussed how the Layout was done, and we finished designing our XAML.
Now let’s see what is going on beyond the scenes.
We will define two helper classes that will help us manage the data in our App.
Function class got:
- Name : Will be shown inside the ComboBox.
- Params: List of Param class, will be used to persist data into the function.
- Run: a Func<double, double> type property, contains a lambda expression representing the mathematical function to be used.
Param class got:
- Name : Will be shown inside the listbox like “a” or “b”.
- Val: The Value that was entered by the user.
In code they look like this.
public class Function
{
public string Name { get; set; }
public List<Param> Params { get; set; }
public Func<double, double> Run { get; set; }
}
public class Param
{
public string Name { get; set; }
public string Val { get; set;}
}
OK let’s get some meat.
- We will define our private members of class Page. We will need a Polyline object and PointCollection for it.
public partial class Page : UserControl
{
PointCollection polyPoints = new PointCollection();
Polyline yellowPolyline = new Polyline();
- Now we will create the data source of the mathematical functions a list of Function objects as a private member (note that the Run property is unassigned we will assign it later) :
List<Function> funcs = new List<Function>
{ new Function { Name = "aX",
Params = new List<Param> { new Param {Name="a"}}},
new Function { Name = "aX + b",
Params = new List<Param> { new Param {Name="a"},
new Param {Name="b"}}},
new Function { Name = "aX^2 + bx + c",
Params = new List<Param> { new Param {Name="a"},
new Param {Name="b"},
new Param {Name="c"}}},
new Function { Name = "aX^d + bx + c",
Params = new List<Param> { new Param {Name="a"},
new Param {Name="b"},
new Param {Name="c"},
new Param {Name="d"}}},
new Function { Name = "Sin(X)"},
new Function { Name = "Cos(X)"},
new Function { Name = "Tag(X)"},
new Function { Name = "Floor(X)"},
new Function { Name = "Abs(X)"}};
- We will define a method called SeedFunc inside Page class, this method will run for each X pixel we have on the X axis of our drawing area we will calculate it’s Y part. In our case we have 400 pixels. We could iterate over them from –200 to 200 for example but you will notice that a method like Sin(x) is giving back Y in the range of –1 to 1. so it will look very very small with that ratios. The trick is to use smaller X numbers from –10 to 10 and translating them to X,Y in pixels. Furthermore if we have a point that the Y value is outside our plotting area we disregard it.
private List<Point> SeedFunc(Func<double, double> func)
{
List<Point> funcPoint = new List<Point>();
for (double i = 10; i >= -10; i -= 0.05)
{
try
{
double y = func(i) * 20;
if (Math.Abs(y) < (Canvas.ActualHeight / 2))
{
funcPoint.Add(new Point(i * 20, y));
}
}
catch (DivideByZeroException ex)
{
continue;
}
}
return funcPoint;
}
- We will define an helper method, a quick and dirty way to retrieve a parameter value from a given Function.
private double Ex(int f, int c)
{
return Convert.ToDouble(funcs[f].Params[c].Val);
}
- Move to the Page constructor just after InitializeComponent(); we will start assigning values to our private members. First we will assign the lambda expressions for each of our Function.
funcs[0].Run = x => x * Ex(0, 0);
funcs[1].Run = x => x * Ex(1, 0) + Ex(1, 1);
funcs[2].Run = x => x * x * Ex(2, 0) + x * Ex(2, 1) + Ex(2,2);
funcs[3].Run = x => Math.Pow(x, Ex(3,3)) * Ex(3, 0) + x * Ex(3, 1) + Ex(3, 2);
funcs[4].Run = x => Math.Sin(x);
funcs[5].Run = x => Math.Cos(x);
funcs[6].Run = x => Math.Tan(x);
funcs[7].Run = x => Math.Floor(x);
funcs[8].Run = x => Math.Abs(x);
- Also inside the constructor, we will assign the ComboBox with this list. And create a few defaults for our Polyline.
fChs.ItemsSource = funcs;
SolidColorBrush yellowBrush = new SolidColorBrush();
yellowBrush.Color = Colors.Yellow;
SolidColorBrush blackBrush = new SolidColorBrush();
blackBrush.Color = Colors.Black;
yellowPolyline.Stroke = blackBrush;
yellowPolyline.Fill = yellowBrush;
yellowPolyline.StrokeThickness = 2;
- Now that we are familiar with Param class we can define a DataTemplate for it. Go back to Page.xaml, locate the listbox and define this template. Notice that the Textbox binding is marked with TwoWay, which mean every change is automatically relayed to the parameter underneath.
<ListBox x:Name="ParamList" Width="200" Height="200">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Margin="5,0,5,0" Text="="/>
<TextBox Width="50" Text="{Binding Val, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
- Switch back to the cs file, let’s handle the Selection change of the combo box. we get the function from the combobox and we set the source of the list with its’ params list.
private void fChs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedFunc = e.AddedItems[0] as Function;
ParamList.ItemsSource = selectedFunc.Params;
}
- Move on to implement the clear button, we will remove the current ployline and clear the pointlist making it ready for new plotting. if there was an error, we eliminate the error message.
private void Clear_Click(object sender, RoutedEventArgs e)
{
Canvas.Children.Clear();
polyPoints.Clear();
ErrorLabel.Visibility = Visibility.Collapsed; }
- Last piece of the puzzle. The plot button event implementation.
- Before we plot we clear again.
- we position our polyline in the middle of the canvas.
- We call the SeedFunc method to generate a list of points.
- We just add them to Polyline list collection.
- And last we add the polyline to the canvas.
- If something bad happened, we warn the user and clear everything.
private void Plot_Click(object sender, RoutedEventArgs e)
{
ErrorLabel.Visibility = Visibility.Collapsed;
if (selectedFunc != null)
{
try
{
Canvas.Children.Clear();
polyPoints.Clear();
Canvas.SetLeft(yellowPolyline, Canvas.ActualWidth / 2);
Canvas.SetTop(yellowPolyline, Canvas.ActualHeight / 2);
List<Point> funcPoint = SeedFunc(selectedFunc.Run);
foreach (Point p in funcPoint)
yellowPolyline.Points = polyPoints;
Canvas.Children.Add(yellowPolyline);
}
catch (Exception ex)
{
Canvas.Children.Clear();
polyPoints.Clear();
ErrorLabel.Visibility = Visibility.Visible;
}
}
}
It should look like this:
We can now conclude this 2 part tutorial, of how I built my Silverlight app to the MIX 10K competition. I hope I will see you apps beside mine their and If I don’t win, at least you will, so it will have a little piece of me inside :).
I urge you to visit my app at MIX and share your support with me.
Thanks
Ariel