10
10
using System . Windows . Input ;
11
11
using System . Windows . Markup ;
12
12
using System . Windows . Media ;
13
+ using System . Windows . Media . Animation ;
13
14
using Controlz ;
14
15
15
16
namespace MaterialDesignThemes . Wpf
@@ -45,23 +46,43 @@ public enum PopupBoxPlacementMode
45
46
TopAndAlignCentres
46
47
}
47
48
49
+ /// <summary>
50
+ /// Defines what causes the <see cref="PopupBox"/> to open it's popup.
51
+ /// </summary>
52
+ public enum PopupBoxPopupMode
53
+ {
54
+ /// <summary>
55
+ /// Open when the toggle button is clicked.
56
+ /// </summary>
57
+ Click ,
58
+ /// <summary>
59
+ /// Open when the mouse goes over the toggle button.
60
+ /// </summary>
61
+ MouseOver ,
62
+ /// <summary>
63
+ /// Open when the mouse goes over the toggle button, or the space in which the popup box would occupy should it be open.
64
+ /// </summary>
65
+ MouseOverEager
66
+ }
67
+
48
68
/// <summary>
49
69
/// Popup box, similar to a <see cref="ComboBox"/>, but allows more customizable content.
50
70
/// </summary>
51
71
[ TemplatePart ( Name = PopupPartName , Type = typeof ( Popup ) ) ]
52
- [ TemplatePart ( Name = TogglePartName , Type = typeof ( ToggleButton ) ) ]
72
+ [ TemplatePart ( Name = PopupContentControlPartName , Type = typeof ( ContentControl ) ) ]
53
73
[ TemplateVisualState ( GroupName = "PopupStates" , Name = PopupIsOpenStateName ) ]
54
74
[ TemplateVisualState ( GroupName = "PopupStates" , Name = PopupIsClosedStateName ) ]
55
75
[ ContentProperty ( "PopupContent" ) ]
56
76
public class PopupBox : ContentControl
57
77
{
58
78
public const string PopupPartName = "PART_Popup" ;
59
79
public const string TogglePartName = "PART_Toggle" ;
80
+ public const string PopupContentControlPartName = "PART_PopupContentControl" ;
60
81
public const string PopupIsOpenStateName = "IsOpen" ;
61
82
public const string PopupIsClosedStateName = "IsClosed" ;
62
83
private PopupEx _popup ;
63
- private ToggleButton _toggleButton ;
64
-
84
+ private ContentControl _popupContentControl ;
85
+
65
86
static PopupBox ( )
66
87
{
67
88
DefaultStyleKeyProperty . OverrideMetadata ( typeof ( PopupBox ) , new FrameworkPropertyMetadata ( typeof ( PopupBox ) ) ) ;
@@ -137,13 +158,21 @@ private static void IsPopupOpenPropertyChangedCallback(DependencyObject dependen
137
158
{
138
159
var popupBox = ( PopupBox ) dependencyObject ;
139
160
var newValue = ( bool ) dependencyPropertyChangedEventArgs . NewValue ;
161
+ if ( popupBox . PopupMode == PopupBoxPopupMode . Click )
162
+ {
163
+ if ( newValue )
164
+ Mouse . Capture ( popupBox , CaptureMode . SubTree ) ;
165
+ else
166
+ Mouse . Capture ( null ) ;
167
+ }
168
+
140
169
if ( newValue )
141
- Mouse . Capture ( popupBox , CaptureMode . SubTree ) ;
142
- else
143
- Mouse . Capture ( null ) ;
170
+ {
171
+ popupBox . AnimateChildren ( ) ;
172
+ }
144
173
145
174
VisualStateManager . GoToState ( popupBox , newValue ? PopupIsOpenStateName : PopupIsClosedStateName , true ) ;
146
- }
175
+ }
147
176
148
177
/// <summary>
149
178
/// Gets or sets whether the popup is currently open.
@@ -169,23 +198,44 @@ public bool StaysOpen
169
198
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty . Register (
170
199
"PlacementMode" , typeof ( PopupBoxPlacementMode ) , typeof ( PopupBox ) , new PropertyMetadata ( default ( PopupBoxPlacementMode ) ) ) ;
171
200
201
+ /// <summary>
202
+ /// Gets or sets how the popup is aligned in relation to the toggle.
203
+ /// </summary>
172
204
public PopupBoxPlacementMode PlacementMode
173
205
{
174
206
get { return ( PopupBoxPlacementMode ) GetValue ( PropertyTypeProperty ) ; }
175
207
set { SetValue ( PropertyTypeProperty , value ) ; }
176
208
}
177
209
210
+ public static readonly DependencyProperty PopupModeProperty = DependencyProperty . Register (
211
+ "PopupMode" , typeof ( PopupBoxPopupMode ) , typeof ( PopupBox ) , new PropertyMetadata ( default ( PopupBoxPopupMode ) ) ) ;
212
+
213
+ /// <summary>
214
+ /// Gets or sets what trigger causes the popup to open.
215
+ /// </summary>
216
+ public PopupBoxPopupMode PopupMode
217
+ {
218
+ get { return ( PopupBoxPopupMode ) GetValue ( PopupModeProperty ) ; }
219
+ set { SetValue ( PopupModeProperty , value ) ; }
220
+ }
221
+
178
222
/// <summary>
179
223
/// Framework use. Provides the method used to position the popup.
180
224
/// </summary>
181
225
public CustomPopupPlacementCallback PopupPlacementMethod => GetPopupPlacement ;
182
226
183
227
public override void OnApplyTemplate ( )
184
228
{
229
+ if ( _popup != null )
230
+ _popup . Loaded -= PopupOnLoaded ;
231
+
185
232
base . OnApplyTemplate ( ) ;
186
233
187
234
_popup = GetTemplateChild ( PopupPartName ) as PopupEx ;
188
- _toggleButton = GetTemplateChild ( TogglePartName ) as ToggleButton ;
235
+ _popupContentControl = GetTemplateChild ( PopupContentControlPartName ) as ContentControl ;
236
+
237
+ if ( _popup != null )
238
+ _popup . Loaded += PopupOnLoaded ;
189
239
190
240
VisualStateManager . GoToState ( this , IsPopupOpen ? PopupIsOpenStateName : PopupIsClosedStateName , false ) ;
191
241
}
@@ -200,7 +250,27 @@ protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChanged
200
250
}
201
251
}
202
252
203
- private void Close ( )
253
+ protected override void OnMouseEnter ( MouseEventArgs e )
254
+ {
255
+ if ( PopupMode == PopupBoxPopupMode . MouseOverEager
256
+ || PopupMode == PopupBoxPopupMode . MouseOver )
257
+
258
+ SetCurrentValue ( IsPopupOpenProperty , true ) ;
259
+
260
+ base . OnMouseEnter ( e ) ;
261
+ }
262
+
263
+ protected override void OnMouseLeave ( MouseEventArgs e )
264
+ {
265
+ if ( PopupMode == PopupBoxPopupMode . MouseOverEager
266
+ || PopupMode == PopupBoxPopupMode . MouseOver )
267
+
268
+ Close ( ) ;
269
+
270
+ base . OnMouseEnter ( e ) ;
271
+ }
272
+
273
+ protected void Close ( )
204
274
{
205
275
if ( IsPopupOpen )
206
276
SetCurrentValue ( IsPopupOpenProperty , false ) ;
@@ -243,6 +313,57 @@ private CustomPopupPlacement[] GetPopupPlacement(Size popupSize, Size targetSize
243
313
return new [ ] { new CustomPopupPlacement ( point , PopupPrimaryAxis . Horizontal ) } ;
244
314
}
245
315
316
+ private void AnimateChildren ( )
317
+ {
318
+ if ( _popupContentControl == null ) return ;
319
+ if ( VisualTreeHelper . GetChildrenCount ( _popupContentControl ) != 1 ) return ;
320
+ var contentPresenter = VisualTreeHelper . GetChild ( _popupContentControl , 0 ) as ContentPresenter ;
321
+
322
+ var controls = contentPresenter . VisualDepthFirstTraversal ( ) . OfType < ButtonBase > ( ) ;
323
+ double translateYFrom ;
324
+ if ( PlacementMode == PopupBoxPlacementMode . TopAndAlignCentres
325
+ || PlacementMode == PopupBoxPlacementMode . TopAndAlignLeftEdges
326
+ || PlacementMode == PopupBoxPlacementMode . TopAndAlignRightEdges )
327
+ {
328
+ controls = controls . Reverse ( ) ;
329
+ translateYFrom = 40 ;
330
+ }
331
+ else
332
+ translateYFrom = - 40 ;
333
+
334
+ var i = 0 ;
335
+ foreach ( var uiElement in controls )
336
+ {
337
+ var transformGroup = new TransformGroup
338
+ {
339
+ Children = new TransformCollection ( new Transform [ ]
340
+ {
341
+ new ScaleTransform ( .5 , .5 ) ,
342
+ new TranslateTransform ( 0 , translateYFrom )
343
+ } )
344
+ } ;
345
+ uiElement . SetCurrentValue ( RenderTransformOriginProperty , new Point ( .5 , .5 ) ) ;
346
+ uiElement . RenderTransform = transformGroup ;
347
+
348
+ var scaleXAnimation = new DoubleAnimation ( .5 , 1 , new Duration ( TimeSpan . FromMilliseconds ( 100 ) ) ) ;
349
+ Storyboard . SetTargetProperty ( scaleXAnimation , new PropertyPath ( "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" ) ) ;
350
+ Storyboard . SetTarget ( scaleXAnimation , uiElement ) ;
351
+ var scaleYAnimation = new DoubleAnimation ( .5 , 1 , new Duration ( TimeSpan . FromMilliseconds ( 100 ) ) ) ;
352
+ Storyboard . SetTargetProperty ( scaleYAnimation , new PropertyPath ( "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" ) ) ;
353
+ Storyboard . SetTarget ( scaleYAnimation , uiElement ) ;
354
+ var translateYAnimation = new DoubleAnimation ( translateYFrom , 0 , new Duration ( TimeSpan . FromMilliseconds ( 100 ) ) ) ;
355
+ Storyboard . SetTargetProperty ( translateYAnimation , new PropertyPath ( "(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)" ) ) ;
356
+ Storyboard . SetTarget ( translateYAnimation , uiElement ) ;
357
+ var storyboard = new Storyboard ( ) ;
358
+ storyboard . Children . Add ( scaleXAnimation ) ;
359
+ storyboard . Children . Add ( scaleYAnimation ) ;
360
+ storyboard . Children . Add ( translateYAnimation ) ;
361
+ storyboard . BeginTime = TimeSpan . FromMilliseconds ( i ++ * 20 ) ;
362
+
363
+ storyboard . Begin ( ) ;
364
+ }
365
+ }
366
+
246
367
#region Capture
247
368
248
369
[ DllImport ( "user32.dll" , CharSet = CharSet . Auto ) ]
@@ -310,6 +431,12 @@ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
310
431
311
432
#endregion
312
433
434
+ private void PopupOnLoaded ( object sender , RoutedEventArgs routedEventArgs )
435
+ {
436
+ if ( PopupMode == PopupBoxPopupMode . MouseOverEager )
437
+ _popup . IsOpen = true ;
438
+ }
439
+
313
440
private static object CoerceToolTipIsEnabled ( DependencyObject dependencyObject , object value )
314
441
{
315
442
var popupBox = ( PopupBox ) dependencyObject ;
0 commit comments