ページ

Wednesday, February 23, 2011

Kinect : Skeleton Tracking

Kinect seems to become hot topic since the end of last year.
So I tried using Kinect and programming for skeleton tracking.


* Note : 
    OpenNI : http://www.openni.org/
    Prime Sensehttp://www.primesense.com/
    SensorKinect : https://github.com/avin2/SensorKinect 
    XNA 4.0 : http://msdn.microsoft.com/en-En/xna/default.aspx




TrackingConfig.xml

<OpenNI>
 <Licenses>
  <License vendor="PrimeSense" key="0KOIk2JeIBYClPWVnMoRKn5cdY4="/>
 </Licenses>
 <Log writeToConsole="true" writeToFile="false">
  <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) -->
  <LogLevel value="3"/>
  <Masks>
   <Mask name="ALL" on="false"/>
  </Masks>
  <Dumps>
  </Dumps>
 </Log>
 <ProductionNodes>
    <Node type="Image" name="Image1">
      <Configuration>
        <MapOutputMode xRes="640" yRes="480" FPS="30"/>
        <Mirror on="true"/>
      </Configuration>
    </Node>
    <Node type="Depth">
   <Configuration>
    <MapOutputMode xRes="640" yRes="480" FPS="30"/>
    <Mirror on="true"/>
   </Configuration>
  </Node>
  <Node type="Gesture" />
  <Node type="Hands" />
 </ProductionNodes>
</OpenNI>

MainWindow.xaml

<Window x:Class="KinectWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Kinect Demo" Height="960" Width="640"
        xmlns:kinect="clr-namespace:KinectWPF">
    <Grid Background="White">
 <Grid.RowDefinitions>
  <RowDefinition Height="*" />
  <RowDefinition Height="*" />
 </Grid.RowDefinitions>
        <Image Name="imgDepth" Width="640" Height="480" />
        <WindowsFormsHost Background="Transparent" Name="windowsFormsHost1" Width="640" Height="480" Grid.Row="1">
            <kinect:KinectCustomControl x:Name="KinectCustomControl" />
        </WindowsFormsHost>
    </Grid>
</Window>


MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using xn;

using System.ComponentModel;
using System.Threading;

namespace KinectWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private const string CONFIGURATION = @"TrackingConfig.xml";
        private readonly int DPI_X = 96;
        private readonly int DPI_Y = 96;
        private Thread _thread;
        private bool _isRunning = true;

        public Context _context;
        public ImageGenerator _image;
        public DepthGenerator _depth;

        private WriteableBitmap _imageBmp;
        private WriteableBitmap _depthBmp;
        private ImageMetaData _imgMD = new ImageMetaData();
        private DepthMetaData _depthMD = new DepthMetaData();

        public int[] _histogram;

        BackgroundWorker _worker = new BackgroundWorker();

        public ImageSource RawImageSource
        {
            get
            {
                if (_imageBmp != null)
                {
                    _imageBmp.Lock();
                    _imageBmp.WritePixels(new Int32Rect(0, 0, _imgMD.XRes, _imgMD.YRes), _imgMD.ImageMapPtr, (int)_imgMD.DataSize, _imageBmp.BackBufferStride);
                    _imageBmp.Unlock();
                }

                return _imageBmp;
            }
        }

        public ImageSource DepthImageSource
        {
            get
            {
                if (_depthBmp != null)
                {
                    UpdateHistogram(_depthMD);

                    _depthBmp.Lock();

                    unsafe
                    {
                        ushort* pDepth = (ushort*)_depth.GetDepthMapPtr().ToPointer();
                        for (int y = 0; y < _depthMD.YRes; ++y)
                        {
                            byte* pDest = (byte*)_depthBmp.BackBuffer.ToPointer() + y * _depthBmp.BackBufferStride;
                            for (int x = 0; x < _depthMD.XRes; ++x, ++pDepth, pDest += 3)
                            {
                                byte pixel = (byte)_histogram[*pDepth];
                                pDest[0] = 0;
                                pDest[1] = pixel;
                                pDest[2] = pixel;
                            }
                        }
                    }

                    _depthBmp.AddDirtyRect(new Int32Rect(0, 0, _depthMD.XRes, _depthMD.YRes));
                    _depthBmp.Unlock();
                }

                return _depthBmp;
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            this.Closing += new CancelEventHandler(Window_Closing);

            InitializeKinect(CONFIGURATION);
            InitializeBitmaps();
            InitializeThread();
        }

        private void InitializeKinect(string configuration)
        {
            _context = new Context(configuration);
            if (_context == null)
                throw new Exception("configuration file is not found.");

            _image = _context.FindExistingNode(NodeType.Image) as ImageGenerator;
            if (_image == null)
                throw new Exception("Viewer must have a image node!");

            _depth = _context.FindExistingNode(NodeType.Depth) as DepthGenerator;
            if (_depth == null)
                throw new Exception("Viewer must have a depth node!");

            _histogram = new int[_depth.GetDeviceMaxDepth()];
        }

        private void InitializeBitmaps()
        {
            MapOutputMode mapMode = _depth.GetMapOutputMode();

            int width = (int)mapMode.nXRes;
            int height = (int)mapMode.nYRes;

            _imageBmp = new WriteableBitmap(width, height, DPI_X, DPI_Y, PixelFormats.Rgb24, null);
            _depthBmp = new WriteableBitmap(width, height, DPI_X, DPI_Y, PixelFormats.Rgb24, null);
        }

        private void InitializeThread()
        {
            _worker.DoWork += new DoWorkEventHandler(Worker_DoWork);
            _thread = new Thread(CameraThread);
            _thread.IsBackground = true;
            _isRunning = true;
            _thread.Start();
        }

        private unsafe void CameraThread()
        {
            while (_isRunning)
            {
                _context.WaitAndUpdateAll();

                _image.GetMetaData(_imgMD);
                _depth.GetMetaData(_depthMD);
            }
        }

        public unsafe void UpdateHistogram(DepthMetaData depthMD)
        {
            for (int i = 0; i < _histogram.Length; ++i)
                _histogram[i] = 0;

            ushort* pDepth = (ushort*)depthMD.DepthMapPtr.ToPointer();

            int points = 0;
            for (int y = 0; y < depthMD.YRes; ++y)
            {
                for (int x = 0; x < depthMD.XRes; ++x, ++pDepth)
                {
                    ushort depthVal = *pDepth;
                    if (depthVal != 0)
                    {
                        _histogram[depthVal]++;
                        points++;
                    }
                }
            }

            for (int i = 1; i < _histogram.Length; i++)
            {
                _histogram[i] += _histogram[i - 1];
            }

            if (points > 0)
            {
                for (int i = 1; i < _histogram.Length; i++)
                {
                    _histogram[i] = (int)(256 * (1.0f - (_histogram[i] / (float)points)));
                }
            }
        }

        private void Dispose()
        {
            _imageBmp = null;
            _depthBmp = null;
            _isRunning = false;
            if (_thread != null)
            {
                _thread.Join();
                _thread = null;
            }
            if (_worker != null)
            {
                _worker.Dispose();
                _worker = null;
            }
            if (_depth != null)
            {
                _depth.Dispose();
                _depth = null;
            }
            if (_image != null)
            {
                _image.Dispose();
                _image = null;
            }
            if (_context != null)
            {
                _context.Dispose();
                _context = null;
            }
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Dispatcher.BeginInvoke((Action)delegate
            {
                imgDepth.Source = DepthImageSource;
            });
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            this.Dispose();
        }

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (!_worker.IsBusy)
            {
                _worker.RunWorkerAsync();
            }
        }
    }
}


KinectCustomControl.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using xn;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Color = Microsoft.Xna.Framework.Color;

namespace KinectWPF
{
    public partial class KinectCustomControl : Control
    {
        private const string CONFIGURATION = @"TrackingConfig.xml";
        private GraphicsDevice _device = null;
        private PresentationParameters _pp = null;
        private BasicEffect _effect = null;
        private List<VertexPositionColor> _vertices;
        
        private System.Windows.Forms.Timer _timer;

        Context _context;
        DepthGenerator _depth;
        UserGenerator _users;
        SkeletonCapability _skeleton;
        PoseDetectionCapability _pose;
        Dictionary<uint, Dictionary<SkeletonJoint, SkeletonJointPosition>> _joints;

        private readonly float _nearPlaneDistance = 1.0f;
        private readonly float _farPlaneDistance = 2000.0f;

        public KinectCustomControl()
        {
            InitializeComponent();
        }

        protected override void OnCreateControl()
        {
            if (this.DesignMode == false)
            {
                InitializeKinect(CONFIGURATION);
                InitializeXnaFramework();
                InitializeUserGenerator();
                InitializePoseDetectionCapability();
                InitializeSkeletonCapability(SkeletonProfile.Upper);
                _users.StartGenerating();
            }
            base.OnCreateControl();
        }

        private void InitializeXnaFramework()
        {
            _vertices = new List<VertexPositionColor>();
            try
            {
                _pp = new PresentationParameters();
                _pp.BackBufferWidth = 640;
                _pp.BackBufferHeight = 480;
                _pp.BackBufferFormat = SurfaceFormat.Color;
                _pp.DeviceWindowHandle = this.Handle;
                _pp.DepthStencilFormat = DepthFormat.Depth16;
                _pp.IsFullScreen = false;
                _device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, _pp);

                _effect = new BasicEffect(_device);
                _effect.VertexColorEnabled = true;
                _effect.View = Matrix.CreateLookAt(
                        new Vector3(0.0f, 0.0f, -1000.0f),
                        Vector3.Zero,
                        Vector3.Down);

                Matrix world = Matrix.Identity;
                world *= Matrix.CreateTranslation(new Vector3(-_device.Viewport.Width/2.0f, -_device.Viewport.Height/2.0f, -500.0f));
                world *= Matrix.CreateScale(1.0f, 1.0f, 1.0f);
                _effect.World = world;
                _effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                    MathHelper.ToRadians(45.0f), _device.Viewport.AspectRatio, _nearPlaneDistance, _farPlaneDistance);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }

        private void InitializeKinect(string config)
        {
            _context = new Context(config);
            if(_context==null)
                throw new Exception("configuration file is not found.");

            _depth = _context.FindExistingNode(NodeType.Depth) as DepthGenerator;
            if (_depth == null)
                throw new Exception("Viewer must have a depth node!");
        }

        private void InitializeUserGenerator()
        {
            _users = new UserGenerator(_context);
            _users.NewUser += new UserGenerator.NewUserHandler(UserGesture_NewUser);
            _users.LostUser += new UserGenerator.LostUserHandler(UserGesture_LostUser);
        }

        private void InitializePoseDetectionCapability()
        {
            _pose = new PoseDetectionCapability(_users);
            _pose.PoseDetected += new PoseDetectionCapability.PoseDetectedHandler(PoseDetectionCapability_PoseDetected);
        }

        private void InitializeSkeletonCapability(SkeletonProfile profile)
        {
            _skeleton = new SkeletonCapability(_users);
            _skeleton.CalibrationEnd += new SkeletonCapability.CalibrationEndHandler(SkeletonCapability_CalibrationEnd);
            _skeleton.SetSkeletonProfile(profile);
            _joints = new Dictionary<uint, Dictionary<SkeletonJoint, SkeletonJointPosition>>();
        }

        private void SkeletonCapability_CalibrationEnd(ProductionNode node, uint id, bool success)
        {
            if (success)
            {
                _skeleton.StartTracking(id);
                _joints.Add(id, new Dictionary<SkeletonJoint, SkeletonJointPosition>());
            }
            else
            {
                _pose.StartPoseDetection(_skeleton.GetCalibrationPose(), id);
            }
        }

        private void PoseDetectionCapability_PoseDetected(ProductionNode node, string pose, uint id)
        {
            _pose.StopPoseDetection(id);
            _skeleton.RequestCalibration(id, true);
        }

        private void UserGesture_LostUser(ProductionNode node, uint id)
        {
            _joints.Remove(id);
        }

        private void UserGesture_NewUser(ProductionNode node, uint id)
        {
            _pose.StartPoseDetection(_skeleton.GetCalibrationPose(), id);
        }

        private void SetUserSkeletonJointPositions(uint user)
        {
            SetSkeletonJointPosition(user, SkeletonJoint.Head);
            SetSkeletonJointPosition(user, SkeletonJoint.LeftHand);
            SetSkeletonJointPosition(user, SkeletonJoint.RightHand);
            SetSkeletonJointPosition(user, SkeletonJoint.Neck);

            SetSkeletonJointPosition(user, SkeletonJoint.LeftShoulder);
            SetSkeletonJointPosition(user, SkeletonJoint.LeftElbow);

            SetSkeletonJointPosition(user, SkeletonJoint.RightShoulder);
            SetSkeletonJointPosition(user, SkeletonJoint.RightElbow);

            SetSkeletonJointPosition(user, SkeletonJoint.Torso);


            // Uncomment When the SkeletonProfile is set as All ( or Lower )
            /*
            GetSkeletonJointPosition(user, SkeletonJoint.LeftHip);
            GetSkeletonJointPosition(user, SkeletonJoint.LeftKnee);
            GetSkeletonJointPosition(user, SkeletonJoint.LeftFoot);

            GetSkeletonJointPosition(user, SkeletonJoint.RightHip);
            GetSkeletonJointPosition(user, SkeletonJoint.RightKnee);
            GetSkeletonJointPosition(user, SkeletonJoint.RightFoot);
            */
        }

        private void SetSkeletonJointPosition(uint user, SkeletonJoint joint)
        {
            try
            {
                SkeletonJointPosition pos = new SkeletonJointPosition();
                _skeleton.GetSkeletonJointPosition(user, joint, ref pos);
                if (pos.position.Z == 0)
                    pos.fConfidence = 0;
                else
                    pos.position = _depth.ConvertRealWorldToProjective(pos.position);
                _joints[user][joint] = pos;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);
        }

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            //base.OnPaintBackground(pevent);
        }

        private void DrawSkeleton(Color color, uint user)
        {
            SetUserSkeletonJointPositions(user);
            Dictionary<SkeletonJoint, SkeletonJointPosition> dict = _joints[user];

            SetVertexPositionColors(dict, SkeletonJoint.Head, SkeletonJoint.Neck, color);
            SetVertexPositionColors(dict, SkeletonJoint.Neck, SkeletonJoint.RightShoulder, color);
            SetVertexPositionColors(dict, SkeletonJoint.Neck, SkeletonJoint.LeftShoulder, color);

            SetVertexPositionColors(dict, SkeletonJoint.RightShoulder, SkeletonJoint.RightElbow, color);
            SetVertexPositionColors(dict, SkeletonJoint.RightElbow, SkeletonJoint.RightHand, color);

            SetVertexPositionColors(dict, SkeletonJoint.LeftShoulder, SkeletonJoint.LeftElbow, color);
            SetVertexPositionColors(dict, SkeletonJoint.LeftElbow, SkeletonJoint.LeftHand, color);

            SetVertexPositionColors(dict, SkeletonJoint.RightHip, SkeletonJoint.Torso, color);
            SetVertexPositionColors(dict, SkeletonJoint.LeftHip, SkeletonJoint.Torso, color);
            SetVertexPositionColors(dict, SkeletonJoint.RightHip, SkeletonJoint.LeftHip, color);

            SetVertexPositionColors(dict, SkeletonJoint.RightHip, SkeletonJoint.RightKnee, color);
            SetVertexPositionColors(dict, SkeletonJoint.RightKnee, SkeletonJoint.RightFoot, color);

            SetVertexPositionColors(dict, SkeletonJoint.LeftHip, SkeletonJoint.LeftKnee, color);
            SetVertexPositionColors(dict, SkeletonJoint.LeftKnee, SkeletonJoint.LeftFoot, color);

            if (_vertices.Count == 0)
                return;
            VertexPositionColor[] vertexis = new VertexPositionColor[_vertices.Count()];
            for (int i = 0; i < _vertices.Count(); i++)
            {
                vertexis[i] = _vertices[i];
            }
            _device.DrawUserPrimitives(PrimitiveType.LineList, vertexis, 0, vertexis.Length / 2);
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            SkeletonThread();
        }

        private void  SetVertexPositionColors(Dictionary<SkeletonJoint, SkeletonJointPosition> joints, SkeletonJoint joint0, SkeletonJoint joint1, Color color)
        {
            if (!joints.ContainsKey(joint0) || !joints.ContainsKey(joint1))
                return ;

            try
            {
                SkeletonJointPosition p1 = joints[joint0];
                SkeletonJointPosition p2 = joints[joint1];
                Vector3 v1 = new Vector3(p1.position.X, p1.position.Y, p1.position.Z);
                Vector3 v2 = new Vector3(p2.position.X, p2.position.Y, p2.position.Z);
                _vertices.Add(new VertexPositionColor(v1, color));
                _vertices.Add(new VertexPositionColor(v2, color));
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }

        private unsafe void SkeletonThread()
        {
            DepthMetaData depthMD = new DepthMetaData();
            try
            {
                _context.WaitAndUpdateAll();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
            _depth.GetMetaData(depthMD);

            lock (this)
            {
                if (_device == null)
                    return;

                _device.Clear(Color.White);
                _effect.Techniques[0].Passes[0].Apply();

                uint[] users = _users.GetUsers();
                foreach (uint user in users)
                {
                    if (_skeleton.IsTracking(user))
                    {
                        // Tracking
                        _vertices.Clear();
                        DrawSkeleton(Color.Red, user);
                    }
                    else if (_skeleton.IsCalibrating(user))
                    {
                        // Calibrating
                    }
                    else
                    {
                        // Looking for pose or something else.
                    }
                }
                try
                {
                    _device.Present();
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Trace.WriteLine(ex.ToString());
                }
            }
        }


    }
}


KinectCustomControl.Designer.cs

namespace KinectWPF
{
    partial class KinectCustomControl
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            if (_timer != null)
            {
                _timer.Dispose();
                _timer = null;
            }
            if (_skeleton != null)
            {
                _skeleton.Dispose();
                _skeleton = null;
            }
            if (_pose != null)
            {
                _pose.Dispose();
                _pose = null;
            }
            if (_users != null)
            {
                _users.Dispose();
                _users = null;
            }
            if (_depth != null)
            {
                _depth.Dispose();
                _depth = null;
            }
            if (_context != null)
            {
                _context.Dispose();
                _context = null;
            }
            if (_effect != null)
            {
                _effect.Dispose();
                _effect = null;
            }
            if (_device != null)
            {
                _device.Dispose();
                _device = null;
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
            _timer = new System.Windows.Forms.Timer(components);
            SuspendLayout();

            _timer.Enabled = true;
            _timer.Interval = 10;
            _timer.Tick += Timer_Tick;
            ResumeLayout(false);
        }

        #endregion

    }
}


Well, this is my first time to use Microsoft.Xna.Framework, and actually, I even tried the same thing by use of DirectX as the picture below.
But I don't know where the best coordinate point as the origin is for the drawing, I mean, different axes between Kinect's and Application's....
So, the knowledge of mathematics is needed..... about matrix or something like that.






Anyway, Kinect is very insteresting.

Thursday, February 10, 2011

WebClient by use of Rx ( Reactive Extensions ) for Silverlight.

I don't think the following code is good, but I wanted to check Rx ( Reactive Extensions ) about how it works.
Anyway, It's time for me to do "memo memo" here~.

Reactive Extensions for .NET (Rx)
http://msdn.microsoft.com/en-us/devlabs/ee794896


Preparation

    1. Download a msi file for Rx and then install it.
        ( See the above link for details )

    2. Add the following 3 Rx assemblies to the References in the target project. 
            System.CoreEx
            System.Observable
            System.Reactive





Sample

 public MainViewModel()
 {
   .
   .
   .
  var client = new WebClient();
  client.DownloadStringCompleted += Wc_DownloadStringCompleted;
  try
  {
   client.DownloadStringAsync(new Uri(<URL>, UriKind.Absolute));
  }
  catch(Exception ex)
  {
   MessageBox.Show(ex.Message);
  }
   .
   .
   .
 }

 void Wc_DownloadStringCompleted(object sender, 
                                 DownloadStringCompletedEventArgs e)
 {
   .
   .
   .
 } 

Reactive Extensions

 public MainViewModel()
 {
   .
   .
   .
  var client = new WebClient();
  Observable.FromEvent<DownloadStringCompletedEventHandler, 
                       DownloadStringCompletedEventArgs>(
        h => h.Invoke,
        h => client.DownloadStringCompleted += h,
        h => client.DownloadStringCompleted -= h)
   .Where(e => !e.EventArgs.Cancelled)
   .Retry(3)
   .SelectMany(
        e => (e.EventArgs.Error == null && !string.IsNullOrEmpty(e.EventArgs.Result))
                 ? Observable.Return(e.EventArgs)
                 : Observable.Throw<DownloadStringCompletedEventArgs>(e.EventArgs.Error))
   .Take(1)
   .Subscribe(
        s => Rx_DownloadStringCompleted(s.Result),
        e => MessageBox.Show(e.Message),
       () => { });


  Observable.Return(<URL>)
   .Where(u => !string.IsNullOrEmpty(u))
   .SelectMany(u => Observable.Return(new Uri(u, UriKind.Absolute)))
   .Subscribe(
        s => client.DownloadStringAsync(s),
        e => MessageBox.Show(e.Message),
       () => { });
   .
   .
   .
 }

 void Rx_DownloadStringCompleted(string result)
 {
   .
   .
   .
 } 


I applied Rx to Silverlight RSS Reader Application, and then, I was able to confirm that it worked as usual.


Very interesting~.

Monday, February 7, 2011

Custom Package ( NuGet Package / NuPack ) for WebMatrix

I was wondering how I can create a custom package for WebMatrix.
it seems it's created by use of NuGet.exe.

NuGet
http://nuget.codeplex.com/



1. Create an xml with the extension ".nuspec".

<?xml version="1.0" encoding="utf-8"?>
<package>
 <metadata>
  <id>CaptchaLibrary</id>
  <version>1.0.0.0</version>
  <title>Captcha Library</title>
  <authors>Hiroshi Nakano</authors>
  <description>
   CaptchaLibrary is a library that provides captcha system in a asp.net web pages.
  </description>
 </metadata>
 <files>
  <file src="app_code\*.cshtml" target="content\App_Code" />
  <file src="bin\*.dll" target="lib" />
  <file src="images\*.*" target="content\Images" />
  <file src="sample\*.*" target="content\Captcha\sample" />
  <file src="styles\*.css" target="content\Styles" />
 </files>
</package>



2.Copy all the necessary files to the folders

Binary files(dll) and content files(cshtml, css, png,jpg) into the folders.





















3. Execute the command
NuGet    pack    *******.nuspec
NuGet.exe  pack  *****.nuspec



















After the command is executed successfully, you can see the file "<id><version>.nupkg" in the same folder.


4. Entry the package Info on Package Manager page.

Entry Name and Source( directory path of the ***.nupkg[in this case, "C:\pkgs"])























5. Select the Source and Click  the install button.























6. Click the Install Button .


Click  the install button.






















7. If the message "The package ******* was sucessfully installed." appears, it's the end of the installation.

The Package was successfully installed.






















This time, I put the sample because I wanted to check if this works.



Wednesday, February 2, 2011

Original Captcha System in an ASP.NET Web Pages on WebMatrix/IIS Express or IIS.

ReCaptcha is great to use Captcha system on a website easily, but if need to customize the sytem, then we'd better create another new one.
So I tried creating the system.


Captcha.cs
put dll file in bin folder after doing build the project on vs2010.

This requires the following resources in the project.
 Font as File (*** The font has to be used under the EULA(End User License Agreement) or something. anyway, you should ask the font vender or the copyright owner. ***)
 SESSION_KEY_NAME as string (ex. value = "CaptchaSessionKey")
 CAPTCHA_CODE_STRING as string (ex. value = "1234567890")

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.SessionState;


namespace CaptchaLibrary
{
    public sealed class Captcha : IDisposable
    {
        private readonly Random _random = new Random();
        private bool _disposed = false;

        private Bitmap Image { get; set; }
        private PrivateFontCollection PFC { set; get; }
        private IntPtr Ptr { set; get; }

        public Captcha(int width, int height)
        {
            string text = GenerateCaptchaCode(5);
            if (width > 0 && height > 0)
            {
                HttpContext.Current.Session[Properties.Resources.SESSION_KEY_NAME] = text;
                this.CreateBitmapImage(text, width, height);
            }
        }

        ~Captcha()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            lock (this)
            {
                if (!this._disposed)
                {
                    if (disposing)
                    {
                        if (this.Image != null)
                        {
                            this.Image.Dispose();
                            this.Image = null;
                        }
                        if (this.PFC != null)
                        {
                            this.PFC.Dispose();
                            this.PFC = null;
                        }
                        if (this.Ptr != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(this.Ptr);
                            this.Ptr = IntPtr.Zero;
                        }
                    }
                }
                _disposed = true;
            }
        }

        public static bool Validate(string tagName)
        {
            bool isValid = false;
            ValidationErrorMessage = null;

            if (string.IsNullOrEmpty(tagName))
                throw new ArgumentNullException("tagName", "対象となるタグ名が指定されていません。");

            string paramValue = HttpContext.Current.Request.Form[tagName];
            if (string.IsNullOrEmpty(paramValue))
                ValidationErrorMessage = "認証コードを入力してください。";

            string sessionValue = HttpContext.Current.Session[Properties.Resources.SESSION_KEY_NAME].ToString();
            if (string.IsNullOrEmpty(sessionValue))
                ValidationErrorMessage = "タイムアウトです。再読み込みして、認証コードを生成しなおしてください。";

            if (string.IsNullOrEmpty(ValidationErrorMessage))
            {
                if (sessionValue.Equals(paramValue))
                    isValid = true;
                else
                    ValidationErrorMessage = "認証コードが違います。";
            }
            HttpContext.Current.Session.Remove(Properties.Resources.SESSION_KEY_NAME);
            return isValid;
        }

        public static string ValidationErrorMessage { private set; get; }
        public void OutputAsJpg()
        {
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.ContentType = "image/jpg";
            HttpContext.Current.Response.Flush();
            if (Image != null)
                this.Image.Save(HttpContext.Current.Response.OutputStream, ImageFormat.Jpeg);
            HttpContext.Current.Response.End();
        }

        private void CreateBitmapImage(string code, int width, int height)
        {
            this.Image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            try
            {
                using (Graphics g = Graphics.FromImage(this.Image))
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    Rectangle rect = new Rectangle(0, 0, width, height);

                    DrawBackground(g, rect);
                    DrawText(g, rect, code, true);
                    DrawRandomNoise(g, rect);
                    DrawRandomLines(g, rect, 2);
                }
            }
            catch (Exception)
            {
                this.Image = null;
            }
        }

        private void DrawBackground(Graphics g, Rectangle rect)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.WhiteSmoke))
                g.FillRectangle(brush, rect);
        }

        private void DrawText(Graphics g, Rectangle rect, string code, bool isWarp)
        {
            Font font = null;
            try
            {
                using (GraphicsPath path = new GraphicsPath())
                using (HatchBrush brush = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray))
                {
                    SizeF size;
                    float fontSize = rect.Height + 1;
                    FontFamily ff = GetFontFamily();
                    do
                    {
                        fontSize--;
                        font = new Font(ff, fontSize, FontStyle.Bold);
                        size = g.MeasureString(code, font);
                    } while (size.Width > rect.Width);
                    if (ff != null)
                    {
                        ff.Dispose();
                        ff = null;
                    }

                    StringFormat format = new StringFormat();
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;
                    path.AddString(code, font.FontFamily, (int)font.Style, font.Size, rect, format);

                    if (isWarp)
                    {
                        PointF[] points =
                            {
                                new PointF(this._random.Next(rect.Width) / 5F, this._random.Next(rect.Height) / 5F),
                                new PointF(rect.Width - this._random.Next(rect.Width) / 5F, this._random.Next(rect.Height) / 5F),
                                new PointF(this._random.Next(rect.Width) / 5F, rect.Height - this._random.Next(rect.Height) / 5F),
                                new PointF(rect.Width - this._random.Next(rect.Width) / 5F, rect.Height - this._random.Next(rect.Height) / 5F)
                            };
                        Matrix matrix = new Matrix();
                        matrix.Translate(0F, 0F);
                        path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);
                    }

                    g.FillPath(brush, path);
                }
            }
            finally
            {
                if (font != null)
                {
                    font.Dispose();
                    font = null;
                }
            }
        }

        private void DrawRandomNoise(Graphics g, Rectangle rect)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.DarkGray))
            {
                int max = Math.Max(rect.Width, rect.Height);
                for (int i = 0; i < (int)(rect.Width * rect.Height / 10F); i++)
                {
                    int x = this._random.Next(rect.Width);
                    int y = this._random.Next(rect.Height);
                    int w = this._random.Next(max / 50);
                    int h = this._random.Next(max / 50);
                    g.FillEllipse(brush, x, y, w, h);
                }
            }
        }

        private void DrawRandomLines(Graphics g, Rectangle rect, int lines)
        {
            using (HatchBrush brush = new HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray))
            using (Pen pen = new Pen(brush, 2))
            {
                for (int i = 0; i < lines; i++)
                {
                    float x = 0L;
                    float y = _random.Next(rect.Height);
                    PointF points = new PointF(x, y);
                    PointF pointe = new PointF(rect.Width - x, rect.Height - y);
                    g.DrawLine(pen, points, pointe);
                }
            }
        }

        private string GenerateCaptchaCode(int length)
        {
            string captchaCode = Properties.Resources.CAPTCHA_CODE_STRING;
            if (length < 0)
                length = 5;
            string code = "";
            for (int i = 0; i < length; i++)
                code = String.Concat(code, captchaCode.Substring(_random.Next(captchaCode.Length), 1));
            return code;
        }

        private FontFamily GetFontFamily()
        {
            try
            {
                byte[] bytes = Properties.Resources.Font;
                this.PFC = new PrivateFontCollection();
                this.Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(bytes[0]) * bytes.Length);
                Marshal.Copy(bytes, 0, this.Ptr, bytes.Length);
                this.PFC.AddMemoryFont(this.Ptr, bytes.Length);
                return this.PFC.Families[0];
            }
            catch (Exception)
            {
                return System.Drawing.FontFamily.GenericSerif;
            }
        }
    }

}


CaptchaImage.cshtml

@using CaptchaLibrary;
@{
    var captchaImg = new Captcha(240,50);
    captchaImg.OutputAsJpg();
    captchaImg.Dispose();
}


CaptchaControlPanel.cshtml
Put this file in App_Code folder

@helper Show(string tagName, string errorMsg ){
<div id="captcha">
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.4.4.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function(){
        $('#captcha_inputtag_reloadbutton').click(function(){
            $.ajax({
                type: 'GET',
                cache: false,
                success: function(msg, status){
                    $('#captcha_imgtag_captchaimage').attr('src', '@Href"~/CaptchaImage")?' + new Date().getTime());
                    return false;
                }
            });
        });
    });
</script>
<fieldset style="width: 320px;height: 120px;background-color: #ffffe0;position: relative; top: 0px;left: 0px;">
    <legend title="認証コード(CAPTCHA)">画像認証(CAPTCHA)</legend>
    <div align="left" style="width:320px;height: 60px;background-color: transparent;position: relative;top: -15px;left: 5px;">
        <img id="captcha_imgtag_captchaimage" src="@Href("~/CaptchaImage")" alt="CAPTCHA IMAGE" title="認証用画像(CAPTCHA IMAGE)" />
        <input type="image" src="[Image path for reload button]" id="captcha_inputtag_reloadbutton" alt="再読込ボタン" title="再読込ボタン" onclick="return false;" style="width:50px;height:50px;position:relative;top:auto;left:5pt;" /><br />
        <strong>上の画像に表示されているコードを入力してください。</strong>
    </div>
    <div align="left" style="width: 320px;height: 60px;background-color: transparent;position: relative;top: 0px;left: 5px;">
        <input type="text" name="@tagName" title="画像認証コード(CAPTCHA Code)入力ボックス" @if(!errorMsg.IsEmpty()){<text>class="error-field"</text>} />
        @if (!errorMsg.IsEmpty()) {
        <label for="@tagName" class="validation-error" style="position: relative;top: 0px;left: 0px;">
            @errorMsg
        </label>
        }
    </div>
</fieldset>
</div>
}

Register.cshtml

@using CaptchaLibrary;
@{
    if(WebSecurity.IsAuthenticated)
    {
        Response.Redirect(Href("~/"));
    }
    Response.CacheControl = "no-cache";
    
    Layout = "~/_SiteLayout.cshtml";
    Page.Title = "アカウントの登録";
    Page.Description = "新規登録フォーム";
    var email = "";
    var password = "";
    var confirmPassword = "";
    
    var isValid = true;
    var emailErrorMessage = "";
    var passwordErrorMessage = "";
    var confirmPasswordMessage = "";
    var accountCreationErrorMessage = "";
    var captchaTextErrorMessage = "";
    
    if (IsPost) {
        email = Request.Form["email"];
        password = Request.Form["password"];
        confirmPassword = Request.Form["confirmPassword"];
        
        if (email.IsEmpty()) {
            emailErrorMessage = "電子メール アドレスを入力してください。";
            isValid = false;
        }
        if (password.IsEmpty()) {
            passwordErrorMessage = "パスワードを空白にすることはできません。";
            isValid = false;
        }
        if (password != confirmPassword) {
            confirmPasswordMessage = "新しいパスワードと確認のパスワードが一致しません。";
            isValid = false;
        }
        if(!Captcha.Validate("captchaText"))
        {
            captchaTextErrorMessage = Captcha.ValidationErrorMessage;
            isValid = false;
        }
        if (isValid) {
            var db = Database.Open([db name(w/o ".sdf"]);
            var user = db.QuerySingle("SELECT Email FROM UserProfile WHERE LOWER(Email) = LOWER(@0)", email);
            if (user == null) {
                db.Execute("INSERT INTO UserProfile (Email) VALUES (@0)", email);
                try {
                    bool requireEmailConfirmation = !WebMail.SmtpServer.IsEmpty();
                    var token = WebSecurity.CreateAccount(email, password, requireEmailConfirmation);
                    if (requireEmailConfirmation) {
                        var hostUrl = Request.Url.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
                        var confirmationUrl = hostUrl + VirtualPathUtility.ToAbsolute("~/Account/Confirm?confirmationCode=" + HttpUtility.UrlEncode(token));
                        WebMail.Send(
                            to: email, 
                            subject: "アカウントを確認してください", 
                            body: "確認コード:  " + token + "。<a href=\"" + confirmationUrl + "\">" + confirmationUrl + "</a> にアクセスしてアカウントを有効にしてください。"
                        ); 
                    }
                    if (requireEmailConfirmation) {
                        Response.Redirect("~/Account/Thanks");
                    } else {
                        WebSecurity.Login(email, password);
                        Response.Redirect("~/");
                    }
                } catch (System.Web.Security.MembershipCreateUserException e) {
                    isValid = false;
                    accountCreationErrorMessage = e.ToString();
                }
            } else {
                isValid = false;
                accountCreationErrorMessage = "電子メール アドレスは既に使用中です。";
            }
        }    
    }
}
<p>
   新しいアカウントを作成するには、以下のフォームを使用してください。 
</p>
@if (!isValid) {
   <p class="message error">
    @if (accountCreationErrorMessage.IsEmpty()) {
        @:エラーを修正し、再試行してください。
    } else {
        @accountCreationErrorMessage
    }
   </p>
}
<form method="post" action="">
    <fieldset>
        <legend>申し込みフォーム</legend>
        <ol>
            <li class="email">
                <label for="email">電子メール:</label>
                <input type="text" id="email" name="email" title="Email address" value="@email" @if(!emailErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!emailErrorMessage.IsEmpty()) {
                    <label for="email" class="validation-error">@emailErrorMessage</label>
                }
            </li>
            <li class="password">
                <label for="password">パスワード:</label>
                <input type="password" id="password" name="password" title="パスワード" @if(!passwordErrorMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!passwordErrorMessage.IsEmpty()) {
                    <label for="password" class="validation-error">@passwordErrorMessage</label>
                }
            </li>
            <li class="confirm-password">
                <label for="confirmPassword">パスワードの確認入力:</label>
                <input type="password" id="confirmPassword" name="confirmPassword" title="パスワードの確認入力" @if(!confirmPasswordMessage.IsEmpty()){<text>class="error-field"</text>} />
                @if (!confirmPasswordMessage.IsEmpty()) {
                    <label for="confirmPassword" class="validation-error">@confirmPasswordMessage</label>
                }
            </li>
            <li class="recaptcha">
                <!-- CAPTCHA Control Panel -->
                <p>
                    @CaptchaControlPanel.Show("captchaText", @captchaTextErrorMessage)
                </p>
                <!-- CAPTCHA Control Panel -->
            </li>
        </ol>
        <p class="form-actions">
            <input type="submit" value="登録" title="登録" />
        </p>
    </fieldset>
</form>


Register.cshtml




Error Message 1











Error Message 2


Silverlight RSS Reader ( Out Of Browser Version )

I posted Silverlight RSS Reader before, but that requires the proxy server to get RSS feeds.
( See Simple Silverlight RSS Reader. (MVVM Pattern) )
So I was wondering if I create the application with OOB(out of browser).

* Out-of-Browser Settings on Visual Studio
Check "Require elevated trust when running outside the browser".


* Install PFX file on the silverlight project.

1-1.Create a self-signed SSL certificate ( use for text, i.e. non trusted )

makecert.exe( Visual studio pro or windows sdk. )
http://msdn.microsoft.com/en-US/library/bfsktky3(v=VS.100).aspx


(1) Create a root certificate authority.
makecert -n "CN=Dummy Certificate Authority" -r -a sha1 -sr LocalMachine -sky signature -sv OOBRootCA.pvk OOBRootCA.cer

(2) Create a code-signing certificate.
makecert -sv OOBCodeSigningCA.pvk -iv OOBRootCA.pvk -n "CN=OOB Code Signing CA" -ic OOBRootCA.cer OOBCodeSigningCA.cer

(3) Convert the certificate and key to pfx file.
pvk2pfx -pvk OOBCodeSigningCA.pvk -spc OOBCodeSigningCA.cer -pfx DummyOOBCodeSigningCA.pfx -po <password>


1-2. Install the file on the project.

(1) In solution explorer, right-click the project name, and then click Properties. In the Properties Pages dialog box, click the Signing tab.
























(2) Click "Select from File" button and Select the pfx file.






















(3) Enter the password.














(4) Save Properties, then it's done.






* Coding Consideration on OOB

1. Check network status and Download itself if updated.

App.xaml.cs
public App()
{
   .
   .
   .
 NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;


        if (this.IsRunningOutOfBrowser)
            this.CheckAndDownloadUpdateCompleted += App_CheckAndDownloadUpdateCompleted;
   .
   .
   .

}

private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
    if (NetworkInterface.GetIsNetworkAvailable())
    {
        // something
    }
    else
    {
        // something
    }
}

private void App_CheckAndDownloadUpdateCompleted(
                     object sender,
                     CheckAndDownloadUpdateCompletedEventArgs e)
{
    if (e.UpdateAvailable)
    {
        MessageBox.Show("The update has been downloaded. Please restart the application.", 
                        "Application Update",MessageBoxButton.OK);
    }
}



2. Custom Windows Control bar for Silverlight OOB.

WindowControlBar.xaml
<UserControl x:Class="RSSReaderOOB.WindowControlBar"
    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="26" d:DesignWidth="300">

    <Grid Background="LightGray" Height="26" MaxHeight="26" MinHeight="26">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <Image x:Name="IconImage" Source="/Images/IconSL16.png" Width="16" Height="16" Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="3,0,0,0" />
        <TextBlock x:Name="TitleTextBlock" Margin="22,0,0,0" FontSize="13" Text="Silverlight OOB Application" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
        <StackPanel x:Name="WindowControlBarRoot" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top"
                        Margin="0,0,0,0" HorizontalAlignment="Right">
            <Canvas Width="30" x:Name="wcbMinimizeButton" Cursor="Hand" Background="#00000000"
                        Visibility="Visible" Height="20" ToolTipService.ToolTip="Minimize">
                <Border Width="30" Height="20" Background="#FF939393" BorderBrush="#FF000000"
                            BorderThickness="1,1,0,1" CornerRadius="0,0,0,5">
                    <Border BorderThickness="1.5,1.5,1.5,1.5" CornerRadius="0,0,0,5">
                        <Border.Background>
                            <LinearGradientBrush EndPoint="0.514,0.623"
                                                     StartPoint="0.514,0.191">
                                <GradientStop Color="#FF828282" Offset="0"/>
                                <GradientStop Color="#FF262626" Offset="1"/>
                            </LinearGradientBrush>
                        </Border.Background>
                        <Rectangle Margin="6,9,6,3" Fill="#FFD6D5D5" Stroke="#FF000000"
                                       StrokeThickness="0.5"/>
                    </Border>
                </Border>
            </Canvas>
            <Canvas Width="30" x:Name="wcbMaximizeButton" Cursor="Hand" Background="#00000000"
                        Visibility="Visible" Height="20" ToolTipService.ToolTip="Maximize">
                <Border Width="30" Height="20" Background="#FF939393" BorderBrush="#FF000000"
                            BorderThickness="1,1,0,1">
                    <Border BorderThickness="1.5,1.5,1.5,1.5" CornerRadius="0,0,0,0" Width="29"
                                Height="18">
                        <Border.Background>
                            <LinearGradientBrush EndPoint="0.514,0.623"
                                                     StartPoint="0.514,0.191">
                                <GradientStop Color="#FF828282" Offset="0"/>
                                <GradientStop Color="#FF262626" Offset="1"/>
                            </LinearGradientBrush>
                        </Border.Background>
                        <Border Background="#FFD6D5D5" Margin="6,2,6,2" BorderBrush="#FF000000"
                                    BorderThickness="0.5,0.5,0.5,0.5">
                            <Rectangle Stroke="#FF000000" Margin="2,2,2,2"
                                           StrokeThickness="0.5">
                                <Rectangle.Fill>
                                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                        <GradientStop Color="#FF828282" Offset="0"/>
                                        <GradientStop Color="#FF262626" Offset="1"/>
                                    </LinearGradientBrush>
                                </Rectangle.Fill>
                            </Rectangle>
                        </Border>
                    </Border>
                </Border>
            </Canvas>
            <Canvas Width="40" x:Name="wcbCloseButton" Cursor="Hand" Background="#00000000"
                        Opacity="1" Height="20" ToolTipService.ToolTip="Close">
                <Border Width="40" Height="20" Background="#FF939393" BorderBrush="#FF000000"
                            BorderThickness="1,1,1,1" CornerRadius="0,0,5,0">
                    <Border BorderThickness="1,1,1,1" CornerRadius="0,0,5,0" Width="37"
                                Height="16" x:Name="border">
                        <Border.Background>
                            <LinearGradientBrush EndPoint="0.514,0.623"
                                                     StartPoint="0.514,0.191">
                                <GradientStop Color="#FF956161" Offset="0"/>
                                <GradientStop Color="#FF490E0E" Offset="1"/>
                            </LinearGradientBrush>
                        </Border.Background>
                        <TextBlock Text="X" TextWrapping="Wrap" Foreground="#FFECECEC"
                                       HorizontalAlignment="Center" VerticalAlignment="Center"
                                       x:Name="textBlock"/>
                    </Border>
                </Border>
            </Canvas>
        </StackPanel>

    </Grid>
</UserControl>

WindowControlBar.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace RSSReaderOOB
{
    public partial class WindowControlBar : UserControl
    {
        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(WindowControlBar), new PropertyMetadata(OnTitleChanged));
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(Uri), typeof(WindowControlBar), new PropertyMetadata(OnIconChanged));

        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        public Uri Icon
        {
            get { return (Uri)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }

        private static void OnTitleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                string newTitle = e.NewValue as string;
                WindowControlBar wcb = sender as WindowControlBar;
                wcb.TitleTextBlock.Text = newTitle;
            }
        }

        private static void OnIconChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                if (e.NewValue is Uri)
                {
                    WindowControlBar wcb = sender as WindowControlBar;
                    wcb.IconImage.Source = new System.Windows.Media.Imaging.BitmapImage(e.NewValue as Uri);
                }
            }
        }

        public WindowControlBar()
        {
            InitializeComponent();
            if (Application.Current.IsRunningOutOfBrowser)
            {
                this.Visibility = Visibility.Visible;
                this.wcbMaximizeButton.MouseLeftButtonDown += new MouseButtonEventHandler(wcbMaximizeButton_MouseLeftButtonDown);
                this.wcbMinimizeButton.MouseLeftButtonDown += new MouseButtonEventHandler(wcbMinimizeButton_MouseLeftButtonDown);
                this.wcbCloseButton.MouseLeftButtonDown += new MouseButtonEventHandler(wcbCloseButton_MouseLeftButtonDown);
                this.MouseLeftButtonDown += new MouseButtonEventHandler(WindowControlBar_MouseLeftButtonDown);
            }
            else
                this.Visibility = Visibility.Collapsed;
        }

        void WindowControlBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Application.Current.MainWindow.DragMove();
        }

        void wcbCloseButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Application.Current.MainWindow.Close();
        }

        void wcbMinimizeButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Application.Current.MainWindow.WindowState = WindowState.Minimized;
        }

        void wcbMaximizeButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (Application.Current.MainWindow.WindowState == WindowState.Normal)
                Application.Current.MainWindow.WindowState = WindowState.Maximized;
            else
                Application.Current.MainWindow.WindowState = WindowState.Normal;
        }
    }
}


Here are the capture images of the application.

1.Start the application and then click Install button.
The Application on the browser.




















2.Click Install button.
* Security Warning appears because of Non-Trusted SSL.




















3.Enter a RSS feed url in the textbox.


















4. Done.