David Kennedy’s Tech Ramblings

Just another WordPress.com weblog

Silverlight, WCF, and Linq to Sql September 18, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 3:52 am

Whilst putting together the Silverlight datagrid sample from a few posts ago, I came across some odd behaviour with my LINQ entity ‘losing’ its schema definition on the client, resulting in the Grid not displaying the data. I’ll explain my approach by way of diagrams:

silverlinq1 silverlinq2

It seems as if the proxy classes for my LINQ entities were not being properly recognised on the client because of the nested WCF calls. I’m still unsure as to the exact cause of this issue, I verified that appropriate attributes added to my LINQ entity classes to support WCF were being propagated to the proxies built from them, however the only way I could get the Grid to successfully display data was by removing the additional tier.

Advertisements
 

Good little Silverlight Tutorials September 2, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 9:02 am

This has some good stuff on it, quick little introductions into various areas of the visual side of Silverlight

http://www.nibblestutorials.net/

 

Silverlight Adventures Part 2 August 26, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 12:29 am

The Grid

Ok, for this exercise we start with a new Silverlight project (you can create this in either Expression Blend or Visual Studio). Next, we want to add a reference to system.windows.controls.data (to make the datagrid control accessible) before dragging the new Silverlight Datagrid control onto the page, producing xaml as follows:

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SilverGridClient.Page" Width="640" Height="480"> <Grid x:Name="LayoutRoot" Background="White"> <my:DataGrid x:Name="dg" AutoGenerateColumns="True"></my:DataGrid> </Grid> </UserControl>

Next, we want to add a WCF Service project to the solution within VS. As a data source for our grid I’ve opted for a simple LinQ to SQL dbml, so add a new ‘LINQ to SQL Classes’ item to the WCF project. From server explorer, define a connection to any DB that has some data in it, and drag a populated table onto the designer. Since we are passing this type via our WCF service, we need to decorate the generated LinQ classes (in the designer.cs file) with the DataContract attribute. Open the Web.config for the WCF service & ensure that the service endpoint is defined with basicHttpBinding as the binding attribute. Finally, we need to define a method to return data for our grid. In my case I was using a table specifiying documents from a recent project I was involved with, so I added a method as follows:

public List<FreestyleDocument> GetAllDocuments() { SilverLinqDataContext linq = new SilverLinqDataContext("Server=.;Database=Data_DavesJuly;Integrated Security=true"); return linq.FreestyleDocuments.Take(500).ToList(); }

Now build the WCF service.

Next we need to generate a proxy for our service, so we want to add a service reference to our WCF service from the Silverlight project. Kick up the WCF service (or host it in IIS if you prefer) and navigate to the service endpoint from the ‘Add Service Reference’ context option. This will create our proxy as well as a ServiceReferences.ClientConfig file defining the endpoint. Finally, we want to include the call via the proxy to our WCF service method which we have defined in order to populate the grid view. Do this in the Page.xaml.cs file; in my case the code looked like this:

public Page() { //InitializeComponent(); SilverProxy.Service1Client webService = new SilverProxy.Service1Client(); webService.GetAllDocumentsCompleted += new EventHandler<SilverProxy.GetAllDocumentsCompletedEventArgs>(webService_GetUsersCompleted); webService.GetAllDocumentsAsync(); } void webService_GetUsersCompleted(object sender, SilverProxy.GetAllDocumentsCompletedEventArgs e) { dg.ItemsSource = e.Result; }

At this stage it would be possible for the Silverlight application to directly access our WCF service if it weren’t for the browser-side sand-boxing which prevent cross domain calls. Attempting to run the application now will result in something similar to the following error:

An error occurred while trying to make a request to URI ‘http://localhost/SilverService/Service1.svc’. This could be due to a cross domain configuration error. Please see the inner exception for more details.

According to online documentation, this problem should be averted by including one or both of the following configuration override files in the root of the hosting web server (in my case I hosted the service in IIS to make this process simpler):

crossdomain.xml:

<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>

clientaccesspolicy.xml

<?xml version="1.0" encoding="utf-8" ?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>

In my case neither of these files made any difference; I continued to receive the cross domain call error. Failing this approach, the next step is to institute an intermediate proxy on the server side of our Silverlight project, thus negating the cross domain call from the browser. After moving the Linq2Sql ORM & WCF service into the main ASP.NET site which included the Silverlight application the datagrid was bound to the data as expected.

 

Silverlight Pacman! August 13, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 12:29 am

I’ve finally got my pacman game hosted on live.com 🙂

http://silverlight.services.live.com/invoke/75950/Pacman/iframe.html

 

Silverlight Adventures Part 1 July 28, 2008

Filed under: Silverlight Tutorials — dotnetdave @ 12:02 am
Tags: , , ,

The Setup

OK, first things first. We need to install Microsoft Expression Blend (I’m using the 2.5 June 2008 Preview which has been updated for Silverlight Beta 2 and version 3.5 SP1 of the .NET Framework)

http://www.microsoft.com/expression/try-it/default.aspx

You will also need to install the Silverlight Tools Beta 2 for VS 2008 in order to edit Silverlight projects & codefiles in VS:

http://www.microsoft.com/downloads/details.aspx?FamilyId=50A9EC01-267B-4521-B7D7-C0DBA8866434&displaylang=en

This includes Silverlight version 2 client which will allow you to run all the samples in your browsers. Note that this one requires all dependant apps be closed & takes 10 to 15 minutes to install, so its a good time to grab a coffee if you’re so inclined.

Now onto the basics. Aside from the demo’s & tutorials available within Expression Blend, I’ve primarily utilised the
following two resources as a basis to develop my understanding of Silverlight:

http://www.ddjsilverlight.com/tutorial/section1_3.asp
http://www.c-sharpcorner.com/UploadFile/nipuntomar/DataBindingSilverlightWCFService07152008011652AM/DataBindingSilverlightWCFService.aspx

As a starting point, I’d like to develop two small projects to demonstrate the application of Silverlight. The first a simple game, and the second a simple client-side grid capable of aggregating data directly from a backend SqlServer database via a WCF service.

The Game

I asked Rachel (my partner) to suggest a simple game to re-author in Silverlight, and she came back with Pacman. There are similar projects online, however most of them seem to utilise Microsofts XNA/Game Studio which is not my interest in this exercise. I came across http://ctrl-alt-die.blogspot.com/2008/03/pacman-silverlight.html which seems to be exactly what I want to accomplish here, so for my very first Silverlight project I’ll use this post from Dominic Green’s blog as a springboard to get me started into re-creating the game myself in order to gain familiarity with both xaml (I didn’t realise Silverlight utilised so much xaml until this week!) and Expression Blend.

So far, I’ve found that using VS & Expression Blend side-by-side has been the best strategy. The majority can be done within VS, however I am finding that Expression Blend is handy for the positioning & layering of objects visually in the designer.

The xaml for this is very simple, as follows:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="SilverlightPacman.Page" Width="590" Height="433" KeyDown="UserControl_KeyDown"> <Canvas x:Name="GameMap" Background="black"> <Image Source="pacman_gamemap.png"/> <TextBlock Height="22.31" Width="62" Foreground="White" x:Name="pacPosition" Canvas.Left="510" Canvas.Top="396.11" Text="00" TextWrapping="Wrap" RenderTransformOrigin="1.95200002193451,-19.9109992980957"/> <Image Source="pacman.png" x:Name="pacman" Height="15" Width="15"/> <TextBlock Height="22.31" Width="62" Foreground="White" x:Name="pacCollision" Text="none" TextWrapping="Wrap" Canvas.Left="510" Canvas.Top="300"/> <TextBlock x:Name="score" Height="21" Width="82" Canvas.Top="60" Canvas.Left="10" Text="00000001" Foreground="White" TextWrapping="Wrap"/> </Canvas> </UserControl>

After following the approach indicated in the blog post, we have a simple pacman screen with a moving pacman & collision detection for the boundaries. Now we’re on our own, so lets take it one step at a time

The next thing we need is for our pacman’s mouth to be facing correctly for his direction of movement.
Lets hit IrfanView (or any simple image editor) and produce rotated images for the pacman facing up, down, and left.

Now the key things we are missing is some tic tacs? for our pacman to munch on, and a couple of monsters.

Rather than deliberate on the specifics of how I tackled each area of the game development, I’ll dump the complete source in which these should be clearly apparent.

The Code

namespace SilverlightPacman { public partial class Page : UserControl { int count = 0; int spriteX = 0; int spriteY = 0; int moveX = 0; int moveY = 0; Rect[] rects; int noRects; /// <summary> /// The current appropriate image given his direction of movement (or attempted movement) /// </summary> string pacImage = "pacman.png"; /// <summary> /// Stores the tictacs so we can make our pacman eat them /// </summary> Ellipse[] tictacs; /// <summary> /// The bad guys! /// </summary> Image[] monsters; // and a timer for monsters System.Threading.Timer aiTimer; public Page() { // Required to initialize variables InitializeComponent(); ResetGame(); } /// <summary> /// Clears the game board and restarts game play /// </summary> private void ResetGame() { // clear any existing game objects if (monsters != null) foreach (Image m in monsters) GameMap.Children.Remove(m); if (aiTimer != null) aiTimer.Dispose(); if (tictacs != null) foreach (Ellipse e in tictacs) GameMap.Children.Remove(e); // create the boundaries createOutOfBounds(); // start pacman at the entrance spriteX = 92; spriteY = 190; pacman.SetValue(Canvas.LeftProperty, (double)spriteX); pacman.SetValue(Canvas.TopProperty, (double)spriteY); // and draw some tic tacs for foodage tictacs = new Ellipse[2000]; // should be enough DrawTicTacs(); // lets add some monsters PlaceMonsters(); // start our timer for the monster movement (gives a 2 second headstart for pacman) aiTimer = new System.Threading.Timer(new System.Threading.TimerCallback(RunMonsterAI), null, 2000, 300); } private enum CoinToss { heads, tails } private CoinToss FlipCoin() { Random rand = new Random(); if (rand.Next(42) % 2 == 0) return CoinToss.heads; else return CoinToss.tails; } /// <summary> /// Makes it easy to re-call the movement code. If reverseOverride is tainted, they will make the first possible move /// </summary> /// <param name="reverseOveride"></param> private delegate void moveDelegate(bool reverseOverride); /// <summary> /// Moves the monsters based on the simple strategy of generally moving towards pacman /// </summary> /// <param name="unused"></param> private void RunMonsterAI(object unused) { // rule 1: Monsters should move towards pacman // need UI thread to access DependencyProperties Dispatcher.BeginInvoke(() => { foreach (Image monster in monsters) { // pull position props double pacLeft = (double)pacman.GetValue(Canvas.LeftProperty); double pacTop = (double)pacman.GetValue(Canvas.TopProperty); double monLeft = (double)monster.GetValue(Canvas.LeftProperty); double monTop = (double)monster.GetValue(Canvas.TopProperty); bool moved = false; // need to mixup the order of these so they won't ALWAYS move horizontally when they have both options available moveDelegate horizontal = delegate(bool logicOverride) { // horizontal first if (logicOverride || pacLeft > monLeft) // try right if (!CheckForCollisionAny(monLeft + 5, monTop, 18, 18)) // can move { monLeft += 5; monster.SetValue(Canvas.LeftProperty, monLeft); moved = true; } if (!moved && logicOverride || pacLeft < monLeft) // try left if (!CheckForCollisionAny(monLeft - 5, monTop, 18, 18)) // can move { monLeft -= 5; monster.SetValue(Canvas.LeftProperty, monLeft); moved = true; } }; moveDelegate vertical = delegate(bool logicOverride) { if (!moved && logicOverride || pacTop > monTop) // try down if (!CheckForCollisionAny(monLeft, monTop + 5, 18, 18)) // can move { monTop += 5; monster.SetValue(Canvas.TopProperty, monTop); moved = true; } if (!moved && pacTop < monTop) // try up if (!CheckForCollisionAny(monLeft, monTop - 5, 18, 18)) // can move { monTop -= 5; monster.SetValue(Canvas.TopProperty, monTop - 5); moved = true; } }; moveDelegate flipMove = delegate(bool logicOverride) { if (FlipCoin() == CoinToss.heads) { horizontal(logicOverride); vertical(logicOverride); } else { vertical(logicOverride); horizontal(logicOverride); } }; // try logical moves first flipMove(false); // if no sensible moves can be made (ie. towards pacman) then override the logic and force any possible move if (!moved) { flipMove(true); } } // and finally lets check to see if they've caught pacman // need to do this based on pacmans moves as well CheckForMonsterSmackdown(); }); } /// <summary> /// Checks if a monster has come in contact with pacman, and Resets the game as needed /// </summary> private void CheckForMonsterSmackdown() { foreach (Image monster in monsters) { // pull position props double pacLeft = (double)pacman.GetValue(Canvas.LeftProperty); double pacTop = (double)pacman.GetValue(Canvas.TopProperty); double monLeft = (double)monster.GetValue(Canvas.LeftProperty); double monTop = (double)monster.GetValue(Canvas.TopProperty); // look for any intersect Rect pacRect = new Rect(pacLeft, pacTop, pacman.Width, pacman.Height); Rect monRect = new Rect(monLeft, monTop, 18, 18); // check for intersect with boundary pacRect.Intersect(monRect); // returns the intersect rect (if there is one) if (pacRect != Rect.Empty) { ResetGame(); break; } } } /// <summary> /// Handler for all keystrokes in the game /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserControl_KeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.Up: moveX = 0; moveY = -5; pacImage = "pacman_up.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Down: moveX = 0; moveY = 5; pacImage = "pacman_down.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Left: moveX = -5; moveY = 0; pacImage = "pacman_left.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; case Key.Right: moveX = 5; moveY = 0; pacImage = "pacman.png"; if (!CheckForCollision()) updatePosition(moveX, moveY); break; } } /// <summary> /// Checks if the currently propositioned move of Pacman will intersect any of the defined boundaries /// </summary> /// <returns></returns> private bool CheckForCollision() { return CheckForCollisionAny(spriteX + moveX, spriteY + moveY, pacman.Width, pacman.Height); } /// <summary> /// Checks if the object specified by the given co-ordinates is colliding with defined boundaries /// </summary> /// <param name="X"></param> /// <param name="Y"></param> /// <param name="Width"></param> /// <param name="Height"></param> /// <returns></returns> private bool CheckForCollisionAny(double X, double Y, double Width, double Height) { bool tmp = false; // and loop through the boundary rects to look for any intersects for (int i = 0; i < noRects; i++) { // construct a rect representative of the move taking place Rect tmpRect = new Rect(X, Y, Width, Height); // check for intersect with boundary tmpRect.Intersect(rects[i]); // returns the intersect rect (if there is one) if (tmpRect != Rect.Empty) { tmp = true; pacCollision.Text = i.ToString(); } } return tmp; } /// <summary> /// Moves pacman by the given offsets /// Implicitly calls AnimatePacman, EatTicTacs, and checks for Monster Collision /// </summary> /// <param name="x"></param> /// <param name="y"></param> private void updatePosition(int x, int y) { spriteX += x; spriteY += y; // i needed to cast to double, looks like the dependency prop def has changed recently pacman.SetValue(Canvas.LeftProperty, (double)spriteX); pacman.SetValue(Canvas.TopProperty, (double)spriteY); animatePacman(); // and mainly to help me define the boundaries, lets throw the pacman position co-ordinates on the page pacPosition.Text = pacman.GetValue(Canvas.LeftProperty).ToString() + "," + pacman.GetValue(Canvas.TopProperty).ToString(); // have pacman eat any tictacs that he crosses paths with EatTicTacs(); // check for death CheckForMonsterSmackdown(); } /// <summary> /// Alternates the image used for pacman between mouth open and mouth closed to visualise a hungry pacman /// </summary> private void animatePacman() { if (count % 4 == 0) pacman.Source = new BitmapImage(new Uri("pacman2.png", UriKind.Relative)); else pacman.Source = new BitmapImage(new Uri(pacImage, UriKind.Relative)); count++; } /// <summary> /// Defines the boundaries of play, based upon the corridors visible on the game map image /// </summary> private void createOutOfBounds() { noRects = 50; int i = 0; // make it easier rects = new Rect[noRects]; // left outer wall = 95-100 2 blocks, 10-135 top & 260-410 bottom rects[i++] = new Rect(95, 10, 5, 125); rects[i++] = new Rect(95, 260, 5, 150); // top outer wall = 95-505, 10-15 rects[i++] = new Rect(95, 10, 410, 5); // right outer wall, 2 blocks rects[i++] = new Rect(505, 10, 5, 125); rects[i++] = new Rect(505, 260, 5, 150); // bottom outer wall rects[i++] = new Rect(95, 408, 410, 5); // and now all the internal stuff (working from top left to bottom right, across and then down) // the top half rects[i++] = new Rect(125, 40, 50, 27); rects[i++] = new Rect(201, 41, 64, 27); rects[i++] = new Rect(290, 15, 19, 50); // top centre 9 rects[i++] = new Rect(335, 40, 65, 27); rects[i++] = new Rect(426, 41, 50, 27); rects[i++] = new Rect(125, 93, 49, 15); rects[i++] = new Rect(201, 93, 20, 92); rects[i++] = new Rect(217, 133, 46, 13); rects[i++] = new Rect(245, 93, 109, 14); rects[i++] = new Rect(290, 106, 21, 40); rects[i++] = new Rect(335, 133, 46, 13); rects[i++] = new Rect(381, 94, 19, 92); rects[i++] = new Rect(426, 93, 49, 14); // the middle block rects[i++] = new Rect(246, 173, 108, 52); // entrance rects[i++] = new Rect(99, 133, 77, 5); // top top of entrance rects[i++] = new Rect(167, 135, 5, 47); // top inside of entrance rects[i++] = new Rect(93, 180, 82, 5); // top corridor of entrance rects[i++] = new Rect(93, 212, 82, 6); // bottom corridor of entrance rects[i++] = new Rect(169, 215, 6, 50); // bottom inside of entrance rects[i++] = new Rect(93, 260, 79, 5); // bottom bottom of entrance // exit rects[i++] = new Rect(427, 133, 80, 5); // top top of exit rects[i++] = new Rect(426, 135, 5, 50); // top inside of exit rects[i++] = new Rect(427, 181, 79, 5); // top corridor of exit rects[i++] = new Rect(427, 212, 79, 5); // bottom corridor of exit rects[i++] = new Rect(427,212, 5, 50); // bottom inside rects[i++] = new Rect(427, 259, 79, 5); // bottom bottom of exit // the bottom half rects[i++] = new Rect(201, 212, 18, 52); rects[i++] = new Rect(246, 252, 108, 12); rects[i++] = new Rect(291, 264, 18, 40); rects[i++] = new Rect(381, 212, 18, 51); rects[i++] = new Rect(126, 291, 50, 12); rects[i++] = new Rect(156, 291, 17, 52); rects[i++] = new Rect(201, 291, 63, 12); rects[i++] = new Rect(336, 292, 62, 12); rects[i++] = new Rect(426, 291, 48, 12); rects[i++] = new Rect(427, 292, 18, 51); rects[i++] = new Rect(96, 331, 34, 12); rects[i++] = new Rect(201, 331, 18, 40); rects[i++] = new Rect(126, 371, 138, 10); rects[i++] = new Rect(247, 331, 108, 12); rects[i++] = new Rect(291, 343, 19, 39); rects[i++] = new Rect(383, 331, 18, 40); rects[i++] = new Rect(337, 371, 139, 13); rects[i++] = new Rect(473, 331, 30, 12); } /// <summary> /// Adds the munchables for our pacman to consume /// </summary> private void DrawTicTacs() { // define our brushes used to draw the biscuits SolidColorBrush fillBrush = new SolidColorBrush() { Color = Colors.Yellow }; SolidColorBrush strokeBrush = new SolidColorBrush() { Color = Colors.White }; // in lines except where crosses bounds foreach (int line in new int[] { 24, 79, 119, 275, 316, 357, 394 }) { for (int i = 107; i < 488; i = i + 20) { Ellipse tt = new Ellipse() { Height = 5, Width = 5, StrokeThickness = 1, Fill = fillBrush, Stroke = strokeBrush }; tt.SetValue(Canvas.TopProperty, (double)line); tt.SetValue(Canvas.LeftProperty, (double)i); // don't draw if it intersects a boundary if (!CheckForCollisionAny(i, line, 10, 10)) // dont want to draw them too close to boundaries { GameMap.Children.Add(tt); // and keep a reference array (no Lists in silverlight!) to check once its eaten tictacs[GameMap.Children.Count - 1] = tt; } } } // and the downwards lines foreach (int line in new int[] { 113, 186, 232, 279, 325, 369, 415, 489 }) { for (int i = 24; i < 388; i = i + 20) { Ellipse tt = new Ellipse() { Height = 5, Width = 5, StrokeThickness = 1, Fill = fillBrush, Stroke = strokeBrush }; tt.SetValue(Canvas.TopProperty, (double)i); tt.SetValue(Canvas.LeftProperty, (double)line); if (!CheckForCollisionAny(line, i, 10, 10)) // leave a buffer as we dont want them hard-up against walls { GameMap.Children.Add(tt); // and keep a reference array (no Lists in silverlight!) to check once its eaten tictacs[GameMap.Children.Count - 1] = tt; } } } } /// <summary> /// If pacman is currently on top of any tictacs, this will have him 'eat' them and update the score /// </summary> private void EatTicTacs() { foreach (Ellipse lolly in tictacs) { if (lolly == null) continue; // and ignore eaten ones if (!GameMap.Children.Contains(lolly)) continue; // check if pacman is on top of the biscuit if ((((double)lolly.GetValue(Canvas.TopProperty)) > ((double)spriteY)) && ((((double)lolly.GetValue(Canvas.TopProperty)) + 5) < ((double)spriteY + pacman.Height)) && (((double)lolly.GetValue(Canvas.LeftProperty)) > ((double)spriteX)) && ((((double)lolly.GetValue(Canvas.LeftProperty)) + 5) < ((double)spriteX + pacman.Width))) { // eat it! GameMap.Children.Remove(lolly); // and update the score int tmp = int.Parse(score.Text.TrimStart('0')); tmp = tmp + 5; score.Text = tmp.ToString().PadLeft(8, '0'); } } } /// <summary> /// Adds the bad guys /// </summary> private void PlaceMonsters() { const int numMonsters = 4; monsters = new Image[numMonsters]; // make it easier to reposition our bad guys double[] monsterX = new double[numMonsters] { 18, 387, 387, 18 }; double[] monsterY = new double[numMonsters] { 478, 480, 103, 103 }; for (int i = 0; i < numMonsters; i++) { monsters[i] = new Image() { Source = new BitmapImage(new Uri("monster_hunter.png", UriKind.Relative)), Width = 18 }; monsters[i].SetValue(Canvas.TopProperty, monsterX[i]); monsters[i].SetValue(Canvas.LeftProperty, monsterY[i]); GameMap.Children.Add(monsters[i]); } } } }