Skip to content

Commit 24853b0

Browse files
authored
chore: add SizedFrame for window sizing (#54)
1 parent 449bbd9 commit 24853b0

File tree

5 files changed

+116
-50
lines changed

5 files changed

+116
-50
lines changed

Diff for: App/Controls/SizedFrame.cs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using Windows.Foundation;
3+
using Microsoft.UI.Xaml;
4+
using Microsoft.UI.Xaml.Controls;
5+
6+
namespace Coder.Desktop.App.Controls;
7+
8+
public class SizedFrameEventArgs : EventArgs
9+
{
10+
public Size NewSize { get; init; }
11+
}
12+
13+
/// <summary>
14+
/// SizedFrame extends Frame by adding a SizeChanged event, which will be triggered when:
15+
/// - The contained Page's content's size changes
16+
/// - We switch to a different page.
17+
///
18+
/// Sadly this is necessary because Window.Content.SizeChanged doesn't trigger when the Page's content changes.
19+
/// </summary>
20+
public class SizedFrame : Frame
21+
{
22+
public delegate void SizeChangeDelegate(object sender, SizedFrameEventArgs e);
23+
24+
public new event SizeChangeDelegate? SizeChanged;
25+
26+
private Size _lastSize;
27+
28+
public void SetPage(Page page)
29+
{
30+
if (ReferenceEquals(page, Content)) return;
31+
32+
// Set the new event listener.
33+
if (page.Content is not FrameworkElement newElement)
34+
throw new Exception("Failed to get Page.Content as FrameworkElement on SizedFrame navigation");
35+
newElement.SizeChanged += Content_SizeChanged;
36+
37+
// Unset the previous event listener.
38+
if (Content is Page { Content: FrameworkElement oldElement })
39+
oldElement.SizeChanged -= Content_SizeChanged;
40+
41+
// We don't use RootFrame.Navigate here because it doesn't let you
42+
// instantiate the page yourself. We also don't need forwards/backwards
43+
// capabilities.
44+
Content = page;
45+
46+
// Fire an event.
47+
Content_SizeChanged(newElement, null);
48+
}
49+
50+
public Size GetContentSize()
51+
{
52+
if (Content is not Page { Content: FrameworkElement frameworkElement })
53+
throw new Exception("Failed to get Content as FrameworkElement for SizedFrame");
54+
55+
frameworkElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
56+
return new Size(frameworkElement.ActualWidth, frameworkElement.ActualHeight);
57+
}
58+
59+
private void Content_SizeChanged(object sender, SizeChangedEventArgs? _)
60+
{
61+
var size = GetContentSize();
62+
if (size == _lastSize) return;
63+
_lastSize = size;
64+
65+
var args = new SizedFrameEventArgs { NewSize = size };
66+
SizeChanged?.Invoke(this, args);
67+
}
68+
}

Diff for: App/Views/SignInWindow.xaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
x:Class="Coder.Desktop.App.Views.SignInWindow"
55
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
66
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7+
xmlns:controls="using:Coder.Desktop.App.Controls"
78
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
89
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
910
mc:Ignorable="d"
@@ -13,5 +14,5 @@
1314
<DesktopAcrylicBackdrop />
1415
</Window.SystemBackdrop>
1516

16-
<Frame x:Name="RootFrame" />
17+
<controls:SizedFrame x:Name="RootFrame" />
1718
</Window>

Diff for: App/Views/SignInWindow.xaml.cs

+31-8
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,72 @@
1+
using System;
12
using Windows.Graphics;
3+
using Coder.Desktop.App.Controls;
24
using Coder.Desktop.App.ViewModels;
35
using Coder.Desktop.App.Views.Pages;
46
using Microsoft.UI.Windowing;
57
using Microsoft.UI.Xaml;
8+
using Microsoft.UI.Xaml.Media;
69

710
namespace Coder.Desktop.App.Views;
811

912
/// <summary>
10-
/// The dialog window to allow the user to sign into their Coder server.
13+
/// The dialog window to allow the user to sign in to their Coder server.
1114
/// </summary>
1215
public sealed partial class SignInWindow : Window
1316
{
14-
private const double WIDTH = 600.0;
15-
private const double HEIGHT = 300.0;
17+
private const double WIDTH = 500.0;
1618

1719
private readonly SignInUrlPage _signInUrlPage;
1820
private readonly SignInTokenPage _signInTokenPage;
1921

2022
public SignInWindow(SignInViewModel viewModel)
2123
{
2224
InitializeComponent();
25+
SystemBackdrop = new DesktopAcrylicBackdrop();
26+
RootFrame.SizeChanged += RootFrame_SizeChanged;
27+
2328
_signInUrlPage = new SignInUrlPage(this, viewModel);
2429
_signInTokenPage = new SignInTokenPage(this, viewModel);
2530

31+
// Prevent the window from being resized.
32+
if (AppWindow.Presenter is not OverlappedPresenter presenter)
33+
throw new Exception("Failed to get OverlappedPresenter for window");
34+
presenter.IsMaximizable = false;
35+
presenter.IsResizable = false;
36+
2637
NavigateToUrlPage();
2738
ResizeWindow();
2839
MoveWindowToCenterOfDisplay();
2940
}
3041

3142
public void NavigateToTokenPage()
3243
{
33-
RootFrame.Content = _signInTokenPage;
44+
RootFrame.SetPage(_signInTokenPage);
3445
}
3546

3647
public void NavigateToUrlPage()
3748
{
38-
RootFrame.Content = _signInUrlPage;
49+
RootFrame.SetPage(_signInUrlPage);
50+
}
51+
52+
private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e)
53+
{
54+
ResizeWindow(e.NewSize.Height);
3955
}
4056

4157
private void ResizeWindow()
4258
{
59+
ResizeWindow(RootFrame.GetContentSize().Height);
60+
}
61+
62+
private void ResizeWindow(double height)
63+
{
64+
if (height <= 0) height = 100; // will be resolved next frame typically
65+
4366
var scale = DisplayScale.WindowScale(this);
44-
var height = (int)(HEIGHT * scale);
45-
var width = (int)(WIDTH * scale);
46-
AppWindow.Resize(new SizeInt32(width, height));
67+
var newWidth = (int)(WIDTH * scale);
68+
var newHeight = (int)(height * scale);
69+
AppWindow.ResizeClient(new SizeInt32(newWidth, newHeight));
4770
}
4871

4972
private void MoveWindowToCenterOfDisplay()

Diff for: App/Views/TrayWindow.xaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919
<controls:TrayIcon x:Name="TrayIcon" />
2020

2121
<!-- This is where the current Page is displayed -->
22-
<Frame x:Name="RootFrame" />
22+
<controls:SizedFrame x:Name="RootFrame" />
2323
</Grid>
2424
</Window>

Diff for: App/Views/TrayWindow.xaml.cs

+14-40
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
22
using System.Runtime.InteropServices;
3-
using Windows.Foundation;
43
using Windows.Graphics;
54
using Windows.System;
65
using Windows.UI.Core;
6+
using Coder.Desktop.App.Controls;
77
using Coder.Desktop.App.Models;
88
using Coder.Desktop.App.Services;
99
using Coder.Desktop.App.Views.Pages;
@@ -48,6 +48,7 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
4848
AppWindow.Hide();
4949
SystemBackdrop = new DesktopAcrylicBackdrop();
5050
Activated += Window_Activated;
51+
RootFrame.SizeChanged += RootFrame_SizeChanged;
5152

5253
rpcController.StateChanged += RpcController_StateChanged;
5354
credentialManager.CredentialsChanged += CredentialManager_CredentialsChanged;
@@ -120,55 +121,31 @@ public void SetRootFrame(Page page)
120121
return;
121122
}
122123

123-
if (ReferenceEquals(page, RootFrame.Content)) return;
124-
125-
if (page.Content is not FrameworkElement newElement)
126-
throw new Exception("Failed to get Page.Content as FrameworkElement on RootFrame navigation");
127-
newElement.SizeChanged += Content_SizeChanged;
128-
129-
// Unset the previous event listener.
130-
if (RootFrame.Content is Page { Content: FrameworkElement oldElement })
131-
oldElement.SizeChanged -= Content_SizeChanged;
132-
133-
// Swap them out and reconfigure the window.
134-
// We don't use RootFrame.Navigate here because it doesn't let you
135-
// instantiate the page yourself. We also don't need forwards/backwards
136-
// capabilities.
137-
RootFrame.Content = page;
138-
ResizeWindow();
139-
MoveWindow();
124+
RootFrame.SetPage(page);
140125
}
141126

142-
private void Content_SizeChanged(object sender, SizeChangedEventArgs e)
127+
private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e)
143128
{
144-
ResizeWindow();
129+
ResizeWindow(e.NewSize.Height);
145130
MoveWindow();
146131
}
147132

148133
private void ResizeWindow()
149134
{
150-
if (RootFrame.Content is not Page { Content: FrameworkElement frameworkElement })
151-
throw new Exception("Failed to get Content as FrameworkElement for window");
152-
153-
// Measure the desired size of the content
154-
frameworkElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
155-
156-
// Adjust the AppWindow size
157-
var scale = GetDisplayScale();
158-
var height = (int)(frameworkElement.ActualHeight * scale);
159-
var width = (int)(WIDTH * scale);
160-
AppWindow.Resize(new SizeInt32(width, height));
135+
ResizeWindow(RootFrame.GetContentSize().Height);
161136
}
162137

163-
private double GetDisplayScale()
138+
private void ResizeWindow(double height)
164139
{
165-
var hwnd = WindowNative.GetWindowHandle(this);
166-
var dpi = NativeApi.GetDpiForWindow(hwnd);
167-
if (dpi == 0) return 1; // assume scale of 1
168-
return dpi / 96.0; // 96 DPI == 1
140+
if (height <= 0) height = 100; // will be resolved next frame typically
141+
142+
var scale = DisplayScale.WindowScale(this);
143+
var newWidth = (int)(WIDTH * scale);
144+
var newHeight = (int)(height * scale);
145+
AppWindow.Resize(new SizeInt32(newWidth, newHeight));
169146
}
170147

171-
public void MoveResizeAndActivate()
148+
private void MoveResizeAndActivate()
172149
{
173150
SaveCursorPos();
174151
ResizeWindow();
@@ -268,9 +245,6 @@ public static class NativeApi
268245
[DllImport("user32.dll")]
269246
public static extern bool SetForegroundWindow(IntPtr hwnd);
270247

271-
[DllImport("user32.dll")]
272-
public static extern int GetDpiForWindow(IntPtr hwnd);
273-
274248
public struct POINT
275249
{
276250
public int X;

0 commit comments

Comments
 (0)