1+ using System ;
2+ using System . Reflection ;
3+ using System . Windows ;
4+ using System . Windows . Controls ;
5+ using System . Windows . Controls . Primitives ;
6+ using System . Windows . Input ;
7+ using iNKORE . UI . WPF . Converters ;
8+
9+ namespace iNKORE . UI . WPF . Modern . Controls . Helpers
10+ {
11+ public sealed class WinUIComboBoxBehaviorHelper
12+ {
13+ private const string c_popupBorderName = "PopupBorder" ;
14+
15+ private const string c_editableTextName = "PART_EditableTextBox" ;
16+
17+ //private const string c_editableTextBorderName = "BorderElement";
18+ private const string c_backgroundName = "Background" ;
19+
20+ private const string c_highlightBackgroundName = "HighlightBackground" ;
21+
22+ //private const string c_controlCornerRadiusKey = "ControlCornerRadius";
23+ private const string c_overlayCornerRadiusKey = "OverlayCornerRadius" ;
24+
25+ /// <summary>
26+ /// Adds WinUI behaviors
27+ /// 1. align selected container in popup to combobox.
28+ /// 2. in case of no selection, first in popup is highlighted (done)
29+ /// 3. mouse hovering shouldn't trigger focus ?! (done)
30+ /// 4. persist selected item only when drop down closes?? (not done)
31+ /// 5. KeepInteriorCornersSquare (already done)
32+ /// </summary>
33+ public static readonly DependencyProperty IsEnabledProperty =
34+ DependencyProperty . RegisterAttached (
35+ "IsEnabled" ,
36+ typeof ( bool ) ,
37+ typeof ( WinUIComboBoxBehaviorHelper ) ,
38+ new PropertyMetadata ( false , OnIsEnabledChanged ) ) ;
39+
40+ public static bool GetIsEnabled ( ComboBox comboBox )
41+ {
42+ return ( bool ) comboBox . GetValue ( IsEnabledProperty ) ;
43+ }
44+
45+ public static void SetIsEnabled ( ComboBox comboBox , bool value )
46+ {
47+ comboBox . SetValue ( IsEnabledProperty , value ) ;
48+ }
49+
50+ private static void OnIsEnabledChanged ( DependencyObject sender ,
51+ DependencyPropertyChangedEventArgs args )
52+ {
53+ if ( sender is ComboBox comboBox )
54+ {
55+ bool shouldMonitorDropDownState = ( bool ) args . NewValue ;
56+ if ( shouldMonitorDropDownState )
57+ {
58+ comboBox . DropDownOpened += OnDropDownOpened ;
59+ comboBox . DropDownClosed += OnDropDownClosed ;
60+ }
61+ else
62+ {
63+ comboBox . DropDownOpened -= OnDropDownOpened ;
64+ comboBox . DropDownClosed -= OnDropDownClosed ;
65+ }
66+ }
67+ }
68+
69+ private static void OnOverrideMouseEnter ( object sender , MouseEventArgs e )
70+ {
71+ e . Handled = true ;
72+ }
73+
74+ private static void OnDropDownClosed ( object sender , object args )
75+ {
76+ var comboBox = ( ComboBox ) sender ;
77+ UpdateCornerRadius ( comboBox , null , /*IsDropDownOpen=*/ false , false ) ;
78+ }
79+
80+ private static void OnDropDownOpened ( object sender , object args )
81+ {
82+ var comboBox = ( ComboBox ) sender ;
83+ comboBox . Dispatcher . BeginInvoke ( ( ) =>
84+ {
85+ var popup = GetTemplateChild < Popup > ( "PART_Popup" , comboBox ) ;
86+ var isOpenDown = IsPopupOpenDown ( comboBox , popup . VerticalOffset ) ;
87+
88+ AlignSelectedContainer ( comboBox , popup , isOpenDown ) ;
89+ UpdateCornerRadius ( comboBox , popup , true , isOpenDown ) ;
90+ } ) ;
91+ }
92+
93+ private static void AlignSelectedContainer ( ComboBox comboBox , Popup popup , bool isOpenDown )
94+ {
95+ if ( ! isOpenDown ||
96+ GetToAlignContainer ( comboBox ) is not { } itemContainer ||
97+ itemContainer . TranslatePoint ( new Point ( 0 , - itemContainer . ActualHeight + comboBox . Padding . Top ) ,
98+ comboBox ) is not { Y : not 0 } itemTop )
99+ {
100+ return ;
101+ }
102+
103+ popup . VerticalOffset -= itemTop . Y ;
104+
105+ if ( itemContainer . ActualHeight - comboBox . ActualHeight > 0 )
106+ {
107+ popup . VerticalOffset -= comboBox . ActualHeight ;
108+ }
109+ }
110+
111+ private static FrameworkElement GetToAlignContainer ( ComboBox comboBox )
112+ {
113+ DependencyObject container ;
114+ if ( comboBox . SelectedItem is null )
115+ {
116+ container = comboBox . ItemContainerGenerator . ContainerFromIndex (
117+ ( int ) Math . Ceiling ( comboBox . Items . Count / 2.0 ) ) ;
118+
119+ if ( comboBox . ItemContainerGenerator . ContainerFromIndex ( 0 ) is ComboBoxItem item )
120+ {
121+ var highlightedInfoProperty = typeof ( ComboBox ) . GetProperty ( "HighlightedInfo" ,
122+ BindingFlags . Instance | BindingFlags . NonPublic ) ;
123+
124+ var setter = highlightedInfoProperty . SetMethod ;
125+
126+ var itemInfo = typeof ( ComboBox )
127+ . GetMethod ( "ItemInfoFromContainer" , BindingFlags . Instance | BindingFlags . NonPublic ) ?
128+ . Invoke ( comboBox , [ item ] ) ;
129+
130+ setter ? . Invoke ( comboBox , [ itemInfo ] ) ;
131+ }
132+ }
133+ else
134+ {
135+ container = comboBox . ItemContainerGenerator . ContainerFromItem ( comboBox . SelectedItem ) ;
136+ }
137+
138+ return container as FrameworkElement ;
139+ }
140+
141+ private static void UpdateCornerRadius ( ComboBox comboBox , Popup ? popup , bool isDropDownOpen , bool isOpenDown )
142+ {
143+ var textBoxRadius = ControlHelper . GetCornerRadius ( comboBox ) ;
144+ var popupRadius = ( CornerRadius ) ResourceLookup ( comboBox , c_overlayCornerRadiusKey ) ;
145+
146+ if ( isDropDownOpen )
147+ {
148+ if ( popup ? . VerticalOffset is 0 )
149+ {
150+ popupRadius = GetFilteredPopupRadius ( popupRadius , isOpenDown ) ;
151+ }
152+
153+ var textBoxRadiusFilter = isOpenDown ? CornerRadiusFilterKind . Top : CornerRadiusFilterKind . Bottom ;
154+ textBoxRadius = CornerRadiusFilterConverter . Convert ( textBoxRadius , textBoxRadiusFilter ) ;
155+ }
156+
157+ if ( GetTemplateChild < Border > ( c_popupBorderName , comboBox ) is { } popupBorder )
158+ {
159+ popupBorder . CornerRadius = popupRadius ;
160+ }
161+
162+ if ( comboBox . IsEditable )
163+ {
164+ if ( GetTemplateChild < TextBox > ( c_editableTextName , comboBox ) is { } textBox )
165+ {
166+ ControlHelper . SetCornerRadius ( textBox , textBoxRadius ) ;
167+ }
168+ }
169+ else
170+ {
171+ if ( GetTemplateChild < Border > ( c_backgroundName , comboBox ) is { } background )
172+ {
173+ background . CornerRadius = textBoxRadius ;
174+ }
175+
176+ if ( GetTemplateChild < Border > ( c_highlightBackgroundName , comboBox ) is { } highlightBackground )
177+ {
178+ highlightBackground . CornerRadius = textBoxRadius ;
179+ }
180+ }
181+ }
182+
183+ private static CornerRadius GetFilteredPopupRadius ( CornerRadius popupRadius , bool isOpenDown )
184+ {
185+ var popupRadiusFilter = isOpenDown ? CornerRadiusFilterKind . Bottom : CornerRadiusFilterKind . Top ;
186+ return CornerRadiusFilterConverter . Convert ( popupRadius , popupRadiusFilter ) ;
187+ }
188+
189+ private static bool IsPopupOpenDown ( ComboBox comboBox , double popupVerticalOffset )
190+ {
191+ double verticalOffset = popupVerticalOffset ;
192+ if ( GetTemplateChild < Border > ( c_popupBorderName , comboBox ) is { } popupBorder )
193+ {
194+ if ( GetTemplateChild < TextBox > ( c_editableTextName , comboBox ) is { } textBox )
195+ {
196+ var popupTop = popupBorder . TranslatePoint ( new Point ( 0 , 0 ) , textBox ) ;
197+ verticalOffset = popupTop . Y ;
198+ }
199+ }
200+
201+ return verticalOffset > popupVerticalOffset ;
202+ }
203+
204+ private static object ResourceLookup ( Control control , object key )
205+ {
206+ return control . TryFindResource ( key ) ;
207+ }
208+
209+ private static T GetTemplateChild < T > ( string childName , Control control ) where T : DependencyObject
210+ {
211+ return control . Template ? . FindName ( childName , control ) as T ;
212+ }
213+ }
214+ }
0 commit comments