Skip to content

Commit 0197ec0

Browse files
authored
Center window on startup (#28)
1 parent ccd1591 commit 0197ec0

File tree

8 files changed

+185
-0
lines changed

8 files changed

+185
-0
lines changed

NativeInterop.Win32.Xaml/DesktopWindow.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Runtime.InteropServices;
34
using Windows.UI;
5+
using Windows.Win32;
6+
using Microsoft.UI;
47
using Microsoft.UI.Xaml;
58
using WinRT;
69

@@ -31,6 +34,8 @@ public DesktopWindow()
3134
_nativeWindow.AddListener(this);
3235
}
3336

37+
public StartupLocation StartupLocation { get; set; } = StartupLocation.Default;
38+
3439
public int Width
3540
{
3641
get => _nativeWindow.Width;
@@ -217,9 +222,64 @@ public void OnActivated(NativeWindow nativeWindow)
217222
}
218223

219224
_loaded = true;
225+
226+
UpdateInitialLocation();
227+
220228
Loaded?.Invoke(this, new WindowLoadedEventArgs(this));
221229
}
222230

231+
private void UpdateInitialLocation()
232+
{
233+
switch (StartupLocation)
234+
{
235+
case StartupLocation.CenterScreen:
236+
CenterOnScreen();
237+
return;
238+
case StartupLocation.CenterParentProcess:
239+
CenterByParentProcess();
240+
return;
241+
default:
242+
return;
243+
}
244+
}
245+
246+
/// <summary>
247+
/// Center the window based on the main window of the parent process (the process which started this application) when it's first shown.
248+
/// </summary>
249+
public void CenterByParentProcess()
250+
{
251+
var parentProcess = Win32.ParentProcessUtilities.GetParentProcess();
252+
if (parentProcess == null)
253+
{
254+
CenterOnScreen();
255+
return;
256+
}
257+
258+
var size = new System.Drawing.Size(Width, Height);
259+
var monitorSize = DisplayInformation.GetMonitorSizeFromWindow(
260+
parentProcess.MainWindowHandle
261+
);
262+
263+
_nativeWindow.Position = new Point(
264+
monitorSize.Width / 2 - size.Width / 2,
265+
monitorSize.Height / 2 - size.Height / 2
266+
);
267+
}
268+
269+
/// <summary>
270+
/// Center the window on the screen when it's first shown.
271+
/// </summary>
272+
public void CenterOnScreen()
273+
{
274+
var size = new System.Drawing.Size(Width, Height);
275+
var monitorSize = DisplayInformation.GetMonitorSizeFromWindow(_hwnd);
276+
277+
_nativeWindow.Position = new Point(
278+
monitorSize.Width / 2 - size.Width / 2,
279+
monitorSize.Height / 2 - size.Height / 2
280+
);
281+
}
282+
223283
private void Window_Activated(object sender, WindowActivatedEventArgs args)
224284
{
225285
if (_backdropConfigurationSource != null)

NativeInterop.Win32/DisplayInformation.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Runtime.InteropServices;
23
using Windows.Win32;
34
using Windows.Win32.Foundation;
5+
using Windows.Win32.Graphics.Gdi;
46

57
namespace NativeInterop.Win32;
68

@@ -24,4 +26,24 @@ public static float GetScalingFactor(IntPtr hwnd)
2426
float scalingFactor = (float)dpi / 96;
2527
return scalingFactor;
2628
}
29+
30+
public static Size GetMonitorSizeFromWindow(IntPtr hWnd)
31+
{
32+
var id = PInvoke.MonitorFromWindow(
33+
new HWND(hWnd),
34+
MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST
35+
);
36+
37+
MONITORINFO info = default;
38+
info.cbSize = (uint)Marshal.SizeOf<MONITORINFO>();
39+
if (PInvoke.GetMonitorInfo(id, ref info))
40+
{
41+
return new Size(
42+
info.rcWork.right - info.rcWork.left,
43+
info.rcWork.bottom - info.rcWork.top
44+
);
45+
}
46+
47+
throw new Exception("Failed to get monitor info");
48+
}
2749
}

NativeInterop.Win32/NativeInterop.Win32.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2323
</PackageReference>
2424
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.230217.4" />
25+
<PackageReference Include="System.Management" Version="6.0.0" />
2526
</ItemGroup>
2627
</Project>

NativeInterop.Win32/NativeMethods.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ LoadImage
1515
SendMessage
1616
DestroyIcon
1717
MINMAXINFO
18+
MonitorFromWindow
19+
GetMonitorInfo
20+
CREATESTRUCTW
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.ComponentModel;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
4+
using System;
5+
using System.Linq;
6+
using System.Management;
7+
8+
namespace NativeInterop.Win32;
9+
10+
/// <summary>
11+
/// A utility class to determine a process parent.
12+
/// </summary>
13+
[StructLayout(LayoutKind.Sequential)]
14+
public struct ParentProcessUtilities
15+
{
16+
// These members must match PROCESS_BASIC_INFORMATION
17+
internal IntPtr Reserved1;
18+
internal IntPtr PebBaseAddress;
19+
internal IntPtr Reserved2_0;
20+
internal IntPtr Reserved2_1;
21+
internal IntPtr UniqueProcessId;
22+
internal IntPtr InheritedFromUniqueProcessId;
23+
24+
[DllImport("ntdll.dll")]
25+
private static extern int NtQueryInformationProcess(
26+
IntPtr processHandle,
27+
int processInformationClass,
28+
ref ParentProcessUtilities processInformation,
29+
int processInformationLength,
30+
out int returnLength
31+
);
32+
33+
/// <summary>
34+
/// Gets the parent process of the current process.
35+
/// </summary>
36+
/// <returns>An instance of the Process class.</returns>
37+
public static Process? GetParentProcess()
38+
{
39+
return GetParentProcess(Process.GetCurrentProcess().Handle);
40+
}
41+
42+
/// <summary>
43+
/// Gets the parent process of specified process.
44+
/// </summary>
45+
/// <param name="id">The process id.</param>
46+
/// <returns>An instance of the Process class.</returns>
47+
public static Process? GetParentProcess(int id)
48+
{
49+
Process process = Process.GetProcessById(id);
50+
return GetParentProcess(process.Handle);
51+
}
52+
53+
/// <summary>
54+
/// Gets the parent process of a specified process.
55+
/// </summary>
56+
/// <param name="handle">The process handle.</param>
57+
/// <returns>An instance of the Process class.</returns>
58+
public static Process? GetParentProcess(IntPtr handle)
59+
{
60+
ParentProcessUtilities pbi = new ParentProcessUtilities();
61+
int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out _);
62+
if (status != 0)
63+
{
64+
throw new Win32Exception(status);
65+
}
66+
67+
try
68+
{
69+
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
70+
}
71+
catch (ArgumentException)
72+
{
73+
// not found
74+
return null;
75+
}
76+
}
77+
}

NativeInterop.Win32/Pinvoke.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Drawing;
23
using System.Runtime.InteropServices;
34
using Windows.Win32.Foundation;
5+
using Windows.Win32.Graphics.Gdi;
46
using Windows.Win32.UI.WindowsAndMessaging;
57

68
// ReSharper disable once CheckNamespace
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace NativeInterop.Win32;
2+
3+
public enum StartupLocation
4+
{
5+
/// <summary>
6+
/// Let the operation system handle the initial location of the window.
7+
/// </summary>
8+
Default,
9+
10+
/// <summary>
11+
/// Center the window on the screen when it's first shown.
12+
/// </summary>
13+
CenterScreen,
14+
15+
/// <summary>
16+
/// Center the window based on the main window of the parent process (the process which started this application) when it's first shown.
17+
/// </summary>
18+
CenterParentProcess
19+
}

PackageInstaller/MainWindow.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
SystemBackdropEnabled="True"
1717
mc:Ignorable="d"
1818
Closing="MainWindow_OnClosing"
19+
StartupLocation="CenterParentProcess"
1920
>
2021
<controls:Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
2122
<controls:Grid.RowDefinitions>

0 commit comments

Comments
 (0)