1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . ComponentModel ;
3
4
using System . Diagnostics ;
4
5
using System . Linq ;
5
6
using System . Windows ;
@@ -26,7 +27,7 @@ public static readonly DependencyProperty UpContentTemplateProperty
26
27
= DependencyProperty . Register ( nameof ( UpContentTemplate ) ,
27
28
typeof ( ControlTemplate ) ,
28
29
typeof ( ComboBoxPopup ) ,
29
- new UIPropertyMetadata ( null ) ) ;
30
+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Classic ) ) ) ;
30
31
31
32
public ControlTemplate UpContentTemplate
32
33
{
@@ -42,7 +43,7 @@ public static readonly DependencyProperty DownContentTemplateProperty
42
43
= DependencyProperty . Register ( nameof ( DownContentTemplate ) ,
43
44
typeof ( ControlTemplate ) ,
44
45
typeof ( ComboBoxPopup ) ,
45
- new UIPropertyMetadata ( null ) ) ;
46
+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Down ) ) ) ;
46
47
47
48
public ControlTemplate DownContentTemplate
48
49
{
@@ -58,7 +59,7 @@ public static readonly DependencyProperty ClassicContentTemplateProperty
58
59
= DependencyProperty . Register ( nameof ( ClassicContentTemplate ) ,
59
60
typeof ( ControlTemplate ) ,
60
61
typeof ( ComboBoxPopup ) ,
61
- new UIPropertyMetadata ( null ) ) ;
62
+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Up ) ) ) ;
62
63
63
64
public ControlTemplate ClassicContentTemplate
64
65
{
@@ -166,9 +167,36 @@ public static readonly DependencyProperty VisiblePlacementWidthProperty
166
167
167
168
#endregion
168
169
170
+ #region OnlyClassicMode property
171
+
172
+ public static readonly DependencyProperty OnlyClassicModeProperty
173
+ = DependencyProperty . Register (
174
+ nameof ( OnlyClassicMode ) ,
175
+ typeof ( bool ) ,
176
+ typeof ( ComboBoxPopup ) ,
177
+ new FrameworkPropertyMetadata ( true ) ) ;
178
+
179
+ public bool OnlyClassicMode
180
+ {
181
+ get { return ( bool ) GetValue ( OnlyClassicModeProperty ) ; }
182
+ set { SetValue ( OnlyClassicModeProperty , value ) ; }
183
+ }
184
+
185
+ #endregion
186
+
169
187
public ComboBoxPopup ( )
170
188
{
171
- this . CustomPopupPlacementCallback = ComboBoxCustomPopupPlacementCallback ;
189
+ CustomPopupPlacementCallback = ComboBoxCustomPopupPlacementCallback ;
190
+
191
+ DependencyPropertyDescriptor . FromProperty ( ComboBoxPopup . ChildProperty , typeof ( ComboBoxPopup ) )
192
+ . AddValueChanged ( this ,
193
+ delegate
194
+ {
195
+ if ( PopupPlacement != ComboBoxPopupPlacement . Undefined )
196
+ {
197
+ UpdateChildTemplate ( PopupPlacement ) ;
198
+ }
199
+ } ) ;
172
200
}
173
201
174
202
private void SetupBackground ( IEnumerable < DependencyObject > visualAncestry )
@@ -198,88 +226,103 @@ private CustomPopupPlacement[] ComboBoxCustomPopupPlacementCallback(
198
226
199
227
SetupVisiblePlacementWidth ( visualAncestry ) ;
200
228
201
- var locationFromScreen = PlacementTarget . PointToScreen ( new Point ( 0 , 0 ) ) ;
202
-
203
- var mainVisual = visualAncestry . OfType < Visual > ( ) . LastOrDefault ( ) ;
204
- if ( mainVisual == null ) return new CustomPopupPlacement [ 0 ] ;
205
-
206
- var screenWidth = ( int ) DpiHelper . TransformToDeviceX ( mainVisual , SystemParameters . PrimaryScreenWidth ) ;
207
- var screenHeight = ( int ) DpiHelper . TransformToDeviceY ( mainVisual , SystemParameters . PrimaryScreenHeight ) ;
208
-
209
- var locationX = ( int ) locationFromScreen . X % screenWidth ;
210
- var locationY = ( int ) locationFromScreen . Y % screenHeight ;
211
-
212
- var realOffsetX = ( popupSize . Width - targetSize . Width ) / 2.0 ;
213
-
214
- double offsetX ;
215
- const int rtlHorizontalOffset = 20 ;
216
-
217
- if ( FlowDirection == FlowDirection . LeftToRight )
218
- offsetX = DpiHelper . TransformToDeviceX ( mainVisual , offset . X ) ;
219
- else
220
- offsetX = DpiHelper . TransformToDeviceX ( mainVisual ,
221
- offset . X - targetSize . Width - rtlHorizontalOffset ) ;
229
+ var data = GetPositioningData ( visualAncestry , popupSize , targetSize , offset ) ;
222
230
223
-
224
- if ( locationX + popupSize . Width - realOffsetX > screenWidth
225
- || locationX - realOffsetX < 0 )
231
+ if ( OnlyClassicMode ||
232
+ ( data . LocationX + data . PopupSize . Width - data . RealOffsetX > data . ScreenWidth
233
+ || data . LocationX - data . RealOffsetX < 0 ) )
226
234
{
227
235
PopupPlacement = ComboBoxPopupPlacement . Classic ;
228
236
229
- var defaultVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( mainVisual , DefaultVerticalOffset ) ;
230
- var newY = locationY + popupSize . Height > screenHeight
231
- ? - ( defaultVerticalOffsetIndepent + popupSize . Height )
232
- : defaultVerticalOffsetIndepent + targetSize . Height ;
233
-
234
- return new [ ] { new CustomPopupPlacement ( new Point ( offsetX , newY ) , PopupPrimaryAxis . Horizontal ) } ;
237
+ return new [ ] { GetClassicPopupPlacement ( this , data ) } ;
235
238
}
236
-
237
- if ( locationY + popupSize . Height > screenHeight )
239
+ else if ( data . LocationY + data . PopupSize . Height > data . ScreenHeight )
238
240
{
239
241
PopupPlacement = ComboBoxPopupPlacement . Up ;
240
242
241
- var upVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( mainVisual , UpVerticalOffset ) ;
242
- var newY = upVerticalOffsetIndepent - popupSize . Height + targetSize . Height ;
243
-
244
- return new [ ] { new CustomPopupPlacement ( new Point ( offsetX , newY ) , PopupPrimaryAxis . None ) } ;
243
+ return new [ ] { GetUpPopupPlacement ( this , data ) } ;
245
244
}
246
245
else
247
246
{
248
247
PopupPlacement = ComboBoxPopupPlacement . Down ;
249
248
250
- var downVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( mainVisual , DownVerticalOffset ) ;
251
- var newY = downVerticalOffsetIndepent ;
252
-
253
- return new [ ] { new CustomPopupPlacement ( new Point ( offsetX , newY ) , PopupPrimaryAxis . None ) } ;
249
+ return new [ ] { GetDownPopupPlacement ( this , data ) } ;
254
250
}
255
251
}
256
252
257
253
private void SetChildTemplateIfNeed ( ControlTemplate template )
258
254
{
259
255
var contentControl = Child as ContentControl ;
260
- if ( contentControl == null ) throw new InvalidOperationException ( "Child must be ContentControl" ) ;
256
+ if ( contentControl == null ) return ;
257
+ //throw new InvalidOperationException($"The type of {nameof(Child)} must be {nameof(ContentControl)}");
261
258
262
259
if ( ! ReferenceEquals ( contentControl . Template , template ) )
263
260
{
264
261
contentControl . Template = template ;
265
262
}
266
263
}
267
264
268
- private void SetChildTemplate ( ComboBoxPopupPlacement placement )
265
+ private PositioningData GetPositioningData ( IEnumerable < DependencyObject > visualAncestry , Size popupSize , Size targetSize , Point offset )
266
+ {
267
+ var locationFromScreen = PlacementTarget . PointToScreen ( new Point ( 0 , 0 ) ) ;
268
+
269
+ var mainVisual = visualAncestry . OfType < Visual > ( ) . LastOrDefault ( ) ;
270
+ if ( mainVisual == null ) throw new ArgumentException ( $ "{ nameof ( visualAncestry ) } must contains unless one { nameof ( Visual ) } control inside.") ;
271
+
272
+ var screenWidth = ( int ) DpiHelper . TransformToDeviceX ( mainVisual , SystemParameters . PrimaryScreenWidth ) ;
273
+ var screenHeight = ( int ) DpiHelper . TransformToDeviceY ( mainVisual , SystemParameters . PrimaryScreenHeight ) ;
274
+
275
+ var locationX = ( int ) locationFromScreen . X % screenWidth ;
276
+ var locationY = ( int ) locationFromScreen . Y % screenHeight ;
277
+
278
+ double offsetX ;
279
+ const int rtlHorizontalOffset = 20 ;
280
+
281
+ if ( FlowDirection == FlowDirection . LeftToRight )
282
+ offsetX = DpiHelper . TransformToDeviceX ( mainVisual , offset . X ) ;
283
+ else
284
+ offsetX = DpiHelper . TransformToDeviceX ( mainVisual ,
285
+ offset . X - targetSize . Width - rtlHorizontalOffset ) ;
286
+
287
+ return new PositioningData (
288
+ mainVisual , offsetX ,
289
+ popupSize , targetSize ,
290
+ locationX , locationY ,
291
+ screenHeight , screenWidth ) ;
292
+ }
293
+
294
+ private static PropertyChangedCallback CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement popupPlacement )
295
+ {
296
+ return delegate ( DependencyObject d , DependencyPropertyChangedEventArgs e )
297
+ {
298
+ var popup = d as ComboBoxPopup ;
299
+ if ( popup == null ) return ;
300
+
301
+ var template = e . NewValue as ControlTemplate ;
302
+ if ( template == null ) return ;
303
+
304
+ if ( popup . PopupPlacement == popupPlacement )
305
+ {
306
+ popup . SetChildTemplateIfNeed ( template ) ;
307
+ }
308
+ } ;
309
+ }
310
+
311
+ private void UpdateChildTemplate ( ComboBoxPopupPlacement placement )
269
312
{
270
313
switch ( placement )
271
314
{
272
315
case ComboBoxPopupPlacement . Classic :
273
316
SetChildTemplateIfNeed ( ClassicContentTemplate ) ;
274
- return ;
317
+ break ;
275
318
case ComboBoxPopupPlacement . Down :
276
319
SetChildTemplateIfNeed ( DownContentTemplate ) ;
277
- return ;
320
+ break ;
278
321
case ComboBoxPopupPlacement . Up :
279
322
SetChildTemplateIfNeed ( UpContentTemplate ) ;
280
- return ;
281
- default :
282
- throw new NotImplementedException ( $ "Unexpected value { placement } of the { nameof ( PopupPlacement ) } property inside the { nameof ( ComboBoxPopup ) } control.") ;
323
+ break ;
324
+ // default:
325
+ // throw new NotImplementedException($"Unexpected value {placement} of the {nameof(PopupPlacement)} property inside the {nameof(ComboBoxPopup)} control.");
283
326
}
284
327
}
285
328
@@ -290,8 +333,59 @@ private static void PopupPlacementPropertyChangedCallback(DependencyObject d, De
290
333
291
334
if ( ! ( e . NewValue is ComboBoxPopupPlacement ) ) return ;
292
335
var placement = ( ComboBoxPopupPlacement ) e . NewValue ;
336
+ popup . UpdateChildTemplate ( placement ) ;
337
+ }
338
+
339
+ private static CustomPopupPlacement GetClassicPopupPlacement ( ComboBoxPopup popup , PositioningData data )
340
+ {
341
+ var defaultVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( data . MainVisual , popup . DefaultVerticalOffset ) ;
342
+ var newY = data . LocationY + data . PopupSize . Height > data . ScreenHeight
343
+ ? - ( defaultVerticalOffsetIndepent + data . PopupSize . Height )
344
+ : defaultVerticalOffsetIndepent + data . TargetSize . Height ;
345
+
346
+ return new CustomPopupPlacement ( new Point ( data . OffsetX , newY ) , PopupPrimaryAxis . Horizontal ) ;
347
+ }
348
+
349
+ private static CustomPopupPlacement GetDownPopupPlacement ( ComboBoxPopup popup , PositioningData data )
350
+ {
351
+ var downVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( data . MainVisual , popup . DownVerticalOffset ) ;
293
352
294
- popup . SetChildTemplate ( placement ) ;
353
+ return new CustomPopupPlacement ( new Point ( data . OffsetX , downVerticalOffsetIndepent ) , PopupPrimaryAxis . None ) ;
354
+ }
355
+
356
+ private static CustomPopupPlacement GetUpPopupPlacement ( ComboBoxPopup popup , PositioningData data )
357
+ {
358
+ var upVerticalOffsetIndepent = DpiHelper . TransformToDeviceY ( data . MainVisual , popup . UpVerticalOffset ) ;
359
+ var newY = upVerticalOffsetIndepent - data . PopupSize . Height + data . TargetSize . Height ;
360
+
361
+ return new CustomPopupPlacement ( new Point ( data . OffsetX , newY ) , PopupPrimaryAxis . None ) ;
362
+ }
363
+
364
+ private struct PositioningData
365
+ {
366
+ public Visual MainVisual { get ; }
367
+ public double OffsetX { get ; }
368
+ public double RealOffsetX => ( PopupSize . Width - TargetSize . Width ) / 2.0 ;
369
+ public Size PopupSize { get ; }
370
+ public Size TargetSize { get ; }
371
+ public double LocationX { get ; }
372
+ public double LocationY { get ; }
373
+ public double ScreenHeight { get ; }
374
+ public double ScreenWidth { get ; }
375
+
376
+ public PositioningData (
377
+ Visual mainVisual ,
378
+ double offsetX ,
379
+ Size popupSize , Size targetSize ,
380
+ double locationX , double locationY ,
381
+ double screenHeight , double screenWidth )
382
+ {
383
+ MainVisual = mainVisual ;
384
+ OffsetX = offsetX ;
385
+ PopupSize = popupSize ; TargetSize = targetSize ;
386
+ LocationX = locationX ; LocationY = locationY ;
387
+ ScreenWidth = screenWidth ; ScreenHeight = screenHeight ;
388
+ }
295
389
}
296
390
}
297
391
}
0 commit comments