Skip to content

Commit df0d9cc

Browse files
committed
Merge pull request #138 from punker76/Controlz-to-ControlzEx
Change Controlz to ControlzEx for PopupEx
2 parents 6356014 + 849871a commit df0d9cc

File tree

7 files changed

+375
-5
lines changed

7 files changed

+375
-5
lines changed

.paket/paket.exe

115 KB
Binary file not shown.

MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
4242
</PropertyGroup>
4343
<ItemGroup>
44-
<Compile Include="..\paket-files\punker76\Controlz\src\Controlz\PopupEx.cs">
44+
<Compile Include="..\paket-files\ControlzEx\ControlzEx\src\Controlz\PopupEx.cs">
4545
<Paket>True</Paket>
46-
<Link>Controlz/PopupEx.cs</Link>
46+
<Link>ControlzEx/PopupEx.cs</Link>
4747
</Compile>
4848
<Reference Include="System" />
4949
<Reference Include="System.Data" />
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
File:PopupEx.cs Controlz
1+
File:PopupEx.cs ControlzEx
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
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+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2f2f5ebc5ce30069cb05774f03fcee722c284fd4

paket.dependencies

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
source https://nuget.org/api/v2
22

3-
github punker76/Controlz:2f2f5ebc5ce30069cb05774f03fcee722c284fd4 src/Controlz/PopupEx.cs
3+
github ControlzEx/ControlzEx:2f2f5ebc5ce30069cb05774f03fcee722c284fd4 src/Controlz/PopupEx.cs

0 commit comments

Comments
 (0)