[C#/WPF] ColorSlider

C#/WPF 2023. 3. 30. 23:35
728x90
728x170

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>
        /// 선택된 색상 Property
        /// </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;

            // Slider 이동 범위
            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)
                    {
                        // work out the track position based on the control's width
                        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)
                        {
                            //we cannot get a better match, break now
                            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

 

728x90
그리드형

'C# > WPF' 카테고리의 다른 글

[WPF] PointConverter  (0) 2023.04.18
[WPF] Freezable  (0) 2023.04.14
[C#/WPF] WPF 로 만든 Scratch 프로그램  (0) 2023.03.28
[WPF]두 컨트롤의 속성 바인딩  (0) 2023.03.17
[WPF] CompositeCollection 사용하여 채팅 방 구현하는 방법  (1) 2023.03.17
Posted by kjun
,