Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Create, mount and manage in-memory volumes that appear as normal drive letters i
**UI**
- Bilingual (English / Simplified Chinese) and light/dark themes, both auto-detected with manual override in Settings, switching instantly without restart
- At-a-glance disk cards with status badges (read-only, current-TEMP, backing image, password-protected) and a usage bar that turns warning-colored past the high-usage threshold
- Maximize/restore button on the main window toolbar, alongside minimize and close
- Main window is freely resizable by dragging its edges, but has no maximize/fullscreen mode
- About dialog with app version and GitHub link

### Installation
Expand Down Expand Up @@ -344,7 +344,7 @@ This project bundles [WinFsp](https://winfsp.dev/) and [SharpCompress](https://g
**界面**
- 双语界面(中文/英文)与浅色/深色主题,均可自动检测或在设置中手动切换,即时生效无需重启
- 一目了然的磁盘卡片,带状态角标(只读、当前临时目录、是否绑定镜像、密码保护)及使用率超阈值时变色的进度条
- 主窗口工具栏新增最大化/还原按钮,与最小化、关闭按钮并列
- 主窗口可通过拖拽边缘自由调整大小,但不支持最大化/全屏
- 关于对话框,显示应用版本及 GitHub 仓库链接

**命令行**
Expand Down
16 changes: 1 addition & 15 deletions src/ManagedDrive.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Title="{DynamicResource Window.Title}"
Height="480" Width="760"
MinHeight="320" MinWidth="520"
ResizeMode="CanResize"
WindowStartupLocation="CenterScreen"
TextElement.Foreground="{DynamicResource AppForeground}"
TextElement.FontWeight="Regular"
Expand Down Expand Up @@ -79,21 +80,6 @@
ToolTip="{DynamicResource Tip.Minimize}">
<TextBlock Style="{StaticResource IconText}" Text="&#xE921;" FontSize="12"/>
</Button>
<Button Style="{StaticResource IconButton}"
Click="MaximizeRestoreButton_Click"
ToolTip="{DynamicResource Tip.MaximizeRestore}">
<TextBlock x:Name="MaximizeRestoreIcon" Text="&#xE922;">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource IconText}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}" Value="Maximized">
<Setter Property="Text" Value="&#xE923;"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Button Style="{StaticResource IconButton}"
Click="CloseButton_Click"
ToolTip="{DynamicResource Tip.HideToTray}">
Expand Down
104 changes: 8 additions & 96 deletions src/ManagedDrive.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace ManagedDrive.App;

/// <summary>
/// Interaction logic for MainWindow.xaml.
/// </summary>
public partial class MainWindow
{
private const uint MonitorDefaultToNearest = 2;
private const int WindowsMessageGetMinMaxInfo = 0x0024;

/// <summary>
/// Initializes the main window and binds the supplied view model.
/// </summary>
Expand All @@ -19,70 +13,23 @@ public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
StateChanged += MainWindow_StateChanged;
}

private void CloseButton_Click(object sender, RoutedEventArgs e) => Close();

/// <summary>
/// Hooks the window's message loop so maximizing clamps to the monitor's
/// work area instead of its full bounds, which WPF does not do automatically
/// for <c>WindowStyle="None"</c> windows and would otherwise cover the taskbar.
/// The window supports resizing but not fullscreen; this reverts any attempt to
/// maximize (Win+Up, edge-drag snap, double-click on the caption area) back to normal.
/// </summary>
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);

if (PresentationSource.FromVisual(this) is HwndSource source)
{
source.AddHook(WndProc);
}
}

[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi);

[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
{
var monitor = MonitorFromWindow(hwnd, MonitorDefaultToNearest);
if (monitor == IntPtr.Zero)
{
return;
}

var monitorInfo = new MonitorInfo { cbSize = Marshal.SizeOf<MonitorInfo>() };
if (!GetMonitorInfo(monitor, ref monitorInfo))
{
return;
}

var workArea = monitorInfo.rcWork;
var monitorArea = monitorInfo.rcMonitor;

var minMaxInfo = Marshal.PtrToStructure<MinMaxInfo>(lParam);
minMaxInfo.ptMaxPosition.X = workArea.Left - monitorArea.Left;
minMaxInfo.ptMaxPosition.Y = workArea.Top - monitorArea.Top;
minMaxInfo.ptMaxSize.X = workArea.Right - workArea.Left;
minMaxInfo.ptMaxSize.Y = workArea.Bottom - workArea.Top;
Marshal.StructureToPtr(minMaxInfo, lParam, fDeleteOld: true);
}

private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
private void MainWindow_StateChanged(object? sender, EventArgs e)
{
if (msg == WindowsMessageGetMinMaxInfo)
if (WindowState == WindowState.Maximized)
{
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
WindowState = WindowState.Normal;
}

return IntPtr.Zero;
}

private void CloseButton_Click(object sender, RoutedEventArgs e) => Close();

private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e) =>
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;

private void MinimizeButton_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;

private void OverflowBtn_Click(object sender, RoutedEventArgs e)
Expand All @@ -94,39 +41,4 @@ private void OverflowBtn_Click(object sender, RoutedEventArgs e)
btn.ContextMenu.IsOpen = true;
}
}

[StructLayout(LayoutKind.Sequential)]
private struct MinMaxInfo
{
public Point ptReserved;
public Point ptMaxSize;
public Point ptMaxPosition;
public Point ptMinTrackSize;
public Point ptMaxTrackSize;
}

[StructLayout(LayoutKind.Sequential)]
private struct MonitorInfo
{
public int cbSize;
public Rect rcMonitor;
public Rect rcWork;
public uint dwFlags;
}

[StructLayout(LayoutKind.Sequential)]
private struct Point
{
public int X;
public int Y;
}

[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
Loading