Showing posts with label wpf. Show all posts
Showing posts with label wpf. Show all posts

Wednesday, February 2, 2011

IronPython & WPF: Data binding with TreeView's selected element

In this post I'm going to show a small example of using data binding with the selected element of a WPF TreeView with an IronPython class.

A couple of days ago I had the necessity of using data binding to keep track of the selected value of a WPF TreeView . At first it seemed to be an easy task so I wrote:

<TreeView SelectedValue="{Binding selected, Mode=TwoWay}" ... />


Running this code results on the following error:

SystemError: 'Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '12' and line position '7'.


The problem is that the SelectedValue (and SelectedItem) property is read-only.

There are several ways to deal with this problem. One alternative is to use a technique similar to the one described in the "Forwarding the Result of WPF Validation in MVVM" post. We're going to define an attached property which works as an "output only" property that could be used with data binding.

Attached property definition



I couldn't find a way to wrote the definition of the attached property in IronPython because it needed to be instanciated by XamlReader. So the definition was written using C#:

using System.Windows.Markup;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Collections.ObjectModel;


namespace Langexplr
{
public static class TreeViewSelectedBehavior
{
public static readonly DependencyProperty MySelectedProperty =
System.Windows.DependencyProperty.RegisterAttached(
"MySelected",
typeof(object),
typeof(TreeViewSelectedBehavior)
);
public static object GetMySelected(TreeView t)
{
return t.GetValue(MySelectedProperty);
}

public static void SetMySelected(TreeView t, object theValue)
{
t.SetValue(MySelectedProperty, theValue);
}


public static readonly DependencyProperty SelectedHelperProperty =
System.Windows.DependencyProperty.RegisterAttached(
"SelectedHelper",
typeof(TreeViewSelectedHelper),
typeof(TreeViewSelectedBehavior),
new UIPropertyMetadata(null,OnSelectedHelperChanged)
);

public static TreeViewSelectedHelper GetSelectedHelper(TreeView t)
{
return (TreeViewSelectedHelper)t.GetValue(SelectedHelperProperty);
}

public static void SetSelectedHelper(TreeView t,
TreeViewSelectedHelper theValue)
{
t.SetValue(SelectedHelperProperty, theValue);
}
static void OnSelectedHelperChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
((TreeViewSelectedHelper)e.NewValue).Register((TreeView)depObj);
}

}


This class define two properties:
  • MySelected: the output property that is used to set the selected element in the view model
  • SelectedHelper: which is used to as an object that modifies the value of MySelected when the selected item changes(see below).


The following helper class is used to subscribe the SelectedItemChanged event and change "MySelected" .

public class TreeViewSelectedHelper 
{
public TreeViewSelectedHelper() { }
void SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> args)
{
(sender as TreeView).SetValue(
TreeViewSelectedBehavior.MySelectedProperty,
((sender as TreeView)).SelectedItem);
}
public void Register(TreeView t)
{
t.SelectedItemChanged += SelectedItemChanged;
}
}

}


We can now compile this class:


set NETFX4=c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\
set WPFPATH=%NETFX4%\WPF
csc /debug /r:%NETFX4%System.Xaml.dll /r:%WPFPATH%\WindowsBase.dll /r:%WPFPATH%\PresentationCore.dll /r:%WPFPATH%\PresentationFramework.dll /target:library utils.cs


The example



Having defined this attached property and helper class we can now write the following example.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utils="clr-namespace:Langexplr;assembly=utils"
Title="TreeView selection test" Width="300" Height="300">
<Window.Resources>
<utils:TreeViewSelectedHelper x:Key="selHelper" />
</Window.Resources>

<StackPanel>

<TextBlock Text="{Binding selected.label}"/>
<TreeView ItemsSource="{Binding roots}"
utils:TreeViewSelectedBehavior.SelectedHelper="{StaticResource selHelper}">
<utils:TreeViewSelectedBehavior.MySelected>
<Binding Path="selected" Mode="OneWayToSource"/>
</utils:TreeViewSelectedBehavior.MySelected>

<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding children}">
<TextBlock Text="{Binding label}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>


With this XAML definition we can write the following IronPython code:

import clr
clr.AddReference("PresentationCore")
clr.AddReference("PresentationFramework")
clr.AddReference("WindowsBase")
clr.AddReference('GalaSoft.MvvmLight.WPF4.dll')


from System.Windows.Markup import XamlReader
from System.Windows import Application
from System.IO import File
from System.Windows.Controls import TreeView
import System
import clrtype
from GalaSoft.MvvmLight import ViewModelBase
from System.Collections.ObjectModel import ObservableCollection


class TestModel(ViewModelBase):
__metaclass__ = clrtype.ClrClass
def __init__(self):
self.name = 'algo'
self.root = NodeModel('x1',[NodeModel('y2',[NodeModel('z2',[])]),
NodeModel('y3',[])])

self.sselected = NodeModel('',[])

@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def selected(self):
result = self.sselected
return result

@selected.setter
@clrtype.accepts(System.Object)
@clrtype.returns()
def selected(self, value):
self.sselected = value
self.RaisePropertyChanged('selected')

@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def roots(self):
return [self.root]

@property
@clrtype.accepts()
@clrtype.returns(System.String)
def label(self):
return self.name

@label.setter
@clrtype.accepts(System.String)
@clrtype.returns()
def label(self, value):
self.name = value
self.RaisePropertyChanged('label')



class NodeModel:
__metaclass__ = clrtype.ClrClass

def __init__(self,label, initchildren):
self.children_collection = ObservableCollection[System.Object](initchildren)
self.name = label


@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def label(self):
return self.name

@property
@clrtype.accepts()
@clrtype.returns(System.Object)
def children(self):
return self.children_collection


xamlFile = File.OpenRead('test.xaml')
window = XamlReader.Load(xamlFile)
window.DataContext = TestModel()
xamlFile.Close()

Application().Run(window)


By running this example we can see how the label of the selected element of the TreeView is reflected in the TextBlock defined above.

Tuesday, August 28, 2007

Exploring L-Systems with F# and C#

In this post I'm going to show a little program for displaying graphical representations of L-Systems using turtle graphics implemented in F#, C# and WPF.

First of all this program could be easily implemented using only F#, but for me is interesting to see the interaction between native F# type/structures and C#. Because of this, the code that performs the L-system rewrite is written in F# and the code that takes the result is written in C# and uses WPF.

The first thing we need is an implementation of the turtle. Since we want to use this library with several graphics toolkits, we define our own point type for the generated data.


#light

namespace Langexplr.Lsystems

open System
open Microsoft.FSharp.Math.Vector

type Point =
{x : int; y : int }

module Funcs = begin

let my_create_vector(i,j) =
let result = (create 2 0.0)
result.[0] <- i
result.[1] <- j
result
end

type TurtleGraphics =
class
val mutable direction : vector
val mutable position : Point

new(iX,iY) = { position = {x = iX; y = iY};
direction = Funcs.my_create_vector(1.0,0.0)}

member t.Position
with get() = t.position and
set(v) = t.position <- {x = v.x; y = v.y }

member t.Direction
with get() = t.direction and
set(v) = t.direction <- v

member t.Advance(distance : int) =
let aX = int_of_float (t.direction.[0] * float_of_int distance)
let aY = int_of_float (t.direction.[1] * float_of_int distance)
t.position <- { x = aX+t.position.x;
y = aY+t.position.y }
t.position

member t.Rotate(angle) =
let nI = (t.direction.[0] * Math.Cos(angle)) - (t.direction.[1] * Math.Sin(angle))
let nJ = (t.direction.[0] * Math.Sin(angle)) + (t.direction.[1] * Math.Cos(angle))
t.direction <- Funcs.my_create_vector(nI,nJ)


end


Now we need to represent the elements required for the L-Systems. The following elements are required:

Start point or axiomthe initial sequence of elements
Rules L-system substitution rules
AngleThe angle used when rotating the turtle
Number of iterationsThe number of times the rules will be applied to the axiom
Size of the initial segmentThe size in pixels of the line that is drawn when the turtle moves forward


Also the elements inside the rule and the axiom must be translated to turtle graphics commands. The following commands are supported:

|Draws a line forward, the size of the line inversely proportional to the iteration number
+Turn left by the specified angle
-Right left by the specified angle
[Saves the position and direction of the turtle in a stack
]Restores the position and direction of the turtle from the stack
LetterIf activated, draws a line forward


The following code shows the implementation of this:


#light

namespace Langexplr.Lsystems

open Langexplr.Lsystems

open System

type LsystemElement =
| Var of String
| Constant of String
| PipeCommand of int

type Rule =
| Rule of LsystemElement * LsystemElement list

module LsystemFuncs = begin
let rec gettingLsystemElements (str:string) i result =
if str.Length > i then
if System.Char.IsLetterOrDigit(str.[i]) then
gettingLsystemElements str (i+1) ((Var(str.[i].ToString()))::result)
else
gettingLsystemElements str (i+1) ((Constant(str.[i].ToString()))::result)
else
List.rev result
let getLsystemElements str =
gettingLsystemElements str 0 []
end


type TurtleGraphicsLsystemProcessor =
class
val start : LsystemElement list
val angle : double
val rules : Rule list
val seg_size : int
val mutable saved_positions : Point list
val mutable saved_directions : vector list
val mutable drawVariables : bool

new (a_start,a_angle,t_rules,s_size,drawVars) = {
start = a_start;
angle = (Math.PI/180.0)* a_angle;
rules = t_rules;
seg_size = s_size;
saved_positions = [];
saved_directions = [];
drawVariables = drawVars}

member lp.generate_for n current =
match n with
| 0 -> current
| o -> lp.generate_for (n - 1) (lp.apply_rules current n)

member lp.apply_rules elements iteration =
match elements with
| ((Var v)::rest) -> List.append (lp.apply_rule_for v) (lp.apply_rules rest iteration)
| ((Constant "|")::rest) -> (PipeCommand iteration)::(lp.apply_rules rest iteration)
| (e::rest) -> e::(lp.apply_rules rest iteration)
| [] -> elements

member lp.apply_rule_for v =
match (List.tryfind (fun r -> match r with
| Rule(Var vvar,_) when vvar = v -> true
| _ -> false)
lp.rules) with
| Some (Rule(_, result)) -> result
| None -> [Var v]


member lp.generate_iteration n (tg:TurtleGraphics)=
let final = lp.generate_for n lp.start
in
List.rev(lp.generate_points n final tg [tg.Position] [])

member lp.generate_points iterations elements (tg:TurtleGraphics) current lines =
match elements with
| ((Var _)::rest) ->
if (lp.drawVariables) then
tg.Advance(lp.seg_size)
lp.generate_points iterations rest tg (tg.Position::current) lines
else
lp.generate_points iterations rest tg current lines
| ((Constant "+")::rest) ->
tg.Rotate(lp.angle)
lp.generate_points iterations rest tg current lines
| ((Constant "-")::rest) ->
tg.Rotate(-1.0*lp.angle)
lp.generate_points iterations rest tg current lines
| ((Constant "[")::rest) ->
lp.saved_directions <- tg.Direction::lp.saved_directions
lp.saved_positions <- tg.Position::lp.saved_positions
lp.generate_points iterations rest tg current lines
| ((Constant "]")::rest) ->
match (lp.saved_directions,lp.saved_positions) with
| (cdir::rest_dir,cpos::rest_pos) ->
lp.saved_directions <- rest_dir
lp.saved_positions <- rest_pos
tg.Position <- cpos
tg.Direction <- cdir
lp.generate_points iterations rest tg [tg.Position] (current::lines)
| _ -> lp.generate_points iterations rest tg current lines
| ((Constant "|")::rest) ->
tg.Advance(lp.seg_size / (iterations) )
lp.generate_points iterations rest tg (tg.Position::current) lines
| ((PipeCommand iteration)::rest) ->
tg.Advance(lp.seg_size / (iterations - iteration) )
lp.generate_points iterations rest tg (tg.Position::current) lines
| (_::rest) -> lp.generate_points iterations rest tg current lines
| [] -> current::lines



end



The generate_iteration method is the one that generates the line information. Its result is a list of lists of elements of type Point.

The C# code that takes this F# list of lists of Points and converts it to a group of Polyline instances is the following:


private void b_Click(object sender, RoutedEventArgs e)
{
string origin = this.originTB.Text;
int originX = 50;
int originY = 50;
string[] oparts = origin.Split(',');
if (oparts.Length == 2)
{
originX = int.Parse(oparts[0]);
originY = int.Parse(oparts[1]);
}

TurtleGraphics tg = new TurtleGraphics(originX, originY);
int iterations = int.Parse(this.iterationsTB.Text);

this.canvas1.Children.Clear();

List<Rule> rules = GetRules();

TurtleGraphicsLsystemProcessor tgls =
new TurtleGraphicsLsystemProcessor(
LsystemFuncs.getLsystemElements(this.axiomTextBox.Text),
int.Parse(this.angleTB.Text),
Microsoft.FSharp.Collections.ListModule.of_IEnumerable<List<Rule>, Rule>(rules),
int.Parse(this.segmentSizeTB.Text),
drawVariablesCB.IsChecked == true);

var pointCollections =
from pc in (tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg)
select (new PointCollection(
from p in pc
select new System.Windows.Point(p.x, p.y)));

foreach (PointCollection pcol in pointCollections)
{
Polyline pLine = new Polyline();

pLine.Points = pcol;
pLine.Stroke = this.colorButton.Background;
this.canvas1.Children.Add(pLine);
}
}



What is interesting to see is that the list generated in F# is easily manipulated using LINQ. The expression that sets the value of pointCollections takes the native list of lists of points and converts it to a list of PointCollection objects in one expression .

Another interesting thing about the interaction between F# and C# is that the definition of the generate_iteration says that it could be applied with one or two arguments (because of Currying) this is used in C# by invoking the result of calling the method with one argument: (tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg).

Executing the program with the following L-system (from Wikipedia):




Also with using the "|" command:




Code for this experiment can be found here.