wpfには色選択ダイアログがないようです。 参照を追加してwpfじゃない方の色選択ダイアログ(System.Windows.Forms.ColorDialog)を使えばいいのかもしれませんが、ちょっと欲しいのと違ったので自作してみました。
ColorDialogもコレもHLS色空間から色を選択できます。 というわけで見た目は似てますが、ちょっとだけ違います。 ColorDialogは輝度を変えても色相-彩度の選択領域が不変になってます。 それに対して、自作した色選択コントロールは輝度に応じて色相-彩度の選択領域も変化します。 それが良いのか、かえって使いづらくなったのは不明。 その実装のせいでちょっと重くなってしまいましたしねぇ。 開発環境のPCはiCore7なので気にする程の重さじゃないんですが、非力なノートマシンとかで使い物になるかは試してません。
とりあえず特徴とか制約とかはこんな感じ。
- RGB色空間およびHLS色空間のパラメータで色を選択できる。
- htmlで色を指定するときのようなテキスト表記(#123456やlightgreenなど)で色を選択できる。
- 色選択「ダイアログ」ではなく色選択「コントロール」として作ったので、微妙に使い方が広がった。
- 選択色が変わったらRgbColorChangedイベントで通知される。
- RgbColorプロパティはバインドできる。
- その他のプロパティ(R、G、BとかHueとか)は面倒なのでバインド不可。 (他のコントロールからはバインドできるけど、Rプロパティにバインドを追加とかはできない。)
- RGB⇔HLSの変換コードは適当なので、丸め誤差あり。 HLSは感覚的なものだからこの用途では問題ないはず。 ただ、変換コードをコピー&ペーストして使いまわすときは要テスト。
- RGB⇔HLSの変換はwikipedia(en)のページ"HSL and HSV"をなんとなく眺めながらコーディング。 その部分は最適化などとは無縁。
- 選択領域にWriteableBitmapを使っているのでコンパイル時にunsafeオプションが必要。
- 等倍表示でのみ動作確認。 LayoutTransformとかしても動くように作った気はするけど、ノーチェックなのでTransformしたら誤動作するかも。
コードはこんな感じ。 まずはColorSelectControl.xaml。
<UserControl
x:Class="UserControls.ColorSelectControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:appCtrls="clr-namespace:UserControls"
mc:Ignorable="d"
d:DesignHeight="420" d:DesignWidth="440"
Loaded="OnLoaded"
>
<StackPanel Name="basePanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" Margin="2">S</TextBlock>
<Border
Grid.Column="1"
Grid.Row="0"
BorderThickness="1"
BorderBrush="Black"
>
<Border BorderThickness="1" BorderBrush="White">
<Canvas
Width="360"
Height="256"
>
<Canvas.Clip>
<RectangleGeometry Rect="0,0,360,256"/>
</Canvas.Clip>
<Image
Name="selectArea"
Width="360"
Height="256"
Stretch="Fill"
MouseLeftButtonDown="OnMouseDownInSelectArea"
MouseMove="OnMouseMoveInSelectArea"
/>
<Canvas
Name="selectAreaCursor"
Canvas.Left="{Binding Hue}"
Canvas.Bottom="{Binding Saturation}"
IsHitTestVisible="False"
>
<Line X1="-10" Y1="0" X2="-6" Y2="0" StrokeThickness="2" Stroke="Black" Opacity="0.7"/>
<Line X1="6" Y1="0" X2="10" Y2="0" StrokeThickness="2" Stroke="Black" Opacity="0.7"/>
<Line X1="0" Y1="-10" X2="0" Y2="-6" StrokeThickness="2" Stroke="Black" Opacity="0.7"/>
<Line X1="0" Y1="6" X2="0" Y2="10" StrokeThickness="2" Stroke="Black" Opacity="0.7"/>
<Line X1="-8" Y1="-8" X2="-5" Y2="-5" StrokeThickness="2" Stroke="White" Opacity="0.7"/>
<Line X1="5" Y1="-5" X2="8" Y2="-8" StrokeThickness="2" Stroke="White" Opacity="0.7"/>
<Line X1="-8" Y1="8" X2="-5" Y2="5" StrokeThickness="2" Stroke="White" Opacity="0.7"/>
<Line X1="5" Y1="5" X2="8" Y2="8" StrokeThickness="2" Stroke="White" Opacity="0.7"/>
</Canvas>
</Canvas>
</Border>
</Border>
<Border
Grid.Column="3"
Grid.Row="0"
BorderThickness="1"
BorderBrush="Black"
>
<Border BorderThickness="1" BorderBrush="White">
<Canvas Width="16" Height="256">
<Image
Name="selectBar"
Width="16"
Height="256"
Stretch="Fill"
MouseLeftButtonDown="OnMouseDownInSelectBar"
MouseLeftButtonUp="OnMouseUpInSelectBar"
MouseMove="OnMouseMoveInSelectBar"
/>
<Thumb
Name="thumbSelectBar"
Canvas.Left="16"
DragDelta="OnSelectBarThumbDragDelta"
Height="16"
>
<Thumb.Template>
<ControlTemplate>
<!-- ホットスポットは左端の真ん中 -->
<Canvas>
<Polygon
Canvas.Top="8"
Points="0,0 6,-6 6,6"
Fill="Black"
Stroke="Black"
StrokeThickness="3"
/>
<Polygon
Canvas.Top="8"
Points="0,0 6,-6 6,6"
Stroke="White"
StrokeThickness="1"
/>
</Canvas>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
</Border>
</Border>
<TextBlock Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" Margin="2">H</TextBlock>
<TextBlock Grid.Column="3" Grid.Row="1" HorizontalAlignment="Center" Margin="2">L</TextBlock>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="8"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
>
<Border
BorderThickness="1"
BorderBrush="Black"
HorizontalAlignment="Center"
VerticalAlignment="Center"
>
<Border BorderThickness="1" BorderBrush="White">
<Rectangle Width="32" Height="32">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding RgbColor}"/>
</Rectangle.Fill>
</Rectangle>
</Border>
</Border>
<Label VerticalAlignment="Bottom">← 選択色</Label>
</StackPanel>
<StackPanel
Grid.Column="2"
Grid.Row="0"
Grid.ColumnSpan="3"
Orientation="Horizontal"
HorizontalAlignment="Right"
>
<Label VerticalAlignment="Center">テキスト表記</Label>
<TextBox
Name="textBoxTextualRepresentation"
Width="100"
VerticalAlignment="Center"
GotFocus="OnTextBoxTextualRepresentationGotFocus"
>
<TextBox.CommandBindings>
<CommandBinding
Command="{x:Static appCtrls:ColorSelectControl.UpdateFromTextualRepresentationCommand}"
Executed="OnUpdateFromTextualRepresentationCommand"
/>
</TextBox.CommandBindings>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{x:Static appCtrls:ColorSelectControl.UpdateFromTextualRepresentationCommand}"/>
</TextBox.InputBindings>
</TextBox>
<Button VerticalAlignment="Center" Click="OnUpdateFromTextualRepresentationButtonClick">↓</Button>
</StackPanel>
<Label Grid.Column="0" Grid.Row="2">H : 色相</Label>
<appCtrls:NumericBox
Grid.Column="1"
Grid.Row="2"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="359"
LargeChange="10"
Value="{Binding Hue}"
/>
<Label Grid.Column="0" Grid.Row="3">S : 彩度</Label>
<appCtrls:NumericBox
Grid.Column="1"
Grid.Row="3"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="255"
LargeChange="10"
Value="{Binding Saturation}"
/>
<Label Grid.Column="0" Grid.Row="4">L : 輝度</Label>
<appCtrls:NumericBox
Grid.Column="1"
Grid.Row="4"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="255"
LargeChange="10"
Value="{Binding Lightness}"
/>
<Label Grid.Column="3" Grid.Row="2">R : 赤</Label>
<appCtrls:NumericBox
Grid.Column="4"
Grid.Row="2"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="255"
LargeChange="10"
Value="{Binding R}"
/>
<Label Grid.Column="3" Grid.Row="3">G : 緑</Label>
<appCtrls:NumericBox
Grid.Column="4"
Grid.Row="3"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="255"
LargeChange="10"
Value="{Binding G}"
/>
<Label Grid.Column="3" Grid.Row="4">B : 青</Label>
<appCtrls:NumericBox
Grid.Column="4"
Grid.Row="4"
Width="140"
TextBoxWidth="40"
ValueMin="0"
ValueMax="255"
LargeChange="10"
Value="{Binding B}"
/>
</Grid>
</StackPanel>
</UserControl>
ColorSelectControl.xaml.cs。
using System;
using System.ComponentModel;
using System.Media;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Diagnostics;
namespace UserControls
{
public partial class ColorSelectControl : UserControl, INotifyPropertyChanged
{
public readonly static RoutedCommand UpdateFromTextualRepresentationCommand = new RoutedCommand();
// Hueの1週を何分割するか? 度数法にあわせて360とする。
public const int HueCircumference = 360;
// SaturationとLightnessの分割数。
// 概念的には0.0~1.0のところを整数値0~BitsPerPixel-1で表す。
public const int BitsPerPixel = 256;
private const int _BitsPerPixel_1 = BitsPerPixel - 1;
public static readonly DependencyProperty RgbColorProperty = DependencyProperty.Register(
"RgbColor",
typeof(Color),
typeof(ColorSelectControl),
new FrameworkPropertyMetadata(
new Color(),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(ColorSelectControl.OnRgbColorChanged)
)
);
public Color RgbColor
{
get { return (Color)GetValue(RgbColorProperty); }
set { SetValue(RgbColorProperty, value); }
}
private static void OnRgbColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
ColorSelectControl thisCtrl = (ColorSelectControl)obj;
Color newColor = (Color)args.NewValue;
newColor.A = 0xff;
thisCtrl.R = newColor.R;
thisCtrl.G = newColor.G;
thisCtrl.B = newColor.B;
RoutedPropertyChangedEventArgs<Color> evt = new RoutedPropertyChangedEventArgs<Color>(
(Color)args.OldValue,
newColor,
RgbColorChangedEvent
);
thisCtrl.OnRgbColorChanged(evt);
}
public static readonly RoutedEvent RgbColorChangedEvent = EventManager.RegisterRoutedEvent(
"RgbColorChanged",
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<Color>),
typeof(ColorSelectControl)
);
public event RoutedPropertyChangedEventHandler<Color> RgbColorChanged
{
add { AddHandler(RgbColorChangedEvent, value); }
remove { RemoveHandler(RgbColorChangedEvent, value); }
}
protected virtual void OnRgbColorChanged(RoutedPropertyChangedEventArgs<Color> args)
{
RaiseEvent(args);
}
WriteableBitmap _selectBarBmp;
WriteableBitmap _selectAreaBmp;
// ↑のWriteableBitmapを描きかえるべきか?
// 1つのプロパティを更新したときに何度もWriteableBitmapの描きかえが呼ばれるコードがある。
// WriteableBitmapの描きかえは重いので、1度の描きかえで済むようにするためのフラグ。
// UpdateBmpメソッド参照。
private bool _shouldUpdateBmp;
private byte _r;
public byte R
{
get
{
return _r;
}
set
{
if (_r == value)
return;
_r = value;
NotifyPropertyChanged("R");
Color c = RgbColor;
c.R = _r;
RgbColor = c;
UpdateTextualRepresentation();
UpdateHsl();
}
}
private byte _g;
public byte G
{
get
{
return _g;
}
set
{
if (_g == value)
return;
_g = value;
NotifyPropertyChanged("G");
Color c = RgbColor;
c.G = _g;
RgbColor = c;
UpdateTextualRepresentation();
UpdateHsl();
}
}
private byte _b;
public byte B
{
get
{
return _b;
}
set
{
if (_b == value)
return;
_b = value;
NotifyPropertyChanged("B");
Color c = RgbColor;
c.B = _b;
RgbColor = c;
UpdateTextualRepresentation();
UpdateHsl();
}
}
private int _hue;
public int Hue
{
get
{
return _hue;
}
set
{
if (_hue == value)
return;
_hue = value;
NotifyPropertyChanged("Hue");
UpdateRgb();
UpdateBmp();
}
}
private int _saturation;
public int Saturation
{
get
{
return _saturation;
}
set
{
if (_saturation == value)
return;
_saturation = value;
NotifyPropertyChanged("Saturation");
UpdateRgb();
UpdateBmp();
}
}
private int _lightness;
public int Lightness
{
get
{
return _lightness;
}
set
{
if (_lightness == value)
return;
_lightness = value;
NotifyPropertyChanged("Lightness");
UpdateRgb();
UpdateBmp();
UpdateThumbSelectBarFromLightness();
}
}
public ColorSelectControl()
{
InitializeComponent();
_selectAreaBmp = new WriteableBitmap(HueCircumference, BitsPerPixel, 96, 96, PixelFormats.Bgr32, null);
_selectBarBmp = new WriteableBitmap(1, BitsPerPixel, 96, 96, PixelFormats.Bgr32, null);
selectArea.Source = _selectAreaBmp;
selectBar.Source = _selectBarBmp;
basePanel.DataContext = this;
}
private void UpdateHsl()
{
ToHsl(_r, _g, _b, out _hue, out _saturation, out _lightness);
NotifyPropertyChanged("Hue");
NotifyPropertyChanged("Saturation");
NotifyPropertyChanged("Lightness");
UpdateBmp();
UpdateThumbSelectBarFromLightness();
}
private void UpdateRgb()
{
Color c = FromHsl(_hue, _saturation, _lightness);
_r = c.R;
_g = c.G;
_b = c.B;
NotifyPropertyChanged("R");
NotifyPropertyChanged("G");
NotifyPropertyChanged("B");
RgbColor = c;
UpdateTextualRepresentation();
}
private void UpdateBmp()
{
Dispatcher.BeginInvoke(new Action(() =>
{
if (_shouldUpdateBmp)
{
_shouldUpdateBmp = false;
UpdateSelectArea();
UpdateSelectBar();
}
}));
_shouldUpdateBmp = true;
}
private void UpdateTextualRepresentation()
{
textBoxTextualRepresentation.Text =
"#" +
_r.ToString("X2") +
_g.ToString("X2") +
_b.ToString("X2");
}
private void UpdateFromTextualRepresentation()
{
string text = textBoxTextualRepresentation.Text;
if (text.ToLower() == "transparent")
{
SystemSounds.Beep.Play();
return;
}
try
{
RgbColor = (Color)ColorConverter.ConvertFromString(text);
}
catch (NullReferenceException)
{
SystemSounds.Beep.Play();
}
catch (FormatException)
{
SystemSounds.Beep.Play();
}
}
private void UpdateHsFromMouseCursor(MouseEventArgs evt)
{
if (evt.LeftButton == MouseButtonState.Released)
return;
double hueRangeOnImage = selectArea.ActualWidth;
double saturationRangeOnImage = selectArea.ActualHeight;
Point p = evt.GetPosition(selectArea);
int x = (int)(p.X * HueCircumference / hueRangeOnImage);
if (HueCircumference <= x)
x = HueCircumference - 1;
int y = (int)((BitsPerPixel - p.Y) * _BitsPerPixel_1 / saturationRangeOnImage);
if (BitsPerPixel <= y)
y = _BitsPerPixel_1;
Hue = x;
Saturation = y;
evt.Handled = true;
}
private void UpdateLightnessFromMouseCursor(MouseEventArgs evt)
{
if (evt.LeftButton == MouseButtonState.Released)
return;
double rangeOnImage = selectBar.ActualHeight;
Point p = evt.GetPosition(selectBar);
int y = (int)((BitsPerPixel - p.Y) * _BitsPerPixel_1 / rangeOnImage);
if (y < 0)
y = 0;
if (BitsPerPixel <= y)
y = _BitsPerPixel_1;
Lightness = y;
evt.Handled = true;
}
// Lightnessプロパティが変更されたときのThumbSelectBarの移動
private void UpdateThumbSelectBarFromLightness()
{
// 座標が2度設定されるのを防ぐため、ThumbSelectBarがドラック中はそのまま返す
if (thumbSelectBar.IsDragging)
return;
double thumbHotspotY = thumbSelectBar.ActualHeight / 2.0;
double rangeOnImage = selectBar.ActualHeight;
double bottom = _lightness * rangeOnImage / _BitsPerPixel_1 - thumbHotspotY;
Canvas.SetBottom(thumbSelectBar, bottom);
}
// ThumbSelectBarがユーザーにドラッグされたときのThumbの移動とLightnessの設定
private void UpdateThumbSelectBarFromDragDelta(DragDeltaEventArgs evt)
{
double rangeOnImage = selectBar.ActualHeight;
double thumbHotspotY = thumbSelectBar.ActualHeight / 2.0;
double bottom = Canvas.GetBottom(thumbSelectBar);
bottom -= evt.VerticalChange;
if (bottom < -thumbHotspotY)
bottom = -thumbHotspotY;
if (rangeOnImage - thumbHotspotY < bottom)
bottom = rangeOnImage - thumbHotspotY;
Canvas.SetBottom(thumbSelectBar, bottom);
Lightness = (int)((bottom + thumbHotspotY) * _BitsPerPixel_1 / rangeOnImage);
evt.Handled = true;
}
private void UpdateSelectArea()
{
_selectAreaBmp.Lock();
int width = HueCircumference;
int height = BitsPerPixel;
Int32Rect dirtyRect = new Int32Rect(0, 0, width, height);
int lightness = _lightness;
unsafe
{
int stride = _selectAreaBmp.BackBufferStride / sizeof(Pixel);
Pixel* p = ((Pixel*)_selectAreaBmp.BackBuffer) + stride * _BitsPerPixel_1;
for (int saturation = 0; saturation < height; saturation++)
{
for (int hue = 0; hue < width; hue++)
{
Color c = FromHsl(hue, saturation, lightness);
p[hue].A = 0xff;
p[hue].R = c.R;
p[hue].G = c.G;
p[hue].B = c.B;
}
p -= stride;
}
}
_selectAreaBmp.AddDirtyRect(dirtyRect);
_selectAreaBmp.Unlock();
}
private void UpdateSelectBar()
{
_selectBarBmp.Lock();
int height = _selectBarBmp.PixelHeight;
Int32Rect dirtyRect = new Int32Rect(0, 0, 1, height);
int hue = _hue;
int saturation = _saturation;
unsafe
{
int stride = _selectBarBmp.BackBufferStride / sizeof(Pixel);
Pixel* p = (Pixel*)_selectBarBmp.BackBuffer + stride * _BitsPerPixel_1;
for (int lightness = 0; lightness < BitsPerPixel; lightness++)
{
Color c = FromHsl(hue, saturation, lightness);
p->A = 0xff;
p->R = c.R;
p->G = c.G;
p->B = c.B;
p -= stride;
}
}
_selectBarBmp.AddDirtyRect(dirtyRect);
_selectBarBmp.Unlock();
}
public void ToHsl(byte r, byte g, byte b, out int hue, out int saturation, out int lightness)
{
double max = Math.Max(Math.Max(r, g), b) / (double)_BitsPerPixel_1;
double min = Math.Min(Math.Min(r, g), b) / (double)_BitsPerPixel_1;
double c = max - min;
int denominator = 2 * r - g - b;
double h =
(denominator != 0
? Math.Atan2(Math.Sqrt(3.0) * (g - b), denominator) * HueCircumference / (Math.PI * 2.0)
: 0);
double l = (max + min) / 2.0;
double s = (c == 0 ? 0 : c / (1.0 - Math.Abs(2.0 * l - 1.0)));
hue = (int)h;
if (hue < 0)
hue += HueCircumference;
saturation = (int)(s * _BitsPerPixel_1);
lightness = (int)(l * _BitsPerPixel_1);
}
public Color FromHsl(int hue, int saturation, int lightness)
{
/*
Debug.Assert(0 <= hue && hue < HueCircumference);
Debug.Assert(0 <= saturation && saturation < BitsPerPixel);
Debug.Assert(0 <= lightness && lightness < BitsPerPixel);
*/
while (hue < 0)
hue += HueCircumference;
while (HueCircumference <= hue)
hue -= HueCircumference;
if (saturation < 0)
saturation = 0;
if (BitsPerPixel <= saturation)
saturation = _BitsPerPixel_1;
if (lightness < 0)
lightness = 0;
if (BitsPerPixel <= lightness)
lightness = _BitsPerPixel_1;
Color res = new Color();
res.A = 255;
double sn = (double)saturation / (double)_BitsPerPixel_1;
double ln = (double)lightness / (double)_BitsPerPixel_1;
double c = (1.0 - Math.Abs(2.0 * ln - 1.0)) * sn;
double regionH = (double)(hue * 6) / (double)HueCircumference;
double x = c * (1.0 - Math.Abs(regionH % 2.0 - 1.0));
double m = ln - c / 2.0;
byte byteC = (byte)((c + m) * _BitsPerPixel_1);
byte byteX = (byte)((x + m) * _BitsPerPixel_1);
byte byteM = (byte)(m * _BitsPerPixel_1);
int region = (int)regionH;
switch (region)
{
case 0: res.R = byteC; res.G = byteX; res.B = byteM; break;
case 1: res.R = byteX; res.G = byteC; res.B = byteM; break;
case 2: res.R = byteM; res.G = byteC; res.B = byteX; break;
case 3: res.R = byteM; res.G = byteX; res.B = byteC; break;
case 4: res.R = byteX; res.G = byteM; res.B = byteC; break;
case 5: res.R = byteC; res.G = byteM; res.B = byteX; break;
default:
throw new ArgumentException("Hue value is illegal.");
}
return res;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
UpdateThumbSelectBarFromLightness(); // Load前にも呼ばれるが、そのときはコントロールのサイズが確定していないため位置が不正になる。
}
private void OnUpdateFromTextualRepresentationCommand(object sender, ExecutedRoutedEventArgs e)
{
UpdateFromTextualRepresentation();
}
private void OnUpdateFromTextualRepresentationButtonClick(object sender, RoutedEventArgs e)
{
UpdateFromTextualRepresentation();
}
private void OnTextBoxTextualRepresentationGotFocus(object sender, RoutedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(() => textBoxTextualRepresentation.SelectAll()));
}
private void OnMouseMoveInSelectArea(object sender, MouseEventArgs evt)
{
UpdateHsFromMouseCursor(evt);
}
private void OnMouseDownInSelectArea(object sender, MouseButtonEventArgs evt)
{
UpdateHsFromMouseCursor(evt);
}
private void OnMouseMoveInSelectBar(object sender, MouseEventArgs evt)
{
UpdateLightnessFromMouseCursor(evt);
}
private void OnMouseDownInSelectBar(object sender, MouseButtonEventArgs evt)
{
selectBar.CaptureMouse();
UpdateLightnessFromMouseCursor(evt);
}
private void OnMouseUpInSelectBar(object sender, MouseButtonEventArgs evt)
{
selectBar.ReleaseMouseCapture();
}
private void OnSelectBarThumbDragDelta(object sender, DragDeltaEventArgs evt)
{
UpdateThumbSelectBarFromDragDelta(evt);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
WriteableBitmapに書き込むときに使っているPixel構造体はこんな感じ。
using System.Runtime.InteropServices;
namespace UserControls
{
[StructLayout(LayoutKind.Explicit)]
struct Pixel
{
[FieldOffset(0)]
public System.UInt32 C;
[FieldOffset(0)]
public byte B;
[FieldOffset(1)]
public byte G;
[FieldOffset(2)]
public byte R;
[FieldOffset(3)]
public byte A;
}
}
C言語で言うところの共用体のような使い方ですね。
あと、前に作ったNumericBoxコントロールが必要です。 NumericBox.xamlとNumericBox.xaml.csとNumericBoxValidationRule.csをコピーしてください。
実は、NumericBoxはこの色選択コントロールの部品に必要だから作ったんですよね。 やっと目的達成ですよ。 「ColorDialogの代わりをチャッチャと作ろう」ってだけだったはずなのに、こんなにダラダラ長いコードになってしまった。
以下、使用例です。 コントロールとして使う場合はこんな感じ。 MainWindow.xamlは、
<Window x:Class="ColorSelectControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:appCtrls="clr-namespace:UserControls"
Title="パネルのサンプル"
Width="460"
Height="524"
>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border Grid.ColumnSpan="3" BorderBrush="Gray" BorderThickness="2" CornerRadius="4">
<appCtrls:ColorSelectControl
x:Name="colorSelectControl"
Margin="4"
RgbColor="{Binding ElementName=brush1, Path=Color}"
RgbColorChanged="OnSelectedColorChanged"
/>
</Border>
<Label Grid.Column="0" Grid.Row="1">バインド</Label>
<Rectangle Grid.Column="1" Grid.Row="1" Width="48" Height="24" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush x:Name="brush1" Color="Green"/>
</Rectangle.Fill>
</Rectangle>
<Label Grid.Column="0" Grid.Row="2">イベント</Label>
<Rectangle Grid.Column="1" Grid.Row="2" Width="48" Height="24" HorizontalAlignment="Left">
<Rectangle.Fill>
<SolidColorBrush x:Name="brush2"/>
</Rectangle.Fill>
</Rectangle>
<Button
Grid.Column="2"
Grid.Row="2"
Content="ダイアログで選択"
Click="OnShowDialogButtonClick"
/>
</Grid>
</Window>
wpfなのでダラダラ長いけど、ColorSelectControl要素の部分は短めで済んでいます。
コードの中身は、1つ目のRectangleは色選択コントロールの選択色とバインドしてます。 2つ目のRectangleはRgbColorChangedイベントで変えます。
MainWindow.xaml.csは、
using System.Windows;
using System.Windows.Media;
namespace ColorSelectControlTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnSelectedColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> evt)
{
brush2.Color = evt.NewValue;
}
private void OnShowDialogButtonClick(object sender, RoutedEventArgs e)
{
ColorSelectWindow colorSelectWindow = new ColorSelectWindow();
colorSelectWindow.Owner = this;
colorSelectWindow.RgbColor = brush1.Color;
if (colorSelectWindow.ShowDialog() ?? false)
{
brush1.Color = colorSelectWindow.RgbColor;
}
}
}
}
OnShowDialogButtonClickには色選択ダイアログを呼び出すコードが書かれています。 色選択ダイアログは、Windowに色選択コントロールを載せて作りました。 ColorSelectWindow.xamlは、
<Window x:Class="ColorSelectControlTest.ColorSelectWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:appCtrls="clr-namespace:UserControls"
Title="ダイアログのサンプル"
Width="480"
Height="520"
ShowInTaskbar="False"
WindowStyle="ToolWindow"
>
<DockPanel Margin="4">
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
<Button IsDefault="True" Width="40" Margin="10,0" Click="OnOkButtonClick">OK</Button>
<Button IsCancel="True">Cancel</Button>
</StackPanel>
<appCtrls:ColorSelectControl x:Name="colorSelectControl"/>
</DockPanel>
</Window>
ColorSelectWindow.xaml.csは、
using System.Windows;
using System.Windows.Media;
namespace ColorSelectControlTest
{
public partial class ColorSelectWindow : Window
{
public Color RgbColor
{
get
{
return colorSelectControl.RgbColor;
}
set
{
colorSelectControl.RgbColor = value;
}
}
public ColorSelectWindow()
{
InitializeComponent();
}
private void OnOkButtonClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}