Sony Arouje

a programmer's log

Posts Tagged ‘Silverlight

Photo Album using Silverlight, Caliburn Micro and Mongo DB

with 14 comments

In this post I am going to explain a small Photo Album I created using Silverlight. With this app I also joined the band wagon of NoSQL movement. I used MongoDB as my backend for this application. This application is still in the development mode. I will add more functionality to this application like Public comments, admin module, etc. Currently this app shows the functionality of admin module, like upload photo, delete photo, etc. The application uses MVVM pattern and as usual I used Caliburn Micro here. I used several framework in the app as shown below

  • Silverlight
  • Caliburn Micro
  • Reactive Extension
  • MongoDB
  • WCF

In this application I used Reactive Extension (Rx) to perform Async service calls. I felt the Rx is a very powerful tool to perform Async calls. The one good advantage of Rx is you can place Async calls in Thread Pool and push it to dispatcher once the call completed. All these can be done in one or two lines of code. This way the UI will always responsive.

This is my first experiment with Mongo db, I can say it’s very easy to use and fast. There are several Mongo drivers for .NET available in the market and is free. I used the one from here. The advantage of Mongodb is, I don’t want to write code to map my POCO to tables. You can directly persist your entity to Mongodb. You can find a lot of article about Mongo db in internet

I don’t think I need to explain Caliburn Micro in detail here. I wrote a post that gives a brief introduction to Caliburn Micro.

Running MongoDB

Below are the steps to run MongoDB

  1. Download the latest version of Mongo db from Mongodb.org
  2. Extract the zip file to C:\Mongodb
  3. Create a Folder called data in C drive
  4. Create a subfolder called db inside data
  5. Goto command prompt and change directory to C:\Mongodb
  6. Type Mongod in the command prompt and hit enter

Now the Mongodb will listen to the default port and our app can make request. There are different ways of hosting Mongodb, you can get more details from Mongodb site.

The App

Below is the screen shot of my application.

image

This page will display the thumbnails of uploaded images. We can see the bigger image by clicking on the thumbnail as show below.

image 

The preview will display in a Windowless popup. We can delete the photo by clicking the Trash icon bottom right side of the image. We can upload images by clicking on the upload icon at the top right of the window. The upload page will get displayed as shown below.

image

When the user upload the image, the application will create a thumbnail version of the image and store along with the original image in the server. This helps to transfer and load the thumbnail page.

Let’s see the entity used in this application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
namespace PicturePortfolio.Domain
{
    [DataContract]
    public class Photo
    {
        private const int THUMBWIDTH = 100;
        private const int THUMBHEIGHT = 100;
        public Photo()
        {
 
        }
        public Photo(PhotoPersistanceObject persistanceObject)
        {
            this.Name = persistanceObject.Name;
            this.Description = persistanceObject.Description;
            this.ID = persistanceObject.ID;
            this.PublicComments = persistanceObject.PublicComments;
            this.GUID = persistanceObject.GUID;
            
            this.LoadThumbNail();
        }

        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string GUID { get; set; }

        [DataMember]
        public byte[] ImageFile { get; set; }

        [DataMember]
        public byte[] ImageThumbNail { get; set; }

        [DataMember]
        public string Description { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public List<PublicComment> PublicComments { get; set; }

        public PhotoPersistanceObject GetDataForPersistance()
        {
            PhotoPersistanceObject photoPersistance = new PhotoPersistanceObject();
            photoPersistance.ID = this.ID;
            photoPersistance.Name = this.Name;
            photoPersistance.ImagePath = "/test/" + this.Name;
            photoPersistance.Description = this.Description;
            photoPersistance.PublicComments = this.PublicComments;
            if (string.IsNullOrEmpty(this.GUID) == true)
                photoPersistance.GUID = Guid.NewGuid().ToString();
            else
                photoPersistance.GUID = this.GUID;
            return photoPersistance;
        }


        public void SaveImage()
        {
            if (this.ImageFile == null)
                throw new ArgumentNullException();
            try
            {
                byte[] buffer = this.ImageFile.ToArray();
                MemoryStream memStream = new MemoryStream();
                memStream.Write(buffer, 0, buffer.Length);

                this.SaveOrginalImage(memStream);
                this.SaveImageThumbNail(memStream);
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        public void LoadImage()
        {
            string fullFileName = this.GetFileFullPath();
            this.ImageFile = this.GetImageAsByteArray(fullFileName);
        }

        public void LoadThumbNail()
        {
            string fullFileName = this.GetFilePath() + this.ThumbNailImageName;
            this.ImageThumbNail = this.GetImageAsByteArray(fullFileName);
        }

        #region Private Methods
        private void SaveOrginalImage(MemoryStream memStream)
        {
            System.Drawing.Image imgToSave = System.Drawing.Image.FromStream(memStream);
            imgToSave.Save(this.GetFileFullPath(), ImageFormat.Jpeg);
        }
        private void SaveImageThumbNail(MemoryStream memStream)
        {
            Image img = Image.FromStream(memStream);
            Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);
            Image imageToSave = img.GetThumbnailImage (THUMBWIDTH, THUMBHEIGHT, myCallback, IntPtr.Zero);
            imageToSave.Save(this.GetFilePath() + ThumbNailImageName, System.Drawing.Imaging.ImageFormat.Jpeg);
        }

        private static bool ThumbnailCallback() { return false; }

        private byte[] GetImageAsByteArray(string fullFileName)
        {
            FileInfo fil = new FileInfo(fullFileName);
            if (fil.Exists == true)
            {
                FileStream fileStream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read);
                byte[] buffer = new byte[fileStream.Length];
                fileStream.Read(buffer, 0, (int)fileStream.Length);
                fileStream.Close();
                return buffer;
            }
            else
                return null;
        }

        private string GetFilePath()
        {
            string filePath = AppDomain.CurrentDomain.BaseDirectory + "Images\\";
            return filePath;
        }
        private string GetFileFullPath()
        {
            string fullPath = this.GetFilePath() + this.Name;
            return fullPath;
        }
        private string ThumbNailImageName
        {
            get
            {
                string fileName = this.Name.Substring(0, Name.LastIndexOf("."));
                return fileName + "_thumb.jpg";
            }
        }
        #endregion
    }
}

 

The entity class is very simple. I may need to give a brief explanation about PhotoPersistanceObject used in the entity class. The PhotoPersistanceObject is used for persisting meta data of the image like Name, description etc. You might think that why cant we save Photo object directly, yes I also thought the same way but their is some issue with Mongodb driver I used here. It’s not supporting byte array neither binary, It allows to store binary in Mongodb but throws error while retrieving. So I used a trimmed down version of Photo entity to persist the data.

Async Calls using Thread Pool

As I told before one of the cool feature of Reactive Extension is it’s advantage of making async calls in thread pool. Even if we place async calls in separate thread in Silverlight still it will block the UI to load if we do it in page load. Because the our thread will work in UI. We can achieve the placing calls in thread pool using Rx. See the below code.

private void GetAllThumbnails()
{
    var func=Observable.FromEvent<GetAllPhotosCompletedEventArgs>(_service,"GetAllPhotosCompleted")
        .ObserveOn(Scheduler.ThreadPool);
    _service.GetAllPhotosAsync();
    func.ObserveOn(Scheduler.Dispatcher)
        .Select(result => result.EventArgs.Result)
        .Subscribe(s => this.ParseThumbnails(s.ToList()));
}

As you can see the service calls are doing in ThreadPool. Once the call is done we will change the ObserveOn to Dispatcher, other wise cross thread expection will throw. I call the GetAllThumbnails in the constructor of a ViewModel. As we are doing the call in Threadpool my UI will loaded without any delay, once the call is completed the thumbnails will start display in the listbox.

Mongodb Repository

I wrote a Generic repository similar to the one I wrote for EF. You can find the generic repository for EF in one of my post. Below is the Generic repository class for Mongodb.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB;
using MongoDB.Linq;
using MongoDB.Connections;
namespace PicturePortfolio.Persistance
{
    public class GenericRepository:IRepository
    {
        public GenericRepository()
        {
 
        }

        Mongo _mongoDb=null;
        public IMongoCollection<TEntity> GetQuery<TEntity>() where TEntity : class
        {
            if (_mongoDb == null)
            {
                _mongoDb = new Mongo();
                _mongoDb.Connect();
            }
            return DataBase.GetCollection<TEntity>(typeof(TEntity).Name + "s");
        }

        IMongoDatabase db = null;
        private IMongoDatabase DataBase
        {
            get
            {
                if (db == null)
                {
                    db = _mongoDb.GetDatabase("Protfolio");
                }
                return db;
            }
        }

        public IList<TEntity> GetAll<TEntity>() where TEntity : class
        {
            try
            {
                return GetQuery<TEntity>().Linq().ToList<TEntity>();
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        public IList<TEntity> Find<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class
        {
            return this.GetQuery<TEntity>().Linq<TEntity>().Where(criteria).ToList<TEntity>();
        }

        public TEntity Single<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class
        {
            return this.GetQuery<TEntity>().Linq<TEntity>().Single(criteria);
        }

        public TEntity First<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class
        {
            return this.GetQuery<TEntity>().Linq<TEntity>().First(criteria);
        }

        public void Add<TEntity>(TEntity entity) where TEntity : class
        {
            this.GetQuery<TEntity>().Save(entity);
        }

        public void Delete<TEntity>(TEntity entity) where TEntity : class
        {
            this.GetQuery<TEntity>().Remove(entity);
            
        }

        public void Delete<TEntity>(IEnumerable<TEntity> entites) where TEntity : class
        {
            throw new NotImplementedException();
        }

        public void Update<TEntity>(TEntity entity, TEntity old) where TEntity : class
        {
            this.GetQuery<TEntity>().Update(entity, old, UpdateFlags.None, true);
        }
    }
}

 

This app is in very primitive stage. you may need to do lot of refactoring to make use in real scenario. I will work on this app and upload it to codeplex some times later.

Download the current source here.

Making Async calls in Silverlight using Reactive Extension (Rx)

with 4 comments

In this blog I am going to explain briefly about how to make async calls using Reactive Extension (Rx). Every one knows that Silverlight is one of the best platform to create RIA’s. One of the architecture constrain of Silverlight is, it wont directly interact with backend DB. So we need to have a webservice to perform the db operations. Silverlight can consume WCF or asmx service, but their is a catch silverlight can only communicate to webservices asynchronously. We all know that async calls have several advantages. One key feature of async call is, it wont block the UI while performing the call. But one downside in async programming is the coding pattern involved in placing async calls. When I place an async calls, I use an anonymous delegate approach or use lambda expression. But some how I am not satisfied with any of these approaches, I feel my code is not clean. So we always look for a better approach and our long waiting is over with the introduction of Reactive Extension (Rx) from MS labs.

You can visit Rx web site to get more details. I am not a person to explain the architecture or indepth details of Rx instead I am going to show how it benefit me in my async programing. I am going to rewrite one of my application I wrote to upload images using Silverlight and WCF, you can get more details of that app from my blog. I will rewrite only the part where I make async calls to server to get images from WCF service. Before get into refactoring, We need to download and install the Rx libraries from Rx web site.

Let’s refactor the code to make our async calls using Rx. We need to add couple of assembly references to add Rx to Silverlight, below are those assemblies.

  • System.Observables
  • System.CoreEx
  • System.Reactive

I demonstrated two ways of interacting with WCF service in the source code I uploaded. A proxy and proxy less approach. We all know the proxy approach, the communication to the service using the class generated by VS. In one of my post I provided some insight of Proxy less approach, you can check it out here.

If you have my source code in hand, have a look at the ImageListViewModel.cs in Silverlight app. You can see how I am making the async call.

In proxy less approach I use lambda expression to make the call.

IImageServiceAsync imgService = ServiceChannelProvider.CreateChannel<IImageServiceAsync>();
imgService.BeginGetAllImage(save =>
{
    try
    {
        imgs = imgService.EndGetAllImage(save);
        this.AddToUserLogoList(imgs);
    }
    catch (Exception ex)
    {
        throw;
    }
}, imgService);

In proxy approach I used a event driven approach as shown below.

ImageServiceClient imgClient = new ImageServiceClient();

imgClient.GetAllImageCompleted += new EventHandler<GetAllImageCompletedEventArgs>(imgClient_GetAllImageCompleted);
imgClient.GetAllImageAsync();

 

void imgClient_GetAllImageCompleted(object sender, GetAllImageCompletedEventArgs e)
{
    for (int i = 0; i < e.Result.Length; i++)
    {
        this.UserLogos.Add(e.Result[i]);
    }
    NotifyOfPropertyChange(() => UserLogos);
}

 

I am not going to explain the downside of these approaches mentioned above, Just like me you all might have experienced it. Lets rewrite the code using Reactive Extension (Rx).

Proxy less approach

private void ReadAllImages()
{
    IImageServiceAsync imgService = ServiceChannelProvider.CreateChannel<IImageServiceAsync>();
    var func = Observable.FromAsyncPattern<IList<UserImage>>(imgService.BeginGetAllImage, imgService.EndGetAllImage)
        .Invoke()
        .SubscribeOnDispatcher()
        .Subscribe(s => this.UserLogos = s.ToList<UserImage>());
}

Rx have one method called FromAsyncPattern, there we can provide our Beginxxx and Endxxx functions. I provided BeginGetAllImages and EndGetAllImages to FromAsyncPattern function. I also provided the return type of EndGetAllImages() to FromAsyncPattern function. Return type of EndGetAllImages is IList<UserImage>, so I called FromAsyncPattern as FromAsyncPattern<IList<UserImage>>. Rx uses Observer pattern to publish the result. So here I added UserLogos properties as my observer, once the execution is done the result will be pushed to the observer. Here the observer is a property in my view model. Below is the UserLogos property

List<UserImage> _logos = new List<UserImage>();
public List<UserImage> UserLogos
{
    get { return _logos; }
    private set
    {
        lock (this)
        {
            _logos = value;
            NotifyOfPropertyChange(() => UserLogos);
        }
    }
}

 

Let’s see how we can make an async call in proxy generated approach.

private void ReadAllImages()
{
    ImageServiceClient imgClient = new ImageServiceClient();
    var o = Observable.FromEvent<GetAllImageCompletedEventArgs>(imgClient, "GetAllImageCompleted")
        .ObserveOn(Scheduler.ThreadPool)
        .Select(result => result.EventArgs.Result)
        .Subscribe(s => this.UserLogos = s.ToList<UserImage>());
    imgClient.GetAllImageAsync();
}

Here I used FromEvent function provided by Rx instead of FromAsyncPattern. FromEvent accepts the event type, here it is GetAllImageCompletedEventArgs. It also accepts the service client object and which event it should handle. I passed the GetAllImageCompleted to the FromEvent function. Then we need to attach UserLogos as observer. After that we called GetAllImageAsync of service proxy.

You can see our code is pretty clean, we don’t have any messy code that we normally write to handle the async calls. Once you start using Rx I think you will never go back to the old approach of placing async calls.

Written by Sony Arouje

October 31, 2010 at 12:06 pm

Image uploading & Retrieving – Silverlight and WCF

with 9 comments

In several forums I saw people asking about how we can upload Images or files to server using silverlight. So I thought of writing one to demonstrate how we can achieve it. I become a big fan of caliburn micro and I used it in this app as well.

In this demo app I demonstrating both approach of accessing WCF service – Proxy less and Proxy approach. I personally like Proxy less approach so I cannot avoid it here also.

Let’s go through my solution structure

image

Repository: This project contains entities and persistence classes. I made it as simple as possible in this demo, so all the persistence related stuff is in the same project.

WCFService: Project contains the service implementation

WCFService.Interfaces: project contains the service contracts.

WCFHost: Project host the WCF service.

In Silverlight folder I have a Project called ServiceLinkProject, that is a project that has link to entity files. It is used for Proxy less WCF communication. In this demo I used my generic repository mentioned in my previous post.

There is nothing much to explain here except the valueconverter I used in the xaml page. I save and retrieve images as byte arrays. But silverlight’s Image control will not be able to render byte arrays. So wrote a Converter inherited from IValueConverter. Let’s see how am using it.

First I created an instance of the converter.

    <navigation:Page.Resources>
        <converter:ByteToImageConverter x:Name="binaryConverter"/>
    </navigation:Page.Resources>

 

Now use this converter while binding the byte[] to image.

<Image x:Name="thumbNail"  Width="100" Height="100" VerticalAlignment="Center" Source="{Binding ImageInBytes,Converter={StaticResource binaryConverter}}"></Image>


I think this project doesn’t requires much explanation. If any one has any questions then am happy to help you out.

Download Source code

Written by Sony Arouje

October 16, 2010 at 4:04 am

Proxy less Silverlight – WCF Async communication

with 12 comments

In this post I am going to explain how a silverlight application can communicate with WCF service without any proxy. I wanted to establish this communication without any autogenerated code because am not a fan of proxies generated by VS. If you want VS to create good autogenerated code then probably invest some time in learning T4 templates. Anyway I haven’t done any research in T4 templates, so let’s do every thing by hand.

Here is my requirement, I have to show a list of customer in my Silverlight app. Keep in mind it just an experiment, so no db involved in this app. For simplicity I hosted my wcf service in IIS.

Let’s straight to the code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using DataModel;
namespace ServiceContracts
{
    [ServiceContract]
    public interface ICustomerService
    {
        [OperationContract]
        ICollection<Customer> GetCustomerList();
    }

}

I only have one function in the service contract, a function return a collection of Customer class. Customer class is a just a C# class that has only two properties as shown below

using System.Runtime.Serialization;
namespace DataModel
{
    [DataContract]
    public class Customer
    {
        [DataMember]
        public int CustomerId { get; set; }

        [DataMember]
        public string CustomerName { get; set; }
    }
}
Let’s give some implementation to our Service Contract
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceContracts; //assembly consist service contracts
using DataModel; //assembly contains data contracts
namespace ServiceLayer
{
    public class CustomerService:ICustomerService
    {
        public ICollection<Customer> GetCustomerList()
        {
            List<Customer> customers = new List<Customer>();
            Customer cust = new Customer();
            cust.CustomerId = 1;
            cust.CustomerName = "sony";

            customers.Add(cust);
            return customers;
        }

        }

}

 

Now our service is ready to host, here I use IIS to host it. But how does Silverlight can utilize the Function we implemented, because I haven’t implemented Async methods. You might know that Silverlight can only work in Async mode. There are several ways of calling the above implemented service in Silverlight. One way is adding Service reference and leave it to VS to create the proxy with Async code generation. Another approach is use Castle Windsor to host the service in Async mode. Some how I didn’t like both the approach so I was trying for a better approach. My search ended up in a blog to achieve the Async communication without much modification to my Service code.

Here is the approach I was looking at. Create a new Service contract with Async functions and make it an alias to my original ICustomerSerivce

[ServiceContract(Name = "ICustomerService")]
public interface ICustomerServiceAsync
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginGetCustomerList(AsyncCallback callback, object state);

    ICollection<Customer> EndGetCustomerList(IAsyncResult result);
}

 

And use the above ICustomerServiceAsync to communicate to ICustomerService. This approach is pretty clean and we don’t want to add any Async methodology to our core service.

Below is my web.config of WCFService host.

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="CustomerService">
        <endpoint address="" contract="ServiceContracts.ICustomerService" binding="basicHttpBinding"></endpoint>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>  
</configuration>

 

As you see my service end point configuration I have not mentioned ICustomerServiceAsync. Let’s go through the silverlight client how we can communicate to our service.

private void button1_Click(object sender, RoutedEventArgs e)
{
    EndpointAddress endPoint = new EndpointAddress("http://localhost:63301/CustomerService.svc");
    BasicHttpBinding binding = new BasicHttpBinding();
    ICustomerServiceAsync customerService = new ChannelFactory<ICustomerServiceAsync>(binding, endPoint).CreateChannel();

    customerService.BeginGetCustomerList(a => 
    {
        try
        {
            ICollection<Customer> person = customerService.EndGetCustomerList(a);
        }
        catch (Exception ex)
        {
            throw ex;
        }

    }, customerService);

}

In a button click am calling the service.  Here I used the ICustomerAsync to create the channel. Ayende’s blog will give you more details.

You might think how I could able to reference ICustomerServiceAsync and Customer classes in my Silverlight client. What I did here is I created a Silverlight class library and add ICustomerAsync and Customer class as a link. In VS we can do it by selecting add existing item and rather than clicking on the Add button, click on the arrow on the right side of the Add and select as link from the option.

In the above client code I used lambda expression to call the service. You can also use a anonymous delegate or Callback method to achieve the same.

One important thing you need to add to our WCFService host project is clientaccesspolicy.xml, other wise Silverlight client will not be able to communicate and throws Security exception. Below is the policy file

<?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>

 

To test my connection and exceptions I used a Winform application. Silverlight wont give proper error details.

Download the code here

 

 

Written by Sony Arouje

October 1, 2010 at 7:14 pm

An Experiment In Caliburn Micro

In this blog I will explain my experiment with Caliburn Micro. The business logic of this app is

  • It should display a list of images
  • Add the image to the canvas when the user selected an image
  • Drag the Image and Position the Image where ever the user required
    For dragging/rotation/resize the image I used an Adorner control. You can get more details about the control here.

I have to add Images displayed in a list box to a canvas. Pretty simple 🙂 and helped me a lot to learn Caliburn Micro. Before going further I wanted to say that the app may not be a full MVVM model, you will come to know as you proceed. I am working on it to make it completely MVVM complaint.

Below is the screen shot of the Silverlight app.

image

Two buttons are there on the top.

  • Show Text: button will load User control that will have a UI with some text box and combo box.
  • Image Controls: Button will load the user control displayed above.

Bare with my naming, I am very poor in it.

When the user clicks on any of these button we need to show the respective View in the left hand side. I used Caliburn Micro as my MVVM frame work.

My project structure is below

image

  • Views: Contains my XAML files
  • ViewModels: contains my view model of the views
  • ValueObject: contains the value object class I used in this app
  • Messages: I used EventAggregator provided by Caliburn Micro to enable messaging between ViewModels. This folder contains the Class I used for messaging between ViewModels.
  • Controls: contains custom controls I used in this app.
  • Adorner: contains the Adorner controls
    Let’s jump into the code

      TabView

    This is the main view which calls other other user controls

    XAML
<navigation:Page x:Class="MVVMTracer.Views.TabView" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
           xmlns:tl="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit"
           xmlns:we="clr-namespace:MVVMTracer.Controls"
           mc:Ignorable="d"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           d:DesignWidth="640" d:DesignHeight="480"
           Title="TabView Page">
    <UserControl.Resources>
        <DataTemplate x:Key="shapeTemplate">
            <we:ControlBase Width="{Binding Width}" Height="{Binding Height}"/>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <StackPanel Grid.Row="0" Orientation="Horizontal" Height="30">
            <Button x:Name="ShowTextControls" Content="Show Text"></Button>
            <Button x:Name="ShowImageControls" Content="Image Controls"></Button>
        </StackPanel>
        
        <tl:TransitioningContentControl Grid.Row="1" Grid.Column="0" x:Name="ActiveScreen"></tl:TransitioningContentControl>

        <we:CustomItemsCollection x:Name="DisplayControls"  Grid.Row="1" Grid.Column="1">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas  Background="AliceBlue"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </we:CustomItemsCollection>
    </Grid>
</navigation:Page>

In the above xaml you might notice that I used TransitioningContentControl. This is the control I am going to use to show different UserControl based on the user request. By the way TransitioningContentControl is part of Silverlight Tool kit,

Here I used a CustomItemsCollection to add my dynamically added controls. CustomItemsCollection is a control derived from ItemsControl. I will show the implementation of it later.

TabViewModel.cs

 

using System;
using System.Net;
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.Windows.Shapes;
using Caliburn.Micro;
using MVVMTracer.Adorners;
using MVVMTracer.Controls;
using MVVMTracer.Messages;
namespace MVVMTracer.ViewModels
{
    public class TabViewModel:PropertyChangedBase,IHandle<Messages.ImageSelectionChanged>
    {
        public TabViewModel()
        {
            EventAggregatorContainer.EventAggregator.Subscribe(this);
        }
        private object _activeScreen;
        private Adorner _adorner = new Adorner();
        private Canvas _parentCanvas = new Canvas();
        public object ActiveScreen
        {
            get { return _activeScreen; }
            set
            {
                _activeScreen = value;
                NotifyOfPropertyChange(() => ActiveScreen);
            }
        }

        public void ShowTextControls()
        {
            ActiveScreen = new ShowTextViewModel();
        }

        public void ShowImageControls()
        {
            ShowImageControlViewModel imgViewModel = new ShowImageControlViewModel();
            ActiveScreen = imgViewModel;
        }
        public void Handle(Messages.ImageSelectionChanged message)
        {
            ControlBase ctrl = ShapeFactory.GetShape(ShapeType.Image, _parentCanvas, _adorner);

            ctrl.Source = message.CurrentImage;
            this.AddToUICollection(ctrl);
        }
        private void AddToUICollection(ControlBase control)
        {
            if (_displayControls == null)
            {
                _displayControls = new System.Collections.ObjectModel.ObservableCollection<UIElement>();
                _adorner.Height = 0;
                _adorner.Width = 0;
                _parentCanvas.Children.Add(_adorner);
                _displayControls.Add(_parentCanvas);
            }
            control.DrawControl();
            control.Height = 100;
            control.Width = 100;
            _parentCanvas.Children.Add(control);
            NotifyOfPropertyChange(() => DisplayControls);
        }
        private System.Collections.ObjectModel.ObservableCollection<UIElement> _displayControls;
        public System.Collections.ObjectModel.ObservableCollection<UIElement> DisplayControls
        {
            get { return _displayControls; }
            private set
            {
                _displayControls = value;
                NotifyOfPropertyChange(() => DisplayControls);
            }
        }




    }
}

The above code explains the ViewModel of TabView. As I mentioned in the beginning it may not completely satisfies MVVM pattern. I will explain the reason. As per my requirement I wanted to add Images to my Canvas and allow the user to reposition it.

To achieve the requirement I initially binded list of UIElement that consist of my Images to the CustomItemsCollection. But the issue I faced is I couldn’’t drag the image to different location. So I find out a work around. The work around is create a Canvas in Viewmodel and add the canvas to DisplayControls. Then add the images to the canvas object. By adding Canvas to DisplayControls will internally bind the Canvas object and its children to CustomItemsCollection in the View.

In this app I used EventAggregator to enable Messaging between ViewModels. To enable Messaging in CaliburnMicro the subscriber should Implement IHandle interface. You can see in the above code I implemented IHandle interface and subscribed to ImageSelectionChanged. Any publisher who publish ImageSelectionChanged message will be notified to TabViewModel.

The custom Control to display Image is not in MVVM structure.

You can find the Image listing control and other piece of codes in the uploaded source code.

You can download the code here

Written by Sony Arouje

September 9, 2010 at 7:06 pm

Silverlight Toolbar control

with 7 comments

In this post I am explaining how to create a basic toolbar in Silverlight. This toolbar has two parts

    • Toolbar panel
    • Toolbar item

Screen shot

image

ToolBar Panel

This user control holds all the Toolbar items. This control internally uses Stackpanel to organize the Toolbar items. Below is the XAML and code behind

XAML

<UserControl x:Class="Controls.ToolBarPanel"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
             Background="Transparent"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <StackPanel x:Name="itemHolder" Orientation="{Binding Orientation}" Background="Transparent"></StackPanel>
    </Grid>
</UserControl>

Code behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Controls
{
    public partial class ToolBarPanel : UserControl
    {
        public event Click ToolBarItemClick;
        public ToolBarPanel()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(ToolBarPanel_Loaded);
        }

        void ToolBarPanel_Loaded(object sender, RoutedEventArgs e)
        {
            this.CreateClickEvent();
        }

        public static DependencyProperty ToolBarItemsProperty = DependencyProperty.Register("ToolBarItems",
        typeof(PresentationFrameworkCollection<ToolBarItem>),
        typeof(ToolBarPanel),
        new PropertyMetadata(null));
        public UIElementCollection ToolBarItems
        {
            get 
            {
                return itemHolder.Children;
            }
        }

        public static DependencyProperty ToolBarItemsOrientation = DependencyProperty.Register("Orientation",
        typeof(Orientation),
        typeof(ToolBarPanel),
        new PropertyMetadata(null));
        public Orientation Orientation
        {
            get { return (itemHolder.Orientation); }
            set { itemHolder.Orientation=value; }
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            itemHolder.DataContext = this;
        }

        private void CreateClickEvent()
        {
            foreach (UIElement elem in itemHolder.Children)
            {
                ToolBarItem item = elem as ToolBarItem;
                if(item!=null)
                    item.ToolBarItemClick += new Click(item_ToolBarItemClick);
            }
        }

        void item_ToolBarItemClick(object sender, string key)
        {
            if (ToolBarItemClick != null)
                this.ToolBarItemClick(sender, ((ToolBarItem)sender).ToolBarItemKey);
        }

    }
}

 

One property I wanted to highlight here is the ToolbarItems dependency property. This property exposes the StackPanels children property. Users can add Toolbar items through this property. At the end of this post, with a sample code I will explain the properties.

The delegate Click used in the ToolbarPanel is declared in ToolBarItem code.

ToolBar Item

This control creates the Toolbar button, it inherits from Button control. To make it similar to toolbar button I modified the Resource template of the button. Apart from the default properties of button a new property is added to provide the ability to add Image.

XAML

<Button x:Class="Controls.ToolBarItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <Button.Resources>
        <ControlTemplate x:Key="BtnToolBarItemStyle" TargetType="Button">
            <Grid>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Disabled"/>
                        <VisualState x:Name="Normal"/>
                        <VisualState x:Name="MouseOver">
                            <Storyboard>
                                <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border" d:IsOptimized="True"/>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="border">
                                    <DiscreteObjectKeyFrame KeyTime="0">
                                        <DiscreteObjectKeyFrame.Value>
                                            <Thickness>0</Thickness>
                                        </DiscreteObjectKeyFrame.Value>
                                    </DiscreteObjectKeyFrame>
                                </ObjectAnimationUsingKeyFrames>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="border">
                                    <DiscreteObjectKeyFrame KeyTime="0">
                                        <DiscreteObjectKeyFrame.Value>
                                            <Thickness>1</Thickness>
                                        </DiscreteObjectKeyFrame.Value>
                                    </DiscreteObjectKeyFrame>
                                </ObjectAnimationUsingKeyFrames>
                                <ColorAnimation Duration="0" To="#FFF9B167" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="border" d:IsOptimized="True"/>
                                <ColorAnimation Duration="0" To="#FFFFEFB6" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="border" d:IsOptimized="True"/>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.CornerRadius)" Storyboard.TargetName="border">
                                    <DiscreteObjectKeyFrame KeyTime="0">
                                        <DiscreteObjectKeyFrame.Value>
                                            <CornerRadius>4</CornerRadius>
                                        </DiscreteObjectKeyFrame.Value>
                                    </DiscreteObjectKeyFrame>
                                </ObjectAnimationUsingKeyFrames>
                                <ColorAnimation Duration="0" To="#FFDEA158" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border" d:IsOptimized="True"/>
                            </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Pressed"/>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <Border x:Name="border" BorderBrush="Black" BorderThickness="1" Margin="1,2,2,1" Opacity="0">
                    <Border.Background>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="Black" Offset="0"/>
                            <GradientStop Color="White" Offset="1"/>
                        </LinearGradientBrush>
                    </Border.Background>
                </Border>
                <StackPanel Orientation="Horizontal">
                    <Border x:Name="imgBorder"  BorderBrush="Black" BorderThickness="0" Margin="0" Width="16" Height="16">
                        <Border.Background>
                            <ImageBrush x:Name="imgBorderBrush" Stretch="Fill" ImageSource="{Binding ToolBarItemImage}"/>
                        </Border.Background>
                    </Border>
                    <TextBlock x:Name="caption" Grid.Column="1" TextWrapping="NoWrap" Text="{Binding Content}" FontSize="9.333" TextAlignment="Center" VerticalAlignment="Center" />
                </StackPanel>
            </Grid>
        </ControlTemplate>
    </Button.Resources>

    <Button.Template>
        <StaticResource ResourceKey="BtnToolBarItemStyle"/>
    </Button.Template>
    
</Button>

 

Code Behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;
using System.ComponentModel;
namespace Controls
{
    public delegate void Click(object sender, string key);
    public partial class ToolBarItem : Button
    {
        public event Click ToolBarItemClick;
        public ToolBarItem()
        {
            InitializeComponent();
            this.Click += new RoutedEventHandler(ToolBarItem_Click);
        }

        void ToolBarItem_Click(object sender, RoutedEventArgs e)
        {
            if (ToolBarItemClick != null)
                this.ToolBarItemClick(sender, (string)GetValue(ToolBarItemKeyProperty));
        }

        public static DependencyProperty ToolBarItemImageProperty = DependencyProperty.Register("ToolBarItemImage",
        typeof(ImageSource),
        typeof(ToolBarItem),
        new PropertyMetadata(null));
        public ImageSource ToolBarItemImage
        {
            get { return (ImageSource)GetValue(ToolBarItemImageProperty); }
            set { SetValue(ToolBarItemImageProperty, value); }
        }

        public static DependencyProperty ToolBarItemKeyProperty = DependencyProperty.Register("ToolBarItemKey",
        typeof(String),
        typeof(ToolBarItem),
        new PropertyMetadata(null));
        public string ToolBarItemKey
        {
            get { return (string)GetValue(ToolBarItemKeyProperty); }
            set { SetValue(ToolBarItemKeyProperty, value); }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            SetDataContext();
        }
        private void SetDataContext()
        {
            Border border = GetTemplateChild("imgBorder") as Border;
            border.DataContext = this;
            TextBlock txtCaption = GetTemplateChild("caption") as TextBlock;
            txtCaption.DataContext = this;

            if (this.Content == null)
            {
                txtCaption.Margin = new Thickness(0);
                border.Margin = new Thickness(3);
            }
            else
            {
                txtCaption.Margin = new Thickness(3);
            }
        }

    }
}
  • ToolBarItemImage: To set the source of the Image to display in the toolbar.

  • ToolBarItemKey: Provide a key to identify which button is clicked.

Test Page

The test page helps you to understand how we can use this new control

XAML

<controls:ChildWindow x:Class="Popups.TestToolBar"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           xmlns:tlBar="clr-namespace:EmblemDesigner.Controls" Title="TestToolBar" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="265" d:DesignWidth="432">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        
        <tlBar:ToolBarPanel Height="50" Background="Transparent" Orientation="Horizontal" ToolBarItemClick="ToolBarPanelExt_ToolBarItemClick" >
            <tlBar:ToolBarPanel.ToolBarItems>
                <tlBar:ToolBarItem Margin="5" ToolBarItemImage="/Ico_Arrow.png"  Content="Arrow" ToolBarItemKey="Arrow"></tlBar:ToolBarItem>
                <tlBar:ToolBarItem Margin="5" ToolBarItemImage="/Ico_Circle.png"   ToolBarItemKey="Circle"></tlBar:ToolBarItem>
            </tlBar:ToolBarPanel.ToolBarItems>
        </tlBar:ToolBarPanel>
        
        <Button x:Name="CancelButton"  Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton"  Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>

 

Code Behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Popups
{
    public partial class TestToolBar : ChildWindow
    {
        public TestToolBar()
        {
            InitializeComponent();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }


        private void ToolBarPanel_ToolBarItemClick(object sender, string key)
        {
            switch (key)
            {
                case "circle":
                    break;
                case "arrow":
                    break;
            }
        }
    }
}
 

If we attach ToolBarItemClick event to ToolBar panel, it will generate an even handler as shown above in italics. The event will provide you a key and the sender. The key provides the value we set for ToolBarItemKey of the ToolBarItem control. Based on the key we can take different actions. As ToolbarItem is inherited from button you can use the default click event provided by Button control.

Written by Sony Arouje

August 25, 2010 at 4:26 pm

Posted in Silverlight

Tagged with ,

%d bloggers like this: