diff --git a/src/CodingWithCalvin.VSToolbox/App.xaml b/src/CodingWithCalvin.VSToolbox/App.xaml
index 54cf87d..52d1542 100644
--- a/src/CodingWithCalvin.VSToolbox/App.xaml
+++ b/src/CodingWithCalvin.VSToolbox/App.xaml
@@ -40,6 +40,64 @@
+
+
+
diff --git a/src/CodingWithCalvin.VSToolbox/App.xaml.cs b/src/CodingWithCalvin.VSToolbox/App.xaml.cs
index 20a8183..50fab76 100644
--- a/src/CodingWithCalvin.VSToolbox/App.xaml.cs
+++ b/src/CodingWithCalvin.VSToolbox/App.xaml.cs
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
+using System.Threading;
using CodingWithCalvin.VSToolbox.Services;
using Microsoft.UI;
using Microsoft.UI.Windowing;
@@ -7,12 +8,66 @@
namespace CodingWithCalvin.VSToolbox;
+// Windows API for setting window corner preference
+internal static class NativeMethods
+{
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
+
+ [DllImport("user32.dll")]
+ internal static extern int GetWindowLong(IntPtr hwnd, int nIndex);
+
+ [DllImport("user32.dll")]
+ internal static extern int SetWindowLong(IntPtr hwnd, int nIndex, int dwNewLong);
+
+ [DllImport("user32.dll")]
+ internal static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
+
+ internal const int DWMWA_WINDOW_CORNER_PREFERENCE = 33;
+ internal const int DWMWCP_DONOTROUND = 1;
+ internal const int DWMWA_CAPTION_COLOR = 35;
+ internal const int DWMWA_BORDER_COLOR = 34;
+
+ internal const int GWL_STYLE = -16;
+ internal const int WS_CAPTION = 0x00C00000;
+ internal const int WS_THICKFRAME = 0x00040000;
+ internal const uint SWP_FRAMECHANGED = 0x0020;
+ internal const uint SWP_NOMOVE = 0x0002;
+ internal const uint SWP_NOSIZE = 0x0001;
+ internal const uint SWP_NOZORDER = 0x0004;
+
+ // For finding and showing existing window
+ internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ internal static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ internal static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+ [DllImport("user32.dll")]
+ internal static extern bool SetForegroundWindow(IntPtr hWnd);
+
+ internal const int SW_RESTORE = 9;
+ internal const int SW_SHOW = 5;
+}
+
public partial class App : Application
{
+ private const string MutexName = "CodingWithCalvin.VSToolbox.SingleInstance";
+ private static Mutex? _mutex;
private Window? _window;
private AppWindow? _appWindow;
private TrayIconService? _trayIconService;
+ public Window? MainWindow => _window;
+
public App()
{
InitializeComponent();
@@ -20,6 +75,16 @@ public App()
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
+ // Check for single instance
+ _mutex = new Mutex(true, MutexName, out var createdNew);
+ if (!createdNew)
+ {
+ // Another instance is already running - try to bring it to front
+ BringExistingInstanceToFront();
+ Environment.Exit(0);
+ return;
+ }
+
_window = new Window
{
Title = "Visual Studio Toolbox"
@@ -37,6 +102,9 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
_appWindow.SetIcon(iconPath);
}
+ // Configure custom title bar with square corners
+ ConfigureCustomTitleBar();
+
// Set up the main content
var rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
@@ -57,6 +125,41 @@ protected override void OnLaunched(LaunchActivatedEventArgs e)
_window.Activate();
}
+ private void ConfigureCustomTitleBar()
+ {
+ if (_appWindow is null || _window is null) return;
+
+ // Get the window handle for native API calls
+ var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(_window);
+
+ // Set square corners using DWM API
+ var cornerPreference = NativeMethods.DWMWCP_DONOTROUND;
+ NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_WINDOW_CORNER_PREFERENCE,
+ ref cornerPreference, sizeof(int));
+
+ // Set caption and border color to purple (#68217A = 0x007A2168 in COLORREF BGR format)
+ var purpleColor = 0x007A2168; // BGR format for #68217A
+ NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_CAPTION_COLOR,
+ ref purpleColor, sizeof(int));
+ NativeMethods.DwmSetWindowAttribute(hwnd, NativeMethods.DWMWA_BORDER_COLOR,
+ ref purpleColor, sizeof(int));
+
+ // Remove the caption from window style to eliminate the title bar area
+ var style = NativeMethods.GetWindowLong(hwnd, NativeMethods.GWL_STYLE);
+ style &= ~NativeMethods.WS_CAPTION; // Remove caption
+ NativeMethods.SetWindowLong(hwnd, NativeMethods.GWL_STYLE, style);
+ NativeMethods.SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0,
+ NativeMethods.SWP_FRAMECHANGED | NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOZORDER);
+
+ // Make window borderless (no system title bar at all)
+ if (_appWindow.Presenter is OverlappedPresenter presenter)
+ {
+ presenter.SetBorderAndTitleBar(false, false);
+ presenter.IsResizable = true;
+ presenter.IsMaximizable = false;
+ }
+ }
+
private void PositionWindowBottomRight(int width, int height)
{
if (_appWindow is null) return;
@@ -86,4 +189,29 @@ private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new InvalidOperationException($"Failed to load Page {e.SourcePageType.FullName}");
}
+
+ private static void BringExistingInstanceToFront()
+ {
+ const string windowTitle = "Visual Studio Toolbox";
+ IntPtr foundWindow = IntPtr.Zero;
+
+ NativeMethods.EnumWindows((hWnd, lParam) =>
+ {
+ var sb = new System.Text.StringBuilder(256);
+ NativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
+ if (sb.ToString() == windowTitle)
+ {
+ foundWindow = hWnd;
+ return false; // Stop enumeration
+ }
+ return true; // Continue enumeration
+ }, IntPtr.Zero);
+
+ if (foundWindow != IntPtr.Zero)
+ {
+ // Show and bring the window to front
+ NativeMethods.ShowWindow(foundWindow, NativeMethods.SW_RESTORE);
+ NativeMethods.SetForegroundWindow(foundWindow);
+ }
+ }
}
diff --git a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
index 8f7071c..73ce610 100644
--- a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
+++ b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml
@@ -8,149 +8,261 @@
xmlns:models="using:CodingWithCalvin.VSToolbox.Core.Models"
Loaded="OnPageLoaded"
mc:Ignorable="d">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
index 21cac23..528ce20 100644
--- a/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
+++ b/src/CodingWithCalvin.VSToolbox/Views/MainPage.xaml.cs
@@ -2,6 +2,7 @@
using CodingWithCalvin.VSToolbox.Services;
using CodingWithCalvin.VSToolbox.ViewModels;
using Microsoft.UI;
+using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
@@ -25,6 +26,10 @@ public MainPage()
private async void OnPageLoaded(object sender, RoutedEventArgs e)
{
+ // Set the title bar drag region
+ var window = ((App)Application.Current).MainWindow;
+ window?.SetTitleBar(AppTitleBar);
+
await ViewModel.LoadInstancesCommand.ExecuteAsync(null);
}
@@ -164,4 +169,43 @@ private void OnButtonPointerExited(object sender, PointerRoutedEventArgs e)
button.Opacity = 0.6;
}
}
+
+ private void OnTabChanged(object sender, RoutedEventArgs e)
+ {
+ // Skip if controls aren't loaded yet
+ if (InstalledContent is null || SettingsContent is null || RefreshButton is null)
+ return;
+
+ if (sender is not RadioButton radioButton)
+ return;
+
+ var isInstalledTab = radioButton == InstalledTab;
+
+ InstalledContent.Visibility = isInstalledTab ? Visibility.Visible : Visibility.Collapsed;
+ SettingsContent.Visibility = isInstalledTab ? Visibility.Collapsed : Visibility.Visible;
+ RefreshButton.Visibility = isInstalledTab ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ private void OnMinimizeClick(object sender, RoutedEventArgs e)
+ {
+ var window = App.Current as App;
+ var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(((App)Application.Current).MainWindow);
+ var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
+ var appWindow = AppWindow.GetFromWindowId(windowId);
+
+ if (appWindow.Presenter is OverlappedPresenter presenter)
+ {
+ presenter.Minimize();
+ }
+ }
+
+ private void OnCloseClick(object sender, RoutedEventArgs e)
+ {
+ var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(((App)Application.Current).MainWindow);
+ var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
+ var appWindow = AppWindow.GetFromWindowId(windowId);
+
+ // This will trigger the Closing event which hides instead of closes
+ appWindow.Hide();
+ }
}