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
1 change: 1 addition & 0 deletions src/ManagedDrive.App/Localization/Strings.en-US.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<sys:String x:Key="Tip.Settings">Application settings</sys:String>
<sys:String x:Key="Tip.ResetTempDirs">Reset Windows Temp Directories to defaults</sys:String>
<sys:String x:Key="Tip.Minimize">Minimize</sys:String>
<sys:String x:Key="Tip.MaximizeRestore">Maximize/Restore</sys:String>
<sys:String x:Key="Tip.HideToTray">Hide to tray</sys:String>
<sys:String x:Key="Tip.CloseDialog">Close</sys:String>
<sys:String x:Key="Col.MountPoint">Mount Point</sys:String>
Expand Down
1 change: 1 addition & 0 deletions src/ManagedDrive.App/Localization/Strings.zh-CN.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<sys:String x:Key="Tip.Settings">应用程序设置</sys:String>
<sys:String x:Key="Tip.ResetTempDirs">将 Windows 临时目录重置为默认值</sys:String>
<sys:String x:Key="Tip.Minimize">最小化</sys:String>
<sys:String x:Key="Tip.MaximizeRestore">最大化/还原</sys:String>
<sys:String x:Key="Tip.HideToTray">最小化到系统托盘</sys:String>
<sys:String x:Key="Tip.CloseDialog">关闭</sys:String>
<sys:String x:Key="Col.MountPoint">挂载点</sys:String>
Expand Down
15 changes: 15 additions & 0 deletions src/ManagedDrive.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@
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
107 changes: 103 additions & 4 deletions src/ManagedDrive.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
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 @@ -15,11 +21,69 @@ public MainWindow(MainViewModel viewModel)
DataContext = viewModel;
}

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.
/// </summary>
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);

private void MinimizeButton_Click(object sender, RoutedEventArgs e) =>
WindowState = WindowState.Minimized;
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)
{
if (msg == WindowsMessageGetMinMaxInfo)
{
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
}

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 @@ -30,4 +94,39 @@ 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