11using System ;
22using System . Collections . Generic ;
3+ using System . ComponentModel ;
34using System . Diagnostics ;
45using System . Linq ;
56using System . Windows ;
@@ -26,7 +27,7 @@ public static readonly DependencyProperty UpContentTemplateProperty
2627 = DependencyProperty . Register ( nameof ( UpContentTemplate ) ,
2728 typeof ( ControlTemplate ) ,
2829 typeof ( ComboBoxPopup ) ,
29- new UIPropertyMetadata ( null ) ) ;
30+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Classic ) ) ) ;
3031
3132 public ControlTemplate UpContentTemplate
3233 {
@@ -42,7 +43,7 @@ public static readonly DependencyProperty DownContentTemplateProperty
4243 = DependencyProperty . Register ( nameof ( DownContentTemplate ) ,
4344 typeof ( ControlTemplate ) ,
4445 typeof ( ComboBoxPopup ) ,
45- new UIPropertyMetadata ( null ) ) ;
46+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Down ) ) ) ;
4647
4748 public ControlTemplate DownContentTemplate
4849 {
@@ -58,7 +59,7 @@ public static readonly DependencyProperty ClassicContentTemplateProperty
5859 = DependencyProperty . Register ( nameof ( ClassicContentTemplate ) ,
5960 typeof ( ControlTemplate ) ,
6061 typeof ( ComboBoxPopup ) ,
61- new UIPropertyMetadata ( null ) ) ;
62+ new UIPropertyMetadata ( null , CreateTemplatePropertyChangedCallback ( ComboBoxPopupPlacement . Up ) ) ) ;
6263
6364 public ControlTemplate ClassicContentTemplate
6465 {
@@ -166,9 +167,36 @@ public static readonly DependencyProperty VisiblePlacementWidthProperty
166167
167168 #endregion
168169
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+
169187 public ComboBoxPopup ( )
170188 {
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+ } ) ;
172200 }
173201
174202 private void SetupBackground ( IEnumerable < DependencyObject > visualAncestry )
@@ -198,88 +226,103 @@ private CustomPopupPlacement[] ComboBoxCustomPopupPlacementCallback(
198226
199227 SetupVisiblePlacementWidth ( visualAncestry ) ;
200228
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 ) ;
222230
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 ) )
226234 {
227235 PopupPlacement = ComboBoxPopupPlacement . Classic ;
228236
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 ) } ;
235238 }
236-
237- if ( locationY + popupSize . Height > screenHeight )
239+ else if ( data . LocationY + data . PopupSize . Height > data . ScreenHeight )
238240 {
239241 PopupPlacement = ComboBoxPopupPlacement . Up ;
240242
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 ) } ;
245244 }
246245 else
247246 {
248247 PopupPlacement = ComboBoxPopupPlacement . Down ;
249248
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 ) } ;
254250 }
255251 }
256252
257253 private void SetChildTemplateIfNeed ( ControlTemplate template )
258254 {
259255 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)}");
261258
262259 if ( ! ReferenceEquals ( contentControl . Template , template ) )
263260 {
264261 contentControl . Template = template ;
265262 }
266263 }
267264
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 )
269312 {
270313 switch ( placement )
271314 {
272315 case ComboBoxPopupPlacement . Classic :
273316 SetChildTemplateIfNeed ( ClassicContentTemplate ) ;
274- return ;
317+ break ;
275318 case ComboBoxPopupPlacement . Down :
276319 SetChildTemplateIfNeed ( DownContentTemplate ) ;
277- return ;
320+ break ;
278321 case ComboBoxPopupPlacement . Up :
279322 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.");
283326 }
284327 }
285328
@@ -290,8 +333,59 @@ private static void PopupPlacementPropertyChangedCallback(DependencyObject d, De
290333
291334 if ( ! ( e . NewValue is ComboBoxPopupPlacement ) ) return ;
292335 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 ) ;
293352
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+ }
295389 }
296390 }
297391}
0 commit comments