diff --git a/src/DesktopMagic/Controls/ScreenSelector.xaml b/src/DesktopMagic/Controls/ScreenSelector.xaml
new file mode 100644
index 0000000..742afbe
--- /dev/null
+++ b/src/DesktopMagic/Controls/ScreenSelector.xaml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DesktopMagic/Controls/ScreenSelector.xaml.cs b/src/DesktopMagic/Controls/ScreenSelector.xaml.cs
new file mode 100644
index 0000000..5ea791d
--- /dev/null
+++ b/src/DesktopMagic/Controls/ScreenSelector.xaml.cs
@@ -0,0 +1,182 @@
+using DesktopMagic.DataContexts;
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Drawing;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace DesktopMagic.Controls;
+
+///
+/// Visual screen selector showing the connected monitors as a mini-map
+/// scaled to the control's size while preserving their relative positions.
+///
+public partial class ScreenSelector : UserControl
+{
+ public static readonly DependencyProperty ItemsSourceProperty =
+ DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(ScreenSelector),
+ new PropertyMetadata(null, OnItemsSourceChanged));
+
+ public IEnumerable? ItemsSource
+ {
+ get => (IEnumerable?)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public static readonly DependencyProperty SelectedItemProperty =
+ DependencyProperty.Register(nameof(SelectedItem), typeof(ScreenDisplay), typeof(ScreenSelector),
+ new PropertyMetadata(null, OnSelectedItemChanged));
+
+ public ScreenDisplay? SelectedItem
+ {
+ get => (ScreenDisplay?)GetValue(SelectedItemProperty);
+ set => SetValue(SelectedItemProperty, value);
+ }
+
+ private INotifyCollectionChanged? notifySource;
+
+ ///
+ /// Raised when the user picks a screen by clicking it.
+ ///
+ public event Action? SelectionChanged;
+
+ public ScreenSelector()
+ {
+ InitializeComponent();
+ }
+
+ private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ScreenSelector control = (ScreenSelector)d;
+ control.AttachSource();
+ control.UpdateCanvas();
+ control.UpdateSelection();
+ }
+
+ private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((ScreenSelector)d).UpdateSelection();
+ }
+
+ private void AttachSource()
+ {
+ if (notifySource is INotifyCollectionChanged oldSource)
+ {
+ oldSource.CollectionChanged -= Source_CollectionChanged;
+ }
+
+ notifySource = ItemsSource as INotifyCollectionChanged;
+ if (notifySource is not null)
+ {
+ notifySource.CollectionChanged += Source_CollectionChanged;
+ }
+ }
+
+ private void Source_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ UpdateCanvas();
+ UpdateSelection();
+ }
+
+ private void UserControl_Loaded(object sender, RoutedEventArgs e)
+ {
+ // Re-attach in case the control was unloaded and the source instance changed.
+ AttachSource();
+ UpdateCanvas();
+ }
+
+ private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ UpdateCanvas();
+ }
+
+ private void UserControl_Unloaded(object sender, RoutedEventArgs e)
+ {
+ if (notifySource is INotifyCollectionChanged source)
+ {
+ source.CollectionChanged -= Source_CollectionChanged;
+ }
+
+ notifySource = null;
+ }
+
+ ///
+ /// Scales the real screen bounds so they fit the control while keeping their
+ /// relative positions (including negative coordinates), then centers them.
+ ///
+ private void UpdateCanvas()
+ {
+ if (ItemsSource is null || ActualWidth <= 0 || ActualHeight <= 0)
+ {
+ return;
+ }
+
+ List screens = ItemsSource.Cast().ToList();
+ if (screens.Count == 0)
+ {
+ return;
+ }
+
+ // Union of all monitor bounds (virtual screen space, negative coords kept).
+ Rectangle totalBounds = new Rectangle();
+ foreach (var item in screens)
+ {
+ totalBounds = Rectangle.Union(totalBounds, item.Bounds);
+ }
+
+ // Uniform scale factor + a little margin so the mini-map never touches the edge.
+ double factor = Math.Max(totalBounds.Height / ActualHeight, totalBounds.Width / ActualWidth) + 2;
+
+ foreach (var item in screens)
+ {
+ item.X = item.Bounds.Left / factor;
+ item.Y = item.Bounds.Top / factor;
+ item.Width = item.Bounds.Width / factor;
+ item.Height = item.Bounds.Height / factor;
+ }
+
+ // Center the whole arrangement in the control.
+ double minLeft = screens.Min(item => item.X);
+ double maxRight = screens.Max(item => item.X + item.Width);
+ double minTop = screens.Min(item => item.Y);
+ double maxBottom = screens.Max(item => item.Y + item.Height);
+
+ double horizontalOffset = ((maxRight + minLeft) / 2) - (ActualWidth / 2);
+ double verticalOffset = ((maxBottom + minTop) / 2) - (ActualHeight / 2);
+
+ foreach (var item in screens)
+ {
+ item.X -= horizontalOffset;
+ item.Y -= verticalOffset;
+ }
+ }
+
+ private void UpdateSelection()
+ {
+ if (ItemsSource is null)
+ {
+ return;
+ }
+
+ foreach (ScreenDisplay item in ItemsSource)
+ {
+ item.IsSelected = item == SelectedItem;
+ }
+ }
+
+ private void Screen_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.DataContext is ScreenDisplay screen)
+ {
+ SelectedItem = screen;
+ SelectionChanged?.Invoke();
+ }
+
+ e.Handled = true;
+ }
+}
diff --git a/src/DesktopMagic/DataContexts/MainWindowDataContext.cs b/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
index 3a2e8fd..829d9ca 100644
--- a/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
+++ b/src/DesktopMagic/DataContexts/MainWindowDataContext.cs
@@ -51,6 +51,19 @@ internal class MainWindowDataContext : INotifyPropertyChanged
}
}
+ public ScreenDisplay? SelectedScreen
+ {
+ get => Screens.FirstOrDefault(screen => screen.DeviceName == SelectedScreenId);
+ set
+ {
+ if (value is not null)
+ {
+ SelectedScreenId = value.DeviceName;
+ }
+ OnPropertyChanged();
+ }
+ }
+
public Layout SelectedLayout => Manager.Instance.SelectedLayout;
public string? SelectedLayoutName
@@ -111,16 +124,26 @@ internal class MainWindowDataContext : INotifyPropertyChanged
public void RefreshScreens()
{
List allScreens = ScreenUtilities.GetAllScreens();
+ Dictionary physicalBounds = ScreenUtilities.GetDisplayPhysicalBounds();
+
+ // Capture the hardware id of the currently selected screen so the selection can be
+ // preserved across display changes (e.g. unplug/replug renumbers device names).
+ ScreenDisplay? previousSelected = SelectedScreen;
Screens.Clear();
for (int i = 0; i < allScreens.Count; i++)
{
- Screens.Add(new ScreenDisplay(allScreens[i], i));
+ System.Windows.Forms.Screen screen = allScreens[i];
+ physicalBounds.TryGetValue(screen.DeviceName, out System.Drawing.Rectangle physicalBoundsRect);
+ Screens.Add(new ScreenDisplay(screen, i, physicalBoundsRect, ScreenUtilities.GetMonitorHardwareId(screen)));
}
if (selectedScreenDeviceName is null || !Screens.Any(screen => screen.DeviceName == selectedScreenDeviceName))
{
- SelectedScreenId = Screens.FirstOrDefault()?.DeviceName;
+ ScreenDisplay? byHardwareId = previousSelected is null
+ ? null
+ : Screens.FirstOrDefault(screen => screen.HardwareId == previousSelected.HardwareId);
+ SelectedScreenId = byHardwareId?.DeviceName ?? Screens.FirstOrDefault()?.DeviceName;
}
else
{
@@ -144,6 +167,7 @@ internal class MainWindowDataContext : INotifyPropertyChanged
Manager.Instance.SelectedScreenDeviceName = selectedScreenDeviceName;
selectedLayoutName = Manager.Instance.SelectedLayout.Name;
OnPropertyChanged(nameof(SelectedScreenId));
+ OnPropertyChanged(nameof(SelectedScreen));
OnPropertyChanged(nameof(SelectedLayout));
OnPropertyChanged(nameof(SelectedLayoutName));
}
@@ -157,15 +181,100 @@ internal class MainWindowDataContext : INotifyPropertyChanged
///
/// A detected screen displayed in the UI.
///
-public class ScreenDisplay
+public class ScreenDisplay : INotifyPropertyChanged
{
- public ScreenDisplay(System.Windows.Forms.Screen screen, int index)
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ private bool isSelected;
+ private double x;
+ private double y;
+ private double width;
+ private double height;
+
+ public ScreenDisplay(System.Windows.Forms.Screen screen, int index, System.Drawing.Rectangle physicalBounds, string hardwareId)
{
DeviceName = screen.DeviceName;
DisplayName = ScreenUtilities.GetScreenLabel(screen, index);
+ Bounds = physicalBounds.Width > 0 && physicalBounds.Height > 0 ? physicalBounds : screen.Bounds;
+ IsPrimary = screen.Primary;
+ Index = index + 1;
+ HardwareId = hardwareId;
}
public string DeviceName { get; }
+ ///
+ /// Stable hardware identifier of the monitor (see ),
+ /// used to persist screen bindings across display changes.
+ ///
+ public string HardwareId { get; }
+
public string DisplayName { get; }
+
+ public System.Drawing.Rectangle Bounds { get; }
+
+ public bool IsPrimary { get; }
+
+ public int Index { get; }
+
+ public string ToolTipText => IsPrimary ? $"{DisplayName} ยท Primary" : DisplayName;
+
+ ///
+ /// Whether this screen is currently selected in the screen selector.
+ ///
+ public bool IsSelected
+ {
+ get => isSelected;
+ set
+ {
+ isSelected = value;
+ OnPropertyChanged();
+ }
+ }
+
+ // Normalized (control-local) position/size set by the screen selector.
+ public double X
+ {
+ get => x;
+ set
+ {
+ x = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public double Y
+ {
+ get => y;
+ set
+ {
+ y = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public double Width
+ {
+ get => width;
+ set
+ {
+ width = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public double Height
+ {
+ get => height;
+ set
+ {
+ height = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private void OnPropertyChanged([CallerMemberName] string? name = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+ }
}
\ No newline at end of file
diff --git a/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml b/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml
new file mode 100644
index 0000000..c3cbc70
--- /dev/null
+++ b/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml.cs b/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml.cs
new file mode 100644
index 0000000..c9ba581
--- /dev/null
+++ b/src/DesktopMagic/Dialogs/ScreenSelectorDialog.xaml.cs
@@ -0,0 +1,26 @@
+using DesktopMagic.DataContexts;
+
+using System.Windows;
+
+namespace DesktopMagic.Dialogs;
+
+///
+/// Modal picker for selecting a screen from a visual monitor layout.
+/// Closes itself once a screen has been picked.
+///
+public partial class ScreenSelectorDialog : Wpf.Ui.Controls.FluentWindow
+{
+ internal ScreenSelectorDialog(MainWindowDataContext dataContext, string title = App.AppName)
+ {
+ InitializeComponent();
+
+ Resources.MergedDictionaries.Add(App.LanguageDictionary);
+
+ DataContext = dataContext;
+ titleBar.Title = title;
+ Title = title;
+
+ // Close the dialog once a screen has been picked.
+ screenSelector.SelectionChanged += () => DialogResult = true;
+ }
+}
diff --git a/src/DesktopMagic/Helpers/ScreenUtilities.cs b/src/DesktopMagic/Helpers/ScreenUtilities.cs
index f7f0187..0e8bf80 100644
--- a/src/DesktopMagic/Helpers/ScreenUtilities.cs
+++ b/src/DesktopMagic/Helpers/ScreenUtilities.cs
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
using System.Windows;
namespace DesktopMagic.Helpers;
@@ -168,25 +170,106 @@ public static class ScreenUtilities
/// or an empty string when it cannot be determined.
///
public static string GetFriendlyName(System.Windows.Forms.Screen screen)
+ {
+ return GetMonitorDisplayDevice(screen)?.DeviceString ?? string.Empty;
+ }
+
+ ///
+ /// Gets a stable hardware identifier for the screen: the PnP device instance ID of its
+ /// monitor (e.g. "MONITOR\DEL41F1\{...}\{0001}"), which is derived from the monitor's
+ /// EDID and survives disconnects and reconnects of the same monitor. When no hardware ID
+ /// is available (e.g. virtual or remote displays), a deterministic SHA-256 hash of the
+ /// bounds is used so the identifier is never empty.
+ ///
+ public static string GetMonitorHardwareId(System.Windows.Forms.Screen screen)
+ {
+ string? deviceId = GetMonitorDisplayDevice(screen)?.DeviceID;
+ if (!string.IsNullOrWhiteSpace(deviceId))
+ {
+ return deviceId;
+ }
+
+ System.Drawing.Rectangle bounds = screen.Bounds;
+ string boundsString = $"{bounds.X}-{bounds.Y}-{bounds.Width}-{bounds.Height}";
+ string hashString = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(boundsString))).ToLowerInvariant();
+ return $"DISPLAY#{hashString}";
+ }
+
+ ///
+ /// Gets the monitor device info (second-level entry) for
+ /// the given screen, or null when it cannot be determined.
+ ///
+ private static DISPLAY_DEVICE? GetMonitorDisplayDevice(System.Windows.Forms.Screen screen)
{
try
{
- var device = new DISPLAY_DEVICE { cb = (uint)Marshal.SizeOf() };
+ DISPLAY_DEVICE device = new DISPLAY_DEVICE { cb = (uint)Marshal.SizeOf() };
if (EnumDisplayDevices(screen.DeviceName, 0, ref device, 0))
{
- var monitor = new DISPLAY_DEVICE { cb = (uint)Marshal.SizeOf() };
+ DISPLAY_DEVICE monitor = new DISPLAY_DEVICE { cb = (uint)Marshal.SizeOf() };
if (EnumDisplayDevices(device.DeviceName, 0, ref monitor, 0))
{
- return monitor.DeviceString;
+ return monitor;
}
}
}
catch (Exception)
{
- // Fall through to an empty name.
+ // Fall through to null.
}
- return string.Empty;
+ return null;
+ }
+
+ ///
+ /// Enumerates monitors in a Per-Monitor V2 DPI awareness context and returns each
+ /// monitor's bounds in physical (device) pixels on the virtual screen, keyed by device
+ /// name (e.g. "\\.\DISPLAY1"). This uses the same coordinate space as DPI-aware apps
+ /// such as Lively Wallpaper, keeping multi-screen layouts with mixed DPI scaling
+ /// consistent regardless of this app's own DPI awareness mode.
+ ///
+ public static Dictionary GetDisplayPhysicalBounds()
+ {
+ Dictionary result = new Dictionary();
+
+ IntPtr prevContext = IntPtr.Zero;
+ bool contextChanged = false;
+ try
+ {
+ prevContext = SetThreadDpiAwarenessContext((IntPtr)DpiAwarenessContextPerMonitorV2);
+ contextChanged = prevContext != IntPtr.Zero;
+ }
+ catch (Exception)
+ {
+ // SetThreadDpiAwarenessContext unavailable; fall back to default context.
+ }
+
+ try
+ {
+ _ = EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
+ {
+ MONITORINFOEX info = new MONITORINFOEX { cbSize = (uint)Marshal.SizeOf() };
+ if (GetMonitorInfo(hMonitor, ref info))
+ {
+ string deviceName = info.szDevice.TrimEnd('\0');
+ result[deviceName] = new System.Drawing.Rectangle(
+ info.rcMonitor.Left, info.rcMonitor.Top,
+ info.rcMonitor.Right - info.rcMonitor.Left,
+ info.rcMonitor.Bottom - info.rcMonitor.Top);
+ }
+
+ return true;
+ }, IntPtr.Zero);
+ }
+ finally
+ {
+ if (contextChanged)
+ {
+ _ = SetThreadDpiAwarenessContext(prevContext);
+ }
+ }
+
+ return result;
}
///
@@ -195,11 +278,7 @@ public static class ScreenUtilities
private static Rect GetScreenDips(System.Drawing.Rectangle bounds)
{
double scale = GetDpiScale(bounds);
- return new Rect(
- bounds.Left / scale,
- bounds.Top / scale,
- bounds.Width / scale,
- bounds.Height / scale);
+ return new Rect(bounds.Left / scale, bounds.Top / scale, bounds.Width / scale, bounds.Height / scale);
}
///
@@ -209,7 +288,7 @@ public static class ScreenUtilities
{
try
{
- var center = new POINT
+ POINT center = new POINT
{
X = bounds.Left + (bounds.Width / 2),
Y = bounds.Top + (bounds.Height / 2)
@@ -263,4 +342,41 @@ public static class ScreenUtilities
public int X;
public int Y;
}
+
+ private const long DpiAwarenessContextPerMonitorV2 = -4;
+
+ private delegate bool EnumMonitorsProc(IntPtr hMonitor, IntPtr hdcMonitor, IntPtr lprcMonitor, IntPtr dwData);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
+
+ [DllImport("user32.dll")]
+ private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsProc lpfnEnum, IntPtr dwData);
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)]
+ private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct MONITORRECT
+ {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ private struct MONITORINFOEX
+ {
+ public uint cbSize;
+
+ public MONITORRECT rcMonitor;
+
+ public MONITORRECT rcWork;
+
+ public uint dwFlags;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string szDevice;
+ }
}
diff --git a/src/DesktopMagic/MainWindow.xaml.cs b/src/DesktopMagic/MainWindow.xaml.cs
index 22233d6..41d163c 100644
--- a/src/DesktopMagic/MainWindow.xaml.cs
+++ b/src/DesktopMagic/MainWindow.xaml.cs
@@ -39,6 +39,8 @@ public partial class MainWindow : FluentWindow
{
App.Logger.LogInfo("Loading application", source: "MainWindow");
+ Microsoft.Win32.SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged;
+
_mainWindowDataContext.IsLoading = true;
// Load plugins and settings through manager
@@ -99,6 +101,8 @@ public partial class MainWindow : FluentWindow
private void Window_Closed(object sender, EventArgs e)
{
+ Microsoft.Win32.SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged;
+
Visibility = Visibility.Collapsed;
UpdateLayout();
_manager.CloseAllPluginWindows();
@@ -113,6 +117,16 @@ public partial class MainWindow : FluentWindow
}
}
+ private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e)
+ {
+ // Re-enumerate screens and reload all widget windows when monitors are added or removed.
+ _ = Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ _mainWindowDataContext.RefreshScreens();
+ _manager.LoadLayout();
+ });
+ }
+
internal void RestoreWindow()
{
for (int i = 0; i < 10; i++)
diff --git a/src/DesktopMagic/Manager.cs b/src/DesktopMagic/Manager.cs
index 70c6267..2e08308 100644
--- a/src/DesktopMagic/Manager.cs
+++ b/src/DesktopMagic/Manager.cs
@@ -398,7 +398,7 @@ public sealed class Manager
if (Settings.ScreenLayouts.Count == 0)
{
- Settings.ScreenLayouts[primaryScreen.DeviceName] = Settings.CurrentLayoutName ?? "Default";
+ Settings.ScreenLayouts[ScreenUtilities.GetMonitorHardwareId(primaryScreen)] = Settings.CurrentLayoutName ?? "Default";
}
App.Logger.LogInfo("Settings migrated to screen-aware layouts", source: "Manager");
@@ -429,7 +429,7 @@ public sealed class Manager
///
public Layout GetLayoutForScreen(System.Windows.Forms.Screen screen)
{
- if (Settings.ScreenLayouts.TryGetValue(screen.DeviceName, out string? layoutName))
+ if (Settings.ScreenLayouts.TryGetValue(ScreenUtilities.GetMonitorHardwareId(screen), out string? layoutName))
{
Layout? bound = Settings.Layouts.FirstOrDefault(layout => layout.Name == layoutName);
if (bound is not null)
@@ -457,7 +457,7 @@ public sealed class Manager
///
public void BindLayoutToScreen(System.Windows.Forms.Screen screen, Layout layout)
{
- Settings.ScreenLayouts[screen.DeviceName] = layout.Name;
+ Settings.ScreenLayouts[ScreenUtilities.GetMonitorHardwareId(screen)] = layout.Name;
SaveSettings();
}
diff --git a/src/DesktopMagic/Pages/MainPage.xaml b/src/DesktopMagic/Pages/MainPage.xaml
index 6d1517d..138b40a 100644
--- a/src/DesktopMagic/Pages/MainPage.xaml
+++ b/src/DesktopMagic/Pages/MainPage.xaml
@@ -76,18 +76,32 @@
-
+
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DesktopMagic/Pages/MainPage.xaml.cs b/src/DesktopMagic/Pages/MainPage.xaml.cs
index 12ea36e..8234516 100644
--- a/src/DesktopMagic/Pages/MainPage.xaml.cs
+++ b/src/DesktopMagic/Pages/MainPage.xaml.cs
@@ -77,6 +77,16 @@ public partial class MainPage : Page
_manager.SetEditMode(editCheckBox.IsChecked == true);
}
+ private void ScreenSelectorButton_Click(object sender, RoutedEventArgs e)
+ {
+ ScreenSelectorDialog dialog = new(_dataContext)
+ {
+ Owner = Window.GetWindow(this)
+ };
+
+ _ = dialog.ShowDialog();
+ }
+
private void PluginCheckBox_Click(object sender, RoutedEventArgs e)
{
if (sender is not Control checkBox)