|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionA few months ago, a friend of mine mentioned Circular Chess to me, and I thought it looks really cool. However, I was unable to find any online Circular Chess game. Implementing such a game with standard HTML and JavaScript would not be possible because of the box model, and I am not familiar with Flash or Java Applets, so I wasn't able to implement the game. And, then I heard about Silverlight, and watched some demos, and thought to myself what a great platform it is for the implementation of Circular Chess. In this article, I want to discuss the methods I used for creating an online game with ASP.NET AJAX 3.5 and WCF Web Services, and with the help of LINQ to SQL and, of course, Silverlight 1.1. If you're interested in the rules of Circular Chess, check out this site. BackgroundYou should be familiar with ASP.NET and databases, in general. I hope to take care of those not familiar with Web Services. Contents
Running the GameIn order for the game to work, make sure you have the Silverlight 1.1 runtime, which you can get here, Siverlight 1.1 SDK installed, which you can get here, and ASP.NET 3.5 Extensions CTP Preview, which you can get here. Other than that, running the game is just a matter of running Default.aspx in the CircularChessWebsite, either with VS2008 or IIS. If you want to test it, you can do it with Firefox or IE. If you want to play with others, make sure they have the Silverlight 1.1 runtime installed, and put it in some server (or in your localhost, if you know how to configure it). The Structure of a Silverlight SolutionFirst of all, you must be using Visual Studio 2008 (the Beta 2 worked just fine, but halfway through the writing, I passed to the complete version). Now, a Silverlight solution has to have at least two projects: The Silverlight Project (which creates a Siverlight 1.1 object) or Silverlight JavaScript Application, and an ASP.NET Website to make use of the Silverlight Project. The website doesn't necessarily need to be ASP.NET, but it is the preferred option, as we get better compatibility. I also suggest that if you can, you make an ASP.NET 3.5 Extensions Web Site, because it's easier to embed Silverlight there. If you need Web Services in your solution, create them as part of the website, because Silverlight can access them only in the same domain (unless you're using IIS). Apart from those two main projects, you may add helper classes. Silverlight controls (or any code used by a Silverlight Project) will be made in Silverlight Class Libraries, because you won't be able to add a reference to them otherwise. In my case, I have a helper Drag and Drop object, and some helper classes used both by the Silverlight project and the ASP.NET Website. Note that if you want to include helper classes, you must make sure those classes do not include any Silverlight features (not even using System;
namespace CChess
{
public class CChessBoard
{
// Implementation of the class
}
}
This is what our solution looks like:
So, that said, I'll leave the Silverlight subject for a while, and concentrate on the other parts of the application. Using ASP.NET AJAX 3.5 with WCF Web ServicesFirst of all, Web Services in a sentence (and believe me, I know people who think Web Services are the same as Web Applications): they're a standard way to expose information in your Web Application. Normally, you'd get access to the information in your database through the C# code in your code-behind files, but with Web Services, you can call a certain method through a URL, get your information back, and use it as you may. So, you may be asking, why WCF Web Services? They are, after all, harder to wire up than simple ASMX Web Services. Well, there are some reasons to prefer them:
So, even though today you may have a harder time building them, tomorrow you will feel happy you did. In order to use WCF Web Services, you must configure them first to work on JSON serialization for now, as it's the only supported method (currently). To do that, you must configure your endpoint to enable client script. That is done in your web.config file, like this: <system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="CChess.CChessBehavior">
<serviceMetadata />
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="jsonConfiguration">
<enableWebScript/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<bindings>
<webHttpBinding>
<binding name="webBindingConfig" allowCookies="true"></binding>
</webHttpBinding>
</bindings>
<services>
<service name="CChess.CChessService" behaviorConfiguration="CChess.CChessBehavior">
<endpoint
address=""
binding="webHttpBinding"
bindingConfiguration="webBindingConfig"
behaviorConfiguration="jsonConfiguration"
contract="CChess.ICChessService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
So, for those of you less familiar with WCF Web Services, I just gave the endpoint a Another thing to take into consideration is the namespace given to the contract. That is the namespace that will later be used in the JavaScript proxy created by ASP.NET (the Microsoft AJAX Library provides an implementation of namespaces for JavaScript, if you were wondering). You do it this way: [ServiceContract(Namespace = "CChessService")]
public interface ICChessService
{
[OperationContract]
void Login(CChessPlayer new_player);
// Other Web Methods
}
One very last thing you must do, is to add an attribute to the implementation of your contract, like this: [AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CChessService : ICChessService
{
// Implementation of the Contract
}
Well, now that we're all set up for using WCF Web Services, let's get to use them! First of all, you must make a <asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/CChessService.svc" />
</Services>
<Scripts>
<asp:ScriptReference Path="~/App_Script/HelperFunctions.js" />
<asp:ScriptReference Path="~/Default.aspx.js" />
</Scripts>
</asp:ScriptManager>
That tells ASP.NET to create a proxy to the Web Service, which you can use in your JavaScript. Note that in VS2008, you even get Intellisense for them, which is pretty neat.
My script manager also has references to two JavaScript files, which would be equal to referencing them in standard HTML, but this is the recommended way of doing this by Microsoft, so be it. One thing to note is that ASP.NET also creates a JavaScript version of classes or enums used by the Web Service, so you can make use of them like I'll show you in a moment. In order to use that proxy through JavaScript, you must first create an instance of it, and you can then call any of the methods you provided, with two callback functions, one for successful calls and another one for failed calls. Here's an example: /// <reference path="Default.aspx" />
/// <reference path="App_Script/HelperFunctions.js" />
// Default.aspx.js
function pageLoad()
{
var nick_textbox = $get("NicknameInput");
var button = $get("SubmitButton");
button.onclick = LoginButtonClicked;
// If 'enter' is pressed on nick_textbox,
// fire whatever event is fired when button is
// clicked
BindTextboxToButton(nick_textbox, button);
}
function LoginButtonClicked()
{
var nick = $get("NicknameInput").value;
var proxy = new CChessService.ICChessService();
$get("ResultsDiv").innerHTML = "Please Wait...";
var new_player = new CChessPlayer();
new_player.player_nick = nick;
proxy.Login(new_player, OnLoginComplete, OnLoginFailed);
}
function OnLoginComplete()
{
$get("ResultsDiv").innerHTML = "Login Successful";
window.location = "MainPage.aspx";
// Redirect the user to the logged in page
}
function OnLoginFailed(error)
{
$get("ResultsDiv").innerHTML = error.get_message();
}
Let's go step by step (like the name of the first ending of Detective Conan!). Because in VS2008 we have Intellisense in JavaScript, we also need a way to "include" .js files in other .js files, in order to get their variables and functions and classes and stuff in the Intellisense too. The way to do that is by using a triple-slash comment, and a reference element, like I did up in the example. That's more or less all there is to it really. If you were expecting a result to be returned by the method, it is passed as the first parameter to the successful callback, but because the
The DatabaseIn this application, I'm using an SQL Server 2005 database. It's a pretty simple database, so let me introduce you to it.
The two main tables of the database are players and games. The players table contains all the players that entered the game ever. Online players are differentiated from offline players with the help of the The games_players is the many-to-many table joining the games and the players. I chose this over hard-coding two players (white and black) into the games table, because this way, it's much easier to filter players, and it's more extensible. The games_moves table stores all the moves done in each game. It may seem like a bit expensive, and maybe it is, and a better solution would be to save the state of the chess board at any given moment, but I prefer it this way because it is, by far, much more flexible. For example, if I needed to implement an undo feature in the future, without storing each move, it wouldn't be possible. The last table is used for the chat box, nothing interesting there. LINQ to SQLBefore I begin, let me point out that this subject contains much more visual activity than our previous subject, and because of that, you may find it easier to watch some movies about it. You can find some here. So, with ASP.NET AJAX and WCF Web Services behind us, let's jump to our next station, LINQ to SQL. As you probably already know, LINQ is a an abbreviation for Language Integrated Query, and it does a good job living up to its name. It is a mini-query language which you write from inside C#, or any other .NET language, and which lets you do queries on collections ( Enough with the "whys", let's get to the hows! You start this by adding a "LINQ to SQL Classes" item to the App_Code folder of your application, which adds a .dbml file for you. You can then open your Server Explorer, open the connection to your database, and drag and drop your tables into the tables area. You can do the same for your Stored Procedures, only you'll drop them in the area on the right of the tables area. The end result looks like this:
Note that, by default, the names of the classes will match the names of your tables, and the names of the attributes will match the names of the columns. If you're not content with this, you can always edit them, but make sure you never do it directly to the generated source code, as it'll be edited automatically sooner or later, but only through the visual editor. You can do that simply by double clicking on the names you want to change. For each of the Stored Procedures you created, also make sure they have the expected return value in the Properties window:
If you have problems changing the return type, it means your Stored Procedure isn't compatible with it. Now comes the moment when we become happy we chose WCF as our web service provider, because the LINQ to SQL editor provides one simple property which we can change, and all the generated classes instantly get the
One last thing, before the examples. Because the classes created are partial, they can be extended (and are actually built with extension in mind). I wanted to show you an example of how it can be useful: // From App_Code/CChessGamesPlayers.cs
using System;
using System.Web;
using System.Data.Linq;
using System.Runtime.Serialization;
public partial class CChessGamesPlayers
{
[DataMember]
public String player_nick
{
get { return CChessPlayer.player_nick; }
set { }
}
partial void OnValidate(ChangeAction action)
{
// A player can't be inserted into a game,
// if he's currently inside another game
if (action == ChangeAction.Insert)
{
CChessDataContext cdc = new CChessDataContext();
CChessPlayer current_player =
((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
if (current_player.IsInAGame())
{
throw new Exception("You're already in a game.");
}
}
}
}
So, the first thing I added was a property to be serialized. Because Another very neat feature is the use of partial methods. They're methods introduced in .NET 3.0 that are called only if an implementation is provided in another partial implementation of the class, otherwise they're totally ignored (unlike events, that are still called, and cause overhead). They're ideal for the creation of extensible classes, and LINQ to SQL makes awesome use of them. Here, we implement the OK, let's see some examples from the service: public List<ChatMessage> GetLastNMessages(int n)
{
CChessDataContext cdc = new CChessDataContext();
List<ChatMessage> messages = (from msgs in cdc.ChatMessages
orderby msgs.message_id descending
select msgs).Take(n).ToList();
return messages;
}
Pretty much self-explanatory for anyone used to writing SQL. You must first create a SELECT msgs.message_id, msgs.message_writer, msgs.message_content, msgs.sent_timestamp
FROM chat_table AS msgs
ORDER BY msgs.message_id DESC
LIMIT n
For simplicity's sake, I used the public CChessGamesPlayers GetYou()
{
CChessDataContext cdc = new CChessDataContext();
ProfileCommon profile = (ProfileCommon)HttpContext.Current.Profile;
CChessGamesPlayers you = cdc.CChessGamesPlayers.Single(
p => p.player_id == profile.CChessPlayer.player_id);
return you;
}
As you can see, the tables inside the data context have some methods (here, we use Because we're talking about a database, chances are you're going to want to update, insert, and delete records. Here's how to insert: public void SendMessage(String content)
{
CChessPlayer current_player =
((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
if (current_player == null)
{
return;
}
ChatMessage msg = new ChatMessage
{
message_writer = current_player.player_id,
message_content = content,
sent_timestamp = UnixDate.GetCurrentUnixTimestamp()
};
CChessDataContext cdc = new CChessDataContext();
cdc.ChatMessages.InsertOnSubmit(msg);
cdc.SubmitChanges();
}
Just create a new message, initialize it (note that I used here the new syntax in .NET 3.5), call its table's Updating is a bit more tricky. In order for a record to be updated, the public void UpdateUserActivity()
{
CChessPlayer player =
((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
if (player == null)
{
throw new Exception("You're not online.");
}
CChessDataContext cdc = new CChessDataContext();
// Now, the reason I've used a stored procedure instead of standard Linq
// is because in order to get player to be traced by the DataContext,
// I'd need to select it again, and then update it. In this case, a
// standard stored procedure is a far better option (I'd even say
// an easier solution).
int affected_rows = cdc.UpdateUserActivity(player.player_id,
UnixDate.GetCurrentUnixTimestamp());
if (affected_rows < 1)
{
throw new Exception("Unable to update activity.");
}
}
And if you're interested, here's the SQL of the procedure: ALTER PROCEDURE UpdateUserActivity
@player_id bigint,
@timestamp int
AS
UPDATE players
SET last_activity = @timestamp
WHERE player_id = @player_id
RETURN @@ROWCOUNT
Note that I return OK, so we're done with LINQ to SQL. Let's take a break from the new functionality for a while, and take a look at how the system works! Login and Logout
Unlike normal websites, where a user logs in and is authorized for certain things, and can then logout, this website needs to keep track of the users at real time. Imagine what would happen if a player disappeared in the middle of a game, and his opponent didn't even know! So, my method for keeping track of the user is by having a // Short version of MainPage.aspx.js
function pageLoad()
{
GetOnlinePlayers();
UpdateUserActivity();
GetLastestFiveMessages();
GetCurrentGamePlayers();
BindTextboxToButton($get("NewMessageInput"),
$get("MessageSubmitButton"));
}
function GetOnlinePlayers()
{
var proxy = new CChessService.ICChessService();
proxy.GetOnlineUsers(OnlinePlayersGotten);
}
function OnlinePlayersGotten(result)
{
var found;
for(var i = 0; i < result.length; i++)
{
found = false;
for(var j = 0; j < online_players.length; j++)
{
if(result[i].player_id == online_players[j].player_id)
{
found = true;
break;
}
}
if(!found)
{
AddUser(result[i]);
}
}
for(var i = 0; i < online_players.length; i++)
{
found = false;
for(var j = 0; j < result.length; j++)
{
if(online_players[i].player_id == result[j].player_id)
{
found = true;
break;
}
}
if(!found)
{
RemoveUser(online_players[i]);
}
}
online_players = result;
setTimeout("GetOnlinePlayers()", 5000);
}
function AddUser(user)
{
var tmp_div = document.createElement("div");
tmp_div.id = "user_" + user.player_id.toString();
tmp_div.innerHTML = user.player_nick;
$get("OnlinePlayers").appendChild(tmp_div);
}
function RemoveUser(user)
{
$get("OnlinePlayers").removeChild($get("user_" + user.player_id));
}
function UpdateUserActivity()
{
var proxy = new CChessService.ICChessService();
// Updates the last activity so you don't get offline
proxy.UpdateUserActivity(UserActivityUpdated, FailedActivityUpdate);
}
function UserActivityUpdated()
{
// Delay the next update 20 seconds
setTimeout("UpdateUserActivity()", 20000);
}
function FailedActivityUpdate()
{
$get("MessageDiv").innerHTML = "You have gone offline. Please login again.";
// Wait 5 seconds for the user to read the message, then redirect him
setInterval("window.location = 'Default.aspx';", 5000);
}
And the relevant service methods: public void Login(CChessPlayer new_player)
{
CChessDataContext cdc = new CChessDataContext();
new_player.last_activity = UnixDate.GetCurrentUnixTimestamp();
cdc.CChessPlayers.InsertOnSubmit(new_player);
cdc.SubmitChanges();
// Store the player in his profile
ProfileCommon profile = (ProfileCommon)HttpContext.Current.Profile;
profile.CChessPlayer = new_player;
}
public void UpdateUserActivity()
{
CChessPlayer player =
((ProfileCommon)HttpContext.Current.Profile).CChessPlayer;
if (player == null)
{
throw new Exception("You're not online.");
}
CChessDataContext cdc = new CChessDataContext();
// Now, the reason I've used a stored procedure instead of standard Linq
// is because in order to get player to be traced by the DataContext,
// I'd need to select it again, and then update it. In this case, a
// standard stored procedure is a far better option (I'd even say
// an easier solution).
int affected_rows = cdc.UpdateUserActivity(player.player_id,
UnixDate.GetCurrentUnixTimestamp());
if (affected_rows < 1)
{
throw new Exception("Unable to update activity.");
}
}
public List<CChessPlayer> GetOnlineUsers()
{
CChessDataContext cdc = new CChessDataContext();
return cdc.GetOnlinePlayers(CChessPlayer.LEGAL_INACTIVITY_SECONDS,
UnixDate.GetCurrentUnixTimestamp()).ToList<CChessPlayer>();
}
And for completeness sake, the ALTER PROCEDURE GetOnlinePlayers
@legal_inactivity_seconds int,
@unix_timestamp int
AS
SELECT *
FROM players
WHERE @unix_timestamp - last_activity < @legal_inactivity_seconds
So, when the page loads, I call all the functions that update the UI regularly. They get the data back, update the UI, and call themselves again with a certain interval (as big as it can get, and still be natural). Note that we could have very easily made a regular registration and login system, and the implementation would have changed much, and that's how you'd probably do it in a complete website, but because this is only a demo, I kept it as anonymous. Note that I used the Creating New GamesSo, before starting to talk about Silverlight in the application, I'll review the creation of new games quickly. Any user who wants to start a new game can create it, and he'll be black or white, depending on his choice. After that, he's redirected to the Circular Chess Board, where he waits for someone to join him, and when someone does, they can start playing! One nice thing I wanted to point out was the function that creates the new game: <a href="javascript: CreateNewGame(CChessGameColors.White);">Create new game as white</a>
<a href="javascript: CreateNewGame(CChessGameColors.Black);">Create new game as black</a>
As you can see, we call the function with the enums we created in the server side. I think it's really neat that even those get translated by the proxy. SilverlightEven though I named the article Online Circular Chess in Silverlight, I get to talk about it only now... Oh well, here we go! At the introduction, I kind of compared Silverlight to Flash and Java Applets, and the truth is that Silverlight is Microsoft's competence towards Adobe's Flash. Because I don't have time to learn ActionScript and Flash from scratch, I learned Silverlight. In this article, I'll talk about the more technical parts of Silverlight, rather than artistic ones (because I'm not an artistic person). If what you need is to create complex XAML (Extensible Application Markup Language, the language that lets you be artistic in Silverlight) to decorate your application, download Expression Blend 2 here (at the moment of the writing of this article being the December Preview trial), and start playing with it. Also, take in mind that right now, there are no common controls in Silverlight, that means no Textboxes, no Buttons, Combo Boxes, etc. A few were released in September (which you can get here), but you can expect a full release in the first quarter of 2008. So, again, before beginning, I wanted to point you out to some great videos from which I learned a lot, you can get them here. Silverlight BasicsA Silverlight 1.1 project has, by default, the following files:
Let's explore each of the files. <Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="parentCanvas"
x:Class="CircularChess.Page;assembly=ClientBin/CircularChess.dll"
xmlns:CChessControls=
"clr-namespace:CircularChess;assembly=ClientBin/CircularChess.dll"
Width="800"
Height="600"
>
<TextBlock Canvas.Left="5" Canvas.Top="3" x:Name="my_textblock"
Canvas.ZIndex="1" Text="Loading..." Foreground="#FFFFFF" />
<CChessControls:PlayerPicture Canvas.Top="10" Canvas.Left="650"
x:Name="opponent_textblock"></CChessControls:PlayerPicture>
<CChessControls:PlayerPicture Canvas.Top="450" Canvas.Left="650"
x:Name="your_textblock"></CChessControls:PlayerPicture>
</Canvas>
That is from ChessBoardPage.xaml, by default called Page.xaml. Self explanatory, isn't it? Well, everything is wrapped in a Let me talk about some of the attributes. The <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<!-- saved from url=(0014)about:internet -->
<head>
<title>Silverlight Project Test Page </title>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript" src="TestPage.js"></script>
<style type="text/css">
body
{
padding: 0;
margin: 0;
}
.silverlightHost { width: 800px; height: 800px; }
</style>
</head>
<body>
<div id="SilverlightControlHost" class="silverlightHost" >
<script type="text/javascript">
createSilverlight();
</script>
</div>
</body>
</html>
TestPage.html.js: // JScript source code
//contains calls to silverlight.js, example below loads Page.xaml
function createSilverlight()
{
Silverlight.createObjectEx({
source: "ChessBoardPage.xaml",
parentElement: document.getElementById("SilverlightControlHost"),
id: "SilverlightControl",
properties: {
width: "100%",
height: "100%",
version: "1.1",
enableHtmlAccess: "true"
},
events: {}
});
// Give the keyboard focus to the Silverlight control by default
document.body.onload = function() {
var silverlightControl = document.getElementById('SilverlightControl');
if (silverlightControl)
silverlightControl.focus();
}
}
As you can see, TestPage.html is extremely simple. The only thing worth noting is that you must give the element containing the Silverlight object a width and height for it to be visible. We do it in the CSS at the top of the page. TestPage.html.js is pretty simple too. It uses the Silverlight.js created for us by VS2008 to create a simple
It'll copy Page.xaml into the website (note that VS2008 takes care of keeping it up to date with any changes you make to it in the Silverlight Project). You can then manually copy TestPage.html.js and Silverlight.js, and embed the Silverlight as shown above. Note that I didn't use this approach in order to embed the Silverlight into my page in the website though. I wanted to show you this approach because chances are your server doesn't yet have ASP.NET 3.5 Extensions installed, and if you want to embed Silverlight in your website, this would be the only way you have. If you do have access to the Extensions though, embedding Silvelight is as easy as inserting any other ASP.NET control into your page. After putting a Hooking Events on Silverlight ControlsThis is quiet trivial, but I felt bad not to mention it at all. Hooking up an event on a Silverlight control is the same as hooking up an event on a WinForms application, or even on a DOM element. You can hook mouse and keyboard events on objects that derive from public void MakeDraggable(FrameworkElement elem)
{
if (_root == null)
// Load the root
{
FrameworkElement tmp = elem.Parent as FrameworkElement;
while (tmp.Parent != null)
{
tmp = tmp.Parent as FrameworkElement;
}
_root = tmp as FrameworkElement;
_root.MouseMove += new MouseEventHandler(MouseMove);
_root.MouseLeftButtonUp += new MouseEventHandler(MouseLeftButtonUp);
}
elem.MouseLeftButtonDown += new MouseEventHandler(MouseLeftButtonDown);
}
public void RemoveDraggable(FrameworkElement elem)
{
elem.MouseLeftButtonDown -= MouseLeftButtonDown;
}
void MouseLeftButtonDown(object sender, MouseEventArgs e)
{
FrameworkElement elem = sender as FrameworkElement;
FrameworkElement parent = (FrameworkElement)elem.Parent;
_current_element = elem;
_starting_point = new Point((double)elem.GetValue(Canvas.LeftProperty),
(double)elem.GetValue(Canvas.TopProperty));
// Do Work.......
}
WCF Web Services in Silverlight 1.1So we have the Silverlight embedded into the website, the next step is to create a proxy to the web service. And this is were Silverlight gets ugly, and that's because, for some reason, VS2008 is unable to create a proxy for WCF web services, although it works great with ASMX web services. I read that some people actually got it to work pretty easily, but no matter how many times I tried, it just doesn't work, and I know I'm not the only one. Well, fortunately, there's a workaround, and while not really nice, it works for now, until this bug, if that's indeed what it is, gets fixed. First, create an ASMX web service that implements only the "skeleton" of the web service. You can easily do this in VS2008 by implementing the contract interface and adding the
After that, back in your Silverlight Project, right click in your project, and select "Add Web Reference".
In the dialog that opens, go to "Web Services in this Solution", and select your ASMX web service, and not you SVC web service (don't worry, we'll end up using the SVC one).
You may now give the reference a name, click on Add Reference, and the proxy will be created for you. Now, luckily for us, the proxy created for ASMX web services works exactly the same for WCF Web Services. The only thing we'd need to do is upon creation of the proxy, we'll change the URL of the service to the SVC one, like this: CChessProxy.CChessSkeleton proxy = new CChessProxy.CChessSkeleton();
proxy.Url = "CChessService.svc";
proxy.DoWork();
The Code-Behind File and Using the ProxyLet's first bring the code-behind file, ChessBoardPage.xaml: using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Collections.Generic;
using Silverlight.Controls;
using CChess.Controls;
using System.Windows.Browser;
using System.Net;
using CircularChess.CChessProxy;
namespace CircularChess
{
public partial class Page : Canvas
{
public Page()
{
Loaded += new EventHandler(Page_Loaded);
current_selected_square = null;
_last_move = new CChessMove() { move_id = 0 };
proxy = new CircularChess.CChessProxy.CChessSkeleton();
// Because the proxy was generated
// with an ASMX web service, we must
// redirect it to the WCF web service.
proxy.Url = "CChessService.svc";
drag_drop = new DragAndDrop { CenterOnMouseDown = true };
drag_drop.CustomMouseDown +=
new DragAndDropEventHandler(_drag_drop_CustomMouseDown);
drag_drop.CustomMouseMove +=
new DragAndDropEventHandler(_drag_drop_CustomMouseMove);
drag_drop.CustomMouseUp +=
new DragAndDropEventHandler(_drag_drop_CustomMouseUp);
}
#region Fields
private DragAndDrop drag_drop;
private CChessSquare current_selected_square;
private CChessBoard my_chessboard;
private CChessProxy.CChessGamesPlayers you;
private CChessProxy.CChessGamesPlayers opponent;
private CChessProxy.CChessSkeleton proxy;
private HtmlTimer opponent_get_timer;
private HtmlTimer new_move_timer;
private CChessMove _last_move;
private const int tile_img_width = 159;
private const int tile_img_height = 159;
private const String tile_img_url = "images/wood7.jpg";
#endregion
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
proxy.BeginGetYou(new AsyncCallback(YouGotten), null);
proxy.BeginGetOpponent(new AsyncCallback(OpponentGotten), null);
opponent_get_timer = new HtmlTimer();
opponent_get_timer.Interval = 5000;
opponent_get_timer.Tick += new EventHandler(opponent_timer_Tick);
new_move_timer = new HtmlTimer();
new_move_timer.Interval = 4000;
new_move_timer.Tick += new EventHandler(_new_move_timer_Tick);
new_move_timer.Start();
SetTileBackground();
}
public void SetTileBackground()
{
Image tmp;
for (int i = 0; tile_img_width * i < Width; i++)
{
for (int j = 0; tile_img_height * j < Height; j++)
{
tmp = new Image();
tmp.SetValue(Canvas.ZIndexProperty, -1);
tmp.SetValue(Canvas.LeftProperty, i * tile_img_width);
tmp.SetValue(Canvas.TopProperty, j * tile_img_height);
tmp.Source = new Uri(tile_img_url, UriKind.RelativeOrAbsolute);
Children.Add(tmp);
}
}
}
public void YouGotten(IAsyncResult iar)
{
you = proxy.EndGetOpponent(iar);
your_textblock.Text = you.player_nick;
proxy.BeginGetAllMoves(you.game_id, AllMovesGotten, null);
}
public void AllMovesGotten(IAsyncResult iar)
{
CChessMove[] moves = proxy.EndGetAllMoves(iar);
CChess.CChessBoard board = new CChess.CChessBoard();
for (int i = 0; i < moves.Length; i++)
{
board.MovePiece(moves[i].piece_id, moves[i].destination_ring,
moves[i].destination_square);
_last_move = moves[i];
}
Color your_color = (you.color == (byte)CChess.CChessColors.White) ?
CChessBoard.white_color : CChessBoard.black_color;
my_chessboard = new CChessBoard(your_color, 275, 100, board);
my_chessboard.SetValue<double>(Canvas.TopProperty, 0);
my_chessboard.SetValue<double>(Canvas.LeftProperty,
(Width / 2 - my_chessboard.Width / 2));
my_chessboard.SetValue<double>(Canvas.TopProperty,
(Height / 2 - my_chessboard.Height / 2));
my_chessboard.OnLoad += new CChessBoard.NoParametersEventHandler(
_my_chessboard_OnLoad);
Children.Add(my_chessboard);
my_textblock.Text = "Ready.";
}
void _my_chessboard_OnLoad()
{
foreach (CChessPiece piece in my_chessboard.Pieces)
{
if (piece.IsAlive)
{
drag_drop.MakeDraggable(piece);
}
}
foreach (CChessSquare square in my_chessboard.Squares)
{
square.MovingAnimationComplete +=
new EventHandler(piece_storyboard_Completed);
}
SelectCurrentPlayer();
}
void SelectCurrentPlayer()
{
if ((byte)my_chessboard.CurrentMoveColor == you.color)
{
opponent_textblock.UnselectPlayer();
your_textblock.SelectPlayer();
}
else
{
your_textblock.UnselectPlayer();
opponent_textblock.SelectPlayer();
}
}
void _new_move_timer_Tick(object sender, EventArgs e)
{
if (opponent != null && (byte)my_chessboard.CurrentMoveColor != you.color)
{
new_move_timer.Stop();
proxy.BeginGetLastMove(_last_move.move_id,
new AsyncCallback(LastMoveGotten), null);
}
}
public void LastMoveGotten(IAsyncResult iar)
{
CChessProxy.CChessMove last_move = proxy.EndGetLastMove(iar);
if (last_move != null && _last_move != null &&
last_move.move_id != _last_move.move_id)
{
_last_move = last_move;
CChessPiece the_piece = my_chessboard.Pieces[last_move.piece_id - 1];
CChessSquare the_square =
my_chessboard.Squares[last_move.destination_ring - 1,
last_move.destination_square - 1];
SetPieceOnTop(the_piece);
the_piece.Square.AnimatePieceToSquare(the_square);
}
else
{
new_move_timer.Start();
}
}
void piece_storyboard_Completed(object sender, EventArgs e)
{
SetPieceOnBottom(my_chessboard.Pieces[_last_move.piece_id - 1]);
my_chessboard.MovePiece(_last_move.piece_id, _last_move.destination_ring,
_last_move.destination_square);
SelectCurrentPlayer();
new_move_timer.Start();
}
void opponent_timer_Tick(object sender, EventArgs e)
{
proxy.BeginGetOpponent(new AsyncCallback(OpponentGotten), null);
opponent_get_timer.Stop();
}
public void OpponentGotten(IAsyncResult iar)
{
CChessProxy.CChessGamesPlayers p = proxy.EndGetOpponent(iar);
if (p == null)
{
if (opponent == null)
{
opponent_textblock.Text = "Waiting for someone to join.";
}
else
{
opponent_textblock.Text = opponent.player_nick + " has left.";
}
}
else
{
opponent = p;
opponent_textblock.Text = opponent.player_nick;
}
opponent_get_timer.Start();
}
/// <summary>
/// Sets a high z-index on the piece
/// </summary>
void SetPieceOnTop(CChessPiece piece)
{
CChessSquare square = piece.Square;
// Keeps the piece on top while dragging it.
square.SetValue<int>(Canvas.ZIndexProperty, 1);
}
/// <summary>
/// Sets a low z-index on the piece
/// </summary>
void SetPieceOnBottom(CChessPiece piece)
{
CChessSquare square = piece.Square;
square.SetValue<int>(Canvas.ZIndexProperty, 0);
}
void LastMoveSent(IAsyncResult iar)
{
try
{
_last_move =
new CChessMove() { move_id = proxy.EndMovePiece(iar) };
SelectCurrentPlayer();
}
catch (WebException ex)
{
my_textblock.Text = ex.GetBaseException().Message;
}
}
}
}
(I left out the drag and drop region, I'll discuss it later). OK, let's explore this little by little. In the constructor, I initialize the fields of the page, between them the proxy like I described in the previous section, and hook the Another thing to note in the code-behind, is how we add and manipulate controls programmatically. Take a look at the Custom Silverlight ControlsIn order to add a new control, add a new Silverlight User Control item. Let's take a look at the <Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="#442233"
Width="100"
>
<Image Source="images/king_picture.png" x:Name="player_img"></Image>
<!-- Set the Textblock under the image -->
<TextBlock Canvas.Top="85" x:Name="player_name"
Foreground="#FFFFFF" TextWrapping="Wrap" Width="100"></TextBlock>
<Rectangle x:Name="border" Canvas.Top="0" Canvas.Left="0"></Rectangle>
</Canvas>
And the code-behind: using System; | ||||||||||||||||||||