Click here to Skip to main content
15,886,519 members
Articles / Desktop Programming / WPF

WPF: A Simple Color Picker With Preview

Rate me:
Please Sign up or sign in to vote.
4.89/5 (73 votes)
17 Apr 2012CPOL4 min read 294.2K   7.4K   135  
A simple Color Picker with preview.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WPFColorPickerLib
{
  /// <summary>
  /// A simple WPF color picker.  The basic idea is to use a Color swatch image and then pick out a single
  /// pixel and use that pixel's RGB values along with the Alpha slider to form a SelectedColor.
  /// 
  /// This class is from Sacha Barber at http://sachabarber.net/?p=424 and http://www.codeproject.com/KB/WPF/WPFColorPicker.aspx.
  /// 
  /// This class borrows an idea or two from the following sources:
  ///  - AlphaSlider and Preview box; Based on an article by ShawnVN's Blog; 
  ///    http://weblogs.asp.net/savanness/archive/2006/12/05/colorcomb-yet-another-color-picker-dialog-for-wpf.aspx.
  ///  - 1*1 pixel copy; Based on an article by Lee Brimelow; http://thewpfblog.com/?p=62.
  /// 
  /// Enhanced by Mark Treadwell (1/2/10):
  ///  - Left click to select the color with no mouse move
  ///  - Set tab behavior
  ///  - Set an initial color (note that the search to set the cursor ellipse delays the initial display)
  ///  - Fix single digit hex displays
  ///  - Add Mouse Wheel support to change the Alpha value
  ///  - Modify color select dragging behavior
  /// </summary>
  public partial class ColorPicker : UserControl
  {
    #region Data

    private DrawingAttributes drawingAttributes = new DrawingAttributes();
    private Color selectedColor = Colors.Transparent;
    private Boolean IsMouseDown = false;

    #endregion

    #region Constructors

    /// <summary>
    /// Default constructor that initializes the ColorPicker to Black.
    /// </summary>
    public ColorPicker()
      : this(Colors.Black)
    { }

    /// <summary>
    /// Constructor that initializes to ColorPicker to the specified color.
    /// </summary>
    /// <param name="initialColor"></param>
    public ColorPicker(Color initialColor)
    {
      InitializeComponent();
      this.selectedColor = initialColor;
    }

    #endregion

    #region Public Properties

    /// <summary>
    /// Gets or privately sets the Selected Color.
    /// </summary>
    public Color SelectedColor
    {
      get { return selectedColor; }
      private set
      {
        if (selectedColor != value)
        {
          this.selectedColor = value;
          CreateAlphaLinearBrush();
          UpdateTextBoxes();
          UpdateInk();
        }
      }
    }

    /// <summary>
    /// Sets the initial Selected Color.
    /// </summary>
    public Color InitialColor
    {
      set
      {
        SelectedColor = value;
        CreateAlphaLinearBrush();
        AlphaSlider.Value = value.A;
        UpdateCursorEllipse(value);
      }
    }

    #endregion

    #region Control Events

    /// <summary>
    /// 
    /// </summary>
    private void AlphaSlider_MouseWheel(object sender, MouseWheelEventArgs e)
    {
      int change = e.Delta / Math.Abs(e.Delta);
      AlphaSlider.Value = AlphaSlider.Value + (double)change;
    }

    /// <summary>
    /// Update SelectedColor Alpha based on Slider value.
    /// </summary>
    private void AlphaSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
      SelectedColor = Color.FromArgb((byte)AlphaSlider.Value, SelectedColor.R, SelectedColor.G, SelectedColor.B);
    }

    /// <summary>
    /// Update the SelectedColor if moving the mouse with the left button down.
    /// </summary>
    private void CanvasImage_MouseMove(object sender, MouseEventArgs e)
    {
      if (IsMouseDown) UpdateColor();
    }

    /// <summary>
    /// Handle MouseDown event.
    /// </summary>
    private void CanvasImage_MouseDown(object sender, MouseButtonEventArgs e)
    {
      IsMouseDown = true;
      UpdateColor();
    }

    /// <summary>
    /// Handle MouseUp event.
    /// </summary>
    private void CanvasImage_MouseUp(object sender, MouseButtonEventArgs e)
    {
      IsMouseDown = false;
      //UpdateColor();
    }

    /// <summary>
    /// Apply the new Swatch image based on user requested swatch.
    /// </summary>
    private void Swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
      Image img = (sender as Image);
      ColorImage.Source = img.Source;
      UpdateCursorEllipse(SelectedColor);
    }

    #endregion // Control Events

    #region Private Methods

    /// <summary>
    /// Creates a new LinearGradientBrush background for the Alpha area slider.  This is based on the current color.
    /// </summary>
    private void CreateAlphaLinearBrush()
    {
      Color startColor = Color.FromArgb((byte)0, SelectedColor.R, SelectedColor.G, SelectedColor.B);
      Color endColor = Color.FromArgb((byte)255, SelectedColor.R, SelectedColor.G, SelectedColor.B);
      LinearGradientBrush alphaBrush = new LinearGradientBrush(startColor, endColor, new Point(0, 0), new Point(1, 0));
      AlphaBorder.Background = alphaBrush;
    }

    /// <summary>
    /// Sets a new Selected Color based on the color of the pixel under the mouse pointer.
    /// </summary>
    private void UpdateColor()
    {
      // Test to ensure we do not get bad mouse positions along the edges
      int imageX = (int)Mouse.GetPosition(canvasImage).X;
      int imageY = (int)Mouse.GetPosition(canvasImage).Y;
      if ((imageX < 0) || (imageY < 0) || (imageX > ColorImage.Width - 1) || (imageY > ColorImage.Height - 1)) return;
      // Get the single pixel under the mouse into a bitmap and copy it to a byte array
      CroppedBitmap cb = new CroppedBitmap(ColorImage.Source as BitmapSource, new Int32Rect(imageX, imageY, 1, 1));
      byte[] pixels = new byte[4];
      cb.CopyPixels(pixels, 4, 0);
      // Update the mouse cursor position and the Selected Color
      ellipsePixel.SetValue(Canvas.LeftProperty, (double)(Mouse.GetPosition(canvasImage).X - (ellipsePixel.Width / 2.0)));
      ellipsePixel.SetValue(Canvas.TopProperty, (double)(Mouse.GetPosition(canvasImage).Y - (ellipsePixel.Width / 2.0)));
      canvasImage.InvalidateVisual();
      // Set the Selected Color based on the cursor pixel and Alpha Slider value
      SelectedColor = Color.FromArgb((byte)AlphaSlider.Value, pixels[2], pixels[1], pixels[0]);
    }

    /// <summary>
    /// Update the mouse cursor ellipse position.
    /// </summary>
    private void UpdateCursorEllipse(Color searchColor)
    {
      // Scan the canvas image for a color which matches the search color
      CroppedBitmap cb;
      Color tempColor = new Color();
      byte[] pixels = new byte[4];
      int searchY = 0;
      int searchX = 0;
      searchColor.A = 255;
      for (searchY = 0; searchY <= canvasImage.Width - 1; searchY++)
      {
        for (searchX = 0; searchX <= canvasImage.Height - 1; searchX++)
        {
          cb = new CroppedBitmap(ColorImage.Source as BitmapSource, new Int32Rect(searchX, searchY, 1, 1));
          cb.CopyPixels(pixels, 4, 0);
          tempColor = Color.FromArgb(255, pixels[2], pixels[1], pixels[0]);
          if (tempColor == searchColor) break;
        }
        if (tempColor == searchColor) break;
      }
      // Default to the top left if no match is found
      if (tempColor != searchColor)
      {
        searchX = 0;
        searchY = 0;
      }
      // Update the mouse cursor ellipse position
      ellipsePixel.SetValue(Canvas.LeftProperty, ((double)searchX - (ellipsePixel.Width / 2.0)));
      ellipsePixel.SetValue(Canvas.TopProperty, ((double)searchY - (ellipsePixel.Width / 2.0)));
    }

    /// <summary>
    /// Update text box values based on the Selected Color.
    /// </summary>
    private void UpdateTextBoxes()
    {
      txtAlpha.Text = SelectedColor.A.ToString();
      txtAlphaHex.Text = SelectedColor.A.ToString("X2");
      txtRed.Text = SelectedColor.R.ToString();
      txtRedHex.Text = SelectedColor.R.ToString("X2");
      txtGreen.Text = SelectedColor.G.ToString();
      txtGreenHex.Text = SelectedColor.G.ToString("X2");
      txtBlue.Text = SelectedColor.B.ToString();
      txtBlueHex.Text = SelectedColor.B.ToString("X2");
      txtAll.Text = String.Format("#{0}{1}{2}{3}", txtAlphaHex.Text, txtRedHex.Text, txtGreenHex.Text, txtBlueHex.Text);
    }

    /// <summary>
    /// Updates the Ink strokes based on the Selected Color.
    /// </summary>
    private void UpdateInk()
    {
      drawingAttributes.Color = SelectedColor;
      drawingAttributes.StylusTip = StylusTip.Ellipse;
      drawingAttributes.Width = 5;
      // Update drawing attributes on previewPresenter
      foreach (Stroke s in previewPresenter.Strokes)
      {
        s.DrawingAttributes = drawingAttributes;
      }
    }

    #endregion // Update Methods

  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions