WPF를 이용해 아래와 같은 ColorSlider 를 만드는 방법입니다.
ColorSliderControl.cs
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ColorSlider
{
public class ColorSliderControl : Slider
{
// 색상 그라데이션을 저장하는 비트맵 소스
private BitmapSource colorGradient;
// 스레드 동기화를 위한 잠금 객체
private object updateLock = new object();
// 값 업데이트 중인지 나타내는 플래그
private bool isValueUpdating = false;
// 첫 렌더링인지 나타내는 플래그
private bool isFirstTime = true;
protected Rect VisualBounds
{
get { return VisualTreeHelper.GetDescendantBounds(this); }
}
/// <summary>
/// 선택된 색상을 위한 의존성 속성 정의
/// </summary>
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register("SelectedColor", typeof(Color), typeof(ColorSliderControl), new UIPropertyMetadata(Colors.LightBlue, new PropertyChangedCallback(SelectedColourChangedCallBack)));
/// <summary>
/// 선택된 색상
/// </summary>
public Color SelectedColor
{
get { return (Color)this.GetValue(SelectedColorProperty); }
set { this.SetValue(SelectedColorProperty, value); }
}
// Constructor
#region ColorSliderControl
static ColorSliderControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorSliderControl), new FrameworkPropertyMetadata(typeof(ColorSliderControl)));
}
public ColorSliderControl()
{
// 슬라이더 값 범위 설정
this.Minimum = 0;
this.Maximum = 1000;
// 슬라이더 이동 단위 설정
this.LargeChange = 50;
this.SmallChange = 5;
// 배경에 색상 그라데이션 설정
this.Background = new LinearGradientBrush(new GradientStopCollection() {
new GradientStop(Colors.Black, 0.0),
new GradientStop(Colors.Red, 0.1),
new GradientStop(Colors.Yellow, 0.25),
new GradientStop(Colors.Lime, 0.4),
new GradientStop(Colors.Aqua, 0.55),
new GradientStop(Colors.Blue, 0.7),
new GradientStop(Colors.Fuchsia, 0.9),
new GradientStop(Colors.White, 0.98),
new GradientStop(Colors.White, 1),
});
}
#endregion
// Events
#region OnRender
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (this.isFirstTime)
{
this.CacheBitmap();
this.SetColor(this.SelectedColor);
this.isFirstTime = false;
}
}
#endregion
#region OnValueChanged
protected override void OnValueChanged(double oldValue, double newValue)
{
// Slider 변경시 SelectedColor 에 색상 설정
if (Monitor.TryEnter(this.updateLock, 0) && !this.isValueUpdating)
{
try
{
this.isValueUpdating = true;
double width = this.VisualBounds.Width;
if (width != double.NegativeInfinity)
{
// 컨트롤 너비에 기반한 트랙 위치 계산
int position = (int)(((newValue - base.Minimum) / (base.Maximum - base.Minimum)) * width);
this.SelectedColor = GetColor(this.colorGradient, position);
}
}
finally
{
isValueUpdating = false;
Monitor.Exit(this.updateLock);
}
}
base.OnValueChanged(oldValue, newValue);
}
#endregion
// Methods
#region SetColor
private void SetColor(Color color)
{
if (Monitor.TryEnter(this.updateLock, 0) && !this.isValueUpdating)
{
try
{
Rect bounds = this.VisualBounds;
double currentDistance = int.MaxValue;
int currentPosition = -1;
// 가장 가까운 색상 위치 찾기
for (int i = 0; i < bounds.Width; i++)
{
Color c = this.GetColor(this.colorGradient, i);
double distance = c.Distance(color);
if (distance == 0.0)
{
// 완벽한 매치를 찾았으므로 루프 종료
currentPosition = i;
break;
}
if (distance < currentDistance)
{
currentDistance = distance;
currentPosition = i;
}
}
// 슬라이더 값 설정
base.Value = (currentPosition / bounds.Width) * (base.Maximum - base.Minimum);
}
finally
{
Monitor.Exit(updateLock);
}
}
}
#endregion
#region GetColor
private Color GetColor(BitmapSource bitmap, int position)
{
if (position >= bitmap.Width - 1)
{
position = (int)bitmap.Width - 2;
}
// 비트맵에서 특정 위치의 픽셀 추출
CroppedBitmap cb = new CroppedBitmap(bitmap, new Int32Rect(position, (int)this.VisualBounds.Height / 2, 1, 1));
byte[] tricolour = new byte[4];
cb.CopyPixels(tricolour, 4, 0);
Color c = Color.FromRgb(tricolour[2], tricolour[1], tricolour[0]);
return c;
}
#endregion
#region CacheBitmap
private void CacheBitmap()
{
Rect bounds = this.VisualBounds;
RenderTargetBitmap source = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(this);
dc.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
source.Render(dv);
this.colorGradient = source;
}
#endregion
#region SelectedColourChangedCallBack
private static void SelectedColourChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
ColorSliderControl colourSlider = (ColorSliderControl)property;
Color colour = (Color)args.NewValue;
colourSlider.SetColor(colour);
}
#endregion
}
}
ColorExtensions.cs
using System.Windows.Media;
namespace ColorSlider
{
public static class ColorExtensions
{
public static double Distance(this Color source, Color target)
{
System.Drawing.Color c1 = source.ToDrawingColor();
System.Drawing.Color c2 = target.ToDrawingColor();
double hue = c1.GetHue() - c2.GetHue();
double saturation = c1.GetSaturation() - c2.GetSaturation();
double brightness = c1.GetBrightness() - c2.GetBrightness();
return (hue * hue) + (saturation * saturation) + (brightness * brightness);
}
public static System.Drawing.Color ToDrawingColor(this Color source)
{
return System.Drawing.Color.FromArgb((int)source.R, (int)source.G, (int)source.B);
}
}
}
MainWindow.xaml
<Window
x:Class="ColorSlider.MainWindow"
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:local="clr-namespace:ColorSlider"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="ColorSlider"
Width="600"
Height="350"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="ColorPickerButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Focusable" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ColorSliderControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ColorSliderControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="25" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Height="{TemplateBinding Slider.Height}"
MinHeight="25"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border Margin="5,5,0.5,5" Background="{TemplateBinding Background}" />
</Border>
<Track Name="PART_Track" Grid.Row="0">
<Track.DecreaseRepeatButton>
<RepeatButton Command="Slider.DecreaseLarge" Style="{StaticResource ColorPickerButtonStyle}" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb>
<Thumb.Template>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Image Grid.Row="0" Width="10">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Geometry="M 30 50 L 50 0 10 0 Z">
<GeometryDrawing.Pen>
<Pen
Brush="Crimson"
LineJoin="Round"
Thickness="25" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Image Grid.Row="2" Width="10">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Geometry="M 25 0 L 10 40 40 40 Z">
<GeometryDrawing.Pen>
<Pen
Brush="Crimson"
LineJoin="Round"
Thickness="25" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Command="Slider.IncreaseLarge" Style="{StaticResource ColorPickerButtonStyle}" />
</Track.IncreaseRepeatButton>
</Track>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<local:ColorSliderControl
x:Name="slider"
Height="30"
Margin="16,22,16,0"
VerticalAlignment="Top"
SelectedColor="Yellow" />
<Rectangle
Height="55"
Margin="80,75,76,0"
VerticalAlignment="Top">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding ElementName=slider, Path=SelectedColor}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
결과
[Source]
WPF PriorityBinding (0) | 2024.10.14 |
---|---|
WPF Popup Drag Move 처리하기 (0) | 2024.10.07 |
WPF Parent Binding (0) | 2024.10.06 |
WPF Font 적용하기 (0) | 2024.10.01 |
WPF Freezable (0) | 2024.09.27 |