[C#프로그래밍] [WPF] PC 카카오톡 만들기 #10 - ComboBox 사용자 정의 컨트롤(UserControl) 만들기

728x90
https://www.youtube.com/watch?v=9bzJhogdxVg&list=PLlrfTSXS0LLKHfOfwM31jJw3SHuDnkF49&index=11

 

📌 ComboBox Control  만들기

- Controls에 사용자 정의 컨트롤 ComboBoxControl 파일 생성

- Commons 파일 생성 및 클래스 ComboBoxColorManager 파일 생성 

- Extensions파일에 클래스 ComboBoxExtension 파일 생성 

 

- ComboBoxContorl.xaml

<UserControl x:Class="wpfLib.Controls.ComboBoxControl"
             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:local="clr-namespace:wpfLib.Controls"
             xmlns:commons="clr-namespace:wpfLib.Commons" 
             xmlns:converters="clr-namespace:wpfLib.Converters"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             x:Name="root">
    <UserControl.Resources>
        <converters:ValidatingBorderBrushConverter x:Key="ValidatingBorderBrushConverter"/>
        <converters:ValidatingBorderThicknessConverter x:Key="ValidatingBorderThicknessConverter"/>

        <Style x:Key="BaseControl" TargetType="{x:Type FrameworkElement}">
            <Setter Property="Control.FontSize" Value="{Binding FontSize, ElementName=root}"/>
            <Setter Property="Control.FontFamily" Value="{Binding FontFamily, ElementName=root}"/>
        </Style>

        <!--워터마크가 될 부분-->
        <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource BaseControl}">
            <Setter Property="Text" Value="{Binding WaterMarkText, ElementName=root}"/>
            <Setter Property="Foreground" Value="{Binding WaterMarkTextColor, ElementName=root}"/>
            <Setter Property="Padding" Value="5 0 0 0"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Visibility" Value="Collapsed"/>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Text, ElementName=cmb}" Value="">
                    <Setter Property="Visibility" Value="Visible"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>

        <Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource BaseControl}">
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="IsEditable" Value="{Binding IsEditable, ElementName=root}"/>
            <Setter Property="Text" Value="{Binding Text, ElementName=root, UpdateSourceTrigger=PropertyChanged}"/>
            <Setter Property="SelectedItem" Value="{Binding SelectedItem, ElementName=root}"/>
            <Setter Property="SelectedIndex" Value="{Binding SelectedIndex, ElementName=root}"/>
            <Setter Property="ItemsSource" Value="{Binding ItemsSource, ElementName=root}"/>
            <Setter Property="ItemContainerStyle" Value="{Binding ItemContainerStyle, ElementName=root}"/>
            <Setter Property="commons:ComboBoxColorManager.Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="0"/>
        </Style>

        <Style TargetType="{x:Type Border}">
            <Setter Property="BorderThickness">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource ValidatingBorderThicknessConverter}">
                        <Binding Path="Validating" ElementName="root"/>
                        <Binding Path="BorderThickness" ElementName="root"/>
                    </MultiBinding>
                </Setter.Value>
            </Setter>

            <Setter Property="BorderBrush">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource ValidatingBorderBrushConverter}">
                        <Binding Path="Validating" ElementName="root"/>
                        <Binding Path="BorderBrush" ElementName="root"/>
                    </MultiBinding>
                </Setter.Value>
            </Setter>
        </Style>


    </UserControl.Resources>
    <Grid>
        <TextBlock x:Name="txt"/>
        <Border>
            <ComboBox x:Name="cmb"/>
        </Border>
    </Grid>
</UserControl>

=> 계속 예외처리 오류가 떠서 한참을 고생했는데 맨 위에 x:name="root"를 안해서 그랬었다 ㅡㅡ

 

- ComboBoxControl.xaml.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace wpfLib.Controls
{
    /// <summary>
    /// ComboBoxControl.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class ComboBoxControl : UserControl
    {
        public ComboBoxControl()
        {
            InitializeComponent();
        }

        #region Static Properties
        public static readonly DependencyProperty IsEditableProperty =
            DependencyProperty.Register("IsEditable", typeof(bool), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static readonly DependencyProperty ValidatingProperty =
            DependencyProperty.Register("Validating", typeof(bool), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static new readonly DependencyProperty BorderBrushProperty =
            DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(ComboBoxControl), new UIPropertyMetadata(Brushes.SkyBlue));
        public static new readonly DependencyProperty BorderThicknessProperty =
            DependencyProperty.Register("BorderThickness", typeof(Thickness), typeof(ComboBoxControl), new UIPropertyMetadata(new Thickness(1)));
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(ComboBoxControl), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public static readonly DependencyProperty WaterMarkTextProperty =
          DependencyProperty.Register("WaterMarkText", typeof(string), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static readonly DependencyProperty WaterMarkTextColorProperty =
           DependencyProperty.Register("WaterMarkTextColor", typeof(Brush), typeof(ComboBoxControl), new UIPropertyMetadata(Brushes.Gray));
        public static readonly DependencyProperty ItemContainerStyleProperty =
           DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static readonly DependencyProperty ItemsSourceProperty =
          DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static readonly DependencyProperty SelectedItemProperty =
           DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxControl), new PropertyMetadata(null));
        public static readonly DependencyProperty SelectedIndexProperty =
          DependencyProperty.Register("SelectedIndex", typeof(int), typeof(ComboBoxControl), new PropertyMetadata(null));
        #endregion

        #region Public Properties
        public bool IsEditable
        {
            get { return (bool)GetValue(IsEditableProperty); }
            set { SetValue(IsEditableProperty, value); }
        }

        public bool Validating
        {
            get { return (bool)GetValue(ValidatingProperty); }
            set { SetValue(ValidatingProperty, value); }
        }

        public new Brush BorderBrush
        {
            get { return (Brush)GetValue(BorderBrushProperty); }
            set { SetValue(BorderBrushProperty, value); }
        }

        public new Thickness BorderThickness
        {
            get { return (Thickness)GetValue(BorderThicknessProperty); }
            set { SetValue(BorderThicknessProperty, value); }
        }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public string WaterMarkText
        {
            get { return (string)GetValue(WaterMarkTextProperty); }
            set { SetValue(WaterMarkTextProperty, value); }
        }

        public Brush WaterMarkTextColor
        {
            get { return (Brush)GetValue(WaterMarkTextColorProperty); }
            set { SetValue(WaterMarkTextColorProperty, value); }
        }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public Style ItemContainerStyle
        {
            get { return (Style)GetValue(ItemContainerStyleProperty); }
            set { SetValue(ItemContainerStyleProperty, value); }
        }

        public object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }

        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set { SetValue(SelectedIndexProperty, value); }
        }
        #endregion
    }
}

 

 

- ComboBoxColorManager.cs

namespace wpfLib.Commons
{
    public class ComboBoxColorManager
    {
        // 필드변수
        private static Brush? _newBackground;
        private static Brush? _newBorderBrush;

        // 프로퍼티 기본값은 null로 지정 
        public static readonly DependencyProperty BackgroundProperty =
            DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(ComboBoxColorManager), new UIPropertyMetadata(null, BackgroundChanged));


        public static readonly DependencyProperty BorderBrushProperty =
           DependencyProperty.RegisterAttached("BorderBrush", typeof(Brush), typeof(ComboBoxColorManager), new UIPropertyMetadata(null, BorderBrushChanged));


        // 메서드
        private static void AddEvents(DependencyObject d, DependencyPropertyChangedEventArgs e, Action<Brush> brushCallBack)
        {
            ComboBox? cmb = d as ComboBox;
            if (cmb == null) return;

            if (e.NewValue != e.OldValue)
            {
                brushCallBack?.Invoke((Brush)e.NewValue);

                cmb.Loaded -= Cmb_Loaded;
                cmb.Unloaded -= Cmb_Unloaded;
                cmb.Loaded += Cmb_Loaded;
                cmb.Unloaded += Cmb_Unloaded;
            }
        }

        public static Brush GetBackground(DependencyObject obj)
        {
            return (Brush)obj.GetValue(BackgroundProperty);
        }

        public static void SetBackground(DependencyObject obj, Brush value)
        {
            obj.SetValue(BackgroundProperty, value);
        }

        public static Brush GetBorderBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(BorderBrushProperty);
        }

        public static void SetBorderBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(BorderBrushProperty, value);
        }

        // 이벤트추가
        private static void BackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //대리자를 이용하여 할당 
            AddEvents(d, e, brush => _newBackground = brush);
        }

        private static void BorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AddEvents(d, e, brush => _newBorderBrush = brush);
        }

        private static void Cmb_Unloaded(object sender, RoutedEventArgs e)
        {
            //unload 시 메모리에서 지워주기 
            ComboBox? cmb = (ComboBox)sender;
            cmb.Loaded -= Cmb_Loaded;
            cmb.Unloaded -= Cmb_Unloaded;
            _newBackground = null;
            _newBorderBrush = null;
        }

        private static void Cmb_Loaded(object sender, RoutedEventArgs e)
        {
            ComboBox? cmb = (ComboBox)sender;
            if (_newBackground != null)
            {
                cmb.SetBackground(_newBackground);
            }
            if (_newBorderBrush != null)
            {
                cmb.SetBorderBrush(_newBorderBrush);
            }
        }
    }
}

 

- ComboBoxExtensions.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Media;

namespace wpfLib.Extensions
{
    public static class ComboBoxExtensions
    {
        public static Border? GetBorder(this ComboBox comboBox)
        {
            ToggleButton? toggleButton = (ToggleButton?)comboBox.Template?.FindName("toggleButton", comboBox);
            return (Border?)toggleButton?.Template?.FindName("templateRoot", toggleButton);
        }

        public static void SetBorderBrush(this ComboBox comboBox, Brush brush)
        {
            Border? border = comboBox.GetBorder();
            if (border != null)
                border.BorderBrush = brush;
        }

        public static void SetBackground(this ComboBox comboBox, Brush brush)
        {
            Border? border = comboBox.GetBorder();
            if (border != null)
                border.Background = brush;

            TextBox? textbox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
            if (textbox != null)
            {
                Border? parent = (Border)textbox.Parent;
                parent.Background = brush;
            }
        }
    }
}

 

- LoginControl.xaml

            <controls:ComboBoxControl Background="White" 
                                     Height="35" 
                                     WaterMarkText="이메일을 입력하세요"
                                     WaterMarkTextColor="blue"
                                     IsEditable="True"/>

 

📌 ComboBox ItemsSource Binding하기

- LoginControl.xaml

            <controls:ComboBoxControl Background="White" 
                                     Height="35" 
                                     WaterMarkText="이메일을 입력하세요"
                                     WaterMarkTextColor="blue"
                                     IsEditable="True"
                                     ItemsSource="{Binding Emails}"/>

 

- LoginControlViewModel.cs

namespace seungjjangTalk.ViewModels
{
    [ObservableObject]
    public partial class LoginControlViewModel
    {
        [ObservableProperty]
        private ObservableCollection<string> _emails;

        public LoginControlViewModel()
        {
            Emails = new ObservableCollection<string>()
            {
                "test1@test.com",
                "test2@test.com"
            };
        }
    }
}

 

728x90