#.think.in
learn.create.enjoy

Silverlight Dynamic Languages SDK

January 26, 2009 15:12 by tarn

I decided to have a look at creating Silverlight applications with IronPython, this led me to the Silverlight Dynamic Languages SDK. The main piece of the SDK is Chiron which is a development utility for creating Silverlight application packages (XAPs) from dynamic languages.

Chiron can be used to:

  • Create dynamic language Silverlight projects
  • Start a local web server which dynamically generates XAPs from the project files
  • Write generated XAPs to disk.   

I needed something to build on Silverlight with IronPython, so I decided to write a small part of the UI for an RSS Feed Visualizer I've been wanting to write. Its only really a demo, the UI could use some more work and I've just used some sample names as data. I hope to build on this example in the future, but I'll need to do some refactoring, the code is pretty scrappy as I wrote most of it after I lost a house poker game. Apparently a stronger understanding of counter intuitive probability problems hasn't helped my game and I don't write my best code after an afternoon of drinking. 

(If there is problems viewing the demo on this page, it can also be viewed here)

Here is the IronPython code that drives the demo, I've also uploaded the entire project. I used examples of dragging and inertia from the excellent Project "Rosetta Stone" Tutorials.

   1: from System.Windows import Application
   2: from System.Windows.Controls import UserControl
   3: from System.Windows.Controls import TextBlock
   4: from System.Windows.Controls import Canvas
   5: from System.Windows.Media import Colors
   6: from System.Windows.Media import SolidColorBrush
   7: from System.Windows import Visibility
   8: from System.Windows import FontWeights
   9: from System.Windows import HorizontalAlignment
  10: from System.Windows.Media.Animation import Storyboard
  11: from System import Random;
  12: import math
  13:  
  14: class App:
  15:   def __init__(self):
  16:     self.root = Application.Current.LoadRootVisual(UserControl(), "app.xaml");
  17:     spinner = Spinner(self.root,['Tarn','Leah','Alex','Mick','John','Rex','Jack','Jill','Elle']);
  18:  
  19:  
  20: class Spinner():
  21:   def __init__(self, root, names):
  22:     self.root = root;
  23:     self.names = names
  24:     self.radius = 0;
  25:     self.textBlocks = [];
  26:     self.mainText = Block(self.root.layout_root,self.centreClick);
  27:     self.mainText.SetPosition(100.0,100.0);
  28:     self.mainText.HorizontalAlignment = HorizontalAlignment.Center;
  29:     self.selectedItem = None;
  30:     self.angle = 0;
  31:     self.diff = 0;
  32:     self.target = 0;
  33:     self.drag = False;
  34:     self.createTextBlocks(self.names[0], self.names);
  35:     self.root.MouseLeftButtonDown += self.leftButtonDown;
  36:     self.root.MouseLeftButtonUp += self.leftButtonUp;
  37:     self.root.MouseMove += self.mouseMove;
  38:     self.sb = Storyboard();
  39:     self.root.Resources.Add("sb",self.sb);
  40:     self.sb.Completed += self.enterFrame;
  41:     self.sb.Begin();
  42:   def centreClick(self,item):
  43:     self.selectedItem = item;
  44:     self.state = "Selected";
  45:     pass;
  46:   def createTextBlocks(self, item, list):
  47:     self.state = "Expanding";
  48:     self.mainText.text.Opacity = 1;
  49:     self.mainText.text.Text = item;
  50:     self.velocity = 0.01;
  51:     for textBlock in self.textBlocks:
  52:       textBlock.dispose();
  53:     textBlocks = [];
  54:     i = 0;
  55:     list = filter(lambda m: (m != item), list)
  56:  
  57:     for title in list:
  58:       textBox = Block(self.root.layout_root,self.centreClick);
  59:       textBox.text.Text = title;
  60:       textBox.angle = i * 2 * math.pi / list.__len__();
  61:       textBox.HorizontalAlignment = HorizontalAlignment.Center;
  62:       textBox.radius = -(100/list.__len__()) * i;
  63:       textBox.SetPosition2(0);
  64:       self.textBlocks.append(textBox);
  65:       i += 1;
  66:   def enterFrame(self, object, e):
  67:     diff = ((self.target - self.diff)-self.angle) ;
  68:     if (self.drag == True):
  69:       oldAngle = self.angle;
  70:       if (diff > math.pi): diff = diff - math.pi * 2;
  71:       if (diff <= -math.pi): diff = diff + math.pi * 2;     
  72:       self.angle  = self.angle + ((diff) * 0.2); 
  73:       self.velocity = self.angle - oldAngle;
  74:     else:
  75:       self.angle = self.angle + self.velocity;
  76:       if (math.fabs(self.velocity) > 0.01): 
  77:         self.velocity = self.velocity * 0.90;
  78:     self.angle = self.angle % (2 * math.pi);
  79:  
  80:     if (self.state == "Expanding"):
  81:       expandComplete = True;
  82:       for textBlock in self.textBlocks:
  83:         if (textBlock.Expand(100) == False): expandComplete = False;    
  84:     if (self.state == "Selected"):
  85:       expandComplete = True;
  86:       if (self.selectedItem != self.mainText): 
  87:         self.mainText.text.Opacity -= 0.10;
  88:         self.selectedItem.moveTo(100,100);
  89:       for textBlock in self.textBlocks: 
  90:         if (self.selectedItem != textBlock):
  91:           textBlock.text.Opacity -= 0.010;
  92:           if (textBlock.Expand(200) == False): expandComplete = False;    
  93:       if (expandComplete == True): 
  94:         self.createTextBlocks(self.selectedItem.text.Text, self.names);
  95:     for textBlock in self.textBlocks:
  96:       if (self.selectedItem != textBlock): textBlock.SetPosition2(self.angle);
  97:     self.sb.Begin();
  98:   def leftButtonUp(self,object,e):
  99:     self.root.ReleaseMouseCapture();
 100:     self.drag = False;
 101:   def mouseMove(self, object, e):
 102:     if (self.drag == True):
 103:         self.target = self.getAngle(e);
 104:   def leftButtonDown(self, object, e):
 105:       self.root.CaptureMouse();
 106:       angle = self.getAngle(e);
 107:       self.diff = angle - self.angle;
 108:       self.target = self.getAngle(e);
 109:       self.drag = True;
 110:   def getAngle(self, e):
 111:     x = e.GetPosition(self.root).X-100;
 112:     y = e.GetPosition(self.root).Y-100;
 113:     angle = (math.atan2(x,y));
 114:     if (angle < 0): angle = angle + (math.pi*2);
 115:     return angle;
 116:  
 117: class Block():
 118:   def __init__(self, canvas, clicked):
 119:     self.clicked = clicked;
 120:     self.canvas = canvas;
 121:     self.text = TextBlock();
 122:     self.text.Foreground = SolidColorBrush(Colors.White);
 123:     self.offset = 100;
 124:     self.radius = 10;
 125:     self.angle = 0;
 126:     canvas.Children.Add(self.text);
 127:     self.text.MouseEnter += self.mouseOver;
 128:     self.text.MouseLeave += self.mouseOut;
 129:     self.text.MouseLeftButtonDown += self.mouseClick;
 130:   def dispose(self):
 131:     self.canvas.Children.Remove(self.text);
 132:   def moveTo(self, x, y):
 133:     self.text.FontWeight = FontWeights.Normal;
 134:     currentX = self.text.GetValue(Canvas.LeftProperty);
 135:     currentY = self.text.GetValue(Canvas.TopProperty);
 136:     newX = currentX + ( (x - currentX) * 0.1);
 137:     newY = currentY + ( (y - currentY) * 0.1);
 138:     self.SetPosition(newX,newY);
 139:   def Expand(self, max):
 140:     if (self.radius < max):
 141:       self.radius += 1;
 142:       return False;
 143:   def mouseClick(self,sender,e):
 144:     self.clicked(self);
 145:   def mouseOver(self,sender,e):
 146:     self.text.FontWeight = FontWeights.Bold;
 147:   def mouseOut(self,sender,e):
 148:     self.text.FontWeight = FontWeights.Normal;
 149:   def SetPosition(self, x, y):
 150:     self.text.SetValue(Canvas.LeftProperty, x);
 151:     self.text.SetValue(Canvas.TopProperty, y);    
 152:   def SetPosition2(self, angle):
 153:     if (self.radius < 20):self.text.Visibility = Visibility.Collapsed;
 154:     else:self.text.Visibility=Visibility.Visible;
 155:     self.SetPosition( (math.sin(self.angle + angle) * self.radius) + self.offset, (math.cos(self.angle + angle) * self.radius) + self.offset);  
 156:   
 157:  
 158: App()

I noticed when I uploaded the XAP that it was quite large (> 1Mb) for a very small demo. I think this is because additional assemblies for dynamic languages and IronPython are not included in the client Silverlight plug-in and have to be included in the XAP.


Tags: , ,
Categories:
Comments (2)

#.think.in infoDose #16 (12th Jan - 16th Jan)

January 19, 2009 09:30 by brodie

Announcements

Management/Agile/Development

Architecture

WCF

Silverlight

UI Design

Singularity Watch

Other

Quotes

“Facebook is about people you used to know; Twitter is about people you’d like to know better” - Ivor Tossel


Tags:
Categories:
Comments (3)

Goats, Cars and Babies

January 18, 2009 14:36 by tarn

I watched the movie "21" the other night and although I thought it was rubbish, I was slightly intrigued in the explanation to a question posed in the movie, which turns out to be the Monty Hall Problem.

I was surprised then, while catching up on feeds, to see Jeff Atwoods post The Problem of the Unfinished Game on a similar topic. Jeff asks

Let's say, hypothetically speaking, you met someone who told you they had two children, and one of them is a girl. What are the odds that person has a boy and a girl?

It turns out that I've always hated this type of math despite generally enjoying using math to solve engineering problems, having a pretty good understanding of No-Limit Texas Hold'em and perpetually trying to crack all those math tricks that make you do a series of seeded numerical operations to magically reveal some significant number. For me the probability math in quantum physics was bewildering and in statistical process control it was incredibly boring as well. 

Asked this question last week and I would probably have meekly replied, "Umm, maybe 50%?" and mumbled something about "stupid probability questions" then maybe attempted to draw out some permutations before giving up with absolutely no confidence in my answer.

Having no confidence in my answer may have been wise in that case, but in his following post about the answer to a similar problem, Jeff made a point that resonated with me:

You don't need to be a mathematician to prove this. I'm just a crappy programmer, and even my crappy code can brute force the answer by simulating results from thousands of games.

As soon as I read that I decided I was going to give the Monty Hall Problem the same treatment, at that stage I'd only heard the explanation in the movie which didn't make much sense to me.

import random;

class GameShow:
  def __init__(self):
    self.prizeDoor = random.randint(1,3); 
    print "\nCreating Game, Prize Door is ", self.prizeDoor;
  def SelectDoor(self, door):
    self.firstSelection = door;
    print "Contestant Selects Door", self.firstSelection;
  def HostOpenDoor(self):
    options = filter(lambda m: (m != self.prizeDoor) and (m != self.firstSelection), range(1,4))
    self.hostOffer = options[random.randint(0, options.__len__()-1)];
    print "Host opens door", self.hostOffer;
  def ChangeDoor(self, swap):
    self.swap = swap;
    print "Contestant", self.swap and "Takes Swap" or "Keeps Original Choice";
  def OpenDoor(self):
    self.alternativeDoor = filter(lambda m: (m != self.hostOffer) and (m != self.firstSelection), range(1,4))[0];
    self.selectedDoor = (self.swap and self.alternativeDoor or self.firstSelection);
    self.wins = (self.selectedDoor == self.prizeDoor);
    print "Contestant Selects", self.selectedDoor, "and", self.wins and "Wins Prize" or "Looses";

def RunGame(swap):  
  game = GameShow();
  game.SelectDoor(random.randint(1,3));
  game.HostOpenDoor();
  game.ChangeDoor(swap);
  game.OpenDoor();
  return game.wins;

def RepeatRun(swap, repeats):
  wins = 0;
  for i in range(repeats): 
    if (RunGame(swap)): wins += 1;
  return wins;
  

if __name__ == "__main__":
  iterations = 1000;
  swapWins = RepeatRun(True, iterations);
  stayWins = RepeatRun(False, iterations);
  print "\nOverall Results\n";
  print "Swap Wins",swapWins/float(iterations),"%";
  print "Stay Wins",stayWins/float(iterations),"%";

After some fooling around with Python I got my brute force results that proved to me making the swap was the right choice. Actually I'd proved it to myself by the time I'd written the code that works out which doors the host can open. It turns out that if you select a door with a goat behind it first, the host can only open one door to reveal a goat, the other is the car. And that is the key; If your first selection is a goat which is a 2/3 chance, then the switch will always yield a car (this is better than the 1/3 chance first selection). 

I've satisfied my interest in counter intuitive probability problems for now, which is good because my friends are sick of me talking about them.


IronPython Asynchronous RSS Reader

January 13, 2009 11:25 by tarn

Reading RSS is a common and simple task in Python using the standard library, unfortunately it uses parts of the standard library that are not implemented in IronPython. Luckily it isn't very difficult using the .NET framework either. There is an example Parsing RSS on the IronPython Cookbook site that demonstrates it. 

While this example was useful, I wanted to make the web request asynchronously so I could use it in a WPF application without blocking the UI. I was happy to find implementing it was quite familiar and easy, I've used the WebClient object asynchronously many times in C# and I've passed event handlers as parameters in Javascript, both of which are supported by IronPython and are used in this module.

In the implementation below I've used all XML processing code from the Parsing RSS example. I removed the class RSSFeedSubscriptions as I didn't need the functionality and I felt it made the sample more clear. I split the RSSFeedFetcher into an RSSFeed class for the feed data and an RSSFeedReader class for handling the downloading and XML processing. I think it is much a better separation of functionality.

I found the the exception handling code in the original sample that outputs an exception message was itself throwing an exception, so I removed it. I don't think its ideal effectively suppressing exceptions and printing a message but its good enough for what I'm using it for. If anyone has a suggestion or I improve the module myself, I'll update this post.   

The example usage is shown in the Main section. I used an AutoResetEvent in the test to keep the tread alive until the asynchronous event is fired and the results are output. 

import clr
clr.AddReference('System.Xml')

from System.Net import WebClient
from System.Xml import XmlDocument, XmlTextReader
from System.IO import StreamReader, MemoryStream
import System

class RSSFeedItem(object):
    def __init__(self):
        self.Title = ""
        self.Description = ""
        self.Link = ""
        self.GUID = ""

class RSSFeed():
    def __init__(self, URL):
        self.FeedURL = URL
        self.ChannelName = ""
        self.Items = []
        self.BadCount = 0        

class RSSFeedReader(object):
    def __init__(self,URL, onComplete):
        self.onComplete = onComplete
        self.feed = RSSFeed(URL)
    def Parse(self, sender, args):
        rssBytes = args.Result
        try:
            xmlDoc = XmlDocument()
            stream = MemoryStream(rssBytes)
            xmlDoc.Load(stream)
            rssNode = xmlDoc.SelectSingleNode("rss")
            channelNodes = rssNode.ChildNodes
            
            for channelNode in channelNodes:
                itemNodes = channelNode.SelectNodes("item")
                
                self.feed.ChannelName = channelNode.SelectSingleNode("title").InnerText
                
                for itemNode in itemNodes:
                    try:
                        newitem = RSSFeedItem()
                        newitem.Title = str(itemNode.SelectSingleNode("title").InnerText)
                        newitem.Description = str(itemNode.SelectSingleNode("description").InnerText)
                        newitem.Link = str(itemNode.SelectSingleNode("link").InnerText)
                        if itemNode.SelectSingleNode("guid"):
                            newitem.GUID = str(itemNode.SelectSingleNode("guid").InnerText)
                        self.feed.Items.append(newitem)
                    except:
                        self.feed.BadCount = self.feed.BadCount + 1
        except:
            print "Error Parsing Results"
        self.onComplete(self.feed)
    def ReadAsync(self):
        try:
            webClient = WebClient()
            webClient.DownloadDataCompleted += self.Parse
            uri = System.Uri(self.feed.FeedURL)
            webClient.DownloadDataAsync(uri)
        except:
            print "Error Starting Read"
        
if __name__ == "__main__":
    from System.Threading import *
    are = AutoResetEvent(False)
    
    def callback(feed): 
        print "Results!"
        for item in feed.Items:
            print item.Title
            print item.Description
            print item.Link
        are.Set()   
        
    feedReader = RSSFeedReader("http://feeds.feedburner.com/sharpthinking", callback)
    feedReader.ReadAsync()

    are.WaitOne()

One of the things I like about IronPython 2 is that this code alone can be run from the command line. Simply saving this code as rssreader.py and running it from the command line with ipy rssreader.py will run the test code at the bottom which will output recent posts from this weblog.


Tags: , ,
Categories:
Comments (3)

#.think.in infoDose #15 (5th Jan - 9th Jan)

January 12, 2009 09:56 by brodie

Announcements

Management/Agile/Development

Architecture

.NET

ASP.NET/jQuery

Silverlight

WCF

Singularity Watch

Utilities

Books

1094-412661cc794ab6ce

The Big Switch: Rewiring the World, from Edison to Google

image

Outliers : Malcolm Gladwell

Other


Tags:
Categories: Links
Comments (0)

#.think.in infoDose #14 (22nd Dec - 2nd Jan)

January 5, 2009 08:47 by brodie

Management/Agile/Development

Architecture

.NET

Web - ASP.NET/jQuery/CSS

Silverlight/WPF

Books

image

Singularity Watch

SQL

Other

Quotes

"My life is kind of incomplete without an arbitrary scoring system to validate me." from @codinghorror tweet

"Google before you speak." from @rands tweet

"Your resume is the pamphlet of your career and not the novel itself " - codesqueeze


Tags:
Categories: Links
Comments (4)