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; } } }