1
+ using System ;
2
+ using System . Diagnostics . CodeAnalysis ;
3
+ using System . Runtime . InteropServices ;
4
+ using System . Security ;
5
+ using System . Windows ;
6
+ using System . Windows . Controls ;
7
+ using System . Windows . Controls . Primitives ;
8
+ using System . Windows . Input ;
9
+ using System . Windows . Interop ;
10
+
11
+ namespace Controlz
12
+ {
13
+ /// <summary>
14
+ /// This custom popup can be used by validation error templates or something else.
15
+ /// It provides some additional nice features:
16
+ /// - repositioning if host-window size or location changed
17
+ /// - repositioning if host-window gets maximized and vice versa
18
+ /// - it's only topmost if the host-window is activated
19
+ /// </summary>
20
+ public class PopupEx : Popup
21
+ {
22
+ public static readonly DependencyProperty CloseOnMouseLeftButtonDownProperty
23
+ = DependencyProperty . Register ( "CloseOnMouseLeftButtonDown" ,
24
+ typeof ( bool ) ,
25
+ typeof ( PopupEx ) ,
26
+ new PropertyMetadata ( false ) ) ;
27
+
28
+ /// <summary>
29
+ /// Gets/sets if the popup can be closed by left mouse button down.
30
+ /// </summary>
31
+ public bool CloseOnMouseLeftButtonDown
32
+ {
33
+ get { return ( bool ) GetValue ( CloseOnMouseLeftButtonDownProperty ) ; }
34
+ set { SetValue ( CloseOnMouseLeftButtonDownProperty , value ) ; }
35
+ }
36
+
37
+ public PopupEx ( )
38
+ {
39
+ this . Loaded += this . PopupEx_Loaded ;
40
+ this . Opened += this . PopupEx_Opened ;
41
+ }
42
+
43
+ private void PopupEx_Loaded ( object sender , RoutedEventArgs e )
44
+ {
45
+ var target = this . PlacementTarget as FrameworkElement ;
46
+ if ( target == null )
47
+ {
48
+ return ;
49
+ }
50
+
51
+ this . hostWindow = Window . GetWindow ( target ) ;
52
+ if ( this . hostWindow == null )
53
+ {
54
+ return ;
55
+ }
56
+
57
+ this . hostWindow . LocationChanged -= this . hostWindow_SizeOrLocationChanged ;
58
+ this . hostWindow . LocationChanged += this . hostWindow_SizeOrLocationChanged ;
59
+ this . hostWindow . SizeChanged -= this . hostWindow_SizeOrLocationChanged ;
60
+ this . hostWindow . SizeChanged += this . hostWindow_SizeOrLocationChanged ;
61
+ target . SizeChanged -= this . hostWindow_SizeOrLocationChanged ;
62
+ target . SizeChanged += this . hostWindow_SizeOrLocationChanged ;
63
+ this . hostWindow . StateChanged -= this . hostWindow_StateChanged ;
64
+ this . hostWindow . StateChanged += this . hostWindow_StateChanged ;
65
+ this . hostWindow . Activated -= this . hostWindow_Activated ;
66
+ this . hostWindow . Activated += this . hostWindow_Activated ;
67
+ this . hostWindow . Deactivated -= this . hostWindow_Deactivated ;
68
+ this . hostWindow . Deactivated += this . hostWindow_Deactivated ;
69
+
70
+ this . Unloaded -= this . PopupEx_Unloaded ;
71
+ this . Unloaded += this . PopupEx_Unloaded ;
72
+ }
73
+
74
+ private void PopupEx_Opened ( object sender , EventArgs e )
75
+ {
76
+ this . SetTopmostState ( true ) ;
77
+ }
78
+
79
+ private void hostWindow_Activated ( object sender , EventArgs e )
80
+ {
81
+ this . SetTopmostState ( true ) ;
82
+ }
83
+
84
+ private void hostWindow_Deactivated ( object sender , EventArgs e )
85
+ {
86
+ this . SetTopmostState ( false ) ;
87
+ }
88
+
89
+ private void PopupEx_Unloaded ( object sender , RoutedEventArgs e )
90
+ {
91
+ var target = this . PlacementTarget as FrameworkElement ;
92
+ if ( target != null )
93
+ {
94
+ target . SizeChanged -= this . hostWindow_SizeOrLocationChanged ;
95
+ }
96
+ if ( this . hostWindow != null )
97
+ {
98
+ this . hostWindow . LocationChanged -= this . hostWindow_SizeOrLocationChanged ;
99
+ this . hostWindow . SizeChanged -= this . hostWindow_SizeOrLocationChanged ;
100
+ this . hostWindow . StateChanged -= this . hostWindow_StateChanged ;
101
+ this . hostWindow . Activated -= this . hostWindow_Activated ;
102
+ this . hostWindow . Deactivated -= this . hostWindow_Deactivated ;
103
+ }
104
+ this . Unloaded -= this . PopupEx_Unloaded ;
105
+ this . Opened -= this . PopupEx_Opened ;
106
+ this . hostWindow = null ;
107
+ }
108
+
109
+ private void hostWindow_StateChanged ( object sender , EventArgs e )
110
+ {
111
+ if ( this . hostWindow != null && this . hostWindow . WindowState != WindowState . Minimized )
112
+ {
113
+ // special handling for validation popup
114
+ var target = this . PlacementTarget as FrameworkElement ;
115
+ var holder = target != null ? target . DataContext as AdornedElementPlaceholder : null ;
116
+ if ( holder != null && holder . AdornedElement != null )
117
+ {
118
+ this . PopupAnimation = PopupAnimation . None ;
119
+ this . IsOpen = false ;
120
+ var errorTemplate = holder . AdornedElement . GetValue ( Validation . ErrorTemplateProperty ) ;
121
+ holder . AdornedElement . SetValue ( Validation . ErrorTemplateProperty , null ) ;
122
+ holder . AdornedElement . SetValue ( Validation . ErrorTemplateProperty , errorTemplate ) ;
123
+ }
124
+ }
125
+ }
126
+
127
+ private void hostWindow_SizeOrLocationChanged ( object sender , EventArgs e )
128
+ {
129
+ var offset = this . HorizontalOffset ;
130
+ // "bump" the offset to cause the popup to reposition itself on its own
131
+ this . HorizontalOffset = offset + 1 ;
132
+ this . HorizontalOffset = offset ;
133
+ }
134
+
135
+ private void SetTopmostState ( bool isTop )
136
+ {
137
+ // Don’t apply state if it’s the same as incoming state
138
+ if ( this . appliedTopMost . HasValue && this . appliedTopMost == isTop )
139
+ {
140
+ return ;
141
+ }
142
+
143
+ if ( this . Child == null )
144
+ {
145
+ return ;
146
+ }
147
+
148
+ var hwndSource = ( PresentationSource . FromVisual ( this . Child ) ) as HwndSource ;
149
+ if ( hwndSource == null )
150
+ {
151
+ return ;
152
+ }
153
+ var hwnd = hwndSource . Handle ;
154
+
155
+ RECT rect ;
156
+ if ( ! GetWindowRect ( hwnd , out rect ) )
157
+ {
158
+ return ;
159
+ }
160
+ //Debug.WriteLine("setting z-order " + isTop);
161
+
162
+ var left = rect . Left ;
163
+ var top = rect . Top ;
164
+ var width = rect . Width ;
165
+ var height = rect . Height ;
166
+ if ( isTop )
167
+ {
168
+ SetWindowPos ( hwnd , HWND_TOPMOST , left , top , width , height , SWP . TOPMOST ) ;
169
+ }
170
+ else
171
+ {
172
+ // Z-Order would only get refreshed/reflected if clicking the
173
+ // the titlebar (as opposed to other parts of the external
174
+ // window) unless I first set the popup to HWND_BOTTOM
175
+ // then HWND_TOP before HWND_NOTOPMOST
176
+ SetWindowPos ( hwnd , HWND_BOTTOM , left , top , width , height , SWP . TOPMOST ) ;
177
+ SetWindowPos ( hwnd , HWND_TOP , left , top , width , height , SWP . TOPMOST ) ;
178
+ SetWindowPos ( hwnd , HWND_NOTOPMOST , left , top , width , height , SWP . TOPMOST ) ;
179
+ }
180
+
181
+ this . appliedTopMost = isTop ;
182
+ }
183
+
184
+ protected override void OnPreviewMouseLeftButtonDown ( MouseButtonEventArgs e )
185
+ {
186
+ if ( CloseOnMouseLeftButtonDown )
187
+ {
188
+ this . IsOpen = false ;
189
+ }
190
+ }
191
+
192
+ private Window hostWindow ;
193
+ private bool ? appliedTopMost ;
194
+ static readonly IntPtr HWND_TOPMOST = new IntPtr ( - 1 ) ;
195
+ static readonly IntPtr HWND_NOTOPMOST = new IntPtr ( - 2 ) ;
196
+ static readonly IntPtr HWND_TOP = new IntPtr ( 0 ) ;
197
+ static readonly IntPtr HWND_BOTTOM = new IntPtr ( 1 ) ;
198
+
199
+ /// <summary>
200
+ /// SetWindowPos options
201
+ /// </summary>
202
+ [ Flags ]
203
+ internal enum SWP
204
+ {
205
+ ASYNCWINDOWPOS = 0x4000 ,
206
+ DEFERERASE = 0x2000 ,
207
+ DRAWFRAME = 0x0020 ,
208
+ FRAMECHANGED = 0x0020 ,
209
+ HIDEWINDOW = 0x0080 ,
210
+ NOACTIVATE = 0x0010 ,
211
+ NOCOPYBITS = 0x0100 ,
212
+ NOMOVE = 0x0002 ,
213
+ NOOWNERZORDER = 0x0200 ,
214
+ NOREDRAW = 0x0008 ,
215
+ NOREPOSITION = 0x0200 ,
216
+ NOSENDCHANGING = 0x0400 ,
217
+ NOSIZE = 0x0001 ,
218
+ NOZORDER = 0x0004 ,
219
+ SHOWWINDOW = 0x0040 ,
220
+ TOPMOST = SWP . NOACTIVATE | SWP . NOOWNERZORDER | SWP . NOSIZE | SWP . NOMOVE | SWP . NOREDRAW | SWP . NOSENDCHANGING ,
221
+ }
222
+
223
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
224
+ internal static int LOWORD ( int i )
225
+ {
226
+ return ( short ) ( i & 0xFFFF ) ;
227
+ }
228
+
229
+ [ StructLayout ( LayoutKind . Sequential ) ]
230
+ internal struct POINT
231
+ {
232
+ public int x ;
233
+ public int y ;
234
+ }
235
+
236
+ [ StructLayout ( LayoutKind . Sequential ) ]
237
+ internal struct SIZE
238
+ {
239
+ public int cx ;
240
+ public int cy ;
241
+ }
242
+
243
+ [ StructLayout ( LayoutKind . Sequential ) ]
244
+ internal struct RECT
245
+ {
246
+ private int _left ;
247
+ private int _top ;
248
+ private int _right ;
249
+ private int _bottom ;
250
+
251
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
252
+ public void Offset ( int dx , int dy )
253
+ {
254
+ _left += dx ;
255
+ _top += dy ;
256
+ _right += dx ;
257
+ _bottom += dy ;
258
+ }
259
+
260
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
261
+ public int Left
262
+ {
263
+ get { return _left ; }
264
+ set { _left = value ; }
265
+ }
266
+
267
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
268
+ public int Right
269
+ {
270
+ get { return _right ; }
271
+ set { _right = value ; }
272
+ }
273
+
274
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
275
+ public int Top
276
+ {
277
+ get { return _top ; }
278
+ set { _top = value ; }
279
+ }
280
+
281
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
282
+ public int Bottom
283
+ {
284
+ get { return _bottom ; }
285
+ set { _bottom = value ; }
286
+ }
287
+
288
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
289
+ public int Width
290
+ {
291
+ get { return _right - _left ; }
292
+ }
293
+
294
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
295
+ public int Height
296
+ {
297
+ get { return _bottom - _top ; }
298
+ }
299
+
300
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
301
+ public POINT Position
302
+ {
303
+ get { return new POINT { x = _left , y = _top } ; }
304
+ }
305
+
306
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
307
+ public SIZE Size
308
+ {
309
+ get { return new SIZE { cx = Width , cy = Height } ; }
310
+ }
311
+
312
+ public static RECT Union ( RECT rect1 , RECT rect2 )
313
+ {
314
+ return new RECT {
315
+ Left = Math . Min ( rect1 . Left , rect2 . Left ) ,
316
+ Top = Math . Min ( rect1 . Top , rect2 . Top ) ,
317
+ Right = Math . Max ( rect1 . Right , rect2 . Right ) ,
318
+ Bottom = Math . Max ( rect1 . Bottom , rect2 . Bottom ) ,
319
+ } ;
320
+ }
321
+
322
+ public override bool Equals ( object obj )
323
+ {
324
+ try
325
+ {
326
+ var rc = ( RECT ) obj ;
327
+ return rc . _bottom == _bottom
328
+ && rc . _left == _left
329
+ && rc . _right == _right
330
+ && rc . _top == _top ;
331
+ }
332
+ catch ( InvalidCastException )
333
+ {
334
+ return false ;
335
+ }
336
+ }
337
+
338
+ public override int GetHashCode ( )
339
+ {
340
+ return ( _left << 16 | LOWORD ( _right ) ) ^ ( _top << 16 | LOWORD ( _bottom ) ) ;
341
+ }
342
+ }
343
+
344
+ [ SecurityCritical ]
345
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
346
+ [ DllImport ( "user32.dll" , EntryPoint = "GetWindowRect" , SetLastError = true ) ]
347
+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
348
+ private static extern bool GetWindowRect ( IntPtr hWnd , out RECT lpRect ) ;
349
+
350
+ [ SecurityCritical ]
351
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
352
+ [ DllImport ( "user32.dll" , EntryPoint = "SetWindowPos" , SetLastError = true ) ]
353
+ [ return : MarshalAs ( UnmanagedType . Bool ) ]
354
+ private static extern bool _SetWindowPos ( IntPtr hWnd , IntPtr hWndInsertAfter , int x , int y , int cx , int cy , SWP uFlags ) ;
355
+
356
+ [ SecurityCritical ]
357
+ [ SuppressMessage ( "Microsoft.Performance" , "CA1811:AvoidUncalledPrivateCode" ) ]
358
+ private static bool SetWindowPos ( IntPtr hWnd , IntPtr hWndInsertAfter , int x , int y , int cx , int cy , SWP uFlags )
359
+ {
360
+ if ( ! _SetWindowPos ( hWnd , hWndInsertAfter , x , y , cx , cy , uFlags ) )
361
+ {
362
+ // If this fails it's never worth taking down the process. Let the caller deal with the error if they want.
363
+ return false ;
364
+ }
365
+
366
+ return true ;
367
+ }
368
+ }
369
+ }
0 commit comments