(X) Hide this
    • Login
    • Join
      • Say No Bots Generate New Image
        By clicking 'Register' you accept the terms of use .

Camdoodle: showcasing Silverlight 4 new features as behaviors

(4 votes)
Andrej Tozon
>
Andrej Tozon
Joined Jan 22, 2009
Articles:   6
Comments:   6
More Articles
9 comments   /   posted on Dec 16, 2009
Categories:   General

This article is compatible with the latest version of Silverlight.

Whenever a new version of Silverlight is released, I start examining its features from two perspectives: how would my current (and planned) LOB applications benefit from using these new features, and what cool new things can I build to entertain my children.

This article will guide you through the process of creating a doodling application (you know, for kids ;)), while covering some of the most visible (or not) new features, coming with Silverlight 4. But this is not just about new features, it’s about how they are prepared and served. Interested? Let’s begin then.

Webcam support / WebcamBehavior / CompositeTransform

One of the most requested Silverlight feature was Webcam support so I’m starting with that. I want go deep on showing how to enable Webcam capturing; to make this sample different from the ones you may have already seen, I baked the basic Webcam support into a behavior. The WebcamBehavior can be attached to any Shape you want, rendering the video being captured by a Webcam.

image

To capture the above image, I only needed to attach the WebcamBehavior to the rectangle on the left. The other two shapes were both getting the feed from the rectangle (serving the original VideoBrush):

<Rectangle Margin="8" Width="200" Height="200" x:Name="source">
    <i:Interaction.Behaviors>
        <local:WebCamBehavior />
    </i:Interaction.Behaviors>
</Rectangle>
<Ellipse Grid.Column="1" Margin="8" Width="200" Height="200" 
         Fill="{Binding Fill, ElementName=source}" />
<Polygon Grid.Column="2" Margin="8" Width="200" Height="200" 
         Fill="{Binding Fill, ElementName=source}" 
         Points="0,200,100,0,200,200" />

Webcam is not on by default, we need to turn it on from code. And how exactly could we actually start capturing the video if Webcam is controlled by the behavior?

Some of the readers may not be familiar with this very convenient Blend 3 feature so I’m explaining it here: you can add commands to any behavior by exposing the properties of type ICommand. For each such property, Blend 3 will show a special property editor, where user would define the trigger(s) for executing that command.

WebcamBehavior properties

For example: I created two commands to control the state of a Webcam. StartCommand would start capturing the camera, while StopCommand will stop it. Both commands get executed by the same ToggleButton - the StartCommand is called on button’s Checked event, while StopCommand is triggered by it’s Unchecked event. You can implement the commands however you like, as long as they implement ICommand [if you don’t feel like creating your own implementation, check out the ActionCommand from Expression Behaviors library].

<Rectangle x:Name="camera" Stretch="Fill" RenderTransformOrigin="0.5,0.5">
    <Rectangle.RenderTransform>
        <CompositeTransform ScaleX="-1" />
    </Rectangle.RenderTransform>
    <i:Interaction.Behaviors>
        <li:WebcamBehavior >
            <i:Interaction.Triggers>
                <i:EventTrigger SourceName="playCameraButton" EventName="Checked">
                    <i:InvokeCommandAction CommandName="StartCommand"/>
                </i:EventTrigger>
                <i:EventTrigger SourceName="playCameraButton" EventName="Unchecked">
                    <i:InvokeCommandAction CommandName="StopCommand"/>
                </i:EventTrigger>
                <i:EventTrigger SourceName="showCameraButton" EventName="Checked">
                    <i:InvokeCommandAction CommandName="ShowCommand"/>
                </i:EventTrigger>
                <i:EventTrigger SourceName="showCameraButton" EventName="Unchecked">
                    <i:InvokeCommandAction CommandName="HideCommand"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </li:WebcamBehavior>
    </i:Interaction.Behaviors>
</Rectangle>

See the RenderTransform value up there? I used it to flip the webcam capture image horizontally, to look like you’re looking into a mirror. But instead of using ScaleTransform, I used CompositeTransform, which is another new Silverlight 4 feature that lets you set many transform values at once and keep the object count down (no need for separate TranslateTransform, ScaleTransform, RotateTransform and SkewTransform).

The webcam feed will serve as a canvas for our doodling. The version shown above features additional commands, enabling user to hide/show the feed.

DataBinding enhancements / DrawingBehavior

This is the main part of the application. Again, I wrapped all of the drawing logic into a DrawingBehavior. Any Grid can be turned into a drawing container by simply attaching this behavior to it. Upon attaching, the behavior adds an InkPresenter control on top of other Grid’s children and hooks up various mouse events to draw the strokes.

The user should be allowed to choose a drawing color. The DrawingBehavior exposes a Color dependency property, which can be set to any color value. But not only that - Silverlight 4 now allows data binding to DependencyObject! With Behavior being a DependencyObject, you now can bind any of its properties and it simply works. For example, something like this isn’t possible with Silverlight 3:

<Grid>
    <i:Interaction.Behaviors>
        <li:DrawingBehavior 
            SelectedColor="{Binding SelectedValue, ElementName=palette}" />
    </i:Interaction.Behaviors>
</Grid>

In the above snippet, the SelectedColor property is bound to the palette’s SelectedValue property. If the SelectedValue property sounds familiar to you, you’re right - the color palette is just a restyled ListBox, having a number of basic colors set through the ViewModel:

Colors = new List<Color>()
{
   System.Windows.Media.Colors.Black,
   System.Windows.Media.Colors.Gray,
   System.Windows.Media.Colors.Brown,
   System.Windows.Media.Colors.Blue,
   System.Windows.Media.Colors.Cyan,
   System.Windows.Media.Colors.Green,
   System.Windows.Media.Colors.Red,
   System.Windows.Media.Colors.Purple,
   System.Windows.Media.Colors.Magenta,
   System.Windows.Media.Colors.Orange,
   System.Windows.Media.Colors.Yellow,
   System.Windows.Media.Colors.White,
};
image

Yet another DrawingBehavior feature is that it automatically changes mouse pointer to a small circle with the same fill color as the one selected in the palette. Similarly to SelectedColor, DrawingBehavior exposes the PointerSize property, which can also be bound to some other data source, controlling the size of the mouse pointer.

DrawingBehavior properties

Oh, and let’s not forget that this behavior also exposes two ICommands – ClearCommand simply clears the canvas of all strokes, while UndoCommand undoes the last stroke. I used a little trick here – because a single ”user” stroke is composed of many actual strokes (every MouseMove creates a new stroke), the DrawingBehavior keeps a history of user strokes by storing the actual strokes’ indexes on the stack. The UndoCommand just clears the last set:

public void OnUndo(object state)
{
    int lastStroke = history.Pop();
    for (int i = lastStroke; i < canvas.Strokes.Count; i++)
    {
        canvas.Strokes.RemoveAt(lastStroke);
    }
}

Behaviors vs. standalone controls

Ok, so why did I choose behaviors over creating standalone controls for webcam feed and drawing canvas? Well, both behaviors contain logic only, without any visual representation. There’s no templates, no content, … – they only need a specific host to attach to.

Printing / Commanding / MVVM

Printing was another Silverlight 4 feature in a high demand – and now it’s here: it’s got a very simple API, but you can print virtually anything you want. For example, these few lines will print the current state of the doodle. Webcam turned on or off: it doesn’t matter - it’s going to get printed.

private void OnPrint(object sender, RoutedEventArgs e)
{
    PrintDocument document = new PrintDocument { DocumentName = "Camdoodle image" };
    document.PrintPage += (s, args) => args.PageVisual = printElement;
    document.Print();
}

Now let’s use this printing example to show another Silverlight 4 feature: commanding. Buttons in Silverlight 4 have a new set of properties (namely Command and CommandParameter), which let you declaratively associate a button with a command, which will execute when that button is clicked. Native commanding support coming to Silverlight is great news for everyone doing MVVM, as 3rd party implementations will no longer be required. When moving the above printing code into the ViewModel, we need to expose a command that will trigger printing and pass the element to print as its parameter:

public ICommand PrintCommand { get; private set; }
 
private void OnPrint(object parameter)
{
    PrintDocument document = new PrintDocument { DocumentName = "Camdoodle image" };
    document.PrintPage += (s, e) => e.PageVisual = parameter as UIElement;
    document.Print();
}

The declaration in XAML couldn’t get simpler than:

<Button Command="{Binding PrintCommand, Source={StaticResource viewModel}}" 
        CommandParameter="{Binding ElementName=image}" 
        Style="{StaticResource ButtonStyle}" >
    <Image Source="Resources/printer.png"/>
</Button>

Implicit styling

I’m going to just quickly mention this one. See the Style declaration in that last piece of XAML above? With Silverlight 4, you can define a default style for a certain type of control (by not specifying the x:Key attribute), and that style would be automatically applied to every control of that type, without having to specify it explicitly. Using implicit styling, the above button could be declared as:

<Button Command="{Binding PrintCommand, Source={StaticResource viewModel}}" 
        CommandParameter="{Binding ElementName=image}">
    <Image Source="Resources/printer.png"/>
</Button>

Drag’n’Drop

This one’s big too. OpenFileDialog has been around in Silverlight since v2, but Silverlight 4 now provides a much more convenient way for user to send files to application – by dragging it from the file system and dropping it over some control. How about using this feature to let the user drag some pictures on the drawing canvas? Let’s create a PanelDropBehavior, that will extend any kind of Panel by letting the user drop her pictures right onto the doodling canvas.

Upon attaching, the hosting control should have its AllowDrop property set to true. This will enable the Drop event to be raised.

protected override void OnAttached()
{
    base.OnAttached();
 
    AssociatedObject.AllowDrop = true;
    AssociatedObject.Drop += OnDrop;
}

 

 

In the OnDrop event handler, an Image control is created, displaying the picture that was dropped on the hosting panel.

private void OnDrop(object sender, DragEventArgs e)
{
    IDataObject dataObject = e.Data as IDataObject;
    if (e.Data == null)
    {
        return;
    }
    FileInfo[] files = dataObject.GetData(DataFormats.FileDrop) as FileInfo[];
 
    try
    {
        foreach (FileInfo file in files)
        {
            Point dropPoint = e.GetPosition(AssociatedObject);
 
            Image image;
            using (Stream stream = file.OpenRead())
            {
                string name = file.Name;
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.SetSource(stream);
                image = new Image { Source = bitmapImage };
            }
 
            image.Width = MaxDropWidth;
            image.Height = MaxDropHeight;
            image.Stretch = Stretch.Uniform;
            image.VerticalAlignment = VerticalAlignment.Top;
            image.HorizontalAlignment = HorizontalAlignment.Left;
 
            double x = Math.Min(dropPoint.X, 
                AssociatedObject.ActualWidth - image.ActualWidth);
            double y = Math.Min(dropPoint.Y, 
                AssociatedObject.ActualHeight - image.ActualHeight);
            if (AssociatedObject is Canvas)
            {
                image.SetValue(Canvas.LeftProperty, x);
                image.SetValue(Canvas.TopProperty, y);
            }
            AssociatedObject.Children.Add(image);
        }
    }
    catch (Exception ex)
    {
        AssociatedObject.Children.Add(new TextBlock { Text = ex.Message });
    }
}

Maximum width and height of dropped object can also be set on the behavior:

PanelDropBehavior properties

Copy / Paste

You probably have a lot of clipart laying stored on the disk somewhere, and you might have also created your own. Wouldn’t it be nice if you could add it to the doodle mix? This last feature will show how to transfer objects from Expression Design over to the doodling canvas by using the Copy/Paste Silverlight 4 feature. The feature is – again, you guessed it – wrapped into a behavior.

To try the PasteAsChildBehavior, create a drawing in Expression Design:

Expression Design drawing … hit Ctrl+Shift+C (Edit | Copy XAML), then click the Paste button in the doodling application – the image will be added to the background.

The behavior implementation is, again, very straightforward:

public class PasteAsChildAction : TriggerAction<Panel>
{
    protected override void Invoke(object o)
    {
        string text = Clipboard.GetText();
        if (string.IsNullOrWhiteSpace(text))
        {
            return;
        }
        UIElement element = XamlReader.Load(text) as UIElement;
        if (element != null)
        {
            AssociatedObject.Children.Add(element);
        }
        else
        {
            AssociatedObject.Children.Add(new TextBlock { Text = text });
        }
    }
}

The first line gets the text content of the clipboard, If there is any, the behavior tries to deserialize it into the UIElement and adds it to associated panel’s Children collection. If that fails, a simple TextBlock is added.

Dude, where’s my codebehind?

We now have a working doodling application. Having a pen tablet as the input device, my 3yo simply loves to play with it. Here’s a quick hand-traced image of myself, with Webcam capture turned off afterwards. Camdoodle

With Silverlight 4 features wrapped into behaviors, there’s no code left in main page’s codebehind. I put the printing logic into the ViewModel to show the new commanding options for MVVM developers, but even printing could be wrapped into a PrintAction – a sample of that one is provided with the source code (see the link to source code below).

Review of behaviors, written for this article:

DrawingBehavior turns any Grid into a drawing canvas.
PanelDropBehavior turns graphic files, dropped over the attached panel, into nice images.
PasteAsChildAction turns a valid XAML object construct into an element and adds it to the attached panel.
PrintAction prints the attached element;
WebCamBehavior turns any Shape into a camera capturing canvas.

Again, what was done in this article, is just a starting point. Many more features are being added to the application, but are not necessarily Silverlight 4 related.

Now excuse me, I have to go; my kids have been waiting patiently for their turn to draw on the computer…

Try Camdoodle for yourself

Source code is available to download from here.


Subscribe

Comments

  • -_-

    RE: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by Henrik on Dec 17, 2009 12:52
    Awesome stuff! Behaviors is the shit!!!
  • -_-

    RE: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by Michael Washington on Dec 18, 2009 15:27

    This article really moved me. I feel I have "seen the way". It seems behaviors provide encapsulation of highly complex functionality, yet they still provide design time, "Blendable" experience. The productivity this provides is extraordinary. This article provides a concrete practical demonstration of how well this works.I printed out the code and could not believe how beautiful it looked. 

    I am still trying to warp my head around the "commanding" stuff (would it kill me to allow a person to just click the print button and raise an event in the code behind?) but everything else is clearly the superior way of constructing the application.

  • andrejt

    RE: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by andrejt on Dec 21, 2009 13:13
    Michael, thanks for nice comments. I did commanding as an example for MVVM, although codebehind is perfectly ok when not using the pattern. Included in the project is the PrintAction behavior, which doesn't require any of those.
  • -_-

    RE: Camdoodle: Beta


    posted by BE on Aug 11, 2010 17:14
    please update the code from beta version ~thanks
  • lnikolov

    RE: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by lnikolov on Feb 17, 2011 10:43
    The article has been updated to the latest version of Silverlight and Visual Studio.
  • BeeOShop

    Re: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by BeeOShop on Apr 29, 2015 14:30

    Mau info toko tas online di Bandung yang menjual berbagai macam tas untuk keperluan wanita dan anak-anak seperti tas-tas wanita yang branded dari berbagai merek terkenal seperti Louis Vuitton, PradaBrussels, Furla, Chanel, Hermes, dan banyak lagi tas branded wanita lainnya dengan bermacam model seperti dompetslingbaghandbag dan tote bag, yang trendy dengan harga yang terjangkau? Silahkan cek di Bee Bagshop grosir tas online di Bandung yang menjual tas-tas dengan moel dan warna yang bervariasi dengan harga yang murah. Selain tas wanita, kami juga menjual tas sekolah anak model selempang dan ransel yang juga branded tiruan seperti jansport dan kipling dengan banyak variasi model dan warna, dijamin putra-putri anda menyukainya.

    Bagi yang ingin merawat kulit wajah tanpa harus repot dan mahal, simak tips kecantikan berikut yang mudah dan murah. Coba Sabun Amoorea. Manfaat sabun ajaib amoorea ini diantaranya untuk berbagai permasalahan kulit anda, seperti jerawat, scars (bekas jerawat), mengencangkan kulit, membuat kulit tampak lebih cerah dan segar, mengatasi lingkar hitam di bawah mata (mata panda), dll. Untuk melihat review sabun amoorea ini klik di sini. Simak juga testimoni yang sudah menggunakan sabun ini, simak selengkapnya. Meski harga sabun amoorea ini cukup terjangkau, produk ini aman, karena terbuat dari 100% bahan alami dan sudah ada ijin dari BPOM sehingga anda tidak perlu khawatir dan ragu memakainya. Jika aman (tidak berbahaya) apakah memakai sabun amoorea ada efek sampingnya? Reaksi apa yang timbul saat pertama menggunakannya? Simak info lengkapnya di sini.

    How To Get Rid Of Acne and Scars site generally tell about  tips for beauty and health skin, and especially tell anything about acne, myths, acne and scars treatments , not only on your face (including acne and scars on your body such as chest and back)which may help you to war against acne and the scars to leaves on your face and body (back and chest), through naturally, home remedies, and medical treatment. Some people believe that natural treatment is most safe. But its seems would take time longer then medical treatment. In my oppinion, using medical treatment is more effective and you'll see the result faster. To find what the best treatment for your acne, better you understand what causes of acne. The following are some factors which can cause acne: Hormones, diet, stress, medication, clothing, environment factors, genes, and cosmetics. For back or chest acne cases, it may caused by your tight clothes. To get rid of acne on back or chest, it's good to wear synthetic loose clothing, such as cotton linen, to prevent excessive rubbing and irritation on the skin. Another treatment that can help you to get rid of back or chest acne is keep your body always clean, with take a bath regularly, preferably with an anti-bacterial soap or a salicylic acid-based cleanser is highly recommended. When acne goes, it will leaves behind the red on skin, and seems impossible to heal it fast overnight. So, it would be better to do something which may offer some remarkable effects on the redness from acne. If you choose naturally treatment,  just take a cube of ice, garlic, or lemon juice mixed with rose water, choose one of them, then apply to your reddish part of your face for a while. Do this continously and measured, then see the result. Below this, some good habits which may help you to get rid of acne scars naturally and fast are: first, drinking lots of water to makes your skin well hydrated, and finally make you skin regenerate faster.  Second, eat the food that containing much the essential vitamins and mineral to help repairing scars skin from the inside. Third, naturally treatment to get rid of acne scar, by applying aloe vera on scars, may help to avoid more scarring, soothe inflamation, and make your skin keep smooth. Fourth, treat your acne scars with natural ingredient, tomato. Its containing much vitamin A, which very efficient in repairing scars skin.  If you want to get rid of acne scars ons faster than naturally or home remedies treatments, using the latest product of technology, such as laser treatment and chemical peeling, or even you can choose microdermabration that guaranteed more effective than other treatments.

    Ada banyak bermacam permasalahan kulit di wajah, salah satu yang paling menggangu adalah jerawat, apalagi jika yang muncul adalah jerawat batu yang ukurannya besar dan menyebabkan rasa sakit. Solusi mengenai berbagai cara menghilangkan jerawat batu ini banyak sekali ditemukan, mulai dari pengobatan alami, maupun secara medis. Untuk kasus jerawat batu ini, sebaiknya anda bisa mengambil tindakan pengobatan yang tepat, untuk kasus jerawat batu disarankan langsung saja ambil perawatan medis, karena beresiko tinggi meninggalkan bekas berupa bopeng di wajah, jika salah menangani. Dengan langsung mengambil tindakan medis, setidaknya akan meminimalisasi resiko timbulnya scars bopeng di wajah yang mana scars jenis ini relatif sulit dihilangkan. Tidak seperti scars berupa noda/ flek hitam. meski tidak mudah juga, namun berkali-kali lipat jauh lebih mudah dan murah dibandingkan dengan mengatasi bopeng bekas jerawat. Perawatan alami yang dapat anda tempuh untuk mengobati jerawat ataupun scars yang berupa noda hitam/ flek bekas jerawat, anda dapat mencoba dengan menggunakan bahan alami misalnya jeruk nipis atau madu yang sudah sangat terkenal khasiatnya yang untuk kesehatan dan kecantikan kulit. Dengan perawatan alami, cenderung lebih aman, hanya saja untuk perawatan ini biasanya dibutuhkan waktu yang lebih lama serta ketelatenan untuk mengatasinya. FYI, Jeruk nipis ini yang tidak hanya dapat memudarkan flek hitam di wajah, tetapi juga dapat memutihkan dan mencerahkan kulit anda. Namun jika anda tidak memiliki cukup kesabaran, sebaiknya langsung saja konsultasi dengan dokter. Untuk menentukan metode pengobatan jerawat dan scars ini biasanya dokter akan melihat tingkat keparahan dari jerawat/ scars anda, bisa dengan pemberian obat minum seperti anti biotik, obat oles (salep), peeling, mikrodermabrasi, laser atau metode pengobatan lainnya. Namun sebelum anda memutuskan menggunakan salah satu treatment tersebut sebaiknya telah jelas terlebih dahulu mengenai efek samping yang ditimbulkan.

    Seiring dengan semakin tingginya pengguna internet dari waktu ke waktu, hal ini menciptakan peluang bisnis tersendiri. Diataranya peluang bejualan tas murah secara online, buatan konveksi-konveksi lokal. Di Bandung cukup banyak konveksi tas yang membuat beraneka macam tas dan dompet tiruan atau kw. Model-model dari tas wanita ini kebanyakan menjiplak tas dan dompet wanita dari brand terkenal luar negeri seperti merek-merek LV atau Louis Vuitton, Hermes, Chanel, Furla, dll yang sangat terkenal. Tas tiruan seperti ini familiar disebut tas replika/ kw, dengan beraneka tingkatanya: Kw I, Kw II, dll. Tas-tas wanita branded ini di Bee Bagshop dijual dengan harga yang bevariasi antara 66-300 ribuan. Pembelinya berasal dari kalangan anak SMA, anak kuliah, pekerja kantoran, serta ibu-ibu muda. Untuk tas sekolah dan kuliah anak, kebanyakan modelnya meniru model tas branded seperti kipling, dan jansport, dengan model tas punggung/ ransel ataupun model selempang. Untuk harga tas-tas branded kw dijual dengan harga yang jauh di bawah harga aslinya, sehingga bisa dipastikan kualitasnya pun berbeda dengan yang original. Di Bee Bagshop sendiri kisaran harga untuk tas jansport tiruan ini dijual dengan harga 60-80 ribuan, sementara harga barang original mungkin sekitar 800-900 ribuan, bahkan mungkin ada juga yang lebih mahal. Untuk tas ransel branded tiruan ini kebanyakan pembelinya berasal dari anak sekolah dan kuliah.

    Jika anda wanita yang terlalu sibuk bekerja, dan selalu bingung bahkan tidak sempat untuk sekedar memikirkan menu masakan sehari-hari untuk keluarga anda, silahkan kunjungi situs Aneka Resep dan Cara membuat Masakan Praktis Khas Indonesia, situs ini berisi kumpulan resep masakan yang menggunakan bahan-bahan umum seperti ayam, daging sapi, serta beraneka seafood (udang, cumi, ikan), dan juga bahan-bahan makanan yang tidak asing bagi  masyarakat Indonesia seperti tahu, tempe, serta nasi, . Selain itu ada juga resep cemilan khas nusantara, yang dapat anda jadikan inspirasi untuk menu masakan dan makanan ringan keluarga anda di rumah. Seperti resep membuat masakan khas suatu daerah seperti soto ayam lamongan, ataupun masakan khas lebaran seperti opor ayam, beraneka masakan berbahan dasar ayam ataupun bahan makanan lainnya, yang pastinya praktis dan mudah dibuat.

  • wardoyoming

    Re: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by wardoyoming on May 17, 2015 14:44
    cue Grill and Standardizing High QualityShould You Use a Gas Grill Or a Chaand g Supplier Besi h beam ks per kg by vehicle deaths involve construction workers Forklift accidentcan purchase eng Jual Baja Bangka Belitung ms that need to be checked regularly to ensure the continuet selection of your p supplier besi beton ulir 13 mm per kg bandung es Contact nanoMAG at http://www nanomag usArticle Source: http:red Metal Buildi perusahaan besi cnp gedung jual besi bajadown freely inside an outer tube which is attached to the base pp TipsSteps to C jual besi pipa hitam medium 1 1/2" inch 6 m 3.2 mm tebingtinggi on DeliciousShare this article on FriendFeedShare this article on DiggaborThere produsen baja pipa hitam medium paling diminati
  • wardoyoming

    Re: Camdoodle: showcasing Silverlight 4 new features as behaviors


    posted by wardoyoming on May 17, 2015 14:45
    ow your visitors to register and create their own personal blogs Apart from cara membuat toko online kursus web

Add Comment

Login to comment:
  *      *