첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
------------------------------------------------------------------------------------------------------------------------------------------------------
728x90
728x170

TestSolution.zip
다운로드

[TestLibrary 프로젝트]

▶ DependencyObjectExtensionMethods.cs

using System.Windows;
using System.Windows.Media;

namespace TestLibrary
{
    /// <summary>
    /// 의존 객체 확장 메소드
    /// </summary>
    public static class DependencyObjectExtensionMethods
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 부모 찾기 - FindParent(dependencyObject)

        /// <summary>
        /// 부모 찾기
        /// </summary>
        /// <typeparam name="T">타입</typeparam>
        /// <param name="dependencyObject">의존 객체</param>
        /// <returns>부모</returns>
        public static T FindParent<T>(this DependencyObject dependencyObject) where T : class
        {
            T result = null;

            DependencyObject parentDependencyObject = VisualTreeHelper.GetParent(dependencyObject);

            if(parentDependencyObject is T)
            {
                result = parentDependencyObject as T;
            }
            else if(parentDependencyObject != null)
            {
                result = FindParent<T>(parentDependencyObject);
            }

            return result;
        }

        #endregion
    }
}

 

▶ ValueChangeHelper.cs

using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace TestLibrary
{
    /// <summary>
    /// 값 변경시 헬퍼
    /// </summary>
    internal class ValueChangedHelper : DependencyObject
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Class
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 블랭크 멀티 값 변환자 - BlankMultiValueConverter

        /// <summary>
        /// 블랭크 멀티 값 변환자
        /// </summary>
        private class BlankMultiValueConverter : IMultiValueConverter
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////// Method
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region 변환하기 - Convert(sourceValueArray, targetType, parameter, cultureInfo)

            /// <summary>
            /// 변환하기
            /// </summary>
            /// <param name="sourceValueArray">소스 값 배열</param>
            /// <param name="targetType">타겟 타입</param>
            /// <param name="parameter">매개 변수</param>
            /// <param name="cultureInfo">문화 정보</param>
            /// <returns>변환 값</returns>
            public object Convert(object[] sourceValueArray, Type targetType, object parameter, CultureInfo cultureInfo)
            {
                return new object();
            }

            #endregion
            #region 역변환하기 - ConvertBack(sourceValue, targetTypeArray, parameter, cultureInfo)

            /// <summary>
            /// 역변환하기
            /// </summary>
            /// <param name="sourceValue">소스 값</param>
            /// <param name="targetTypeArray">타겟 타입 배열</param>
            /// <param name="parameter">매개 변수</param>
            /// <param name="cultureInfo">문화 정보</param>
            /// <returns>역변환 값</returns>
            public object[] ConvertBack(object sourceValue, Type[] targetTypeArray, object parameter, CultureInfo cultureInfo)
            {
                throw new InvalidOperationException();
            }

            #endregion
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Event
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 값 변경시 - ValueChanged

        /// <summary>
        /// 값 변경시
        /// </summary>
        public event EventHandler ValueChanged;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 값 의존 속성 - ValueProperty

        /// <summary>
        /// 값 의존 속성
        /// </summary>
        private static readonly DependencyProperty ValueProperty = DependencyProperty.Register
        (
            "Value",
            typeof(object),
            typeof(ValueChangedHelper),
            new UIPropertyMetadata
            (
                null,
                valuePropertyChangedCallback
            )
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 값 - Value

        /// <summary>
        /// 값
        /// </summary>
        private object Value
        {
            get
            {
                return (object)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - ValueChangeHelper(valueChangedAction)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="valueChangedAction">값 변경시 액션</param>
        public ValueChangedHelper(Action valueChangedAction)
        {
            if(valueChangedAction == null)
            {
                throw new ArgumentNullException("changeCallbackAction");
            }

            ValueChanged += (sender, e) => valueChangedAction();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 값 속성 변경시 콜백 처리하기 - valuePropertyChangedCallback(d, e)

        /// <summary>
        /// 값 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void valuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ValueChangedHelper)d).FireValueChangedEvent();
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 값 소스 갱신하기 - UpdateValueSource(sourceItem, bindingPath)

        /// <summary>
        /// 값 소스 갱신하기
        /// </summary>
        /// <param name="sourceItem">소스 항목</param>
        /// <param name="bindingPath">바인딩 경로</param>
        public void UpdateValueSource(object sourceItem, string bindingPath)
        {
            BindingBase bindingBase = null;

            if(sourceItem != null && bindingPath != null)
            {
                bindingBase = new Binding(bindingPath) { Source = sourceItem };
            }

            UpdateBinding(bindingBase);
        }

        #endregion
        #region 값 소스 갱신하기 - UpdateValueSource(sourceItems, bindingPath)

        /// <summary>
        /// 값 소스 갱신하기
        /// </summary>
        /// <param name="sourceItems">소스 항목들</param>
        /// <param name="bindingPath">바인딩 경로</param>
        public void UpdateValueSource(IEnumerable sourceItems, string bindingPath)
        {
            BindingBase bindingBase = null;

            if(sourceItems != null && bindingPath != null)
            {
                MultiBinding multiBinding = new MultiBinding();

                multiBinding.Converter = new BlankMultiValueConverter();

                foreach(object item in sourceItems)
                {
                    multiBinding.Bindings.Add(new Binding(bindingPath) { Source = item });
                }

                bindingBase = multiBinding;
            }

            UpdateBinding(bindingBase);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 바인딩 지우기 - ClearBinding()

        /// <summary>
        /// 바인딩 지우기
        /// </summary>
        private void ClearBinding()
        {
            BindingOperations.ClearBinding(this, ValueChangedHelper.ValueProperty);
        }

        #endregion
        #region 바인딩 갱신하기 - UpdateBinding(bindingBase)

        /// <summary>
        /// 바인딩 갱신하기
        /// </summary>
        /// <param name="bindingBase">바인딩 베이스</param>
        private void UpdateBinding(BindingBase bindingBase)
        {
            if(bindingBase != null)
            {
                BindingOperations.SetBinding(this, ValueChangedHelper.ValueProperty, bindingBase);
            }
            else
            {
                ClearBinding();
            }
        }

        #endregion

        #region 값 변경시 이벤트 발생시키기 - FireValueChangedEvent()

        /// <summary>
        /// 값 변경시 이벤트 발생시키기
        /// </summary>
        private void FireValueChangedEvent()
        {
            if(ValueChanged != null)
            {
                ValueChanged(this, EventArgs.Empty);
            }
        }

        #endregion
    }
}

 

▶ ItemSelectionChangedEventArgs.cs

using System.Windows;

namespace TestLibrary
{
    /// <summary>
    /// 항목 선택 변경시 이벤트 인자
    /// </summary>
    public class ItemSelectionChangedEventArgs : RoutedEventArgs
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 선택 여부 - IsSelected

        /// <summary>
        /// 선택 여부
        /// </summary>
        public bool IsSelected { get; private set; }

        #endregion
        #region 항목 - Item

        /// <summary>
        /// 항목
        /// </summary>
        public object Item { get; private set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - ItemSelectionChangedEventArgs(routedEvent, source, item, isSelected)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="routedEvent">라우팅 이벤트</param>
        /// <param name="source">소스</param>
        /// <param name="item">항목</param>
        /// <param name="isSelected">선택 여부</param>
        public ItemSelectionChangedEventArgs(RoutedEvent routedEvent, object source, object item, bool isSelected) : base(routedEvent, source)
        {
            Item       = item;
            IsSelected = isSelected;
        }

        #endregion
    }
}

 

▶ ItemSelectionChangedEventHandler.cs

namespace TestLibrary
{
    /// <summary>
    /// 항목 선택 변경시 이벤트 핸들러
    /// </summary>
    /// <param name="sender">이벤트 발생자</param>
    /// <param name="e">이벤트 인자</param>
    public delegate void ItemSelectionChangedEventHandler(object sender, ItemSelectionChangedEventArgs e);
}

 

▶ SelectorItem.cs

using System.Windows;
using System.Windows.Controls;

namespace TestLibrary
{
    /// <summary>
    /// 선택자 항목
    /// </summary>
    public class SelectorItem : ContentControl
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 선택시 이벤트 - SelectedEvent

        /// <summary>
        /// 선택시 이벤트
        /// </summary>
        public static readonly RoutedEvent SelectedEvent = Selector.SelectedEvent.AddOwner(typeof(SelectorItem));

        #endregion
        #region 선택 해제시 이벤트 - UnselectedEvent

        /// <summary>
        /// 선택 해제시 이벤트
        /// </summary>
        public static readonly RoutedEvent UnselectedEvent = Selector.UnSelectedEvent.AddOwner(typeof(SelectorItem));

        #endregion

        #region 선택 여부 의존 속성 - IsSelectedProperty

        /// <summary>
        /// 선택 여부 의존 속성
        /// </summary>
        public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register
        (
            "IsSelected",
            typeof(bool),
            typeof(SelectorItem),
            new UIPropertyMetadata
            (
                false,
                isSelectedPropertyChangedCallback
            )
        );

        #endregion
        #region 드래그 여부 의존 속성 - IsDraggingProperty

        /// <summary>
        /// 드래그 여부 의존 속성
        /// </summary>
        public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register
        (
            "IsDragging",
            typeof(bool),
            typeof(SelectorItem),
            new UIPropertyMetadata
            (
                false,
                isDraggingPropertyChangedCallback
            )
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Internal

        #region 셀렉터 - Selector

        /// <summary>
        /// 셀렉터
        /// </summary>
        internal Selector Selector
        {
            get
            {
                return ItemsControl.ItemsControlFromItemContainer(this) as Selector;
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 선택 여부 - IsSelected

        /// <summary>
        /// 선택 여부
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return (bool)GetValue(IsSelectedProperty);
            }
            set
            {
                SetValue(IsSelectedProperty, value);
            }
        }

        #endregion
        #region 드래그 여부 - IsDragging

        /// <summary>
        /// 드래그 여부
        /// </summary>
        public bool IsDragging
        {
            get
            {
                return (bool)GetValue(IsDraggingProperty);
            }
            set
            {
                SetValue(IsDraggingProperty, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - SelectorItem()

        /// <summary>
        /// 생성자
        /// </summary>
        static SelectorItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SelectorItem), new FrameworkPropertyMetadata(typeof(SelectorItem)));
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 선택 여부 속성 변경시 콜백 처리하기 - isSelectedPropertyChangedCallback(d, e)

        /// <summary>
        /// 선택 여부 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void isSelectedPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SelectorItem selectorItem = d as SelectorItem;

            if (selectorItem != null)
            {
                selectorItem.OnIsSelectedChanged((bool)e.OldValue, (bool)e.NewValue);
            }
        }

        #endregion
        #region 드래그 여부 속성 변경시 콜백 처리하기 - isDraggingPropertyChangedCallback(d, e)

        /// <summary>
        /// 드래그 여부 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void isDraggingPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SelectorItem selectorItem = d as SelectorItem;
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Protected

        #region 선택 여부 변경시 처리하기 - OnIsSelectedChanged(oldValue, newValue)

        /// <summary>
        /// 선택 여부 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnIsSelectedChanged(bool oldValue, bool newValue)
        {
            if(newValue)
            {
                RaiseEvent(new RoutedEventArgs(Selector.SelectedEvent, this));
            }
            else
            {
                RaiseEvent(new RoutedEventArgs(Selector.UnSelectedEvent, this));
            }
        }

        #endregion
    }
}

 

▶ Selector.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TestLibrary
{
    /// <summary>
    /// 셀렉터
    /// </summary>
    public class Selector : ItemsControl, IWeakEventListener
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Class
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 값 상등 비교자 - ValueEqualityComparer

        /// <summary>
        /// 값 상등 비교자
        /// </summary>
        private class ValueEqualityComparer : IEqualityComparer<string>
        {
            //////////////////////////////////////////////////////////////////////////////////////////////////// Method
            ////////////////////////////////////////////////////////////////////////////////////////// Public

            #region 상등 여부 구하기 - Equals(x, y)

            /// <summary>
            /// 상등 여부 구하기
            /// </summary>
            /// <param name="x">X 문자열</param>
            /// <param name="y">Y 문자열</param>
            /// <returns>상등 여부</returns>
            public bool Equals(string x, string y)
            {
                return string.Equals(x, y, StringComparison.InvariantCultureIgnoreCase);
            }

            #endregion
            #region 해시 코드 구하기 - GetHashCode(sourceObject)

            /// <summary>
            /// 해시 코드 구하기
            /// </summary>
            /// <param name="sourceObject">소스 객체</param>
            /// <returns>해시 코드</returns>
            public int GetHashCode(string sourceObject)
            {
                return 1;
            }

            #endregion
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Routed Event
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 선택시 이벤트 - SelectedEvent

        /// <summary>
        /// 선택시 이벤트
        /// </summary>
        public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent
        (
            "SelectedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(Selector)
        );

        #endregion
        #region 선택 해제시 이벤트 - UnSelectedEvent

        /// <summary>
        /// 선택 해제시 이벤트
        /// </summary>
        public static readonly RoutedEvent UnSelectedEvent = EventManager.RegisterRoutedEvent
        (
            "UnSelectedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(Selector)
        );

        #endregion
        #region 항목 선택 변경시 이벤트 - ItemSelectionChangedEvent

        /// <summary>
        /// 항목 선택 변경시 이벤트
        /// </summary>
        public static readonly RoutedEvent ItemSelectionChangedEvent = EventManager.RegisterRoutedEvent
        (
            "ItemSelectionChanged",
            RoutingStrategy.Bubble,
            typeof(ItemSelectionChangedEventHandler),
            typeof(Selector)
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 명령 의존 속성 - CommandProperty

        /// <summary>
        /// 명령 의존 속성
        /// </summary>
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register
        (
            "Command",
            typeof(ICommand),
            typeof(Selector),
            new PropertyMetadata((ICommand)null)
        );

        #endregion
        #region 구분자 의존 속성 - DelimiterProperty

        /// <summary>
        /// 구분자 의존 속성
        /// </summary>
        public static readonly DependencyProperty DelimiterProperty = DependencyProperty.Register
        (
            "Delimiter",
            typeof(string),
            typeof(Selector),
            new UIPropertyMetadata
            (
                ",",
                delimiterPropertyChangedCallback
            )
        );

        #endregion
        #region 선택 항목 의존 속성 - SelectedItemProperty

        /// <summary>
        /// 선택 항목 의존 속성
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register
        (
            "SelectedItem",
            typeof(object),
            typeof(Selector),
            new UIPropertyMetadata
            (
                null,
                selectedItemPropertyChangedCallback
            )
        );

        #endregion
        #region 선택 항목들 (오버라이드) 의존 속성 - SelectedItemsOverrideProperty

        /// <summary>
        /// 선택 항목들 (오버라이드) 의존 속성
        /// </summary>
        public static readonly DependencyProperty SelectedItemsOverrideProperty = DependencyProperty.Register
        (
            "SelectedItemsOverride",
            typeof(IList),
            typeof(Selector),
            new UIPropertyMetadata
            (
                null,
                selectedItemsOverridePropertyChangedCallback
            )
        );

        #endregion
        #region 선택 멤버 경로 의존 속성 - SelectedMemberPathProperty

        /// <summary>
        /// 선택 멤버 경로 의존 속성
        /// </summary>
        public static readonly DependencyProperty SelectedMemberPathProperty = DependencyProperty.Register
        (
            "SelectedMemberPath",
            typeof(string),
            typeof(Selector),
            new UIPropertyMetadata
            (
                null,
                selectedMemberPathPropertyChangedCallback
            )
        );

        #endregion
        #region 선택 값 의존 속성 - SelectedValueProperty

        /// <summary>
        /// 선택 값 의존 속성
        /// </summary>
        public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register
        (
            "SelectedValue",
            typeof(string),
            typeof(Selector),
            new FrameworkPropertyMetadata
            (
                null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                selectedValuePropertyChangedCallback
            )
        );

        #endregion
        #region 값 멤버 경로 의존 속성 - ValueMemberPathProperty

        /// <summary>
        /// 값 멤버 경로 의존 속성
        /// </summary>
        public static readonly DependencyProperty ValueMemberPathProperty = DependencyProperty.Register
        (
            "ValueMemberPath",
            typeof(string),
            typeof(Selector),
            new UIPropertyMetadata(valueMemberPathPropertyChangedCallback)
        );

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Protected

        #region Field

        /// <summary>
        /// 항목 선택 변경시 보류 여부
        /// </summary>
        protected bool surpressItemSelectionChanged;

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 선택 항목 변경시 무시 여부
        /// </summary>
        private bool ignoreSelectedItemChanged;

        /// <summary>
        /// 선택 값 변경시 무시 여부
        /// </summary>
        private bool ignoreSelectedValueChanged;

        /// <summary>
        /// 선택 항목들 컬렉션 변경시 무시 카운트
        /// </summary>
        private int countIgnoreSelectedItemsCollectionChanged;

        /// <summary>
        /// 선택 멤버 경로 값 변경시 무시 카운트
        /// </summary>
        private int countIgnoreSelectedMemberPathValuesChanged;

        /// <summary>
        /// 선택 항목들
        /// </summary>
        private IList selectedItems;

        /// <summary>
        /// 제거 항목들
        /// </summary>
        private IList removedItems = new ObservableCollection<object>();

        /// <summary>
        /// 선택 멤버 경로 값 변경시 헬퍼
        /// </summary>
        private ValueChangedHelper selectedMemberPathValueChangedHelper;

        /// <summary>
        /// 값 멤버 경로 값 변경시 헬퍼
        /// </summary>
        private ValueChangedHelper valueMemberPathValueChangedHelper;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Event
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 항목 선택 변경시 - ItemSelectionChanged

        /// <summary>
        /// 항목 선택 변경시
        /// </summary>
        public event ItemSelectionChangedEventHandler ItemSelectionChanged
        {
            add
            {
                AddHandler(ItemSelectionChangedEvent, value);
            }
            remove
            {
                RemoveHandler(ItemSelectionChangedEvent, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 명령 - Command

        /// <summary>
        /// 명령
        /// </summary>
        [Category("Selector")]
        [Description("명령을 가져오거나 설정합니다.")]
        [TypeConverter(typeof(CommandConverter))]
        public ICommand Command
        {
            get
            {
                return (ICommand)GetValue(CommandProperty);
            }
            set
            {
                SetValue(CommandProperty, value);
            }
        }

        #endregion
        #region 구분자 - Delimiter

        /// <summary>
        /// 구분자
        /// </summary>
        public string Delimiter
        {
            get
            {
                return (string)GetValue(DelimiterProperty);
            }
            set
            {
                SetValue(DelimiterProperty, value);
            }
        }

        #endregion
        #region 선택 항목 - SelectedItem

        /// <summary>
        /// 선택 항목
        /// </summary>
        public object SelectedItem
        {
            get
            {
                return (object)GetValue(SelectedItemProperty);
            }
            set
            {
                SetValue(SelectedItemProperty, value);
            }
        }

        #endregion
        #region 선택 항목들 - SelectedItems

        /// <summary>
        /// 선택 항목들
        /// </summary>
        public IList SelectedItems
        {
            get
            {
                return this.selectedItems;
            }
            private set
            {
                if(value == null)
                {
                    throw new ArgumentNullException("value");
                }

                INotifyCollectionChanged oldNotifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
                INotifyCollectionChanged newNotifyCollectionChanged = value              as INotifyCollectionChanged;

                if(oldNotifyCollectionChanged != null)
                {
                    CollectionChangedEventManager.RemoveListener(oldNotifyCollectionChanged, this);
                }

                if(newNotifyCollectionChanged != null)
                {
                    CollectionChangedEventManager.AddListener(newNotifyCollectionChanged, this);
                }

                this.selectedItems = value;
            }
        }

        #endregion
        #region 선택 항목들 (오버라이드) - SelectedItemsOverride

        /// <summary>
        /// 선택 항목들 (오버라이드)
        /// </summary>
        public IList SelectedItemsOverride
        {
            get
            {
                return (IList)GetValue(SelectedItemsOverrideProperty);
            }
            set
            {
                SetValue(SelectedItemsOverrideProperty, value);
            }
        }

        #endregion
        #region 선택 멤버 경로 - SelectedMemberPath

        /// <summary>
        /// 선택 멤버 경로
        /// </summary>
        public string SelectedMemberPath
        {
            get
            {
                return (string)GetValue(SelectedMemberPathProperty);
            }
            set
            {
                SetValue(SelectedMemberPathProperty, value);
            }
        }

        #endregion
        #region 선택 값 - SelectedValue

        /// <summary>
        /// 선택 값
        /// </summary>
        public string SelectedValue
        {
            get
            {
                return (string)GetValue(SelectedValueProperty);
            }
            set
            {
                SetValue(SelectedValueProperty, value);
            }
        }

        #endregion
        #region 값 멤버 경로 - ValueMemberPath

        /// <summary>
        /// 값 멤버 경로
        /// </summary>
        public string ValueMemberPath
        {
            get
            {
                return (string)GetValue(ValueMemberPathProperty);
            }
            set
            {
                SetValue(ValueMemberPathProperty, value);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 항목들 컬렉션 - ItemsCollection

        /// <summary>
        /// 항목들 컬렉션
        /// </summary>
        protected IEnumerable ItemsCollection
        {
            get
            {
                return ItemsSource ?? ((IEnumerable)Items ?? (IEnumerable)new object[0]);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - Selector()

        /// <summary>
        /// 생성자
        /// </summary>
        public Selector()
        {
            SelectedItems = new ObservableCollection<object>();

            AddHandler(Selector.SelectedEvent  , new RoutedEventHandler((sender, e) => OnItemSelectionChangedCore(e, false)));
            AddHandler(Selector.UnSelectedEvent, new RoutedEventHandler((sender, e) => OnItemSelectionChangedCore(e, true )));

            this.selectedMemberPathValueChangedHelper = new ValueChangedHelper(OnSelectedMemberPathValuesChanged);
            this.valueMemberPathValueChangedHelper    = new ValueChangedHelper(OnValueMemberPathValuesChanged);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 구분자 속성 변경시 콜백 처리하기 - delimiterPropertyChangedCallback(d, e)

        /// <summary>
        /// 구분자 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void delimiterPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Selector)d).OnSelectedItemChanged((string)e.OldValue, (string)e.NewValue);
        }

        #endregion
        #region 선택 항목 속성 변경시 콜백 처리하기 - selectedItemPropertyChangedCallback(d, e)

        /// <summary>
        /// 선택 항목 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void selectedItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Selector)d).OnSelectedItemChanged(e.OldValue, e.NewValue);
        }

        #endregion
        #region 선택 항목들 (오버라이드) 속성 변경시 콜백 처리하기 - selectedItemsOverridePropertyChangedCallback(d, e)

        /// <summary>
        /// 선택 항목들 (오버라이드) 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void selectedItemsOverridePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Selector)d).OnSelectedItemsOverrideChanged((IList)e.OldValue, (IList)e.NewValue);
        }

        #endregion
        #region 선택 멤버 경로 속성 변경시 콜백 처리하기 - selectedMemberPathPropertyChangedCallback(d, e)

        /// <summary>
        /// 선택 멤버 경로 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void selectedMemberPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Selector)d).OnSelectedMemberPathChanged((string)e.OldValue, (string)e.NewValue);
        }

        #endregion
        #region 선택 값 속성 변경시 콜백 처리하기 - selectedValuePropertyChangedCallback(d, e)

        /// <summary>
        /// 선택 값 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void selectedValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Selector selector = d as Selector;

            if(selector != null)
            {
                selector.OnSelectedValueChanged((string)e.OldValue, (string)e.NewValue);
            }
        }

        #endregion
        #region 값 멤버 경로 속성 변경시 콜백 처리하기 - valueMemberPathPropertyChangedCallback(d, e)

        /// <summary>
        /// 값 멤버 경로 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void valueMemberPathPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Selector)d).OnValueMemberPathChanged((string)e.OldValue, (string)e.NewValue);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Internal

        #region 리스트에서 갱신하기 - UpdateFromList(selectedValues, getItemfunction)

        /// <summary>
        /// 리스트에서 갱신하기
        /// </summary>
        /// <param name="selectedValues">선택 값들</param>
        /// <param name="getItemfunction">항목 구하기 함수</param>
        internal void UpdateFromList(List<string> selectedValues, Func<object, object> getItemfunction)
        {
            this.countIgnoreSelectedItemsCollectionChanged++;

            SelectedItems.Clear();

            if((selectedValues != null) && (selectedValues.Count > 0))
            {
                ValueEqualityComparer comparer = new ValueEqualityComparer();

                foreach(object item in ItemsCollection)
                {
                    object itemValue = getItemfunction(item);

                    bool isSelected = (itemValue != null) && selectedValues.Contains(itemValue.ToString(), comparer);

                    if(isSelected)
                    {
                        SelectedItems.Add(item);
                    }
                }
            }

            this.countIgnoreSelectedItemsCollectionChanged--;

            UpdateFromSelectedItems();
        }

        #endregion
        #region 선택 항목들 갱신하기 - UpdateSelectedItems(selectedList)

        /// <summary>
        /// 선택 항목들 갱신하기
        /// </summary>
        /// <param name="selectedList">선택 리스트</param>
        internal void UpdateSelectedItems(IList selectedList)
        {
            if(selectedList == null)
            {
                throw new ArgumentNullException("selectedList");
            }

            if(selectedList.Count == this.SelectedItems.Count && selectedList.Cast<object>().SequenceEqual(this.SelectedItems.Cast<object>()))
            {
                return;
            }

            this.countIgnoreSelectedItemsCollectionChanged++;

            SelectedItems.Clear();

            foreach(object newItem in selectedList)
            {
                SelectedItems.Add(newItem);
            }

            this.countIgnoreSelectedItemsCollectionChanged--;

            UpdateFromSelectedItems();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Public

        #region 초기화 종료하기 - EndInit()

        /// <summary>
        /// 초기화 종료하기
        /// </summary>
        public override void EndInit()
        {
            base.EndInit();

            if(SelectedItemsOverride != null)
            {
                OnSelectedItemsOverrideChanged(null, SelectedItemsOverride);
            }
            else if(SelectedMemberPath != null)
            {
                OnSelectedMemberPathChanged(null, SelectedMemberPath);
            }
            else if(SelectedValue != null)
            {
                OnSelectedValueChanged(null, SelectedValue);
            }
            else if(SelectedItem != null)
            {
                OnSelectedItemChanged(null, SelectedItem);
            }

            if(ValueMemberPath != null)
            {
                OnValueMemberPathChanged(null, ValueMemberPath);
            }
        }

        #endregion

        // IWeakEventListener
        #region 약한 이벤트 수신하기 - ReceiveWeakEvent(managerType, sender, e)

        /// <summary>
        /// 약한 이벤트 수신하기
        /// </summary>
        /// <param name="managerType">관리자 타입</param>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        /// <returns>처리 결과</returns>
        public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if(managerType == typeof(CollectionChangedEventManager))
            {
                if(object.ReferenceEquals(this.selectedItems, sender))
                {
                    OnSelectedItemsCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);

                    return true;
                }
                else if(object.ReferenceEquals(ItemsCollection, sender))
                {
                    OnItemsSourceCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);

                    return true;
                }
            }

            return false;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Protected

        #region 선택 항목 변경시 처리하기 - OnSelectedItemChanged(oldValue, newValue)

        /// <summary>
        /// 선택 항목 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnSelectedItemChanged(string oldValue, string newValue)
        {
            if(!IsInitialized)
            {
                return;
            }

            UpdateSelectedValue();
        }

        #endregion
        #region 선택 항목 변경시 처리하기 - OnSelectedItemChanged(oldValue, newValue)

        /// <summary>
        /// 선택 항목 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnSelectedItemChanged(object oldValue, object newValue)
        {
            if(!IsInitialized || this.ignoreSelectedItemChanged)
            {
                return;
            }

            this.countIgnoreSelectedItemsCollectionChanged++;

            SelectedItems.Clear();

            if(newValue != null)
            {
                SelectedItems.Add(newValue);
            }

            UpdateFromSelectedItems();

            this.countIgnoreSelectedItemsCollectionChanged--;
        }

        #endregion
        #region 선택 항목들 (오버라이드) 변경시 처리하기 - OnSelectedItemsOverrideChanged(oldValue, newValue)

        /// <summary>
        /// 선택 항목들 (오버라이드) 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnSelectedItemsOverrideChanged(IList oldValue, IList newValue)
        {
            if(!IsInitialized)
            {
                return;
            }

            this.SelectedItems = (newValue != null) ? newValue : new ObservableCollection<object>();

            UpdateFromSelectedItems();
        }

        #endregion
        #region 선택 멤버 경로 변경시 처리하기 - OnSelectedMemberPathChanged(oldValue, newValue)
 
               /// <summary>
        /// 선택 멤버 경로 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnSelectedMemberPathChanged(string oldValue, string newValue)
        {
            if(!IsInitialized)
            {
                return;
            }

            UpdateSelectedMemberPathValuesBindings();
        }

        #endregion
        #region 선택 값 변경시 처리하기 - OnSelectedValueChanged(oldValue, newValue)

        /// <summary>
        /// 선택 값 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnSelectedValueChanged(string oldValue, string newValue)
        {
            if(!IsInitialized || this.ignoreSelectedValueChanged)
            {
                return;
            }

            UpdateFromSelectedValue();
        }

        #endregion
        #region 값 멤버 경로 변경시 처리하기 - OnValueMemberPathChanged(oldValue, newValue)

        /// <summary>
        /// 값 멤버 경로 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected virtual void OnValueMemberPathChanged(string oldValue, string newValue)
        {
            if(!IsInitialized)
            {
                return;
            }

            UpdateValueMemberPathValuesBindings();
        }

        #endregion
        #region 항목들 소스 변경시 처리하기 - OnItemsSourceChanged(oldValue, newValue)

        /// <summary>
        /// 항목들 소스 변경시 처리하기
        /// </summary>
        /// <param name="oldValue">기존 값</param>
        /// <param name="newValue">신규 값</param>
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            base.OnItemsSourceChanged(oldValue, newValue);

            INotifyCollectionChanged oldNotifyCollectionChanged = oldValue as INotifyCollectionChanged;
            INotifyCollectionChanged newNotifyCollectionChanged = newValue as INotifyCollectionChanged;

            if(oldNotifyCollectionChanged != null)
            {
                CollectionChangedEventManager.RemoveListener(oldNotifyCollectionChanged, this);
            }

            if(newNotifyCollectionChanged != null)
            {
                CollectionChangedEventManager.AddListener(newNotifyCollectionChanged, this);
            }

            if(!IsInitialized)
            {
                return;
            }

            if(!VirtualizingStackPanel.GetIsVirtualizing(this) || (VirtualizingStackPanel.GetIsVirtualizing(this) && (newValue != null)))
            {
                RemoveUnavailableSelectedItems();
            }

            UpdateSelectedMemberPathValuesBindings();
            UpdateValueMemberPathValuesBindings();
        }

        #endregion

        #region 선택 항목들 컬렉션 변경시 처리하기 - OnSelectedItemsCollectionChanged(sender, e)

        /// <summary>
        /// 선택 항목들 컬렉션 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        protected virtual void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if(this.countIgnoreSelectedItemsCollectionChanged > 0)
            {
                return;
            }

            UpdateFromSelectedItems();
        }

        #endregion

        #region 항목 선택 변경시 처리하기 - OnItemSelectionChanged(e)

        /// <summary>
        /// 항목 선택 변경시 처리하기
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        protected virtual void OnItemSelectionChanged(ItemSelectionChangedEventArgs e)
        {
            if(this.surpressItemSelectionChanged)
            {
                return;
            }

            RaiseEvent(e);

            if(Command != null)
            {
                Command.Execute(e.Item);
            }
        }

        #endregion

        #region 항목 자신 컨테이너 여부 구하기 (오버라이드) - IsItemItsOwnContainerOverride(item)

        /// <summary>
        /// 항목 자신 컨테이너 여부 구하기 (오버라이드)
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>항목 자신 컨테이너 여부</returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is SelectorItem;
        }

        #endregion
        #region 항목용 컨테이너 구하기 (오버라이드) - GetContainerForItemOverride()

        /// <summary>
        /// 항목용 컨테이너 구하기 (오버라이드)
        /// </summary>
        /// <returns>항목용 컨테이너</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new SelectorItem();
        }

        #endregion
        #region 항목용 컨테이너 준비하기 (오버라이드) - PrepareContainerForItemOverride(dependencyObject, item)

        /// <summary>
        /// 항목용 컨테이너 준비하기 (오버라이드)
        /// </summary>
        /// <param name="dependencyObject">의존 객체</param>
        /// <param name="item">항목</param>
        protected override void PrepareContainerForItemOverride(DependencyObject dependencyObject, object item)
        {
            base.PrepareContainerForItemOverride(dependencyObject, item);

            this.surpressItemSelectionChanged = true;

            FrameworkElement frameworkElement = dependencyObject as FrameworkElement;

            frameworkElement.SetValue(SelectorItem.IsSelectedProperty, SelectedItems.Contains(item));

            this.surpressItemSelectionChanged = false;
        }

        #endregion

        #region 속성 경로 값 구하기 - GetPropertyPathValue(item, propertyPath)

        /// <summary>
        /// 속성 경로 값 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <param name="propertyPath">속성 경로</param>
        /// <returns>경로 값</returns>
        protected object GetPropertyPathValue(object item, string propertyPath)
        {
            if(item == null)
            {
                throw new ArgumentNullException("item");
            }

            if(string.IsNullOrEmpty(propertyPath) || propertyPath == ".")
            {
                return item;
            }

            PropertyInfo propertyInfo = item.GetType().GetProperty(propertyPath);

            return (propertyInfo != null) ? propertyInfo.GetValue(item, null) : null;
        }

        #endregion
        #region 항목 값 구하기 - GetItemValue(item)

        /// <summary>
        /// 항목 값 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>항목 값</returns>
        protected object GetItemValue(object item)
        {
            return (item != null) ? this.GetPropertyPathValue(item, this.ValueMemberPath) : null;
        }

        #endregion
        #region 값으로 항목 결정하기 - ResolveItemByValue(value)

        /// <summary>
        /// 값으로 항목 결정하기
        /// </summary>
        /// <param name="value">값</param>
        /// <returns>항목</returns>
        protected object ResolveItemByValue(string value)
        {
            if(!string.IsNullOrEmpty(ValueMemberPath))
            {
                foreach(object item in ItemsCollection)
                {
                    PropertyInfo propertyInfo = item.GetType().GetProperty(ValueMemberPath);

                    if(propertyInfo != null)
                    {
                        object propertyValue = propertyInfo.GetValue(item, null);

                        if(value.Equals(propertyValue.ToString(), StringComparison.InvariantCultureIgnoreCase))
                        {
                            return item;
                        }
                    }
                }
            }

            return value;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Private

        #region 선택 멤버 경로 값 구하기 - GetSelectedMemberPathValue(item)

        /// <summary>
        /// 선택 멤버 경로 값 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>선택 멤버 경로 값</returns>
        private bool? GetSelectedMemberPathValue(object item)
        {
            PropertyInfo propertyInfo = this.GetSelectedMemberPathProperty(item);

            return (propertyInfo != null) ? (bool)propertyInfo.GetValue(item, null) : (bool?)null;
        }

        #endregion
        #region 선택 멤버 경로 값 설정하기 - SetSelectedMemberPathValue(item, value)

        /// <summary>
        /// 선택 멤버 경로 값 설정하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <param name="value">값</param>
        private void SetSelectedMemberPathValue(object item, bool value)
        {
            PropertyInfo propertyInfo = GetSelectedMemberPathProperty(item);

            if(propertyInfo != null)
            {
                propertyInfo.SetValue(item, value, null);
            }
        }

        #endregion
        #region 선택 멤버 경로 속성 구하기 - GetSelectedMemberPathProperty(item)

        /// <summary>
        /// 선택 멤버 경로 속성 구하기
        /// </summary>
        /// <param name="item">항목</param>
        /// <returns>선택 멤버 경로 속성</returns>
        private PropertyInfo GetSelectedMemberPathProperty(object item)
        {
            if(!string.IsNullOrEmpty(SelectedMemberPath) && (item != null))
            {
                PropertyInfo propertyInfo = item.GetType().GetProperty(SelectedMemberPath);

                if(propertyInfo != null && propertyInfo.PropertyType == typeof(bool))
                {
                    return propertyInfo;
                }
            }

            return null;
        }

        #endregion

        #region 항목 선택 변경시 처리하기 (코어) - OnItemSelectionChangedCore(e, unselected)

        /// <summary>
        /// 항목 선택 변경시 처리하기 (코어)
        /// </summary>
        /// <param name="e">이벤트 인자</param>
        /// <param name="unselected">선택 해제 여부</param>
        private void OnItemSelectionChangedCore(RoutedEventArgs e, bool unselected)
        {
            object item = this.ItemContainerGenerator.ItemFromContainer((DependencyObject)e.OriginalSource);

            if(item == DependencyProperty.UnsetValue)
            {
                item = e.OriginalSource;
            }

            if(unselected)
            {
                while(SelectedItems.Contains(item))
                {
                    SelectedItems.Remove(item);
                }
            }
            else
            {
                if(!SelectedItems.Contains(item))
                {
                    SelectedItems.Add(item);
                }
            }

            OnItemSelectionChanged(new ItemSelectionChangedEventArgs(Selector.ItemSelectionChangedEvent, this, item, !unselected));
        }

        #endregion
        #region 항목들 소스 컬렉션 변경시 처리하기 - OnItemsSourceCollectionChanged(sender, e)

        /// <summary>
        /// 항목들 소스 컬렉션 변경시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            RemoveUnavailableSelectedItems();

            AddAvailableRemovedItems();

            UpdateSelectedMemberPathValuesBindings();

            UpdateValueMemberPathValuesBindings();
        }

        #endregion
        #region 선택 멤버 경로 값들 변경시 처리하기 - OnSelectedMemberPathValuesChanged()

        /// <summary>
        /// 선택 멤버 경로 값들 변경시 처리하기
        /// </summary>
        private void OnSelectedMemberPathValuesChanged()
        {
            if(this.countIgnoreSelectedMemberPathValuesChanged > 0)
            {
                return;
            }

            UpdateFromSelectedMemberPathValues();
        }

        #endregion
        #region 값 멤버 경로 값들 변경시 처리하기 - OnValueMemberPathValuesChanged()

        /// <summary>
        /// 값 멤버 경로 값들 변경시 처리하기
        /// </summary>
        private void OnValueMemberPathValuesChanged()
        {
            UpdateSelectedValue();
        }

        #endregion

        #region 선택 멤버 경로 값들의 바인딩들 갱신하기 - UpdateSelectedMemberPathValuesBindings()

        /// <summary>
        /// 선택 멤버 경로 값들의 바인딩들 갱신하기
        /// </summary>
        private void UpdateSelectedMemberPathValuesBindings()
        {
            this.selectedMemberPathValueChangedHelper.UpdateValueSource(ItemsCollection, SelectedMemberPath);
        }

        #endregion
        #region 값 멤버 경로 값들의 바인딩들 갱신하기 - UpdateValueMemberPathValuesBindings()

        /// <summary>
        /// 값 멤버 경로 값들의 바인딩들 갱신하기
        /// </summary>
        private void UpdateValueMemberPathValuesBindings()
        {
            this.valueMemberPathValueChangedHelper.UpdateValueSource(ItemsCollection, ValueMemberPath);
        }

        #endregion

        #region 선택 값 갱신하기 - UpdateSelectedValue()

        /// <summary>
        /// 선택 값 갱신하기
        /// </summary>
        private void UpdateSelectedValue()
        {
            string newValue = string.Join(Delimiter, SelectedItems.Cast<object>().Select(x => GetItemValue(x)));

            if(string.IsNullOrEmpty(SelectedValue) || !SelectedValue.Equals(newValue))
            {
                this.ignoreSelectedValueChanged = true;

                SelectedValue = newValue;

                this.ignoreSelectedValueChanged = false;
            }
        }

        #endregion
        #region 선택 항목 갱신하기 - UpdateSelectedItem()

        /// <summary>
        /// 선택 항목 갱신하기
        /// </summary>
        private void UpdateSelectedItem()
        {
            if(!SelectedItems.Contains(SelectedItem))
            {
                this.ignoreSelectedItemChanged = true;

                SelectedItem = (SelectedItems.Count > 0) ? SelectedItems[0] : null;

                this.ignoreSelectedItemChanged = false;
            }
        }

        #endregion
        #region 선택 멤버 경로 값들에서 갱신하기 - UpdateFromSelectedMemberPathValues()

        /// <summary>
        /// 선택 멤버 경로 값들에서 갱신하기
        /// </summary>
        private void UpdateFromSelectedMemberPathValues()
        {
            this.countIgnoreSelectedItemsCollectionChanged++;

            foreach(object item in ItemsCollection)
            {
                bool? isSelected = GetSelectedMemberPathValue(item);

                if(isSelected != null)
                {
                    if(isSelected.Value)
                    {
                        if(!SelectedItems.Contains(item))
                        {
                            SelectedItems.Add(item);
                        }
                    }
                    else
                    {
                        if(SelectedItems.Contains(item))
                        {
                            SelectedItems.Remove(item);
                        }
                    }
                }
            }

            this.countIgnoreSelectedItemsCollectionChanged--;

            UpdateFromSelectedItems();
        }

        #endregion
        #region 선택 항목들에서 갱신하기 - UpdateFromSelectedItems()

        /// <summary>
        /// 선택 항목들에서 갱신하기
        /// </summary>
        private void UpdateFromSelectedItems()
        {
            foreach(object item in ItemsCollection)
            {
                bool isSelected = SelectedItems.Contains(item);

                this.countIgnoreSelectedMemberPathValuesChanged++;

                SetSelectedMemberPathValue(item, isSelected);

                this.countIgnoreSelectedMemberPathValuesChanged--;

                SelectorItem selectorItem = ItemContainerGenerator.ContainerFromItem(item) as SelectorItem;

                if(selectorItem != null)
                {
                    selectorItem.IsSelected = isSelected;
                }
            }

            UpdateSelectedItem();

            UpdateSelectedValue();
        }

        #endregion
        #region 선택 값에서 갱신하기 - UpdateFromSelectedValue()

        /// <summary>
        /// 선택 값에서 갱신하기
        /// </summary>
        private void UpdateFromSelectedValue()
        {
            List<string> selectedValueList = null;

            if(!string.IsNullOrEmpty(SelectedValue))
            {
                selectedValueList = SelectedValue.Split(new string[] { Delimiter }, StringSplitOptions.RemoveEmptyEntries).ToList();
            }

            UpdateFromList(selectedValueList, GetItemValue);
        }

        #endregion

        #region 이용 불가한 선택 항목들 제거하기 - RemoveUnavailableSelectedItems()

        /// <summary>
        /// 이용 불가한 선택 항목들 제거하기
        /// </summary>
        private void RemoveUnavailableSelectedItems()
        {
            this.countIgnoreSelectedItemsCollectionChanged++;

            HashSet<object> hashSet = new HashSet<object>(ItemsCollection.Cast<object>());

            for(int i = 0; i < SelectedItems.Count; i++)
            {
                if(!hashSet.Contains(SelectedItems[ i ]))
                {
                    this.removedItems.Add(SelectedItems[ i ]);

                    SelectedItems.RemoveAt(i);

                    i--;
                }
            }

            this.countIgnoreSelectedItemsCollectionChanged--;

            UpdateSelectedItem();

            UpdateSelectedValue();
        }

        #endregion
        #region 이용 가능한 제거 항목들 추가하기 - AddAvailableRemovedItems()

        /// <summary>
        /// 이용 가능한 제거 항목들 추가하기
        /// </summary>
        private void AddAvailableRemovedItems()
        {
            HashSet<object> hashSet = new HashSet<object>(ItemsCollection.Cast<object>());

            for(int i = 0; i < this.removedItems.Count; i++)
            {
                if(hashSet.Contains(this.removedItems[ i ]))
                {
                    SelectedItems.Add(this.removedItems[ i ]);

                    this.removedItems.RemoveAt(i);          

                    i--;
                }
            }
        }

        #endregion
    }
}

 

▶ ListControl.cs

using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace TestLibrary
{
    /// <summary>
    /// 리스트 컨트롤
    /// </summary>
    public class ListControl : Selector
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 단일 선택 의존 속성 - SingleSelectProperty

        /// <summary>
        /// 단일 선택 의존 속성
        /// </summary>
        public static readonly DependencyProperty SingleSelectProperty = DependencyProperty.Register
        (
            "SingleSelect",
            typeof(bool),
            typeof(ListControl),
            new UIPropertyMetadata(false, singleSelectPropertyChangedCallback)
        );

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// PREVIEW 마우스 DOWN 이벤트 핸들러
        /// </summary>
        private MouseButtonEventHandler previewMouseDownEventHandler = null;

        /// <summary>
        /// PREVIEW 마우스 UP 이벤트 핸들러
        /// </summary>
        private MouseButtonEventHandler previewMouseUpEventHandler = null;

        /// <summary>
        /// PREVIEW 마우스 이동시 이벤트 핸들러
        /// </summary>
        private MouseEventHandler previewMouseMoveEventHandler = null;

        /// <summary>
        /// 드래그 시작 인덱스
        /// </summary>
        private int dragStartIndex = -1;

        /// <summary>
        /// 드래그 종료 인덱스
        /// </summary>
        private int dragEndIndex = -1;

        /// <summary>
        /// 스크롤 뷰어
        /// </summary>
        private ScrollViewer scrollViewer = null;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 단일 선택 - SingleSelect

        /// <summary>
        /// 단일 선택
        /// </summary>
        public bool SingleSelect
        {
            get
            {
                return (bool)GetValue(SingleSelectProperty);
            }
            set
            {
                SetValue(SingleSelectProperty, value);
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Static

        #region 생성자 - CheckListBox()

        /// <summary>
        /// 생성자
        /// </summary>
        static ListControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ListControl), new FrameworkPropertyMetadata(typeof(ListControl)));
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - CheckListBox()

        /// <summary>
        /// 생성자
        /// </summary>
        public ListControl()
        {
            this.previewMouseDownEventHandler = new MouseButtonEventHandler(ItemsControl_PreviewMouseDown);
            this.previewMouseUpEventHandler   = new MouseButtonEventHandler(ItemsControl_PreviewMouseUp  );
            this.previewMouseMoveEventHandler = new MouseEventHandler      (ItemsControl_PreviewMouseMove);


            AddHandler(ItemsControl.PreviewMouseDownEvent, this.previewMouseDownEventHandler);
            AddHandler(ItemsControl.PreviewMouseUpEvent  , this.previewMouseUpEventHandler  );
            AddHandler(ItemsControl.PreviewMouseMoveEvent, this.previewMouseMoveEventHandler);

            Loaded += Selector_Loaded;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 단일 선택 속성 변경시 콜백 처리하기 - singleSelectPropertyChangedCallback(dependencyObject, e)

        /// <summary>
        /// 단일 선택 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="d">의존 객체</param>
        /// <param name="e">이벤트 인자</param>
        private static void singleSelectPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ListControl checkListBox = d as ListControl;

            bool newValue = (bool)e.NewValue;

            if(newValue)
            {
                checkListBox.surpressItemSelectionChanged = true;

                IList selectedList = checkListBox.SelectedItems;

                if(selectedList != null)
                {
                    if(selectedList.Count > 1)
                    {
                        IList sourceList = (checkListBox.ItemsSource ?? checkListBox.Items) as IList;

                        for(int i = 0; i < sourceList.Count; i++)
                        {
                            object item = sourceList[i];

                            SelectorItem selectorItem = checkListBox.ItemContainerGenerator.ContainerFromItem(item) as SelectorItem;

                            selectorItem.IsSelected = false;
                        }
                    }
                }

                checkListBox.surpressItemSelectionChanged = false;

                ItemSelectionChangedEventArgs itemSelectionChangedEventArgs = new ItemSelectionChangedEventArgs
                (
                    Selector.ItemSelectionChangedEvent,
                    checkListBox,
                    null,
                    true
                );

                checkListBox.RaiseEvent(itemSelectionChangedEventArgs);
            }
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 셀렉터 로드시 처리하기 - Selector_Loaded(sender, e)

        /// <summary>
        /// 셀렉터 로드시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void Selector_Loaded(object sender, RoutedEventArgs e)
        {
            this.scrollViewer = Template.FindName("scrollViewer", this) as ScrollViewer;
        }

        #endregion

        #region 항목들 컨트롤 PREVIEW 마우스 DOWN 처리하기 - ItemsControl_PreviewMouseDown(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 DOWN 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if(e.ChangedButton == MouseButton.Left)
            {
                if(ItemContainerGenerator.Items.Count == 0)
                {
                    return;
                }
        
                HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, e.GetPosition(this));
        
                if(hitTestResult == null)
                {
                    return;
                }
        
                SelectorItem mouseOverItem = hitTestResult.VisualHit.FindParent<SelectorItem>();
        
                if(mouseOverItem == null)
                {
                    return;
                }
        
                mouseOverItem.IsDragging = true;
        
                this.dragStartIndex = ItemContainerGenerator.IndexFromContainer(mouseOverItem);
        
                Mouse.Capture(this);
            }
        }

        #endregion
        #region 항목들 컨트롤 PREVIEW 마우스 이동시 처리하기 - ItemsControl_PreviewMouseMove(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 이동시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if(e.LeftButton == MouseButtonState.Pressed)
            {
                if(ItemContainerGenerator.Items.Count == 0)
                {
                    return;
                }
        
                Point mousePosition = e.GetPosition(this);
        
                if(this.scrollViewer != null)
                {
                    if(mousePosition.Y > Height - 8)
                    {
                        this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset + 4);
                    }
                    else if(mousePosition.Y < 8)
                    {
                        this.scrollViewer.ScrollToVerticalOffset(this.scrollViewer.VerticalOffset-4);
                    }
                }
        
                HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, e.GetPosition(this));
        
                if(hitTestResult == null)
                {
                    return;
                }
        
                SelectorItem mouseOverItem = hitTestResult.VisualHit.FindParent<SelectorItem>();
        
                int mouseOverIndex;
        
                if(mouseOverItem == null)
                {
                    mouseOverIndex = -1;
                }
                else
                {
                    mouseOverIndex = ItemContainerGenerator.IndexFromContainer(mouseOverItem);
                }
        
                int temporaryStartIndex;
                int temporaryEndIndex;
        
                if(SingleSelect)
                {
                    temporaryStartIndex = this.dragStartIndex;
                    temporaryEndIndex   = this.dragStartIndex;
                }
                else
                {
                    if(mouseOverIndex == -1)
                    {
                        if(this.dragEndIndex == -1)
                        {
                            temporaryStartIndex = this.dragStartIndex;
                            temporaryEndIndex   = this.dragStartIndex;
                        }
                        else
                        {
                            if(this.dragStartIndex > this.dragEndIndex)
                            {
                                temporaryStartIndex = this.dragEndIndex;
                                temporaryEndIndex   = this.dragStartIndex;
                            }
                            else
                            {
                                temporaryStartIndex = this.dragStartIndex;
                                temporaryEndIndex   = this.dragEndIndex;
                            }
                        }
                    }
                    else
                    {
                        if(this.dragStartIndex > mouseOverIndex)
                        {
                            temporaryStartIndex = mouseOverIndex;
                            temporaryEndIndex   = this.dragStartIndex;
                        }
                        else
                        {
                            temporaryStartIndex = this.dragStartIndex;
                            temporaryEndIndex   = mouseOverIndex;
                        }
                
                        this.dragEndIndex = mouseOverIndex;
                    }
                }
            
                if(temporaryStartIndex == -1 || temporaryEndIndex == -1)
                {
                    return;
                }
        
                this.surpressItemSelectionChanged = true;
        
                for(int i = 0; i < ItemContainerGenerator.Items.Count; i++)
                {
                    SelectorItem selectorItem = ItemContainerGenerator.ContainerFromIndex(i) as SelectorItem;
            
                    if(i >= temporaryStartIndex && i <= temporaryEndIndex)
                    {
                        selectorItem.IsDragging = true;
                    }
                    else
                    {
                        selectorItem.IsDragging = false;
                    }
                }
        
                this.surpressItemSelectionChanged = false;
            }
        }

        #endregion
        #region 항목들 컨트롤 PREVIEW 마우스 UP 처리하기 - ItemsControl_PreviewMouseUp(sender, e)

        /// <summary>
        /// 항목들 컨트롤 PREVIEW 마우스 UP 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void ItemsControl_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if(e.ChangedButton == MouseButton.Left)
            {
                Mouse.Capture(null);

                if(ItemContainerGenerator.Items.Count == 0)
                {
                    return;
                }

                HitTestResult hitTestResult = VisualTreeHelper.HitTest(this, e.GetPosition(this));

                int mouseOverIndex;

                if(hitTestResult == null)
                {
                    mouseOverIndex = -1;
                }
                else
                {
                    SelectorItem mouseOverItem = hitTestResult.VisualHit.FindParent<SelectorItem>();

                    if(mouseOverItem == null)
                    {
                        return;
                    }

                    mouseOverIndex = ItemContainerGenerator.IndexFromContainer(mouseOverItem);
                }

                int temporaryStartIndex;
                int temporaryEndIndex;

                if(SingleSelect)
                {
                    temporaryStartIndex = this.dragStartIndex;
                    temporaryEndIndex = this.dragStartIndex;
                }
                else
                {
                    if(mouseOverIndex == -1)
                    {
                        if(this.dragEndIndex == -1)
                        {
                            temporaryStartIndex = this.dragStartIndex;
                            temporaryEndIndex   = this.dragStartIndex;
                        }
                        else
                        {
                            if(this.dragStartIndex > this.dragEndIndex)
                            {
                                temporaryStartIndex = this.dragEndIndex;
                                temporaryEndIndex   = this.dragStartIndex;
                            }
                            else
                            {
                                temporaryStartIndex = this.dragStartIndex;
                                temporaryEndIndex   = this.dragEndIndex;
                            }
                        }
                    }
                    else
                    {
                        if(this.dragStartIndex > mouseOverIndex)
                        {
                            temporaryStartIndex = mouseOverIndex;
                            temporaryEndIndex   = this.dragStartIndex;
                        }
                        else
                        {
                            temporaryStartIndex = this.dragStartIndex;
                            temporaryEndIndex   = mouseOverIndex;
                        }

                        this.dragEndIndex = mouseOverIndex;
                    }
                }

                if(temporaryStartIndex == -1 || temporaryEndIndex == -1)
                {
                    return;
                }

                this.surpressItemSelectionChanged = true;

                for(int i = 0; i <ItemContainerGenerator.Items.Count; i++)
                {
                    SelectorItem selectorItem = ItemContainerGenerator.ContainerFromIndex(i) as SelectorItem;

                    selectorItem.IsDragging = false;

                    if(i >= temporaryStartIndex && i <= temporaryEndIndex)
                    {
                        selectorItem.IsSelected = !selectorItem.IsSelected;
                    }
                }

                this.surpressItemSelectionChanged = false;

                this.dragStartIndex = -1;
                this.dragEndIndex   = -1;

                ItemSelectionChangedEventArgs itemSelectionChangedEventArgs = new ItemSelectionChangedEventArgs
                (
                    Selector.ItemSelectionChangedEvent,
                    this,
                    null,
                    true
                );

                RaiseEvent(itemSelectionChangedEventArgs);
            }
        }

        #endregion
    }
}

 

[TestProject 프로젝트]

▶ MainWindow.xaml

<Window x:Class="TestProject.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestProject"
    xmlns:library="clr-namespace:TestLibrary;assembly=TestLibrary"
    mc:Ignorable="d"
    Width="800"
    Height="600"
    Title="ItemsControl 클래스 : 리스트 컨트롤 사용하기 (드래그 기능 개선)"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <StackPanel VerticalAlignment="Center">
        <library:ListControl x:Name="listControl"
            Margin="5"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Width="250"
            Height="150"
            ValueMemberPath="CustomerID"
            DisplayMemberPath="CustomerName">
            <local:Customer CustomerID="1" CustomerName="고객 1" />
            <local:Customer CustomerID="2" CustomerName="고객 2" />
            <local:Customer CustomerID="3" CustomerName="고객 3" />
            <local:Customer CustomerID="4" CustomerName="고객 4" />
            <local:Customer CustomerID="5" CustomerName="고객 5" />
            <local:Customer CustomerID="6" CustomerName="고객 6" />
            <local:Customer CustomerID="7" CustomerName="고객 7" />
            <local:Customer CustomerID="8" CustomerName="고객 8" />
            <local:Customer CustomerID="9" CustomerName="고객 9" />
        </library:ListControl>
        <CheckBox x:Name="checkBox"
            Margin="5"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            VerticalContentAlignment="Center"
            Content="Single Select"
            IsChecked="{Binding ElementName=listControl, Path=SingleSelect}" />
        <TextBlock
            Margin="5"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Text="{Binding ElementName=listControl, Path=SelectedValue}" />
    </StackPanel>
</Window>
728x90
그리드형(광고전용)
Posted by icodebroker
,