KeiStory

WPF ColorSlider 만들기

 

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]

ColorSlider.zip
0.08MB

 

반응형

'코딩 > WPF' 카테고리의 다른 글

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

공유하기

facebook twitter kakaoTalk kakaostory naver band