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

App/Controls/SizedFrame.cs

Lines changed: 68 additions & 0 deletions
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+
}

App/Views/SignInWindow.xaml

Lines changed: 2 additions & 1 deletion
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>

App/Views/SignInWindow.xaml.cs

Lines changed: 31 additions & 8 deletions
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()

App/Views/TrayWindow.xaml

Lines changed: 1 addition & 1 deletion
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>

App/Views/TrayWindow.xaml.cs

Lines changed: 14 additions & 40 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy