drupal hit counter
Jerry Huang | UI

Jerry Huang apps and developing apps

ListPicker performance tuning

8. June 2011 22:53 by Jerry in UI, Windows Phone Development

The ListPicker control in Silverlight for Windows Phone Toolkit (Feb 2011 release) has become famous after its poor performance.wink My app IP CAM Controller for example, starts from v1.6, I used ListPicker to accommodate the full list of supported camera types and brands. The total item number is about 20 to 30, while it took 3 to 5 seconds to get response when openning the setup page. It's really a huge lag.

Thanks to Pedro Lamas who solved this problem by changing the source code in a few places. Details please refer to here:

http://www.pedrolamas.com/2011/06/02/listpicker-a-cafeina/

It's not in english, but translator is available on the right up side of the page. He also submitted a patch to the official Toolkit project on CodePlex here:

http://silverlight.codeplex.com/SourceControl/list/patches

patch ID 9632, providing a brief summary in english.

Good news for those lazy people (such as meblush), I attached the compiled binary and source file base on Pedro's blog, feel free to download from here.

Unzip the file and copy the two files (Microsoft.Phone.Controls.Toolkit.dll and Microsoft.Phone.Controls.Toolkit.pdb) to your Toolkit installation folder, e.g.

C:\Program Files\Microsoft SDKs\Windows Phone\v7.0\Toolkit\Feb11\Bin\

 

A WinForm Image Selector Control

24. April 2011 12:35 by Jerry in UI

First of all, I have to point out that the control is far from perfect. So far it's just workable for my Smart Dial Agent. If you want to make use of it, you will need to do your own customization or make it flexible by using control property. You are welcome to send me back an enhanced version if you have done some works on itLaughing

Before going into technical details, let's have a look how it works

The rectangle with black border in the photo is the selector, while the image on the left is the target cropped photo. The selector could move or resize, but it couldn't smaller than 96*96.

The original source code is from a Chinese website (I forgot which one now, sorry) and base on .Net Framework 1.1. It doesn't work probably at the beginning, I'm not sure if relates to the framework. I just fix some problems and make some changes to fit my needs.

Usage:

New a Form in Visual Studio, drap a PictureBox and then place a Selector control on top of the PictureBox. drap another PictureBox as target image. Trigger the Mouse-Up event to retrieve target image. Below is a sample:

[code language=C#]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 SmartDialAgent.Utils;
using SmartDialAgent.Controls;

namespace SmartDialAgent
{
    public partial class CropImage : Form
    {
        public CropImage()
        {
            InitializeComponent();
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            try
            {
                OpenFileDialog dlg = new OpenFileDialog();
                dlg.Filter = "Images|*.jpg;*.jpeg;*.png;*.bmp";
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    LoadImage(dlg.FileName);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void LoadImage(string path)
        {
            Image img = Image.FromFile(path);
            if (img.Width > 640 || img.Height > 480)
            {
                //source img too big, adjust size
                int x1, y1;//base on width
                if (img.Width > 640)
                {
                    x1 = 640;
                    y1 = 640 * img.Height / img.Width;
                }
                else
                {
                    x1 = img.Width;
                    y1 = img.Height;
                }
                int x2, y2;//base on height
                if (img.Height > 480)
                {
                    y2 = 480;
                    x2 = 480 * img.Width / img.Height;
                }
                else
                {
                    x2 = img.Width;
                    y2 = img.Height;
                }
                //choose the smaller pairs
                if (x1 > x2)
                {
                    img = img.GetThumbnailImage(x2, y2, null, new IntPtr());
                }
                else
                {
                    img = img.GetThumbnailImage(x1, y1, null, new IntPtr());
                }
            }
            pbImage.Image = img;
            pbImage.Width = img.Width;
            pbImage.Height = img.Height;
            int len;
            if (img.Width > img.Height)
                len = img.Height;
            else
                len = img.Width;
            if ((len / 2) > 96)
            {
                pictureBoxSelection1.Width = len / 2;
                pictureBoxSelection1.Height = pictureBoxSelection1.Width;
            }
            else
            {
                pictureBoxSelection1.Width = 96;
                pictureBoxSelection1.Height = 96;
            }
            pictureBoxSelection1.PictureBox = pbImage;
            groupBox1.Width = pbImage.Width;
            groupBox1.Height = pbImage.Height;
            pictureBoxSelection1.Left = pbImage.Left;
            pictureBoxSelection1.Top = pbImage.Top;
            this.GetTargetImage();

        }


        private void GetTargetImage()
        {
            Image img;
            if (pictureBoxSelection1.SelectedImage == null)
            {
                img = PictureBoxSelector.Crop(pbImage.Image, pictureBoxSelection1.Size, pictureBoxSelection1.Left - pbImage.Left, pictureBoxSelection1.Top - pbImage.Top);
            }
            else
            {
                img = pictureBoxSelection1.SelectedImage;
            }
             if (img != null && img.Width > 96)
                    img = img.GetThumbnailImage(96, 96, null, new IntPtr());
                pbTarget.Image = img;
        }

        private void pictureBoxSelection1_MouseUp(object sender, MouseEventArgs e)
        {
            GetTargetImage();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
        }
        private Image Image
        {
            get
            {
                return pbTarget.Image;
            }
        }
        private void btnSave_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        public static Image GetCropImage(string path)
        {
            using (CropImage frm = new CropImage())
            {
                frm.LoadImage(path);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    return frm.Image;
                }
                else
                    return null;
            }
        }

 

    }
}

[/code]

 

Control source code:

[code language=C#]

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using SmartDialAgent.Utils;
using System.Drawing.Drawing2D;

namespace SmartDialAgent.Controls
{

    public class PictureBoxSelector : System.Windows.Forms.Control
    {

        private Color _BorderColor = new Color();
        //private Color _BackColor = new Color();
        private bool _ReSizeble;
        private Point _SelfLocation = new Point();
        private Point _MouseLocation = new Point();
        private int _SelfWidth;
        private int _SelfHeight;
        private int _SelectSelctedIndex;//0-8,0:SizeAll
        private Rectangle _rectLeftSelector = new Rectangle();
        private Rectangle _rectTopSelector = new Rectangle();
        private Rectangle _rectRightSelector = new Rectangle();
        private Rectangle _rectBottomSelector = new Rectangle();
        private Rectangle _rectLeftTopSelector = new Rectangle();
        private Rectangle _rectRightTopSelector = new Rectangle();
        private Rectangle _rectRightBottomSelector = new Rectangle();
        private Rectangle _rectLeftBottomSelector = new Rectangle();
        private System.ComponentModel.Container components = null;
        public PictureBoxSelector()
        {
            InitializeComponent();
        }
        [DefaultValue("Black"), Description("color of the border"), Category("Appearance")]
        public Color BorderColor
        {
            get
            {
                // Insert code here.
                return _BorderColor;
            }
            set
            {
                _BorderColor = value;
                this.Invalidate();
            }
        }
        //[DefaultValue("Control"), Description("Background color"), Category("Appearance")]
        //public override Color BackColor
        //{
        //    get
        //    {
        //        // Insert code here.
        //        return _BackColor;
        //    }
        //    set
        //    {
        //        _BackColor = value;
        //        this.Invalidate();
        //    }
        //}
        [DefaultValue(false), Description("Indicates the control coud be moved and resized"), Category("Behavior")]
        public bool ReSizeble
        {
            get
            {
                // Insert code here.
                return _ReSizeble;
            }
            set
            {
                _ReSizeble = value;
                this.Invalidate();
            }
        }

        [Description("Selected area"), Category("Behavior")]
        public Rectangle SelectedRectangle
        {
            get
            {
                Rectangle selectRectangler = new Rectangle();
                selectRectangler.X = this.Location.X ;
                selectRectangler.Y = this.Location.Y ;
                selectRectangler.Height = this.Height ;
                selectRectangler.Width = this.Width ;
                return selectRectangler;
            }

        }
        /// <summary>
        /// the selected image
        /// </summary>
        public Image SelectedImage { get; private set; }

        public static Bitmap Crop(Image image, Size targetSize, int x, int y)
        {
            try
            {
                Bitmap bmp = new Bitmap(targetSize.Width, targetSize.Height);//, PixelFormat.Format24bppRgb);
                bmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);

                Graphics gfx = Graphics.FromImage(bmp);
                gfx.SmoothingMode = SmoothingMode.AntiAlias;
                gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
                gfx.DrawImage(image, new Rectangle(0, 0, targetSize.Width, targetSize.Height),
                    x, y, targetSize.Width, targetSize.Height, GraphicsUnit.Pixel);

                gfx.Dispose();

                return bmp;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// repaint the background by copying crop image from parent picturebox
        /// </summary>
        /// <param name="e"></param>
        private void RepaintBackground(PaintEventArgs e)
        {
            if (PictureBox != null && PictureBox.Image!=null)
            {
                Image img = Crop(PictureBox.Image, Size, Left - PictureBox.Left, Top - PictureBox.Top);
                if (img != null)
                    e.Graphics.DrawImage(img, 0, 0, Width, Height);
                SelectedImage = img;
            }
        }
        protected override void OnPaint(PaintEventArgs e)
        {
            RepaintBackground(e);
            ReDrawControl(e.Graphics);
        }
        private void DrawSelector(Graphics graphics)
        {
            SolidBrush SelectorPen = new SolidBrush(Color.White);
            Pen borderPen = new Pen(this._BorderColor, 1);
            try
            {
                //solid
                PointF[] LeftPoints = getPointF(0, this.Height / 2 - 3, 6, 6);
                graphics.FillClosedCurve(SelectorPen, LeftPoints);

                PointF[] TopPoints = getPointF(this.Width / 2 - 3, 0, 6, 6);
                graphics.FillClosedCurve(SelectorPen, TopPoints);

                PointF[] RightPoints = getPointF(this.Width - 7, this.Height / 2 - 3, 6, 6);
                graphics.FillClosedCurve(SelectorPen, RightPoints);

                PointF[] BottomPoints = getPointF(this.Width / 2 - 3, this.Height - 7, 6, 6);
                graphics.FillClosedCurve(SelectorPen, BottomPoints);
               
                //remove the 4 solid resize selectors, I don't need it, if you need to, add them back
                //PointF[] LeftTopPoints = getPointF(0, 0, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, LeftTopPoints);

                //PointF[] RightTopPoints = getPointF(this.Width - 7, 0, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, RightTopPoints);

                //PointF[] RightBottomPoints = getPointF(this.Width - 7, this.Height - 7, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, RightBottomPoints);

                //PointF[] LeftBottomPoints = getPointF(0, this.Height - 7, 6, 6);
                //graphics.FillClosedCurve(SelectorPen, LeftBottomPoints);
                //borders
                int border = 6;
                _rectLeftSelector.X = 0;
                _rectLeftSelector.Y = this.Height / 2 - 3;
                _rectLeftSelector.Height = border;
                _rectLeftSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectLeftSelector);

                _rectTopSelector.X = this.Width / 2 - 3;
                _rectTopSelector.Y = 0;
                _rectTopSelector.Height = border;
                _rectTopSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectTopSelector);

                _rectRightSelector.X = this.Width - 7;
                _rectRightSelector.Y = this.Height / 2 - 3;
                _rectRightSelector.Height = border;
                _rectRightSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectRightSelector);

                _rectBottomSelector.X = this.Width / 2 - 3;
                _rectBottomSelector.Y = this.Height - 7;
                _rectBottomSelector.Height = border;
                _rectBottomSelector.Width = border;
                graphics.DrawRectangle(borderPen, _rectBottomSelector);
               
                //remove the 4 solid resize selectors, I don't need it, if you need to, add them back
                //_rectLeftTopSelector.X = 0;
                //_rectLeftTopSelector.Y = 0;
                //_rectLeftTopSelector.Width = border;
                //_rectLeftTopSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectLeftTopSelector);

                //_rectRightTopSelector.X = this.Width - 7;
                //_rectRightTopSelector.Y = 0;
                //_rectRightTopSelector.Width = border;
                //_rectRightTopSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectRightTopSelector);

                //_rectRightBottomSelector.X = this.Width - 7;
                //_rectRightBottomSelector.Y = this.Height - 7;
                //_rectRightBottomSelector.Width = border;
                //_rectRightBottomSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectRightBottomSelector);

                //_rectLeftBottomSelector.X = 0;
                //_rectLeftBottomSelector.Y = this.Height - 7;
                //_rectLeftBottomSelector.Width = border;
                //_rectLeftBottomSelector.Height = border;
                //graphics.DrawRectangle(borderPen, _rectLeftBottomSelector);
            }
            catch (Exception E)
            {
                throw E;
            }
            finally
            {
                SelectorPen.Dispose();
                borderPen.Dispose();
            }

        }
        private void ReDrawControl(Graphics graphics)
        {

            try
            {

                //draw background
               
                //graphics.Clear(this._BackColor);
                //SolidBrush backPen=new SolidBrush(this._BackColor);
                //PointF point1 = new PointF(1,1);
                //PointF point2 = new PointF(this.Width-2,1);
                //PointF point3 = new PointF(this.Width-2,this.Height-2);
                //PointF point4 = new PointF(1,this.Height-2);
                //PointF[] points = {point1, point2, point3, point4};
                //graphics.DrawImage(ImageUtils.CropImage(this.PictureBox, new Rectangle(Left, Top, Width, Height)),);
                //graphics.FillClosedCurve(backPen, points);
               
                //border
                Rectangle rectBorder = new Rectangle();
                Pen borderPen = new Pen(this._BorderColor, 1);
                rectBorder.X = 0;
                rectBorder.Y = 0;
                rectBorder.Height = this.Height - 1;
                rectBorder.Width = this.Width - 1;
                graphics.DrawRectangle(borderPen, rectBorder);
                //selector
                if (_ReSizeble)
                {
                    DrawSelector(graphics);
                }
            }
            catch (Exception E)
            {
                throw E;
            }
            finally
            {
                graphics.Dispose();
            }
        }
        public PictureBox PictureBox { get; set; }
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            this.RepaintBackground(e);
        }


        private PointF[] getPointF(int x, int y, int Width, int Height)
        {
            PointF point1 = new PointF(x, y);
            PointF point2 = new PointF(x + Width, y);
            PointF point3 = new PointF(x + Width, y + Height);
            PointF point4 = new PointF(x, y + Height);
            PointF[] points = { point1, point2, point3, point4 };
            return points;
        }
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                    components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region generated by Visual Studio

        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
            this.Resize += new EventHandler(ShapeEx_Resize);
            this.MouseDown += new MouseEventHandler(ShapeEx_MouseDown);
            this.MouseMove += new MouseEventHandler(ShapeEx_MouseMove);
            this.MouseLeave += new EventHandler(ShapeEx_MouseLeave);
            this.MouseUp += new MouseEventHandler(ShapeEx_MouseUp);
            this.Move += new EventHandler(Selection_Move);
            this._BorderColor = Color.Black;
            //this._BackColor = Color.Transparent;//.FromName("Control");
            this._ReSizeble = false;
            this._SelectSelctedIndex = -1;
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);  
            //SetStyle(ControlStyles.OptimizedDoubleBuffer, true);  

        }
        #endregion

        private void ShapeEx_Resize(object sender, EventArgs e)
        {
            //hard code 96, the smallest size could be,
            //will be better if use a Size property
            if (this.Width < 96 || this.Height < 96)
            {
                this.Width = 96;
                this.Height = 96;
            }
            this.Invalidate();
        }

 

        private void ShapeEx_MouseDown(object sender, MouseEventArgs e)
        {
            if (_ReSizeble)
            {
                if (_rectLeftSelector.Contains(e.X, e.Y) || _rectRightSelector.Contains(e.X, e.Y) || _rectTopSelector.Contains(e.X, e.Y) || _rectBottomSelector.Contains(e.X, e.Y) || _rectLeftTopSelector.Contains(e.X, e.Y) || _rectRightTopSelector.Contains(e.X, e.Y) || _rectRightBottomSelector.Contains(e.X, e.Y) || _rectLeftBottomSelector.Contains(e.X, e.Y))
                {
                    if (_rectLeftTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNWSE;
                        this._SelectSelctedIndex = 1;
                    }

                    if (_rectTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNS;
                        this._SelectSelctedIndex = 2;
                    }
                    if (_rectRightTopSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNESW;
                        this._SelectSelctedIndex = 3;
                    }
                    if (_rectRightSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeWE;
                        this._SelectSelctedIndex = 4;
                    }
                    if (_rectRightBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNWSE;
                        this._SelectSelctedIndex = 5;
                    }
                    if (_rectBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNS;
                        this._SelectSelctedIndex = 6;
                    }
                    if (_rectLeftBottomSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeNESW;
                        this._SelectSelctedIndex = 7;
                    }
                    if (_rectLeftSelector.Contains(e.X, e.Y))
                    {
                        this.Cursor = Cursors.SizeWE;
                        this._SelectSelctedIndex = 8;
                    }

                }
                else
                {
                    this.Cursor = Cursors.SizeAll;
                    this._SelectSelctedIndex = 0;
                }
                this._SelfLocation.X = this.Location.X;
                this._SelfLocation.Y = this.Location.Y;
                this._MouseLocation.X = Cursor.Position.X;
                this._MouseLocation.Y = Cursor.Position.Y;
                this._SelfWidth = this.Width;
                this._SelfHeight = this.Height;
            }
        }

        private void ShapeEx_MouseMove(object sender, MouseEventArgs e)
        {
           
            //move and resize
            //since I removed 4 resize selector, it will only fall into cases like 2,4,6,8,etc
            switch (this._SelectSelctedIndex)
            {
                case 0:
                    this.Location = new Point(Cursor.Position.X - (_MouseLocation.X - _SelfLocation.X), Cursor.Position.Y - (_MouseLocation.Y - _SelfLocation.Y));
                    break;
                case 1:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, Cursor.Position.Y - _MouseLocation.Y + _SelfLocation.Y);
                    break;
                case 2:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = Height;//remove if Width<> Height
                    this.Location = new Point(_SelfLocation.X, Cursor.Position.Y - _MouseLocation.Y + _SelfLocation.Y);
                    break;
                case 3:
                    this.Height = this._SelfHeight - (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(_SelfLocation.X, Cursor.Position.Y - (_MouseLocation.Y - _SelfLocation.Y));
                    break;
                case 4:
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    Height = Width;//remove if Width<> Height
                    break;
                case 5:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth + (Cursor.Position.X - _MouseLocation.X);
                    break;
                case 6:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    Width = Height;//remove if Width<> Height
                    break;
                case 7:
                    this.Height = this._SelfHeight + (Cursor.Position.Y - _MouseLocation.Y);
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, _SelfLocation.Y);
                    break;
                case 8:
                    this.Width = this._SelfWidth - (Cursor.Position.X - _MouseLocation.X);
                    Height = Width;//remove if Width<> Height
                    this.Location = new Point(Cursor.Position.X - _MouseLocation.X + _SelfLocation.X, _SelfLocation.Y);
                    break;
            }
 
           
        }

        private void Selection_Move(object sender, EventArgs e)
        {
            Invalidate();
        }

        private void ShapeEx_MouseLeave(object sender, EventArgs e)
        {
            this.Cursor = Cursors.Default;
            this._SelectSelctedIndex = -1;
            Invalidate();
        }

        private void ShapeEx_MouseUp(object sender, MouseEventArgs e)
        {
            this.Cursor = Cursors.Default;
            this._SelectSelctedIndex = -1;
            Invalidate();
        }

    }
}

[/code]

As I stated at the beginning, this is a naive version, below is a list of known issues

  1. when the selector is out of the parent PictureBox, the area of out of PictureBox can't be blank, but some remaining pixes with lagacy color (I don't know how to say, you will see if you run it). Currently I use a groupBox as a container for the PictureBox so that it doesn't look so ugly. Ideally the selector is not allowed to move out size of parent PictureBox
  2. should proper control the PictureBox property, should throw error if the property is null, or force the control parent is PictureBox
  3. current version seems not very efficient, the image is flicking when moving the selector

 

source code also available here:

PictureBoxSelection.cs (19.17 kb)