WP7: Display Different Data Template for Different Items in the List - DataTemplateSelectors with the help of Caliburn.Micro

by Mikael Koskinen 20. January 2012 16:27

imageIn this Windows Phone 7 tutorial you will learn how to take advantage of Caliburn.Micro when displaying a list of non-identical objects to the user.

Background

Displaying a ListBox for a list which contains different kinds of items is a common scenario when developing applications for Windows Phone. For example your list can present a folder which contains files and subfolders. When presenting this list to the user you usually want the files and folders to look different. There’s many ways to make this happen, like ValueConverters and the following implementation using abstract DataTemplateSelector.

But if you’re using the Caliburn.Microframrwork, you’re all set. The functionalities inside the framework will take care of this automatically for you.

Scenario

You want to present a folder structure to the end user. You have one bindable list which contains both the subfolders and files and you have one ListBox inside the XAML which shows both types of items. The File.cs and Folder.cs files contain our models:

    public class File
    {
        public int Id { get; private set; }
        public string Name { get; private set; }
        public double Size { get; private set; }

        public File(int id, string name, double size)
        {
            Id = id;
            Name = name;
            Size = size;
        }
    }
    public class Folder
    {
        public int Id { get; private set; }
        public string Name { get; private set; }
        public List<Folder> SubFolders { get; private set; }

        public Folder(int id, string name, List<Folder> subFolders)
        {
            Id = id;
            Name = name;
            SubFolders = subFolders;
        }
    }

In the ListBox you want to display the item’s name and a little icon next to it to indicate the item’s type.

Implementation – The ViewModel

What we’re going to need is just one ViewModel. We’re not going to create ViewModels for our models:

  • MainPageViewModel – The main VM which contains the list which is shown to the user.

The MainPageViewModel inherit’s the Caliburn.Micro’s Conductor-class. The Conductor provides our VM an observable list where we can add items. When the list is modified, our view automatically gets a notification and knows to update itself.

You could manually create a new ObservableCollection , but in our case we try to take the full advantage out of our framework of choice.

The list can be accessed through the Items-property. So, to add a new item into our observable list, we can just call Items.Add(OurObject). As you’ll notice, we have defined the conductor as type of object. This mean that the Items-property inside the conductor is of type BindableCollection<object>. Meaning, you can actually add any type of items inside the collection. You can use more precise type if you know what kind of items your list contains.

The actual work the MainPageViewModel has to do is to create some fixed data for our application:

    public class MainPageViewModel : Conductor<object>.Collection.AllActive
    {
        protected override void OnInitialize()
        {
            Items.AddRange(new List<object>()
                               {
                                   new Folder(1, "Folder 1", null),
                                   new Folder(2, "Folder 2", null),
                                   new File(1, "File 1", 30.0),
                                   new File(2, "File 2", 30.0),
                                   new File(3, "File 3", 30.0),
                                   new Folder(4, "Folder 3", null),
                               });
        }
    }

We now have our ViewModel all set up so let’s concentrate on the Views next.

Implementation – The Views

We need three different Views:

  • MainPage.xaml – The page which contains our ListBox
  • FolderView – A UserControl which displays the Folder-item
  • FileViewModel – A UserControl which displays the File-item

Our MainPage.xaml is simple, containing only the ListBox. We’ll name it “Items” so that the Caliburn.Micro automatically binds it to our MainPageViewModel’s Items-property. What differs from the usual is our DataTemplate. We’re making the Caliburn.Micro to find the correct UserControl to represent our item.

            <ListBox x:Name="Items">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <ContentControl cal:View.Model="{Binding .}" HorizontalAlignment="Left" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

Now that our MainPage is all set we can create the UserControls which represent the items inside the folder. Here’s the implementation of FileView:

 

    <Grid x:Name="LayoutRoot" Background="Transparent" Margin="12 0 0 0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Image Grid.Column="0" Source="/Icons/appbar.save.rest.png"/>
        <TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center" Style="{StaticResource PhoneTextNormalStyle}"/>
    </Grid>

And the FolderView looks similar, we’ll just change the Image. But notice, you could customize these views anyway you want, for example displaying the folder’s subfolders inline.

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Image Grid.Column="0" Source="/Icons/appbar.folder.rest.png"/>
        <TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center" Style="{StaticResource PhoneTextTitle3Style}"/>
    </Grid>

imageIf we now run the application we will see that everything is almost set, except that the Caliburn.Micro can’t find the correct views for the File and Folder-classes. Let’s take care of that next.

Matching the ViewModel and Models to Views

As you remember, we didn’t create ViewModel-classes for the models. Instead we’ll tell Caliburn.Micro how to match the File and Folder –models to their views.

Caliburn.Micro uses conventions to match Views and ViewModels. The most common convention for finding a View for a ViewModel is to just drop the Model from the type name, for example CustomerViewModel –> CustomerView. The framework is highly configurable and you can add your own conventions. 

In order for the framework to find our views, we’ll add couple rules to its ViewLocator. We configure the ViewLocator inside the application’s bootstrapper. We could have named our models FileViewModel and FolderViewModel and if we had done so, we wouldn’t need the following because the Caliburn.Micro could automatically find the views:

            ViewLocator.NameTransformer.AddRule("caliburn_micro_datatemplate_selector.File", "caliburn_micro_datatemplate_selector.FileView");
            ViewLocator.NameTransformer.AddRule("caliburn_micro_datatemplate_selector.Folder", "caliburn_micro_datatemplate_selector.FolderView");

The code explains itself quite well. We just pass in the ViewModel-type’s full name and the matching View’s full name. To make the code refactoring easier we could use types instead of the magic strings:

            ViewLocator.NameTransformer.AddRule(typeof(File).FullName, typeof(FileView).FullName);
            ViewLocator.NameTransformer.AddRule(typeof (Folder).FullName, typeof (FolderView).FullName);

And that’s it. We now have an application which can handle the situation where one list contains multiple types of objects.

image

Conclusion

Caliburn.Micro offers a powerful solution for those situations where your list contains multiple types of objects and you want to display them in a single ListBox.

Full source code for the sample app is available from the GitHub.

Links

Marketwatcher: WP7 Class Library and Sample Application for Fetching Application Reviews from the Marketplace

by Mikael Koskinen 19. January 2012 19:10

imageI’ve just committed the first working version of Marketwatcher, a library for Windows Phone 7 which can be used to fetch application reviews from the Windows Phone Marketplace. It is available from the GitHub and it’s licensed with MIT, so you can use the library anyway you want.

Get the source code.

Get the compiled binaries.

Sample application

The Marketwatcher GitHub repository contains a sample app which can used to check out how the library is used.

Review data

At the moment an app review is described with the following model:

        public string Id { get; private set; }
        public string Author { get; private set; }
        public DateTime UpdateTime { get; private set; }
        public int Score { get; private set; }
        public string Comments { get; private set; }
        public string CountryCode { get; private set; }

Implementation

Marketwatcher uses the Reactive Extensions. It may be that the RX is dropped at some point in favor of a implementation that doesn’t require any other DLLs. The library also references System.ServiceModel.Syndication which is used to parse the review data. The referenced dlls are included in the repository.

Usage

Marketwatcher.Fetcher:

Use either:

public IObservable<List<Review>> FetchReviewsForApp(string appId)

or

public IObservable<List<Review>> FetchReviewsForAppFromOneMarketplace(string appId, string marketplaceCountryCode)

In the app you can subscribe to these. The following example is from the sample app:

            var fetcher = new Fetcher();

            progressIndicator.IsVisible = true;

            var reviews = new ObservableCollection<Review>();
            Items.ItemsSource = reviews;

            fetcher.FetchReviewsForApp(this.Appid.Text)
                .ObserveOn(SynchronizationContext.Current)
                .Subscribe(x =>
                               {
                                   foreach (var review in x)
                                   {
                                       reviews.Add(review);
                                   }
                               },
                               ex => Debug.WriteLine("error"),
                               () => progressIndicator.IsVisible = false);

Nuget

The Nuget package is coming!

Download

GitHub repository.

Binaries.

WP7 Application’s Mango-upgrade: Remember to check TextBox InputScopes

by Mikael Koskinen 5. January 2012 10:19

We recently upgraded one of our Windows Phone 7 apps to Mango and things went quite smoothly using the Visual Studio’s built-in “Upgrade to Windows Phone 7.1” –functionality.

image

But when we started testing the app, we noticed that some of our textboxes had a wrong input scope. In one case this made our app impossible to use with virtual keyboard because the textbox was missing the Enter-key.

We had previously defined the textbox using the InputScope “Number”:

<TextBox InputScope="Number"/>

And with out pre-Mango version this was the output:

image

But, after upgrading the app to Mango, the keyboard was completely different:

image

The Enter-key was gone, rendering the app useless. We had to change the InputScope to “PostalCode” to get the required functionality back:

image

So, even if the Mango-upgrade goes smoothly, it’s a good idea to test all the textboxes before submitting your app.

Links

Tags:

wp7

Caliburn.Micro: RadTransitionControl custom convention

by Mikael Koskinen 25. December 2011 09:06

I’ve recently started using the Telerik’s RadControls for Windows Phone. Here’s a convention for Caliburn.Micro which allows the easier usage of RadTransitionControl:

            ConventionManager.AddElementConvention<RadTransitionControl>(ContentControl.ContentProperty, "DataContext", "Loaded").GetBindableProperty =
        delegate(DependencyObject foundControl)
        {
            var element = (ContentControl)foundControl;

            var useViewModel = element.ContentTemplate == null;

            if (useViewModel)
            {
                return View.ModelProperty;
            }

            return ContentControl.ContentProperty;
        };

With this convention you can use the RadTransitionControl similar to ContentControl but with the difference that RadTransitionControl will show an effect (like fading) when the content changes. Here’s an example where the convention is used by Caliburn.Micro to automatically bind a control named Target to the ViewModel’s property:

XAML:

<telerikPrimitives:RadTransitionControl x:Name="Target" />

View model:

        public NewTargetBaseViewModel Target
        {
            get { return target; }
            set
            {
                target = value;
                NotifyOfPropertyChange(() => Target);
            }
        }

WP7 Application bar icons in a separate assembly

by Mikael Koskinen 14. December 2011 20:49

During today’s Windows Phone development session I encountered a problem with the application bar icons. The problem was simple: The application bar and its icons worked correctly inside the startup project but my pages inside the secondary project didn’t show the application bar icons.  I had the icons inside the secondary project and I checked million times that their build action was set to Content. And that was the problem: I forgot that the application bar icons must always be inside the startup project.

I suppose this is basic stuff and I should have known better, but sometimes these just take too much time to solve so hopefully the following helps someone else fighting with the same problem.

So, to solve my problems I had to do the following:

  1. Move the icons from the secondary module into the startup project
  2. Leave the IconUri-property as it was. No component keyword, nothing.

Now the startup project contains the icons:

image

The secondary module project contains the page and no icons:

image

The page inside the secondary module references the icons as if they were inside its own project:

image

ReSharper will tell you that the file doesn’t exist but it still works.

Windows Phone Mango Panorama: Working around the minimized appbar bug

by Mikael Koskinen 3. December 2011 18:04

Background

imageBefore the Mango update, Windows Phone developers were advised against using the application bar with a Panorama control. This was somewhat changed with the Mango which includes  a new minimized mode for the application bar. This mode can be accessed using the Mode-property of the application bar.

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True" Mode="Minimized">

For example the built-in People hub uses this new mode. But if you cycle through the panorama items in the People hub, you’ll notice that there’s one panorama item where the application bar is shown in its default mode. This is a nice behavior because it draws the user’s attention  to the functionalities provided by the application bar. And this same behavior has been replicated by many 3rd part applications. But be careful with it, because in its current state, changing between the two different modes may cut some pixels from your panorama’s background.

The problem

The problem manifests itself if you meet the following conditions in you app:

  • You have a page which includes the panorama
  • You have an application bar
  • You’re switching between the two different app bar modes (minimized / default)
  • User can arrive to the panorama page so that the app bar is in default mode.

When the panorama is first drawn with the application bar in default mode, it doesn’t draw the background properly when the app bar is switched to the minimized mode.

Let’s go through some examples.

Example 1: Application starts with the application bar in minimized mode

If the app bar is in minimized mode when the app is started, everything works correctly:

Application bar minimized:

 

image

Application bar in default mode:

image

Example 2: Application starts with the application bar in default mode

If the app bar is in default mode when the app is started, the background loses some pixels when the app bar is minimized:

Application bar in default mode:

image

Application bar minimized:

image

As you can see, the background is cut. This happens also with the first example if the user is able to navigate forward when the application bar is in default mode. When the user navigates back, the background doesn’t draw correctly.

Workaround

Until the bug is fixed, probably the simples workaround is to set application bar’s opacity to less than 1.

        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True" Mode="Default" Opacity="0.99">
            <shell:ApplicationBarIconButton IconUri="appbar.questionmark.rest.png" Text="help"/>
        </shell:ApplicationBar>

Example 3: Application starts with the application bar in default mode and the application bar’s opacity is less than 1

If the app bar is in default mode when the app is started, and its opacity is less than 1, everything works correctly:

Application bar in default mode:

image

Application bar minimized:

image

Tags:

wp7

Windows Phone: Deep linking and the back button

by Mikael Koskinen 20. November 2011 15:33

Windows Phone Mango adds the ability to create secondary tiles for your applications. We have recently upgraded most of our applications to the Mango and in doing so, few of our apps now allow users to add “deep links” to the Start menu, in the form of secondary tiles. Imagine an app where the first page (MainPage.xaml) shows a list of products and the selection of a product takes him/her to the next page for product details (ProductDetails.xaml). A deep link is an application startup tile which opens the app from the ProductDetails.xaml instead of the MainPage.xaml.

Deep links and the back button

Creating these deep links is a straightforward task using the tools provided by the Windows Phone platform, but what we didn’t anticipate is that these links cause a design issue: What should happen when the user starts the application using a deep link and then presses the Back-button? We did some search on this question and didn’t find any guidance so we followed our initial idea: the back button should take the user to app’s main page. Turns out we were wrong.

Since then Peter Torr has written an excellent guidance for this specific design issue. The main point from his post is that the back button should always take the user back to the previous page. If the previous page is the Start menu, then so be it.

Pinned secondary tiles for marketplace applications should behave exactly the same way. Tapping on a tile should deep-link directly into the content that the user pinned, and the Back key should always exit the application and return to Start.

Home button

The design dilemma between the deep links and the back button also touches an another issue: The home button. Windows Phone design guidelines have previously stated that the application shouldn’t have a Home-button. But in some situations it would be handy if the user could navigate to the main page of your application even if the app has been started from a deep link. Peter Torr shortly discusses this issue in his post so I recommend to check it out.

We have found the home button very useful when used in combination with the deep linkis. Some of our apps have the home button and it is really useful in these situations. Even though it is against the design guidelines, we think it improves the usability so much that we are going to use it also in the future.

Links:

How to fix error “The partner transaction manager has disabled its support for remote/network transactions (HRESULT 0x8004D025)” when using NServiceBus with SQL Server

by Mikael Koskinen 16. November 2011 18:21

We recently upgraded our database server but in doing so, we broke all our NServiceBus-based Windows Services. Through the logs we noticed that the NServiceBus message handlers weren’t able to open database connections to our new SQL Server instance, instead throwing the following exception:

System.Transactions.TransactionException: The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025) ---> System.Runtime.InteropServices.COMException: The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025)

   at System.Transactions.Oletx.ITransactionShim.Export(UInt32 whereaboutsSize, Byte[] whereabouts, Int32& cookieIndex, UInt32& cookieSize, CoTaskMemHandle& cookieBuffer)

   at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)

Turns out Microsoft has an excellent step-by-step tutorial on how to fix this issue. The exception was thrown because the database server didn’t have the network DTC access enabled, but after following the steps from the guide and restarting the server, the problem went away.

Microsoft TechNet: Enable Network DTC Access

Windows Phone 7 Sockets: How to send a message

by Mikael Koskinen 22. October 2011 00:41
Advert: IRC7 is the IRC-client for Windows Phone 7. Learn more at irc7.org.

This is the part III of a tutorial series which will describe the WP7’s sockets-support from a developer’s perspective. This tutorial series will focus on developing WP7 applications which require a long running TCP-connection that send and receive text-based data. In these posts we will go through of building a complete IRC-client.

The focus of these posts is in the WP7’s developer’s perspective. If you want to better understand the inner details of the Internet sockets, Wikipedia has a good introduction on the subject.

We’re almost there. We have opened the connection to the server and we have also received some messages from it. But, we must identify ourselves to the server which we’re not doing yet. This means that we’re not following the IRC-specification, making the server close our connection almost immediately. In this tutorial we will start sending messages to the server.

Background

The IRC-specification states the following:

The recommended order for a client to register is as follows:

  1. Pass message
  2. Nick message
  3. User message

… The PASS command is used to set a 'connection password'.

… NICK message is used to give user a nickname or change the previous one.

… The USER message is used at the beginning of connection to specify the username, hostname, servername and realname of s new user. It is also used in communication between servers to indicate new user arriving on IRC, since only after both USER and NICK have been received from a client does a user become registered.

The server is currently disconnecting us because we’re not sending the required messages to the server. In this tutorial we can skip the PASS-message completely, because it’s only required when connecting to a password protected server. But, we have to send the NICK and USER-messages.

The syntax for the messages are listed in the specification:

Command:    NICK
Parameters:    <nickname>

Command:    USER
Parameters:    <username> <hostname> <servername> <realname>

The code

The server is now waiting for us to identify ourselves. We do this by sending the NICK and USER-messages with the correct parameters. Sending a message to the server happens through our Socket-instance’s SendAsync-method. Similar to receiving a message,  we need a buffer and a SocketAsyncEventArgs-class to do this. We could handle the Completed-event from the SocketAsyncEventArgs-instance to get a notification when the message has been send but we’re going to skip this part: If the connection is open, there’s no reason for the sending to fail.

        public void SendToServer(string message)
        {
            var asyncEvent = new SocketAsyncEventArgs { RemoteEndPoint = new DnsEndPoint(server, serverPort) };

            var buffer = Encoding.UTF8.GetBytes(message + Environment.NewLine);
            asyncEvent.SetBuffer(buffer, 0, buffer.Length);

            connection.SendAsync(asyncEvent);
        }

Every message we send must end with a newline. If the newline is missing, the server won’t process the message and this can cause problems which are hard to debug. Because of this we always add the newline to the message inside the SendToServer-message.

The NICK-message

Every user in the IRC-sever has a nick. This is their unique ID and also a display name which other users see. Sending the NICK-message to the server sets your  unique nick name. Currently our IrcClient.cs raises the CreateConnectionCompleted-event when a connection has been established to the server and we use this event to notify our application so that it can ask the IrcClient to send our NICK to the server.

        private void OnCreateConnectionCompleted(object sender, CreateConnectionAsyncArgs e)
        {
            if (!e.ConnectionOk)
            {
                UpdateStatus("Connection failed");
                return;
            }

            UpdateStatus("Connection OK");

            SendCredentialsToServer();
        }

        private void SendCredentialsToServer()
        {
            var myNickName = "myNick " + DateTime.Now.Millisecond;

            client.SendToServer("NICK " + myNickName);
        }
The USER-message

Sending the NICK-message isn’t enough because the server expects to see the USER-message also. The syntax for this message is little harder: <username> <hostname> <servername> <realname>. Especially the hostname and servername –parameters may be hard to figure out. But, things get easier when we figure out that those parameters aren’t actually needed and one can hardcode the values ‘0’ and ‘*’ instead. So the only parameters we have to deal with are the username and the realname.

Many people use their email-addresses as their realname. And even more people use their nicknames in both the username and realname variables. It’s up to you (or the user) to decide how much info you want to give out of yourself to the other users. We’re going to use our nickname in both of the parameters:

        private void SendCredentialsToServer()
        {
            var myNickName = "myNick " + DateTime.Now.Millisecond;
            client.SendToServer("NICK " + myNickName);

            var userMessage = string.Format("USER {0} 0 * :{1}", myNickName, myNickName);
            client.SendToServer(userMessage);
        }

Almost there

Based on our previous knowledge this should be enough. We’re sending the NICK-message and the USER-message. And in some cases this would be enough but because we are trying to connect to a QuakeNet-server, it isn’t. If you execute the code now you will see some new messages. First, there’s a PING-message with a random number after it. And then, little later the server will close the connection because “Your client may not be compatible with this server.”

image

The problem is that the server expects us to answer to the PING-message. This is done by sending it a PONG-message with the same random number it sent us. This doesn’t happen only when connecting to the server: As long as you are connected to the server, you will receive these PING-messages from time to time. And you always have to respond to them or the server will close the connection.

Some servers send the PING-message without the ‘:’-character. This is one of the big problems when creating a well-working IRC-client: There’s these small differences between the servers which makes parsing the messages rather hard.

So, we must read the number from the PING-message and send the correct PONG-message to the server. For this we’re going to create a new MessageObserver-class which will register to the client’s IrcMessageReceivedFromServer-event, parse the received PING-message and send the answer. First we create the new class:

    public class MessageObserver
    {
        private readonly IrcClient client;

        public MessageObserver(IrcClient client)
        {
            this.client = client;

            this.client.IrcMessageReceivedFromServer += OnIrcMessageReceivedFromServer;
        }

        private void OnIrcMessageReceivedFromServer(object sender, IrcMessageReceivedFromServer e)
        {
        }
    }

And then we create a one instance of it in the same place where the IrcClient-instance is created, in our MainPage’s constructor:

        public MainPage()
        {
            InitializeComponent();

            this.client = new IrcClient();
            this.client.CreateConnectionCompleted += OnCreateConnectionCompleted;

            this.observer = new MessageObserver(client);
        }

Now we just need to create the required parsing logic for the PING-message:

        private void OnIrcMessageReceivedFromServer(object sender, IrcMessageReceivedFromServer e)
        {
            if (string.IsNullOrWhiteSpace(e.Message))
                return;

            if (e.Message.IndexOf("PING :") == 0)
            {
                HandlePing(e.Message);
                return;
            }
        }

        private void HandlePing(string message)
        {
            var index = message.LastIndexOf(":");
            var pingNumber = message.Substring(index + 1);

            var pongMessage = string.Format("PONG :{0}", pingNumber);
            client.SendToServer(pongMessage);
        }

Current functionality

imageAfter these modifications our app is ready from the server connection’s point of view. We can connect to the server and send and receive messages with it. We’re now in point where the server sends us the MOTD (message of the day) message, welcoming us to the server.

Because our app can respond to the PING-messages, it can stay connected to the server as long as it wants. The app isn’t very useful yet because it outputs only into the Visual Studio’s output-window and it doesn’t have a textbox for sending messages to the server. But we have the framework on which we can build on. It’s now just about adding more features.

Next steps

We are off to a good start but there’s still many topics to discuss about:

  • Debugging the socket-connections
  • Adding some basic functions: Joining a channel and sending a message to it
  • Handling application switching / tombstoning

Source code

The whole source code for this tutorial is available from the GitHub.

Links

Windows Phone 7 Sockets: How to receive a message

by Mikael Koskinen 21. October 2011 12:22
Advert: IRC7 is the IRC-client for Windows Phone 7. Learn more at irc7.org.

This is the part II of a tutorial series which will describe the WP7’s sockets-support from a developer’s perspective. This tutorial series will focus on developing WP7 applications which require a long running TCP-connection that send and receive text-based data. In these posts we will go through of building a complete IRC-client.

The focus of these posts is in the WP7’s developer’s perspective. If you want to better understand the inner details of the Internet sockets, Wikipedia has a good introduction on the subject.

This second installment will continue from where the first part stopped: We have the TCP-connection open and now we must start listening to the messages that the server sends us.

Basics

Like we previously went through, the Socket and the SockectAsyncEventArgs are the two key classes when dealing with sockets in Windows Phone platform. They are used when a connection is created and they are used again when we want to receive messages from the server. We are going to continue with the pattern we established in the first tutorial: An instance of the SockectAsyncEventArgs is always used just once and then disposed.

To receive a message we need a buffer. The buffer is simply an array of byte which is created and attached to the SocketAsyncEventArgs-instance. When the server sends a message to our client, it may or may not fit into a single buffer. If it doesn’t fit, the rest of the message (or the next part) can be received by again creating a new SocketAsyncEventArgs-instance and a buffer for it.

Creating a new buffer for every message is not the only available solution. One could use a Circular buffer to optimize things performance wise. You can check out the “Circular Buffer for .NET”, a project from CodePlex, to see an example implementation.

The code

Like previously mentioned, in addition to the byte array we need an instance of the SocketAsyncEventArgs to receive a message from the server. This is passed to the ReceiveAsync-method of our socket-instance.

        private void ReceiveMessage()
        {
            var responseListener = new SocketAsyncEventArgs();
            responseListener.Completed += OnMessageReceivedFromServer;
            
            var responseBuffer = new byte[bufferSize];
            responseListener.SetBuffer(responseBuffer, 0, bufferSize);

            connection.ReceiveAsync(responseListener);
        }

The new ReceiveMessage-method can be called after the connection to the server has been opened. Now, when the server sends us a message, our method OnMessageReceivedFromServer is executed. In this method we need to do few things:

  1. Convert the buffer (the byte-array) into a string. This is the message (or the messages) that the server sent us.
  2. Parse the message and react to it according to the IRC-specification.
  3. Call the ReceiveMessage-method which will create a new buffer and a new SocketAsyncEventArgs and starts listening for the next message from the server.

One buffer can contain multiple messages from the server. Server can send multiple messages to us, meaning that the even though the OnMessageReceivedFromServer is executed only once, there may be multiple messages we have to deal with. In the IRC-protocol every message ends with a newline and we can use this to our advantage: After turning the byte-array into a string, we split it by the newline characters into a string-array. Every instance in the array is one individual message from the server.

But how can we be sure that the server’s message fit into our buffer? If the last character in the buffer is a newline, all is well. But if it isn’t, our buffer is full and there’s a new message waiting for us. And we have to do some stitching: We can’t process the current message because it’s not complete so we store it into a variable, wait for the next message and combine those two. Using the previously mentioned circular buffer would make this somewhat easier.

        private string trailingMessage;
        private void OnMessageReceivedFromServer(object sender, SocketAsyncEventArgs e)
        {
            // Convert the received message into a string
            var message = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);

            var bufferWasPreviouslyFull = !string.IsNullOrWhiteSpace(trailingMessage);
            if (bufferWasPreviouslyFull)
            {
                message = trailingMessage + message;
                trailingMessage = null;
            }

            var isConnectionLost = string.IsNullOrWhiteSpace(message);
            if (isConnectionLost)
            {
                // We lost the connection for some reason
                // Handle the situation
                return;
            }

            // Convert the received string into a string array
            var lines = new List<string>(message.Split("\n\r".ToCharArray(), StringSplitOptions.None));

            var lastLine = lines.LastOrDefault();
            var isBufferFull = !string.IsNullOrWhiteSpace(lastLine);
            if (isBufferFull)
            {
                trailingMessage = lastLine;
                lines.Remove(lastLine);
            }

            foreach (var line in lines)
            {
                if (string.IsNullOrWhiteSpace(line))
                    continue;

                ProcessIncomingMessage(line);
            }

            // Start listening for the next message
            ReceiveMessage();
        }

Now we just have to implement the ProcessIncomingMessage-method. In this method we will parse the message and react to it accordingly. It may contain a private message from an another user, a server check to make sure we’re still alive or anything else mentioned in the IRC specifications.

As you can see, we presume that the incoming message is UTF8-encoded. Unfortunately we can’t be sure of this. The IRC-protocol doesn’t dictate on what encoding should be used and this may vary between the clients. The UTF8 is nowadays the most used encoding so we use it.

At this point we’re going to just echo the message into our Debug-output but we’re going to add some basic parsing logic in the future episodes of this tutorial series.

        private void ProcessIncomingMessage(string ircMessage)
        {
            Debug.WriteLine(ircMessage);

            // Future hook for handling the message in somewhere else.
            // It's most probably wise to put the parsing logic in some other class.
           if (IrcMessageReceivedFromServer != null)
               IrcMessageReceivedFromServer(this, new IrcMessageReceivedFromServer(ircMessage));
        }

Current functionality

Now we’re almost there. We can already connect to a server and receive messages from it. You can see this in action if you run our app with the new code we have added in this tutorial.

image

But, if you let the app run long enough, you’ll also notice that we get two additional messages. One states that there has been an error and the other is just an empty string. The first message is received because we didn’t follow the IRC-specification: The server wants us to identify ourselves before it continues. Because we didn’t, the server disconnects our connection and the empty string is received because of this. If we try to call the ReceiveMessage-method after receiving the empty string, we will get an exception because the connection isn’t open anymore.

The next step

The next step is to start following the IRC-specification and actually send messages to the server. We will go through of this in the next part tomorrow.

Source code

The whole source code for this tutorial is available from the GitHub.

Links

About the author

Mikael is a systems architect at Digia. Writing .NET is what he does most of the days. He is also the founder of a Finnish software and web systems consulting company Software Mikael Koskinen. 

SilverlightShow ContributorStay updated with latest posts by following Mikael on Twitter.

 

Advert

IRC7 is the IRC-client for Windows Phone 7. Learn more at irc7.org.

Month List