Dreamine.UI.Wpf.Controls 1.0.1
Dreamine.UI.Wpf.Controls 사용자 인터페이스 기능과 구성 요소를 제공합니다.
로딩중...
검색중...
일치하는것 없음
DreamineTextBox.cs
이 파일의 문서화 페이지로 가기
1// =====================================================================
2// \file DreamineTextBox.cs
3// \brief VsLibrary용 커스텀 TextBox. Hint/Error + CommandTrigger + Enter Focus UX 지원.
4// =====================================================================
5
6using System;
7using System.Linq;
8using System.Windows;
9using System.Windows.Controls;
10using System.Windows.Controls.Primitives;
11using System.Windows.Input;
12using System.Windows.Media;
13
15{
24 public class DreamineTextBox : TextBox
25 {
34 private IInputElement? _previousFocusedElement;
35
45 {
46 DefaultStyleKeyProperty.OverrideMetadata(typeof(DreamineTextBox),
47 new FrameworkPropertyMetadata(typeof(DreamineTextBox)));
48
49 var uri = new Uri("/Dreamine.UI.Wpf.Themes;component/DreamineTextBoxStyle.xaml", UriKind.RelativeOrAbsolute);
50
51 if (Application.Current != null)
52 {
53 bool alreadyAdded = Application.Current.Resources.MergedDictionaries
54 .OfType<ResourceDictionary>()
55 .Any(x => x.Source != null && x.Source.Equals(uri));
56
57 if (!alreadyAdded)
58 {
59 var dict = new ResourceDictionary { Source = uri };
60 Application.Current.Resources.MergedDictionaries.Add(dict);
61 }
62 }
63 }
64
82 {
83 // \brief Enter 복귀를 위해 이전 포커스 저장
84 GotKeyboardFocus += OnGotKeyboardFocus_StorePreviousFocus;
85 }
86
111 private void OnGotKeyboardFocus_StorePreviousFocus(object sender, KeyboardFocusChangedEventArgs e)
112 {
113 // \brief OldFocus가 Window/ContentControl로 들어오는 경우가 있으므로 교정
114 var old = e.OldFocus;
115
116 if (old is null)
117 {
119 return;
120 }
121
122 if (ReferenceEquals(old, this))
123 {
124 // \brief 자기 자신이면 의미 없음
125 return;
126 }
127
128 if (old is DependencyObject dobj)
129 {
130 if (old is Window || old is ContentControl)
131 {
132 var child = FindFirstFocusableChild(dobj);
133 _previousFocusedElement = child ?? old;
134 return;
135 }
136 }
137
139 }
140
173 protected override void OnPreviewKeyDown(KeyEventArgs e)
174 {
175 base.OnPreviewKeyDown(e);
176
177 if (e.Key != Key.Enter)
178 return;
179
180 var target = _previousFocusedElement;
181
182 if (target == null || ReferenceEquals(target, this))
183 return;
184
185 // \brief Enter 키 입력은 여기서 소비
186 e.Handled = true;
187
188 // \brief 이벤트 파이프라인/내부 TextBox 처리 후 포커스 이동
189 Dispatcher.BeginInvoke(new Action(() =>
190 {
191 try
192 {
193 // \brief UIElement이면 Focus()가 더 잘 먹는 경우가 많음
194 if (target is UIElement uie)
195 {
196 uie.Focus();
197 Keyboard.Focus(uie);
198 return;
199 }
200
201 Keyboard.Focus(target);
202 }
203 catch
204 {
205 // \brief 포커스 실패는 치명적이지 않으므로 무시(정책에 맞게 로그로 바꿔도 됨)
206 }
207 }), System.Windows.Threading.DispatcherPriority.Input);
208 }
209
242 private UIElement? FindFirstFocusableChild(DependencyObject parent)
243 {
244 for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
245 {
246 var child = VisualTreeHelper.GetChild(parent, i);
247
248 if (child is UIElement uie && uie.Focusable && uie.IsVisible && uie.IsEnabled)
249 return uie;
250
251 var result = FindFirstFocusableChild(child);
252 if (result != null)
253 return result;
254 }
255 return null;
256 }
257
258 // =====================================================================
259 // Attached Command: Command / CommandParameter / CommandTriggerName
260 // =====================================================================
261
270 public static readonly DependencyProperty CommandProperty =
271 DependencyProperty.RegisterAttached(
272 "Command",
273 typeof(ICommand),
274 typeof(DreamineTextBox),
275 new PropertyMetadata(null, OnCommandChanged));
276
285 public static readonly DependencyProperty CommandParameterProperty =
286 DependencyProperty.RegisterAttached(
287 "CommandParameter",
288 typeof(object),
289 typeof(DreamineTextBox),
290 new PropertyMetadata(null));
291
300 public static readonly DependencyProperty CommandTriggerNameProperty =
301 DependencyProperty.RegisterAttached(
302 "CommandTriggerName",
303 typeof(string),
304 typeof(DreamineTextBox),
305 new PropertyMetadata(null));
306
339 public static void SetCommandTriggerName(DependencyObject obj, string value)
340 => obj.SetValue(CommandTriggerNameProperty, value);
341
374 public static string GetCommandTriggerName(DependencyObject obj)
375 => (string)obj.GetValue(CommandTriggerNameProperty);
376
409 public static void SetCommand(DependencyObject obj, ICommand value)
410 => obj.SetValue(CommandProperty, value);
411
452 public static ICommand GetCommand(DependencyObject obj)
453 => (ICommand)obj.GetValue(CommandProperty);
454
487 public static void SetCommandParameter(DependencyObject obj, object value)
488 => obj.SetValue(CommandParameterProperty, value);
489
522 public static object GetCommandParameter(DependencyObject obj)
523 => obj.GetValue(CommandParameterProperty);
524
557 private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
558 {
559 if (d is not UIElement element)
560 return;
561
562 element.AddHandler(UIElement.PreviewMouseUpEvent, new MouseButtonEventHandler((s, args) =>
563 {
564 TryExecuteCommand(d, "PreviewMouseUp", args);
565 }), true);
566
567 element.AddHandler(Control.MouseDoubleClickEvent, new MouseButtonEventHandler((s, args) =>
568 {
569 TryExecuteCommand(d, "MouseDoubleClick", args);
570 }), true);
571
572 element.AddHandler(UIElement.PreviewKeyDownEvent, new KeyEventHandler((s, args) =>
573 {
574 // \brief 기존 규칙 유지: PreviewKeyDown 훅이지만 이름은 "PreviewKeyUp"
575 TryExecuteCommand(d, "PreviewKeyUp", args);
576 }), true);
577
578 element.AddHandler(UIElement.TouchUpEvent, new EventHandler<TouchEventArgs>((s, args) =>
579 {
580 TryExecuteCommand(d, "TouchUp", args);
581 }));
582
583 element.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler((s, args) =>
584 {
585 TryExecuteCommand(d, "Click", args);
586 }));
587
588 element.AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler((s, args) =>
589 {
590 TryExecuteCommand(d, "TextChanged", args);
591 }));
592 }
593
634 private static void TryExecuteCommand(DependencyObject d, string eventName, RoutedEventArgs eventArgs)
635 {
636 var rawTrigger = GetCommandTriggerName(d);
637 if (string.IsNullOrEmpty(rawTrigger))
638 return;
639
640 var triggerList = rawTrigger.Split(',')
641 .Select(x => x.Trim())
642 .Where(x => !string.IsNullOrEmpty(x));
643
644 if (!triggerList.Contains(eventName, StringComparer.OrdinalIgnoreCase))
645 return;
646
647 var command = GetCommand(d);
648 var parameter = GetCommandParameter(d) ?? eventArgs;
649
650 if (command?.CanExecute(parameter) == true)
651 command.Execute(parameter);
652 }
653
654 // =====================================================================
655 // Hint / Error
656 // =====================================================================
657
666 public static readonly DependencyProperty HintProperty =
667 DependencyProperty.Register(nameof(Hint), typeof(string), typeof(DreamineTextBox), new PropertyMetadata(string.Empty));
668
677 public string Hint
678 {
679 get => (string)GetValue(HintProperty);
680 set => SetValue(HintProperty, value);
681 }
682
691 public static readonly DependencyProperty ErrorProperty =
692 DependencyProperty.Register(nameof(Error), typeof(string), typeof(DreamineTextBox), new PropertyMetadata(string.Empty));
693
702 public string Error
703 {
704 get => (string)GetValue(ErrorProperty);
705 set => SetValue(ErrorProperty, value);
706 }
707 }
708}
static readonly DependencyProperty HintProperty
static ICommand GetCommand(DependencyObject obj)
static readonly DependencyProperty CommandParameterProperty
void OnGotKeyboardFocus_StorePreviousFocus(object sender, KeyboardFocusChangedEventArgs e)
static string GetCommandTriggerName(DependencyObject obj)
static void TryExecuteCommand(DependencyObject d, string eventName, RoutedEventArgs eventArgs)
static void SetCommandParameter(DependencyObject obj, object value)
static readonly DependencyProperty CommandTriggerNameProperty
static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
static object GetCommandParameter(DependencyObject obj)
UIElement? FindFirstFocusableChild(DependencyObject parent)
static readonly DependencyProperty CommandProperty
override void OnPreviewKeyDown(KeyEventArgs e)
static void SetCommandTriggerName(DependencyObject obj, string value)
static readonly DependencyProperty ErrorProperty
static void SetCommand(DependencyObject obj, ICommand value)