Dreamine.UI.Wpf.Controls 1.0.1
Dreamine.UI.Wpf.Controls 사용자 인터페이스 기능과 구성 요소를 제공합니다.
로딩중...
검색중...
일치하는것 없음
DreamineComboBox.cs
이 파일의 문서화 페이지로 가기
1// =====================================================================
2// \file DreamineComboBox.cs
3// \brief MVVM-style attached command trigger ComboBox
4// \details
5// - Supports triggers: PreviewMouseUp, MouseDoubleClick, PreviewKeyDown, PreviewKeyUp, TouchUp,
6// Click, SelectionChanged, DropDownClosed
7// - Prevents duplicated handler hookup by using attached flags
8// - Unhooks DropDownClosed on Unloaded to avoid leaks / duplicates
9// - If CommandParameter is null, it passes a rich context object including Sender + EventArgs
10// \date 2026-02-10
11// =====================================================================
12
13using System;
14using System.Linq;
15using System.Windows;
16using System.Windows.Controls;
17using System.Windows.Controls.Primitives;
18using System.Windows.Input;
19
21{
30 public class DreamineComboBox : ComboBox
31 {
32 #region Command Context
33
43 {
52 public string EventName { get; }
53
62 public object? Sender { get; }
63
72 public object? EventArgs { get; }
73
82 public ComboBox? ComboBox { get; }
83
92 public object? SelectedItem { get; }
93
102 public object? SelectedValue { get; }
103
112 public int SelectedIndex { get; }
113
154 public DreamineComboBoxCommandContext(string eventName, object? sender, object? args, ComboBox? combo)
155 {
156 EventName = eventName ?? string.Empty;
157 Sender = sender;
158 EventArgs = args;
159 ComboBox = combo;
160
161 if (combo != null)
162 {
163 SelectedItem = combo.SelectedItem;
164 SelectedValue = combo.SelectedValue;
165 SelectedIndex = combo.SelectedIndex;
166 }
167 else
168 {
169 SelectedItem = null;
170 SelectedValue = null;
171 SelectedIndex = -1;
172 }
173 }
174
191 public override string ToString()
192 => $"Event={EventName}, SelectedItem={SelectedItem ?? "(null)"}, SelectedIndex={SelectedIndex}";
193 }
194
195 #endregion
196
197 #region Attached Flags (Private)
198
207 private static readonly DependencyProperty IsHandlersHookedProperty =
208 DependencyProperty.RegisterAttached(
209 "IsHandlersHooked",
210 typeof(bool),
211 typeof(DreamineComboBox),
212 new PropertyMetadata(false));
213
222 private static readonly DependencyProperty IsDropDownClosedHookedProperty =
223 DependencyProperty.RegisterAttached(
224 "IsDropDownClosedHooked",
225 typeof(bool),
226 typeof(DreamineComboBox),
227 new PropertyMetadata(false));
228
237 private static readonly DependencyProperty IsUnloadedHookedProperty =
238 DependencyProperty.RegisterAttached(
239 "IsUnloadedHooked",
240 typeof(bool),
241 typeof(DreamineComboBox),
242 new PropertyMetadata(false));
243
252 private static readonly DependencyProperty DropDownClosedHandlerProperty =
253 DependencyProperty.RegisterAttached(
254 "DropDownClosedHandler",
255 typeof(EventHandler),
256 typeof(DreamineComboBox),
257 new PropertyMetadata(null));
258
267 private static readonly DependencyProperty UnloadedHandlerProperty =
268 DependencyProperty.RegisterAttached(
269 "UnloadedHandler",
270 typeof(RoutedEventHandler),
271 typeof(DreamineComboBox),
272 new PropertyMetadata(null));
273
306 private static bool GetIsHandlersHooked(DependencyObject o) => (bool)o.GetValue(IsHandlersHookedProperty);
339 private static void SetIsHandlersHooked(DependencyObject o, bool v) => o.SetValue(IsHandlersHookedProperty, v);
340
373 private static bool GetIsDropDownClosedHooked(DependencyObject o) => (bool)o.GetValue(IsDropDownClosedHookedProperty);
406 private static void SetIsDropDownClosedHooked(DependencyObject o, bool v) => o.SetValue(IsDropDownClosedHookedProperty, v);
407
440 private static bool GetIsUnloadedHooked(DependencyObject o) => (bool)o.GetValue(IsUnloadedHookedProperty);
473 private static void SetIsUnloadedHooked(DependencyObject o, bool v) => o.SetValue(IsUnloadedHookedProperty, v);
474
507 private static void SetDropDownClosedHandler(DependencyObject o, EventHandler? h) => o.SetValue(DropDownClosedHandlerProperty, h);
540 private static EventHandler? GetDropDownClosedHandler(DependencyObject o) => (EventHandler?)o.GetValue(DropDownClosedHandlerProperty);
541
574 private static void SetUnloadedHandler(DependencyObject o, RoutedEventHandler? h) => o.SetValue(UnloadedHandlerProperty, h);
607 private static RoutedEventHandler? GetUnloadedHandler(DependencyObject o) => (RoutedEventHandler?)o.GetValue(UnloadedHandlerProperty);
608
609 #endregion
610
611 #region Attached DPs (Public)
612
621 public static readonly DependencyProperty CommandProperty =
622 DependencyProperty.RegisterAttached(
623 "Command",
624 typeof(ICommand),
625 typeof(DreamineComboBox),
626 new PropertyMetadata(null, OnCommandChanged));
627
636 public static readonly DependencyProperty CommandParameterProperty =
637 DependencyProperty.RegisterAttached(
638 "CommandParameter",
639 typeof(object),
640 typeof(DreamineComboBox),
641 new PropertyMetadata(null));
642
651 public static readonly DependencyProperty CommandTriggerNameProperty =
652 DependencyProperty.RegisterAttached(
653 "CommandTriggerName",
654 typeof(string),
655 typeof(DreamineComboBox),
656 new PropertyMetadata(null));
657
690 public static void SetCommandTriggerName(DependencyObject obj, string value) => obj.SetValue(CommandTriggerNameProperty, value);
731 public static string GetCommandTriggerName(DependencyObject obj) => (string)obj.GetValue(CommandTriggerNameProperty);
732
765 public static void SetCommand(DependencyObject obj, ICommand value) => obj.SetValue(CommandProperty, value);
806 public static ICommand GetCommand(DependencyObject obj) => (ICommand)obj.GetValue(CommandProperty);
807
840 public static void SetCommandParameter(DependencyObject obj, object value) => obj.SetValue(CommandParameterProperty, value);
873 public static object GetCommandParameter(DependencyObject obj) => obj.GetValue(CommandParameterProperty);
874
875 #endregion
876
877 #region Hook Logic
878
911 private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
912 {
913 if (d is not UIElement element)
914 return;
915
916 // \brief Routed 이벤트 훅은 1회만
917 if (!GetIsHandlersHooked(d))
918 {
919 // \brief Mouse
920 element.AddHandler(UIElement.PreviewMouseUpEvent, new MouseButtonEventHandler((s, ev) =>
921 {
922 TryExecuteCommand(d, s, "PreviewMouseUp", ev);
923 }), true);
924
925 element.AddHandler(Control.MouseDoubleClickEvent, new MouseButtonEventHandler((s, ev) =>
926 {
927 TryExecuteCommand(d, s, "MouseDoubleClick", ev);
928 }), true);
929
930 // \brief Keyboard (Down/Up)
931 element.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler((s, ev) =>
932 {
933 TryExecuteCommand(d, s, "PreviewKeyDown", ev);
934 }), true);
935
936 element.AddHandler(UIElement.PreviewKeyUpEvent, new KeyEventHandler((s, ev) =>
937 {
938 TryExecuteCommand(d, s, "PreviewKeyUp", ev);
939 }), true);
940
941 // \brief Touch
942 element.AddHandler(UIElement.TouchUpEvent, new EventHandler<TouchEventArgs>((s, ev) =>
943 {
944 TryExecuteCommand(d, s, "TouchUp", ev);
945 }), true);
946
947 // \brief Click (경로 유지)
948 element.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler((s, ev) =>
949 {
950 TryExecuteCommand(d, s, "Click", ev);
951 }), true);
952
953 // \brief SelectionChanged
954 element.AddHandler(Selector.SelectionChangedEvent, new SelectionChangedEventHandler((s, ev) =>
955 {
956 TryExecuteCommand(d, s, "SelectionChanged", ev);
957 }), true);
958
959 SetIsHandlersHooked(d, true);
960 }
961
962 // \brief Unloaded 훅도 1회만 (DropDownClosed 해제용)
963 if (d is FrameworkElement fe && !GetIsUnloadedHooked(d))
964 {
965 RoutedEventHandler unloadedHandler = (_, __) =>
966 {
967 // \brief DropDownClosed 구독 해제
968 if (d is ComboBox combo)
969 {
970 var h = GetDropDownClosedHandler(d);
971 if (h != null)
972 {
973 combo.DropDownClosed -= h;
975 }
976 }
977
979 };
980
981 fe.Unloaded += unloadedHandler;
982 SetUnloadedHandler(d, unloadedHandler);
983 SetIsUnloadedHooked(d, true);
984 }
985
986 // \brief DropDownClosed는 ComboBox 이벤트 -> 중복 구독 방지 + 해제 가능하게 저장
987 if (d is ComboBox comboBox && !GetIsDropDownClosedHooked(d))
988 {
989 EventHandler handler = (s, ev) =>
990 {
991 TryExecuteCommand(d, s, "DropDownClosed", ev);
992 };
993
994 comboBox.DropDownClosed += handler;
995 SetDropDownClosedHandler(d, handler);
997 }
998 }
999
1000 #endregion
1001
1002 #region Execute Logic
1003
1060 private static void TryExecuteCommand(DependencyObject d, object? sender, string eventName, RoutedEventArgs eventArgs)
1061 {
1062 // \brief 키 반복 입력 방지 (원하면 옵션화 가능)
1063 if (eventName.Equals("PreviewKeyDown", StringComparison.OrdinalIgnoreCase) &&
1064 eventArgs is KeyEventArgs ke && ke.IsRepeat)
1065 {
1066 return;
1067 }
1068
1069 var rawTrigger = GetCommandTriggerName(d);
1070 if (string.IsNullOrWhiteSpace(rawTrigger))
1071 return;
1072
1073 var triggerList = rawTrigger.Split(',')
1074 .Select(x => x.Trim())
1075 .Where(x => !string.IsNullOrWhiteSpace(x));
1076
1077 if (!triggerList.Contains(eventName, StringComparer.OrdinalIgnoreCase))
1078 return;
1079
1080 var command = GetCommand(d);
1081 if (command == null)
1082 return;
1083
1084 var explicitParam = GetCommandParameter(d);
1085 var combo = (sender as ComboBox) ?? (d as ComboBox);
1086
1087 var parameter = explicitParam ?? new DreamineComboBoxCommandContext(eventName, sender, eventArgs, combo);
1088
1089 if (command.CanExecute(parameter))
1090 command.Execute(parameter);
1091 }
1092
1149 private static void TryExecuteCommand(DependencyObject d, object? sender, string eventName, EventArgs eventArgs)
1150 {
1151 var rawTrigger = GetCommandTriggerName(d);
1152 if (string.IsNullOrWhiteSpace(rawTrigger))
1153 return;
1154
1155 var triggerList = rawTrigger.Split(',')
1156 .Select(x => x.Trim())
1157 .Where(x => !string.IsNullOrWhiteSpace(x));
1158
1159 if (!triggerList.Contains(eventName, StringComparer.OrdinalIgnoreCase))
1160 return;
1161
1162 var command = GetCommand(d);
1163 if (command == null)
1164 return;
1165
1166 var explicitParam = GetCommandParameter(d);
1167 var combo = (sender as ComboBox) ?? (d as ComboBox);
1168
1169 var parameter = explicitParam ?? new DreamineComboBoxCommandContext(eventName, sender, eventArgs, combo);
1170
1171 if (command.CanExecute(parameter))
1172 command.Execute(parameter);
1173 }
1174
1175 #endregion
1176 }
1177}
static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
static readonly DependencyProperty CommandProperty
static ? EventHandler GetDropDownClosedHandler(DependencyObject o)
static object GetCommandParameter(DependencyObject obj)
static bool GetIsHandlersHooked(DependencyObject o)
static void SetUnloadedHandler(DependencyObject o, RoutedEventHandler? h)
static readonly DependencyProperty IsHandlersHookedProperty
static void SetIsDropDownClosedHooked(DependencyObject o, bool v)
static ? RoutedEventHandler GetUnloadedHandler(DependencyObject o)
static readonly DependencyProperty UnloadedHandlerProperty
static void SetIsHandlersHooked(DependencyObject o, bool v)
static void SetDropDownClosedHandler(DependencyObject o, EventHandler? h)
static bool GetIsDropDownClosedHooked(DependencyObject o)
static readonly DependencyProperty CommandParameterProperty
static readonly DependencyProperty IsDropDownClosedHookedProperty
static void SetCommandParameter(DependencyObject obj, object value)
static void SetCommand(DependencyObject obj, ICommand value)
static void SetCommandTriggerName(DependencyObject obj, string value)
static readonly DependencyProperty IsUnloadedHookedProperty
static readonly DependencyProperty DropDownClosedHandlerProperty
static readonly DependencyProperty CommandTriggerNameProperty
static void TryExecuteCommand(DependencyObject d, object? sender, string eventName, RoutedEventArgs eventArgs)
static ICommand GetCommand(DependencyObject obj)
static void TryExecuteCommand(DependencyObject d, object? sender, string eventName, EventArgs eventArgs)
static bool GetIsUnloadedHooked(DependencyObject o)
static string GetCommandTriggerName(DependencyObject obj)
static void SetIsUnloadedHooked(DependencyObject o, bool v)
DreamineComboBoxCommandContext(string eventName, object? sender, object? args, ComboBox? combo)