728x90
728x170
[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
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] 타원 상에서 호(Arc) 시작점/종료점 구하기 (0) | 2020.12.26 |
---|---|
[C#/WPF] 타원과 직선 교차점 배열 찾기 (0) | 2020.12.26 |
[C#/WPF] Canvas 클래스 : 타원 도형 추가하기 (0) | 2020.12.26 |
[C#/WPF] Canvas 클래스 : 사각형 도형 추가하기 (0) | 2020.12.26 |
[C#/WPF] FFMpeg을 사용해 웹 카메라 사용하기 (기능 개선) (0) | 2020.12.25 |
[C#/WPF] MarkupExtension 클래스 : 마크업 확장 사용하기 (0) | 2020.12.15 |
[C#/WPF] Image 클래스 : LayoutUpdated 이벤트를 사용해 이미지 픽셀 보정하기 (0) | 2020.12.13 |
[C#/WPF] FrameworkElement 클래스 : 장평을 설정하는 커스텀 텍스트 블럭 사용하기 (0) | 2020.12.13 |
[C#/WPF] Grid 클래스 : 환형 패널 사용하기 (0) | 2020.12.12 |
[C#/WPF] ObjectDataProvider 엘리먼트 : Colors 클래스의 색상 정적 속성 구하기 (0) | 2020.12.07 |