[FancyZones] Editor multi monitor support (#6562)

Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
Co-authored-by: Enrico Giordani <enricogior@users.noreply.github.com>
This commit is contained in:
Seraphima Zykova 2020-11-17 11:38:19 +03:00 committed by GitHub
parent 687fc2e169
commit b8e5ccfb7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 4887 additions and 1503 deletions

View File

@ -1456,6 +1456,7 @@ NDEBUG
ndp
neq
NESW
netcore
netcoreapp
netframework
netfx

View File

@ -13,8 +13,8 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.ActiveCfg = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.Build.0 = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Release|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -3,4 +3,7 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
<runtime>
<AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>
</configuration>

View File

@ -1,18 +1,17 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
using ManagedCommon;
namespace FancyZonesEditor
@ -24,6 +23,7 @@ namespace FancyZonesEditor
{
// Non-localizable strings
private const string CrashReportLogFile = "FZEditorCrashLog.txt";
private const string ErrorReportLogFile = "FZEditorErrorLog.txt";
private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug";
private const string CrashReportExceptionTag = "Exception";
@ -44,57 +44,76 @@ namespace FancyZonesEditor
private readonly IFileSystem _fileSystem = new FileSystem();
public Settings ZoneSettings { get; }
public MainWindowSettingsModel MainWindowSettings { get; }
public static FancyZonesEditorIO FancyZonesEditorIO { get; private set; }
public static Overlay Overlay { get; private set; }
public static int PowerToysPID { get; set; }
public static bool DebugMode
{
get
{
return _debugMode;
}
}
private static bool _debugMode = false;
[Conditional("DEBUG")]
private void DebugModeCheck()
{
_debugMode = true;
}
public App()
{
ZoneSettings = new Settings();
DebugModeCheck();
FancyZonesEditorIO = new FancyZonesEditorIO();
Overlay = new Overlay();
MainWindowSettings = new MainWindowSettingsModel();
}
private void OnStartup(object sender, StartupEventArgs e)
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
RunnerHelper.WaitForPowerToysRunner(Settings.PowerToysPID, () =>
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Environment.Exit(0);
});
LayoutModel foundModel = null;
FancyZonesEditorIO.ParseCommandLineArguments();
FancyZonesEditorIO.ParseDeviceInfoData();
foreach (LayoutModel model in ZoneSettings.DefaultModels)
MainWindowSettingsModel settings = ((App)Current).MainWindowSettings;
settings.UpdateSelectedLayoutModel();
Overlay.Show();
}
public static void ShowExceptionMessageBox(string message, Exception exception = null)
{
string fullMessage = FancyZonesEditor.Properties.Resources.Error_Report + PowerToysIssuesURL + " \n" + message;
if (exception != null)
{
if (model.Type == Settings.ActiveZoneSetLayoutType)
{
// found match
foundModel = model;
break;
}
fullMessage += ": " + exception.Message;
}
if (foundModel == null)
{
foreach (LayoutModel model in Settings.CustomModels)
{
if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper())
{
// found match
foundModel = model;
break;
}
}
}
MessageBox.Show(fullMessage, FancyZonesEditor.Properties.Resources.Error_Exception_Message_Box_Title);
}
if (foundModel == null)
{
foundModel = ZoneSettings.DefaultModels[0];
}
public static void ShowExceptionReportMessageBox(string reportData)
{
var fileStream = File.OpenWrite(ErrorReportLogFile);
var sw = new StreamWriter(fileStream);
sw.Write(reportData);
sw.Flush();
fileStream.Close();
foundModel.IsSelected = true;
EditorOverlay overlay = new EditorOverlay();
overlay.Show();
overlay.DataContext = foundModel;
ShowReportMessageBox(fileStream.Name);
}
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
@ -103,16 +122,22 @@ namespace FancyZonesEditor
var sw = new StreamWriter(fileStream);
sw.Write(FormatException((Exception)args.ExceptionObject));
fileStream.Close();
ShowReportMessageBox(fileStream.Name);
}
private static void ShowReportMessageBox(string fileName)
{
MessageBox.Show(
FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part1 +
Path.GetFullPath(fileStream.Name) +
Path.GetFullPath(fileName) +
"\n" +
FancyZonesEditor.Properties.Resources.Crash_Report_Message_Box_Text_Part2 +
PowerToysIssuesURL,
FancyZonesEditor.Properties.Resources.Fancy_Zones_Editor_App_Title);
}
private string FormatException(Exception ex)
private static string FormatException(Exception ex)
{
var sb = new StringBuilder();
sb.AppendLine();

View File

@ -5,7 +5,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Canvas x:Name="Preview"/>
<Grid x:Name="Body">
<Viewbox Stretch="Uniform">
<Canvas x:Name="Preview"/>
</Viewbox>
</Grid>
</UserControl>

View File

@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows;
using System.Windows.Controls;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
@ -46,6 +47,10 @@ namespace FancyZonesEditor
private void UpdateZoneRects()
{
var workArea = App.Overlay.WorkArea;
Preview.Width = workArea.Width;
Preview.Height = workArea.Height;
UIElementCollection previewChildren = Preview.Children;
int previewChildrenCount = previewChildren.Count;
while (previewChildrenCount < _model.Zones.Count)
@ -54,6 +59,7 @@ namespace FancyZonesEditor
{
Model = _model,
};
Preview.Children.Add(zone);
previewChildrenCount++;
}

View File

@ -13,7 +13,7 @@
SizeToContent="Height"
Background="White"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowStartupLocation="CenterOwner"
Closed="OnClosed">

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@ -19,24 +19,28 @@ namespace FancyZonesEditor
KeyUp += CanvasEditorWindow_KeyUp;
_model = EditorOverlay.Current.DataContext as CanvasLayoutModel;
_model = App.Overlay.CurrentDataContext as CanvasLayoutModel;
_stashedModel = (CanvasLayoutModel)_model.Clone();
}
private void OnAddZone(object sender, RoutedEventArgs e)
{
if (_offset + (int)(Settings.WorkArea.Width * 0.4) < (int)Settings.WorkArea.Width
&& _offset + (int)(Settings.WorkArea.Height * 0.4) < (int)Settings.WorkArea.Height)
Rect workingArea = App.Overlay.WorkArea;
int offset = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(_offset);
if (offset + (int)(workingArea.Width * 0.4) < (int)workingArea.Width
&& offset + (int)(workingArea.Height * 0.4) < (int)workingArea.Height)
{
_model.AddZone(new Int32Rect(_offset, _offset, (int)(Settings.WorkArea.Width * 0.4), (int)(Settings.WorkArea.Height * 0.4)));
_model.AddZone(new Int32Rect(offset, offset, (int)(workingArea.Width * 0.4), (int)(workingArea.Height * 0.4)));
}
else
{
_offset = 100;
_model.AddZone(new Int32Rect(_offset, _offset, (int)(Settings.WorkArea.Width * 0.4), (int)(Settings.WorkArea.Height * 0.4)));
offset = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(_offset);
_model.AddZone(new Int32Rect(offset, offset, (int)(workingArea.Width * 0.4), (int)(workingArea.Height * 0.4)));
}
_offset += 50;
_offset += 50; // TODO: replace hardcoded numbers
}
protected new void OnCancel(object sender, RoutedEventArgs e)

View File

@ -8,6 +8,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
@ -84,7 +85,7 @@ namespace FancyZonesEditor
}
}
foreach (Rect singleMonitor in Settings.UsedWorkAreas)
foreach (Rect singleMonitor in App.Overlay.WorkAreas)
{
int monitorPositionLow = (int)(isX ? singleMonitor.Left : singleMonitor.Top);
int monitorPositionHigh = (int)(isX ? singleMonitor.Right : singleMonitor.Bottom);
@ -213,8 +214,9 @@ namespace FancyZonesEditor
private SnappyHelperBase NewDefaultSnappyHelper(bool isX, ResizeMode mode)
{
int screenAxisOrigin = (int)(isX ? Settings.WorkArea.Left : Settings.WorkArea.Top);
int screenAxisSize = (int)(isX ? Settings.WorkArea.Width : Settings.WorkArea.Height);
Rect workingArea = App.Overlay.WorkArea;
int screenAxisOrigin = (int)(isX ? workingArea.Left : workingArea.Top);
int screenAxisSize = (int)(isX ? workingArea.Width : workingArea.Height);
return new SnappyHelperMagnetic(Model.Zones, ZoneIndex, isX, mode, screenAxisOrigin, screenAxisSize);
}

View File

@ -13,7 +13,7 @@ namespace FancyZonesEditor.Converters
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Settings.IsPredefinedLayout((LayoutModel)value) ? Visibility.Collapsed : Visibility.Visible;
return MainWindowSettingsModel.IsPredefinedLayout((LayoutModel)value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

View File

@ -1,137 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
/// <summary>
/// Interaction logic for EditorOverlay.xaml
/// </summary>
public partial class EditorOverlay : Window
{
public static EditorOverlay Current { get; set; }
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
private LayoutPreview _layoutPreview;
private UserControl _editor;
private static MainWindow _mainWindow = new MainWindow();
public Int32Rect[] GetZoneRects()
{
if (_editor != null)
{
if (_editor is GridEditor gridEditor)
{
return ZoneRectsFromPanel(gridEditor.PreviewPanel);
}
else
{
// CanvasEditor
return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview);
}
}
else
{
// One of the predefined zones (neither grid or canvas editor used).
return _layoutPreview.GetZoneRects();
}
}
private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel)
{
// TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info
int count = previewPanel.Children.Count;
Int32Rect[] zones = new Int32Rect[count];
for (int i = 0; i < count; i++)
{
FrameworkElement child = (FrameworkElement)previewPanel.Children[i];
Point topLeft = child.TransformToAncestor(previewPanel).Transform(default);
zones[i].X = (int)topLeft.X;
zones[i].Y = (int)topLeft.Y;
zones[i].Width = (int)child.ActualWidth;
zones[i].Height = (int)child.ActualHeight;
}
return zones;
}
public EditorOverlay()
{
InitializeComponent();
Current = this;
Left = Settings.WorkArea.Left;
Top = Settings.WorkArea.Top;
Width = Settings.WorkArea.Width;
Height = Settings.WorkArea.Height;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
ShowLayoutPicker();
}
public void ShowLayoutPicker()
{
_editor = null;
_layoutPreview = new LayoutPreview
{
IsActualSize = true,
Opacity = 0.5,
};
Content = _layoutPreview;
_mainWindow.Owner = this;
_mainWindow.ShowActivated = true;
_mainWindow.Topmost = true;
_mainWindow.Show();
_mainWindow.LeftWindowCommands = null;
_mainWindow.RightWindowCommands = null;
// window is set to topmost to make sure it shows on top of PowerToys settings page
// we can reset topmost flag now
_mainWindow.Topmost = false;
}
// These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard
// They reflect that current state into properties on the Settings object, which the Zone view will listen to in editing mode
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
base.OnPreviewKeyDown(e);
}
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
_settings.IsShiftKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
_settings.IsCtrlKeyPressed = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
base.OnPreviewKeyUp(e);
}
public void Edit()
{
_layoutPreview = null;
if (DataContext is GridLayoutModel)
{
_editor = new GridEditor();
}
else if (DataContext is CanvasLayoutModel)
{
_editor = new CanvasEditor();
}
Content = _editor;
}
}
}

View File

@ -13,8 +13,8 @@ namespace FancyZonesEditor
{
protected void OnSaveApplyTemplate(object sender, RoutedEventArgs e)
{
EditorOverlay mainEditor = EditorOverlay.Current;
if (mainEditor.DataContext is LayoutModel model)
var mainEditor = App.Overlay;
if (mainEditor.CurrentDataContext is LayoutModel model)
{
// If new custom Canvas layout is created (i.e. edited Blank layout),
// it's type needs to be updated
@ -30,14 +30,14 @@ namespace FancyZonesEditor
_backToLayoutPicker = false;
Close();
EditorOverlay.Current.Close();
mainEditor.CloseEditor();
}
protected void OnClosed(object sender, EventArgs e)
{
if (_backToLayoutPicker)
{
EditorOverlay.Current.ShowLayoutPicker();
App.Overlay.CloseEditor();
}
}

View File

@ -89,6 +89,9 @@
<PropertyGroup>
<ApplicationIcon>images\FancyZonesEditor.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
@ -118,9 +121,16 @@
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<Compile Include="Models\LayoutSettings.cs" />
<Compile Include="DashCaseNamingPolicy.cs" />
<Compile Include="GridData.cs" />
<Compile Include="GridDragHandles.cs" />
<Compile Include="LayoutOverlayWindow.xaml.cs">
<DependentUpon>LayoutOverlayWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Models\LayoutType.cs" />
<Compile Include="Models\Monitor.cs" />
<Compile Include="Overlay.cs" />
<Compile Include="StringUtils.cs" />
<Compile Include="Converters\BooleanToBrushConverter.xaml.cs" />
<Compile Include="Converters\BooleanToIntConverter.xaml.cs" />
@ -140,10 +150,7 @@
<Compile Include="Models\CanvasLayoutModel.cs" />
<Compile Include="Models\GridLayoutModel.cs" />
<Compile Include="Models\LayoutModel.cs" />
<Compile Include="Models\Settings.cs" />
<Compile Include="EditorOverlay.xaml.cs">
<DependentUpon>EditorOverlay.xaml</DependentUpon>
</Compile>
<Compile Include="Models\MainWindowSettingsModel.cs" />
<Compile Include="Converters\ModelToVisibilityConverter.xaml.cs" />
<Compile Include="Native.cs" />
<Compile Include="RowColInfo.cs" />
@ -151,6 +158,15 @@
<DependentUpon>GridEditorWindow.xaml</DependentUpon>
</Compile>
<Compile Include="SplitEventArgs.cs" />
<Compile Include="Utils\FancyZonesEditorIO.cs" />
<Compile Include="Utils\EventArgs`1.cs" />
<Compile Include="Utils\EventRaiser.cs" />
<Compile Include="Utils\MonitorChangedEventArgs.cs" />
<Compile Include="Models\MonitorInfoModel.cs" />
<Compile Include="Utils\RelayCommand.cs" />
<Compile Include="Utils\RelayCommand`1.cs" />
<Compile Include="Models\Device.cs" />
<Compile Include="ViewModels\MonitorViewModel.cs" />
<Compile Include="WindowLayout.xaml.cs">
<DependentUpon>WindowLayout.xaml</DependentUpon>
</Compile>
@ -175,6 +191,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="LayoutOverlayWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="LayoutPreview.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -191,10 +211,6 @@
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="EditorOverlay.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="GridEditorWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -235,6 +251,7 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.*.resx" />
<None Include="app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -253,6 +270,9 @@
<PackageReference Include="System.IO.Abstractions">
<Version>12.2.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.DpiAwareness">
<Version>6.6.30107</Version>
</PackageReference>
<PackageReference Include="System.Text.Json">
<Version>4.7.2</Version>
</PackageReference>

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{

View File

@ -19,33 +19,35 @@
<Setter Property="Width" Value="150" />
</Style>
</UserControl.Resources>
<Grid>
<Canvas x:Name="Preview" />
<Canvas x:Name="AdornerLayer" />
<Canvas
x:Name="MergePanel"
MouseUp="MergePanelMouseUp"
Visibility="Collapsed">
<Viewbox Stretch="Uniform">
<Grid>
<Canvas x:Name="Preview" />
<Canvas x:Name="AdornerLayer" />
<Canvas
x:Name="MergePanel"
MouseUp="MergePanelMouseUp"
Visibility="Collapsed">
<StackPanel
x:Name="MergeButtons"
Background="Gray"
Orientation="Horizontal">
<Button
Width="134"
Height="36"
Margin="0"
Click="MergeClick">
<StackPanel Orientation="Horizontal">
<Image
Height="16"
Margin="0,0,12,0"
HorizontalAlignment="Left"
Source="images/Merge.png" />
<TextBlock Text="Merge zones" />
</StackPanel>
</Button>
</StackPanel>
</Canvas>
</Grid>
<StackPanel
x:Name="MergeButtons"
Background="Gray"
Orientation="Horizontal">
<Button
Width="134"
Height="36"
Margin="0"
Click="MergeClick">
<StackPanel Orientation="Horizontal">
<Image
Height="16"
Margin="0,0,12,0"
HorizontalAlignment="Left"
Source="images/Merge.png" />
<TextBlock Text="Merge zones" />
</StackPanel>
</Button>
</StackPanel>
</Canvas>
</Grid>
</Viewbox>
</UserControl>

View File

@ -8,6 +8,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
@ -32,7 +33,7 @@ namespace FancyZonesEditor
InitializeComponent();
Loaded += GridEditor_Loaded;
Unloaded += GridEditor_Unloaded;
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
}
@ -69,7 +70,8 @@ namespace FancyZonesEditor
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Size actualSize = new Size(ActualWidth, ActualHeight);
Rect workingArea = App.Overlay.WorkArea;
Size actualSize = new Size(workingArea.Width, workingArea.Height);
// Only enter if this is the newest instance
if (actualSize.Width > 0 && gridEditorUniqueId == gridEditorUniqueIdCounter)
@ -248,7 +250,7 @@ namespace FancyZonesEditor
}
_dragHandles.UpdateAfterVerticalSplit(foundCol);
_data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, ActualWidth);
_data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Width);
_dragHandles.AddDragHandle(Orientation.Vertical, foundRow, foundCol, model);
}
else
@ -298,11 +300,12 @@ namespace FancyZonesEditor
}
_dragHandles.UpdateAfterHorizontalSplit(foundRow);
_data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, ActualHeight);
_data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Height);
_dragHandles.AddDragHandle(Orientation.Horizontal, foundRow, foundCol, model);
}
Size actualSize = new Size(ActualWidth, ActualHeight);
var workArea = App.Overlay.WorkArea;
Size actualSize = new Size(workArea.Width, workArea.Height);
ArrangeGridRects(actualSize);
}
@ -354,7 +357,8 @@ namespace FancyZonesEditor
private void OnGridDimensionsChanged()
{
Size actualSize = new Size(ActualWidth, ActualHeight);
Rect workingArea = App.Overlay.WorkArea;
Size actualSize = new Size(workingArea.Width, workingArea.Height);
if (actualSize.Width > 0)
{
ArrangeGridRects(actualSize);
@ -363,6 +367,10 @@ namespace FancyZonesEditor
private void ArrangeGridRects(Size arrangeSize)
{
var workArea = App.Overlay.WorkArea;
Preview.Width = workArea.Width;
Preview.Height = workArea.Height;
GridLayoutModel model = Model;
if (model == null)
{
@ -375,7 +383,7 @@ namespace FancyZonesEditor
return;
}
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
int spacing = settings.ShowSpacing ? settings.Spacing : 0;
@ -403,7 +411,7 @@ namespace FancyZonesEditor
if (_dragHandles.HasSnappedNonAdjacentResizers(resizer))
{
double spacing = 0;
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
if (settings.ShowSpacing)
{
spacing = settings.Spacing;
@ -422,7 +430,8 @@ namespace FancyZonesEditor
}
}
Size actualSize = new Size(ActualWidth, ActualHeight);
Rect workingArea = App.Overlay.WorkArea;
Size actualSize = new Size(workingArea.Width, workingArea.Height);
ArrangeGridRects(actualSize);
AdornerLayer.UpdateLayout();
}
@ -433,7 +442,8 @@ namespace FancyZonesEditor
int index = _data.SwappedIndexAfterResize(resizer);
if (index != -1)
{
Size actualSize = new Size(ActualWidth, ActualHeight);
Rect workingArea = App.Overlay.WorkArea;
Size actualSize = new Size(workingArea.Width, workingArea.Height);
ArrangeGridRects(actualSize);
}
}
@ -478,7 +488,6 @@ namespace FancyZonesEditor
if (_startDragPos.X != -1)
{
Point dragPos = e.GetPosition(Preview);
_startRow = -1;
_endRow = -1;
_startCol = -1;

View File

@ -13,7 +13,7 @@
SizeToContent="Height"
Background="White"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowStartupLocation="CenterOwner"
Closed="OnClosed">
<Window.Resources>

View File

@ -5,6 +5,7 @@
using System.Windows;
using System.Windows.Input;
using FancyZonesEditor.Models;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
@ -19,13 +20,13 @@ namespace FancyZonesEditor
KeyUp += GridEditorWindow_KeyUp;
_stashedModel = (GridLayoutModel)(EditorOverlay.Current.DataContext as GridLayoutModel).Clone();
_stashedModel = (GridLayoutModel)(App.Overlay.CurrentDataContext as GridLayoutModel).Clone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
GridLayoutModel model = EditorOverlay.Current.DataContext as GridLayoutModel;
GridLayoutModel model = App.Overlay.CurrentDataContext as GridLayoutModel;
_stashedModel.RestoreTo(model);
}

View File

@ -69,14 +69,14 @@ namespace FancyZonesEditor
};
Body.Children.Add(_splitter);
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
}
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == PropertyIsShiftKeyPressedID)
{
_switchOrientation = ((App)Application.Current).ZoneSettings.IsShiftKeyPressed;
_switchOrientation = ((App)Application.Current).MainWindowSettings.IsShiftKeyPressed;
if (_lastPos.X != -1)
{
UpdateSplitter();
@ -108,7 +108,7 @@ namespace FancyZonesEditor
{
get
{
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
if (!settings.ShowSpacing)
{
return 1;
@ -283,7 +283,7 @@ namespace FancyZonesEditor
private void DoSplit(Orientation orientation, double offset)
{
int spacing = 0;
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
if (settings.ShowSpacing)
{
spacing = settings.Spacing;

View File

@ -1,14 +1,13 @@
<Window x:Class="FancyZonesEditor.EditorOverlay"
<Window x:Class="FancyZonesEditor.LayoutOverlayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
Title="FancyZones Editor" Height="450" Width="800"
Title="FancyZones Layout" Height="450" Width="800"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
Loaded="OnLoaded"/>
Background="Transparent"/>

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows;
using System.Windows.Media;
namespace FancyZonesEditor
{
/// <summary>
/// Interaction logic for LayoutOverlayWindow.xaml
/// </summary>
public partial class LayoutOverlayWindow : Window
{
public LayoutOverlayWindow()
{
InitializeComponent();
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@ -29,11 +29,22 @@ namespace FancyZonesEditor
private LayoutModel _model;
private List<Int32Rect> _zones = new List<Int32Rect>();
public bool IsActualSize
{
get { return (bool)GetValue(IsActualSizeProperty); }
set { SetValue(IsActualSizeProperty, value); }
}
public LayoutPreview()
{
InitializeComponent();
DataContextChanged += LayoutPreview_DataContextChanged;
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged;
}
public void UpdatePreview()
{
RenderPreview();
}
private void LayoutPreview_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
@ -42,12 +53,6 @@ namespace FancyZonesEditor
RenderPreview();
}
public bool IsActualSize
{
get { return (bool)GetValue(IsActualSizeProperty); }
set { SetValue(IsActualSizeProperty, value); }
}
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == PropertyZoneCountID)
@ -109,27 +114,25 @@ namespace FancyZonesEditor
RowColInfo[] colInfo = (from percent in grid.ColumnPercents
select new RowColInfo(percent)).ToArray();
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
int spacing = settings.ShowSpacing ? settings.Spacing : 0;
int width = (int)Settings.WorkArea.Width;
int height = (int)Settings.WorkArea.Height;
double totalWidth = width - (spacing * (cols + 1));
double totalHeight = height - (spacing * (rows + 1));
var workArea = App.Overlay.WorkArea;
double width = workArea.Width - (spacing * (cols + 1));
double height = workArea.Height - (spacing * (rows + 1));
double top = spacing;
for (int row = 0; row < rows; row++)
{
double cellHeight = rowInfo[row].Recalculate(top, totalHeight);
double cellHeight = rowInfo[row].Recalculate(top, height);
top += cellHeight + spacing;
}
double left = spacing;
for (int col = 0; col < cols; col++)
{
double cellWidth = colInfo[col].Recalculate(left, totalWidth);
double cellWidth = colInfo[col].Recalculate(left, width);
left += cellWidth + spacing;
}
@ -140,8 +143,8 @@ namespace FancyZonesEditor
Body.Children.Add(viewbox);
Canvas frame = new Canvas
{
Width = width,
Height = height,
Width = workArea.Width,
Height = workArea.Height,
};
viewbox.Child = frame;
@ -183,6 +186,14 @@ namespace FancyZonesEditor
}
}
}
if (App.DebugMode)
{
TextBlock text = new TextBlock();
text.Text = "(" + workArea.X + "," + workArea.Y + ")";
text.FontSize = 42;
frame.Children.Add(text);
}
}
private void RenderSmallScalePreview(GridLayoutModel grid)
@ -205,7 +216,7 @@ namespace FancyZonesEditor
Body.ColumnDefinitions.Add(def);
}
Settings settings = ((App)Application.Current).ZoneSettings;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
Thickness margin = new Thickness(settings.ShowSpacing ? settings.Spacing / 20 : 0);
List<int> visited = new List<int>();
@ -265,6 +276,12 @@ namespace FancyZonesEditor
private void RenderCanvasPreview(CanvasLayoutModel canvas)
{
var workArea = canvas.CanvasRect;
if (workArea.Width == 0 || workArea.Height == 0 || App.Overlay.SpanZonesAcrossMonitors)
{
workArea = App.Overlay.WorkArea;
}
Viewbox viewbox = new Viewbox
{
Stretch = Stretch.Uniform,
@ -272,10 +289,11 @@ namespace FancyZonesEditor
Body.Children.Add(viewbox);
Canvas frame = new Canvas
{
Width = Settings.WorkArea.Width,
Height = Settings.WorkArea.Height,
Width = workArea.Width,
Height = workArea.Height,
};
viewbox.Child = frame;
foreach (Int32Rect zone in canvas.Zones)
{
Rectangle rect = new Rectangle();
@ -288,6 +306,14 @@ namespace FancyZonesEditor
rect.Fill = Brushes.LightGray;
frame.Children.Add(rect);
}
if (App.DebugMode)
{
TextBlock text = new TextBlock();
text.Text = "(" + App.Overlay.WorkArea.X + "," + App.Overlay.WorkArea.Y + ")";
text.FontSize = 42;
frame.Children.Add(text);
}
}
}
}

View File

@ -1,324 +1,464 @@
<Controls:MetroWindow x:Class="FancyZonesEditor.MainWindow"
x:Name="MainWindow1"
AutomationProperties.Name="{x:Static props:Resources.Fancy_Zones_Main_Editor}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:Converters="clr-namespace:FancyZonesEditor.Converters"
xmlns:props="clr-namespace:FancyZonesEditor.Properties"
mc:Ignorable="d"
Title=""
Width="810"
SizeToContent="Height"
Background="White"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Initialized="OnInitialized"
Closing="OnClosing">
<Window.Resources>
<Converters:BooleanToBrushConverter x:Key="BooleanToBrushConverter" />
<Converters:ModelToVisibilityConverter x:Key="ModelToVisibilityConverter" />
<Converters:BooleanToIntConverter x:Key="BooleanToIntConverter" />
<Style x:Key="titleText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="LineHeight" Value="24" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="16,20,0,0" />
</Style>
<Style x:Key="zoneCount" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="FontSize" Value="24"/>
<Setter Property="LineHeight" Value="24" />
<Setter Property="Margin" Value="20,0,20,0" />
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="tabText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#767676"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="LineHeight" Value="20" />
<Setter Property="Margin" Value="24,20,0,0" />
<Setter Property="TextAlignment" Value="Center" />
</Style>
<Style x:Key="settingText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="LineHeight" Value="20" />
<Setter Property="Margin" Value="7,10,0,0" />
<Setter Property="TextAlignment" Value="Left" />
</Style>
<Style x:Key="settingCheckBoxText" TargetType="CheckBox">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5,10,0,0" />
</Style>
<Style x:Key="templateTitleText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="customButtonFocusVisualStyle">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="1" Stroke="White" StrokeDashArray="1 2" StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="secondaryButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="0,5,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#767676"/>
<Setter Property="Margin" Value="16,0,0,0" />
<Setter Property="Width" Value="378"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FocusVisualStyle" Value="{DynamicResource customButtonFocusVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#5D5D5D"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="primaryButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="0,5,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#0078D7"/>
<Setter Property="Margin" Value="16,0,16,0" />
<Setter Property="Width" Value="377"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FocusVisualStyle" Value="{DynamicResource customButtonFocusVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#024D89"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="spinnerButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="Padding" Value="0,0,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="Margin" Value="0,0,0,0" />
</Style>
<Style x:Key="newLayoutButton" TargetType="Button">
<Setter Property="Background" Value="#f2f2f2"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontFamily" Value="SegoeUI"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="FontSize" Value="148"/>
</Style>
<Style x:Key="textBox" TargetType="TextBox">
<Setter Property="BorderBrush" Value="#cccccc"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontFamily" Value="SegoeUI"/>
<Setter Property="FontWeight" Value="Regular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="16,6,0,0"/>
<Setter Property="Padding" Value="5,5,5,5"/>
</Style>
<Style x:Key="templateBackground" TargetType="Border">
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<ControlTemplate x:Key="myTabs">
<Grid Width="404">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="2"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="myTabText" Text="{TemplateBinding TabItem.Header}" Style="{StaticResource tabText}" />
<Rectangle Grid.Row="1" x:Name="myTabLine" Fill="#F2F2F2" Height="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabItem.IsSelected" Value="True">
<Setter TargetName="myTabText" Property="Foreground" Value="#2A79D7"/>
<Setter TargetName="myTabLine" Property="Fill" Value="#2A79D7" />
<Setter TargetName="myTabLine" Property="Height" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<StackPanel FocusManager.FocusedElement="{Binding ElementName=decrementZones}">
<TextBlock Name="DialogTitle" Text="{x:Static props:Resources.Choose_Layout}" Style="{StaticResource titleText}" />
<TabControl BorderThickness="0" x:Name="TemplateTab" AutomationProperties.LabeledBy="{Binding ElementName=DialogTitle}" SelectedIndex="{Binding IsCustomLayoutActive, Mode=OneWay, Converter={StaticResource BooleanToIntConverter}}">
<TabItem Header="{x:Static props:Resources.Templates}" Template="{StaticResource myTabs}" AutomationProperties.Name="{x:Static props:Resources.Tab_Item_Templates}">
<StackPanel>
<StackPanel Margin="0,15,0,8" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="decrementZones" Width="40" Height="40" AutomationProperties.Name="{x:Static props:Resources.Zone_Count_Decrement}" Content="-" Style="{StaticResource spinnerButton}" Click="DecrementZones_Click"/>
<TextBlock x:Name="zoneCount" Text="{Binding ZoneCount}" Style="{StaticResource zoneCount}"/>
<Button x:Name="incrementZones" Width="40" Height="40" AutomationProperties.Name="{x:Static props:Resources.Zone_Count_Increment}" Content="+" Style="{StaticResource spinnerButton}" Click="IncrementZones_Click"/>
</StackPanel>
<ItemsControl ItemsSource="{Binding DefaultModels}" Margin="8,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ItemWidth="{Binding ElementName=MainWindow1, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Path=WrapPanelItemSize}" ItemHeight="{Binding ElementName=WrapPanel1, Path=ItemWidth}" x:Name="WrapPanel1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="8"
BorderBrush="{Binding Path=IsSelected, Converter={StaticResource BooleanToBrushConverter}}"
Style="{StaticResource templateBackground}"
MouseDown="LayoutItem_Click"
Focusable="True"
FocusManager.GotFocus="LayoutItem_Focused"
KeyDown="LayoutItem_Apply">
<DockPanel Margin="0,20,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LastChildFill="True">
<TextBlock Padding="8,0,8,0"
TextTrimming="CharacterEllipsis"
DockPanel.Dock="Top"
Text="{Binding Name}"
Style="{StaticResource templateTitleText}" />
<local:LayoutPreview Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</TabItem>
<TabItem Header="{x:Static props:Resources.Custom}" Template="{StaticResource myTabs}" AutomationProperties.Name="{x:Static props:Resources.Tab_Item_Custom}">
<StackPanel>
<ItemsControl ItemsSource="{Binding CustomModels}" Margin="8,8,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ItemWidth="{Binding ElementName=MainWindow1, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Path=WrapPanelItemSize}" ItemHeight="{Binding ElementName=WrapPanel2, Path=ItemWidth}" x:Name="WrapPanel2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="8"
BorderBrush="{Binding Path=IsSelected, Converter={StaticResource BooleanToBrushConverter}}"
Style="{StaticResource templateBackground}"
MouseDown="LayoutItem_Click"
Focusable="True"
FocusManager.GotFocus="LayoutItem_Focused"
KeyDown="LayoutItem_Apply">
<DockPanel Margin="0,20,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LastChildFill="True">
<DockPanel DockPanel.Dock="Top" LastChildFill="True" >
<Button x:Name="DeleteButton" AutomationProperties.Name="{x:Static props:Resources.Custom_Layout_Delete_Button}"
Visibility="{Binding Converter={StaticResource ModelToVisibilityConverter}}"
DockPanel.Dock="Right"
MaxHeight="10"
Click="OnDelete"
Padding="8,4,8,4"
Margin="0,0,8,0"
BorderThickness="0"
Background="#f2f2f2">
<Image Source="images/Delete.png" />
</Button>
<TextBlock DockPanel.Dock="Top"
TextTrimming="CharacterEllipsis" Padding="8,0,8,0"
Text="{Binding Name}"
Style="{StaticResource templateTitleText}" />
</DockPanel>
<local:LayoutPreview Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</TabItem>
</TabControl>
<Grid Margin="10,4,10,8" >
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="spaceAroundSetting" Content="{x:Static props:Resources.Show_Space_Zones}" Style="{StaticResource settingCheckBoxText}" IsChecked="{Binding ShowSpacing}" Grid.Row="0" Grid.Column="0"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
<TextBlock x:Name="paddingValue"
Text="{x:Static props:Resources.Space_Around_Zones}"
Style="{StaticResource settingText}"
IsEnabled="{Binding ShowSpacing}"
MaxWidth="{Binding Path=SettingsTextMaxWidth, ElementName=MainWindow1}"
TextWrapping="Wrap"/>
<TextBox AutomationProperties.LabeledBy="{Binding ElementName=paddingValue}" Text="{Binding Path=Spacing,Mode=TwoWay}" Style="{StaticResource textBox}" MinWidth="32" IsEnabled="{Binding ShowSpacing}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<TextBlock x:Name="sensitivityRadiusValue"
Text="{x:Static props:Resources.Distance_adjacent_zones}"
Style="{StaticResource settingText}"
MaxWidth="{Binding Path=SettingsTextMaxWidth, ElementName=MainWindow1}"
TextWrapping="Wrap"/>
<TextBox AutomationProperties.LabeledBy="{Binding ElementName=sensitivityRadiusValue}" Text="{Binding Path=SensitivityRadius,Mode=TwoWay}" Style="{StaticResource textBox}" MinWidth="40"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,10,0,16">
<Button x:Name="EditCustomButton" Content="{x:Static props:Resources.Edit_Selected_Layout}" Padding="8" Style="{StaticResource secondaryButton}" Click="EditLayout_Click"/>
<Button x:Name="ApplyCustomButton" Content="{x:Static props:Resources.Apply}" Padding="8" Style="{StaticResource primaryButton}" Click="Apply_Click"/>
</StackPanel>
</StackPanel>
</Controls:MetroWindow>
<Controls:MetroWindow x:Class="FancyZonesEditor.MainWindow"
x:Name="MainWindow1"
AutomationProperties.Name="{x:Static props:Resources.Fancy_Zones_Main_Editor}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:Converters="clr-namespace:FancyZonesEditor.Converters"
xmlns:props="clr-namespace:FancyZonesEditor.Properties" xmlns:local1="clr-namespace:FancyZonesEditor.ViewModels"
mc:Ignorable="d"
Title=""
Width="810"
SizeToContent="Height"
Background="White"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Initialized="OnInitialized"
Closing="OnClosing">
<Window.Resources>
<Converters:BooleanToBrushConverter x:Key="BooleanToBrushConverter" />
<Converters:ModelToVisibilityConverter x:Key="ModelToVisibilityConverter" />
<Converters:BooleanToIntConverter x:Key="BooleanToIntConverter" />
<Style x:Key="titleText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="LineHeight" Value="24" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="16,20,0,0" />
</Style>
<Style x:Key="zoneCount" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="FontSize" Value="24"/>
<Setter Property="LineHeight" Value="24" />
<Setter Property="Margin" Value="20,0,20,0" />
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="tabText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#767676"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="LineHeight" Value="20" />
<Setter Property="Margin" Value="24,20,0,0" />
<Setter Property="TextAlignment" Value="Center" />
</Style>
<Style x:Key="settingText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="LineHeight" Value="20" />
<Setter Property="Margin" Value="7,10,0,0" />
<Setter Property="TextAlignment" Value="Left" />
</Style>
<Style x:Key="settingCheckBoxText" TargetType="CheckBox">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5,10,0,0" />
</Style>
<Style x:Key="templateTitleText" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="customButtonFocusVisualStyle">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="1" Stroke="White" StrokeDashArray="1 2" StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="secondaryButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="0,5,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#767676"/>
<Setter Property="Margin" Value="16,0,0,0" />
<Setter Property="Width" Value="245"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FocusVisualStyle" Value="{DynamicResource customButtonFocusVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#5D5D5D"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="primaryButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="0,5,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#0078D7"/>
<Setter Property="Margin" Value="16,0,0,0" />
<Setter Property="Width" Value="245"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FocusVisualStyle" Value="{DynamicResource customButtonFocusVisualStyle}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#024D89"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="spinnerButton" TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="Padding" Value="0,0,0,5"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="Margin" Value="0,0,0,0" />
</Style>
<Style x:Key="newLayoutButton" TargetType="Button">
<Setter Property="Background" Value="#f2f2f2"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontFamily" Value="SegoeUI"/>
<Setter Property="FontWeight" Value="Light"/>
<Setter Property="FontSize" Value="148"/>
</Style>
<Style x:Key="textBox" TargetType="TextBox">
<Setter Property="BorderBrush" Value="#cccccc"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontFamily" Value="SegoeUI"/>
<Setter Property="FontWeight" Value="Regular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="16,6,0,0"/>
<Setter Property="Padding" Value="5,5,5,5"/>
</Style>
<Style x:Key="desktopIdText" TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Light" />
<Setter Property="FontSize" Value="42" />
<Setter Property="Foreground" Value="#303030" />
</Style>
<Style x:Key="desktopDimensionsText" TargetType="TextBlock">
<Setter Property="TextAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="Light" />
<Setter Property="FontSize" Value="10" />
<Setter Property="Foreground" Value="#303030" />
</Style>
<Style x:Key="desktopButtonStyle" TargetType="Button">
<Setter Property="Background" Value="#DADADA" />
<Setter Property="Width" Value="{Binding DisplayWidth}" />
<Setter Property="Height" Value="{Binding DisplayHeight}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="0"
BorderBrush="#303030"
BorderThickness="2" >
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#C2C2C2" />
</Trigger>
<DataTrigger Binding="{Binding Selected}" Value="true">
<Setter Property="Background" Value="#0078D7" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="templateBackground" TargetType="Border">
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
<ControlTemplate x:Key="myTabs">
<Grid Width="404">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="2"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="myTabText" Text="{TemplateBinding TabItem.Header}" Style="{StaticResource tabText}" />
<Rectangle Grid.Row="1" x:Name="myTabLine" Fill="#F2F2F2" Height="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabItem.IsSelected" Value="True">
<Setter TargetName="myTabText" Property="Foreground" Value="#2A79D7"/>
<Setter TargetName="myTabLine" Property="Fill" Value="#2A79D7" />
<Setter TargetName="myTabLine" Property="Height" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="desktopButton">
<Button Style="{StaticResource desktopButtonStyle}"
Command="{Binding DataContext.SelectCommand, ElementName= MainWindowItemControl}"
CommandParameter="{Binding}"
Margin="1,6,1,6">
<Button.Content>
<Grid Name="MonitorsGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="0.40*"/>
</Grid.RowDefinitions>
<TextBlock Name="desktopId" Text="{Binding Index}" Style="{StaticResource desktopIdText}" Grid.Row="0"/>
<TextBlock Name="desktopDimensions" Text="{Binding Dimensions}" Style="{StaticResource desktopDimensionsText}" Grid.Row="1" />
</Grid>
</Button.Content>
</Button>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Selected}" Value="true">
<Setter TargetName="desktopId" Property="Foreground" Value="#F2F2F2" />
<Setter TargetName="desktopDimensions" Property="Foreground" Value="#F2F2F2" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<StackPanel FocusManager.FocusedElement="{Binding ElementName=decrementZones}">
<TextBlock Name="DialogTitle" Text="{x:Static props:Resources.Choose_Layout}" Style="{StaticResource titleText}" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
ScrollViewer.PanningMode="HorizontalOnly"
Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<ScrollViewer.DataContext>
<local1:MonitorViewModel></local1:MonitorViewModel>
</ScrollViewer.DataContext>
<ItemsControl x:Name="MainWindowItemControl"
ItemsSource="{Binding MonitorInfoForViewModel}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="Transparent"
Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="8, 16, 8, 8"
Visibility="{Binding DesktopsPanelVisibility}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Template="{StaticResource desktopButton}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
<TabControl BorderThickness="0" x:Name="TemplateTab" AutomationProperties.LabeledBy="{Binding ElementName=DialogTitle}" SelectedIndex="{Binding IsCustomLayoutActive, Mode=OneWay, Converter={StaticResource BooleanToIntConverter}}">
<TabItem Header="{x:Static props:Resources.Templates}" Template="{StaticResource myTabs}" AutomationProperties.Name="{x:Static props:Resources.Tab_Item_Templates}">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Margin="0,15,0,8" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="decrementZones" Width="40" Height="40" AutomationProperties.Name="{x:Static props:Resources.Zone_Count_Decrement}" Content="-" Style="{StaticResource spinnerButton}" Click="DecrementZones_Click"/>
<TextBlock x:Name="zoneCount" Text="{Binding ZoneCount}" Style="{StaticResource zoneCount}"/>
<Button x:Name="incrementZones" Width="40" Height="40" AutomationProperties.Name="{x:Static props:Resources.Zone_Count_Increment}" Content="+" Style="{StaticResource spinnerButton}" Click="IncrementZones_Click"/>
</StackPanel>
</Grid>
<ItemsControl ItemsSource="{Binding DefaultModels}" Margin="8,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Margin="2"
ItemWidth="{Binding ElementName=MainWindow1, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Path=WrapPanelItemSize}"
ItemHeight="{Binding ElementName=WrapPanel1, Path=ItemWidth}"
x:Name="WrapPanel1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="8"
BorderBrush="{Binding Path=IsSelected, Converter={StaticResource BooleanToBrushConverter}}"
Background="{Binding Path=IsApplied, Converter={StaticResource BooleanToBrushConverter}}"
Style="{StaticResource templateBackground}"
MouseDown="LayoutItem_Click"
Focusable="True"
FocusManager.GotFocus="LayoutItem_Focused"
KeyDown="LayoutItem_Apply">
<DockPanel Margin="0,20,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LastChildFill="True">
<TextBlock Name="layoutName"
Padding="8,0,8,0"
TextTrimming="CharacterEllipsis"
DockPanel.Dock="Top"
Text="{Binding Name}"
Style="{StaticResource templateTitleText}" />
<local:LayoutPreview Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsApplied}" Value="true">
<Setter TargetName="layoutName" Property="Foreground" Value="#F2F2F2" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</TabItem>
<TabItem Header="{x:Static props:Resources.Custom}" Template="{StaticResource myTabs}" AutomationProperties.Name="{x:Static props:Resources.Tab_Item_Custom}">
<StackPanel>
<ItemsControl ItemsSource="{Binding CustomModels}" Margin="8,8,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ItemWidth="{Binding ElementName=MainWindow1, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Path=WrapPanelItemSize}" ItemHeight="{Binding ElementName=WrapPanel2, Path=ItemWidth}" x:Name="WrapPanel2" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="8"
BorderBrush="{Binding Path=IsSelected, Converter={StaticResource BooleanToBrushConverter}}"
Background="{Binding Path=IsApplied, Converter={StaticResource BooleanToBrushConverter}}"
Style="{StaticResource templateBackground}"
MouseDown="LayoutItem_Click"
Focusable="True"
FocusManager.GotFocus="LayoutItem_Focused"
KeyDown="LayoutItem_Apply">
<DockPanel Margin="0,20,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
LastChildFill="True">
<DockPanel DockPanel.Dock="Top" LastChildFill="True" >
<Button x:Name="DeleteButton" AutomationProperties.Name="{x:Static props:Resources.Custom_Layout_Delete_Button}"
Visibility="{Binding Converter={StaticResource ModelToVisibilityConverter}}"
DockPanel.Dock="Right"
MaxHeight="10"
Click="OnDelete"
Padding="8,4,8,4"
Margin="0,0,8,0"
BorderThickness="0"
Background="#f2f2f2">
<Image Source="images/Delete.png" />
</Button>
<TextBlock Name="layoutName"
DockPanel.Dock="Top"
TextTrimming="CharacterEllipsis" Padding="8,0,8,0"
Text="{Binding Name}"
Style="{StaticResource templateTitleText}" />
</DockPanel>
<local:LayoutPreview Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsApplied}" Value="true">
<Setter TargetName="layoutName" Property="Foreground" Value="#F2F2F2" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</TabItem>
</TabControl>
<Grid Margin="10,4,10,8" >
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="spaceAroundSetting" Content="{x:Static props:Resources.Show_Space_Zones}" Style="{StaticResource settingCheckBoxText}" IsChecked="{Binding ShowSpacing}" Grid.Row="0" Grid.Column="0"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
<TextBlock x:Name="paddingValue"
Text="{x:Static props:Resources.Space_Around_Zones}"
Style="{StaticResource settingText}"
IsEnabled="{Binding ShowSpacing}"
MaxWidth="{Binding Path=SettingsTextMaxWidth, ElementName=MainWindow1}"
TextWrapping="Wrap"/>
<TextBox AutomationProperties.LabeledBy="{Binding ElementName=paddingValue}" Text="{Binding Path=Spacing,Mode=TwoWay}" Style="{StaticResource textBox}" MinWidth="32" IsEnabled="{Binding ShowSpacing}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<TextBlock x:Name="sensitivityRadiusValue"
Text="{x:Static props:Resources.Distance_adjacent_zones}"
Style="{StaticResource settingText}"
MaxWidth="{Binding Path=SettingsTextMaxWidth, ElementName=MainWindow1}"
TextWrapping="Wrap"/>
<TextBox AutomationProperties.LabeledBy="{Binding ElementName=sensitivityRadiusValue}" Text="{Binding Path=SensitivityRadius,Mode=TwoWay}" Style="{StaticResource textBox}" MinWidth="40"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,10,0,16">
<Button x:Name="EditCustomButton" Content="{x:Static props:Resources.Edit_Selected_Layout}" Padding="8" Style="{StaticResource secondaryButton}" Click="EditLayout_Click"/>
<Button x:Name="ApplyCustomButton" Content="{x:Static props:Resources.Apply}" Padding="8" Style="{StaticResource primaryButton}" Click="Apply_Click"/>
<Button x:Name="CloseButton"
Content="{x:Static props:Resources.Close}"
Visibility="{Binding DesktopsPanelVisibility}"
Padding="8"
Style="{StaticResource secondaryButton}"
Click="CloseButton_Click" >
<Button.DataContext>
<local1:MonitorViewModel></local1:MonitorViewModel>
</Button.DataContext>
</Button>
</StackPanel>
</StackPanel>
</Controls:MetroWindow>

View File

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@ -19,12 +18,16 @@ namespace FancyZonesEditor
{
// TODO: share the constants b/w C# Editor and FancyZoneLib
public const int MaxZones = 40;
private readonly Settings _settings = ((App)Application.Current).ZoneSettings;
private const int DefaultWrapPanelItemSize = 262;
private const int SmallWrapPanelItemSize = 180;
private const int MinimalForDefaultWrapPanelsHeight = 900;
private readonly MainWindowSettingsModel _settings = ((App)Application.Current).MainWindowSettings;
// Localizable string
private static readonly string _defaultNamePrefix = "Custom Layout ";
public int WrapPanelItemSize { get; set; } = 262;
public int WrapPanelItemSize { get; set; } = DefaultWrapPanelItemSize;
public double SettingsTextMaxWidth
{
@ -34,20 +37,31 @@ namespace FancyZonesEditor
}
}
public MainWindow()
public MainWindow(bool spanZonesAcrossMonitors, Rect workArea)
{
InitializeComponent();
DataContext = _settings;
KeyUp += MainWindow_KeyUp;
if (Settings.WorkArea.Height < 900)
if (spanZonesAcrossMonitors)
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
if (workArea.Height < MinimalForDefaultWrapPanelsHeight || App.Overlay.MultiMonitorMode)
{
SizeToContent = SizeToContent.WidthAndHeight;
WrapPanelItemSize = 180;
WrapPanelItemSize = SmallWrapPanelItemSize;
}
}
public void Update()
{
DataContext = _settings;
SetSelectedItem();
}
private void MainWindow_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
@ -101,19 +115,19 @@ namespace FancyZonesEditor
private void Select(LayoutModel newSelection)
{
if (EditorOverlay.Current.DataContext is LayoutModel currentSelection)
if (App.Overlay.CurrentDataContext is LayoutModel currentSelection)
{
currentSelection.IsSelected = false;
}
newSelection.IsSelected = true;
EditorOverlay.Current.DataContext = newSelection;
App.Overlay.CurrentDataContext = newSelection;
}
private void EditLayout_Click(object sender, RoutedEventArgs e)
{
EditorOverlay mainEditor = EditorOverlay.Current;
if (!(mainEditor.DataContext is LayoutModel model))
var mainEditor = App.Overlay;
if (!(mainEditor.CurrentDataContext is LayoutModel model))
{
return;
}
@ -121,19 +135,19 @@ namespace FancyZonesEditor
model.IsSelected = false;
Hide();
bool isPredefinedLayout = Settings.IsPredefinedLayout(model);
bool isPredefinedLayout = MainWindowSettingsModel.IsPredefinedLayout(model);
if (!Settings.CustomModels.Contains(model) || isPredefinedLayout)
if (!MainWindowSettingsModel.CustomModels.Contains(model) || isPredefinedLayout)
{
if (isPredefinedLayout)
{
// make a copy
model = model.Clone();
mainEditor.DataContext = model;
mainEditor.CurrentDataContext = model;
}
int maxCustomIndex = 0;
foreach (LayoutModel customModel in Settings.CustomModels)
foreach (LayoutModel customModel in MainWindowSettingsModel.CustomModels)
{
string name = customModel.Name;
if (name.StartsWith(_defaultNamePrefix))
@ -151,32 +165,7 @@ namespace FancyZonesEditor
model.Name = _defaultNamePrefix + (++maxCustomIndex);
}
mainEditor.Edit();
EditorWindow window;
bool isGrid = false;
if (model is GridLayoutModel)
{
window = new GridEditorWindow();
isGrid = true;
}
else
{
window = new CanvasEditorWindow();
}
window.Owner = EditorOverlay.Current;
window.DataContext = model;
window.Show();
if (isGrid)
{
(window as GridEditorWindow).NameTextBox().Focus();
}
window.LeftWindowCommands = null;
window.RightWindowCommands = null;
mainEditor.OpenEditor(model);
}
private void Apply_Click(object sender, RoutedEventArgs e)
@ -186,20 +175,16 @@ namespace FancyZonesEditor
private void Apply()
{
EditorOverlay mainEditor = EditorOverlay.Current;
((App)Application.Current).MainWindowSettings.ResetAppliedModel();
if (mainEditor.DataContext is LayoutModel model)
var mainEditor = App.Overlay;
if (mainEditor.CurrentDataContext is LayoutModel model)
{
// If custom canvas layout has been scaled, persisting is needed
if (model is CanvasLayoutModel && (model as CanvasLayoutModel).IsScaled)
{
model.Persist();
}
else
{
model.Apply();
}
model.Apply();
}
if (!mainEditor.MultiMonitorMode)
{
Close();
}
}
@ -207,7 +192,7 @@ namespace FancyZonesEditor
private void OnClosing(object sender, EventArgs e)
{
LayoutModel.SerializeDeletedCustomZoneSets();
EditorOverlay.Current.Close();
App.Overlay.CloseLayoutWindow();
}
private void OnInitialized(object sender, EventArgs e)
@ -217,7 +202,7 @@ namespace FancyZonesEditor
private void SetSelectedItem()
{
foreach (LayoutModel model in Settings.CustomModels)
foreach (LayoutModel model in MainWindowSettingsModel.CustomModels)
{
if (model.IsSelected)
{
@ -237,5 +222,25 @@ namespace FancyZonesEditor
model.Delete();
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollviewer = sender as ScrollViewer;
if (e.Delta > 0)
{
scrollviewer.LineLeft();
}
else
{
scrollviewer.LineRight();
}
e.Handled = true;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@ -1,10 +1,9 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Text.Json;
using System.Windows;
@ -14,33 +13,21 @@ namespace FancyZonesEditor.Models
// Free form Layout Model, which specifies independent zone rects
public class CanvasLayoutModel : LayoutModel
{
// Localizable strings
private const string ErrorPersistingCanvasLayout = "Error persisting canvas layout";
// Non-localizable strings
private const string ModelTypeID = "canvas";
public CanvasLayoutModel(string uuid, string name, LayoutType type, IList<Int32Rect> zones, int workAreaWidth, int workAreaHeight)
public Rect CanvasRect { get; private set; }
public CanvasLayoutModel(string uuid, string name, LayoutType type, IList<Int32Rect> zones, int width, int height)
: base(uuid, name, type)
{
lastWorkAreaWidth = workAreaWidth;
lastWorkAreaHeight = workAreaHeight;
IsScaled = false;
if (ShouldScaleLayout())
{
ScaleLayout(zones);
}
else
{
Zones = zones;
}
Zones = zones;
CanvasRect = new Rect(new Size(width, height));
}
public CanvasLayoutModel(string name, LayoutType type)
: base(name, type)
{
IsScaled = false;
}
public CanvasLayoutModel(string name)
@ -51,12 +38,6 @@ namespace FancyZonesEditor.Models
// Zones - the list of all zones in this layout, described as independent rectangles
public IList<Int32Rect> Zones { get; private set; } = new List<Int32Rect>();
private int lastWorkAreaWidth = (int)Settings.WorkArea.Width;
private int lastWorkAreaHeight = (int)Settings.WorkArea.Height;
public bool IsScaled { get; private set; }
// RemoveZoneAt
// Removes the specified index from the Zones list, and fires a property changed notification for the Zones property
public void RemoveZoneAt(int index)
@ -102,34 +83,6 @@ namespace FancyZonesEditor.Models
}
}
private bool ShouldScaleLayout()
{
// Scale if:
// - at least one dimension changed
// - orientation remained the same
return (lastWorkAreaHeight != Settings.WorkArea.Height || lastWorkAreaWidth != Settings.WorkArea.Width) &&
((lastWorkAreaHeight > lastWorkAreaWidth && Settings.WorkArea.Height > Settings.WorkArea.Width) ||
(lastWorkAreaWidth > lastWorkAreaHeight && Settings.WorkArea.Width > Settings.WorkArea.Height));
}
private void ScaleLayout(IList<Int32Rect> zones)
{
foreach (Int32Rect zone in zones)
{
double widthFactor = (double)Settings.WorkArea.Width / lastWorkAreaWidth;
double heightFactor = (double)Settings.WorkArea.Height / lastWorkAreaHeight;
int scaledX = (int)(zone.X * widthFactor);
int scaledY = (int)(zone.Y * heightFactor);
int scaledWidth = (int)(zone.Width * widthFactor);
int scaledHeight = (int)(zone.Height * heightFactor);
Zones.Add(new Int32Rect(scaledX, scaledY, scaledWidth, scaledHeight));
}
lastWorkAreaHeight = (int)Settings.WorkArea.Height;
lastWorkAreaWidth = (int)Settings.WorkArea.Width;
IsScaled = true;
}
private struct Zone
{
public int X { get; set; }
@ -165,13 +118,15 @@ namespace FancyZonesEditor.Models
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
AddCustomLayout(this);
CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo
{
RefWidth = lastWorkAreaWidth,
RefHeight = lastWorkAreaHeight,
RefWidth = (int)CanvasRect.Width,
RefHeight = (int)CanvasRect.Height,
Zones = new Zone[Zones.Count],
};
for (int i = 0; i < Zones.Count; ++i)
{
Zone zone = new Zone
@ -187,26 +142,19 @@ namespace FancyZonesEditor.Models
CanvasLayoutJson jsonObj = new CanvasLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Uuid = Uuid,
Name = Name,
Type = ModelTypeID,
Info = layoutInfo,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
string jsonString = JsonSerializer.Serialize(jsonObj, options);
FileSystem.File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{
ShowExceptionMessageBox(ErrorPersistingCanvasLayout, ex);
}
string jsonString = JsonSerializer.Serialize(jsonObj, options);
AddCustomLayoutJson(JsonSerializer.Deserialize<JsonElement>(jsonString));
SerializeCreatedCustomZonesets();
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using System.Windows;
namespace FancyZonesEditor.Utils
{
public class Device
{
public string Id { get; set; }
public Rect UnscaledBounds { get; private set; }
public Rect ScaledBounds { get; private set; }
public Rect WorkAreaRect { get; private set; }
public int Dpi { get; set; }
public bool Primary { get; private set; }
public Device(string id, int dpi, Rect bounds, Rect workArea, bool primary)
{
Id = id;
Dpi = dpi;
WorkAreaRect = workArea;
UnscaledBounds = bounds;
ScaledBounds = bounds;
Primary = primary;
}
public Device(Rect bounds, Rect workArea, bool primary)
{
WorkAreaRect = workArea;
UnscaledBounds = bounds;
ScaledBounds = bounds;
Primary = primary;
}
public void Scale(double scaleFactor)
{
WorkAreaRect = new Rect(Math.Round(WorkAreaRect.X * scaleFactor), Math.Round(WorkAreaRect.Y * scaleFactor), Math.Round(WorkAreaRect.Width * scaleFactor), Math.Round(WorkAreaRect.Height * scaleFactor));
ScaledBounds = new Rect(Math.Round(ScaledBounds.X * scaleFactor), Math.Round(ScaledBounds.Y * scaleFactor), Math.Round(ScaledBounds.Width * scaleFactor), Math.Round(ScaledBounds.Height * scaleFactor));
}
public double ScaleCoordinate(double coordinate)
{
float dpi = Dpi != 0 ? Dpi : 96f;
double scaleFactor = 96f / dpi;
return Math.Round(coordinate * scaleFactor);
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append("ID: ");
sb.AppendLine(Id);
sb.Append("DPI: ");
sb.AppendLine(Dpi.ToString());
sb.Append("Is primary: ");
sb.AppendLine(Primary.ToString());
string workArea = string.Format("({0}, {1}, {2}, {3})", WorkAreaRect.X, WorkAreaRect.Y, WorkAreaRect.Width, WorkAreaRect.Height);
string bounds = string.Format("({0}, {1}, {2}, {3})", UnscaledBounds.X, UnscaledBounds.Y, UnscaledBounds.Width, UnscaledBounds.Height);
string scaledBounds = string.Format("({0}, {1}, {2}, {3})", ScaledBounds.X, ScaledBounds.Y, ScaledBounds.Width, ScaledBounds.Height);
sb.Append("Work area: ");
sb.AppendLine(workArea);
sb.Append("Unscaled bounds: ");
sb.AppendLine(bounds);
sb.Append("Scaled bounds: ");
sb.AppendLine(scaledBounds);
return sb.ToString();
}
}
}

View File

@ -13,9 +13,6 @@ namespace FancyZonesEditor.Models
// Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans
public class GridLayoutModel : LayoutModel
{
// Localizable strings
private const string ErrorPersistingGridLayout = "Error persisting grid layout";
// Non-localizable strings
private const string ModelTypeID = "grid";
@ -204,6 +201,8 @@ namespace FancyZonesEditor.Models
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
AddCustomLayout(this);
GridLayoutInfo layoutInfo = new GridLayoutInfo
{
Rows = Rows,
@ -212,6 +211,7 @@ namespace FancyZonesEditor.Models
ColumnsPercentage = ColumnPercents,
CellChildMap = new int[Rows][],
};
for (int row = 0; row < Rows; row++)
{
layoutInfo.CellChildMap[row] = new int[Columns];
@ -223,7 +223,7 @@ namespace FancyZonesEditor.Models
GridLayoutJson jsonObj = new GridLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Uuid = Uuid,
Name = Name,
Type = ModelTypeID,
Info = layoutInfo,
@ -233,15 +233,9 @@ namespace FancyZonesEditor.Models
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
string jsonString = JsonSerializer.Serialize(jsonObj, options);
FileSystem.File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{
ShowExceptionMessageBox(ErrorPersistingGridLayout, ex);
}
string jsonString = JsonSerializer.Serialize(jsonObj, options);
AddCustomLayoutJson(JsonSerializer.Deserialize<JsonElement>(jsonString));
SerializeCreatedCustomZonesets();
}
}
}

View File

@ -6,77 +6,15 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows;
namespace FancyZonesEditor.Models
{
public enum LayoutType
{
Blank = -1,
Focus,
Columns,
Rows,
Grid,
PriorityGrid,
Custom,
}
// Base LayoutModel
// Manages common properties and base persistence
public abstract class LayoutModel : INotifyPropertyChanged
{
protected static readonly IFileSystem FileSystem = new FileSystem();
// Localizable strings
private const string ErrorMessageBoxTitle = "FancyZones Editor Exception Handler";
private const string ErrorMessageBoxMessage = "Please report the bug to ";
private const string ErrorLayoutMalformedData = "Layout '{0}' has malformed data";
private const string ErrorSerializingDeletedLayouts = "Error serializing deleted layouts";
private const string ErrorLoadingCustomLayouts = "Error loading custom layouts";
private const string ErrorApplyingLayout = "Error applying layout";
// Non-localizable strings
private const string NameStr = "name";
private const string CustomZoneSetsJsonTag = "custom-zone-sets";
private const string TypeJsonTag = "type";
private const string UuidJsonTag = "uuid";
private const string InfoJsonTag = "info";
private const string GridJsonTag = "grid";
private const string RowsJsonTag = "rows";
private const string ColumnsJsonTag = "columns";
private const string RowsPercentageJsonTag = "rows-percentage";
private const string ColumnsPercentageJsonTag = "columns-percentage";
private const string CellChildMapJsonTag = "cell-child-map";
private const string ZonesJsonTag = "zones";
private const string CanvasJsonTag = "canvas";
private const string RefWidthJsonTag = "ref-width";
private const string RefHeightJsonTag = "ref-height";
private const string XJsonTag = "X";
private const string YJsonTag = "Y";
private const string WidthJsonTag = "width";
private const string HeightJsonTag = "height";
private const string FocusJsonTag = "focus";
private const string PriorityGridJsonTag = "priority-grid";
private const string CustomJsonTag = "custom";
private const string PowerToysIssuesURL = "https://aka.ms/powerToysReportBug";
public static void ShowExceptionMessageBox(string message, Exception exception = null)
{
string fullMessage = ErrorMessageBoxMessage + PowerToysIssuesURL + " \n" + message;
if (exception != null)
{
fullMessage += ": " + exception.Message;
}
MessageBox.Show(fullMessage, ErrorMessageBoxTitle);
}
protected LayoutModel()
{
_guid = Guid.NewGuid();
@ -136,6 +74,14 @@ namespace FancyZonesEditor.Models
private Guid _guid;
public string Uuid
{
get
{
return "{" + Guid.ToString().ToUpper() + "}";
}
}
// IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker
// TODO: once we switch to a picker per monitor, we need to move this state to the view
public bool IsSelected
@ -157,6 +103,25 @@ namespace FancyZonesEditor.Models
private bool _isSelected;
public bool IsApplied
{
get
{
return _isApplied;
}
set
{
if (_isApplied != value)
{
_isApplied = value;
FirePropertyChanged();
}
}
}
private bool _isApplied;
// implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
@ -177,186 +142,52 @@ namespace FancyZonesEditor.Models
}
}
private struct DeletedCustomZoneSetsWrapper
// Adds new custom Layout
public void AddCustomLayout(LayoutModel model)
{
public List<string> DeletedCustomZoneSets { get; set; }
bool updated = false;
for (int i = 0; i < _customModels.Count && !updated; i++)
{
if (_customModels[i].Uuid == model.Uuid)
{
_customModels[i] = model;
updated = true;
}
}
if (!updated)
{
_customModels.Add(model);
}
}
// Add custom layouts json data that would be serialized to a temp file
public void AddCustomLayoutJson(JsonElement json)
{
_createdCustomLayouts.Add(json);
}
public static void SerializeDeletedCustomZoneSets()
{
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
{
DeletedCustomZoneSets = _deletedCustomModels,
};
App.FancyZonesEditorIO.SerializeDeletedCustomZoneSets(_deletedCustomModels);
}
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
FileSystem.File.WriteAllText(Settings.DeletedCustomZoneSetsTmpFile, jsonString);
}
catch (Exception ex)
{
ShowExceptionMessageBox(ErrorSerializingDeletedLayouts, ex);
}
public static void SerializeCreatedCustomZonesets()
{
App.FancyZonesEditorIO.SerializeCreatedCustomZonesets(_createdCustomLayouts);
}
// Loads all the custom Layouts from tmp file passed by FancyZonesLib
public static ObservableCollection<LayoutModel> LoadCustomModels()
{
_customModels = new ObservableCollection<LayoutModel>();
try
{
Stream inputStream = FileSystem.File.Open(Settings.FancyZonesSettingsFile, FileMode.Open);
JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default);
JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray();
while (customZoneSetsEnumerator.MoveNext())
{
var current = customZoneSetsEnumerator.Current;
string name = current.GetProperty(NameStr).GetString();
string type = current.GetProperty(TypeJsonTag).GetString();
string uuid = current.GetProperty(UuidJsonTag).GetString();
var info = current.GetProperty(InfoJsonTag);
if (type.Equals(GridJsonTag))
{
bool error = false;
int rows = info.GetProperty(RowsJsonTag).GetInt32();
int columns = info.GetProperty(ColumnsJsonTag).GetInt32();
List<int> rowsPercentage = new List<int>(rows);
JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray();
List<int> columnsPercentage = new List<int>(columns);
JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray();
if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns)
{
error = true;
}
while (!error && rowsPercentageEnumerator.MoveNext())
{
int percentage = rowsPercentageEnumerator.Current.GetInt32();
if (percentage <= 0)
{
error = true;
break;
}
rowsPercentage.Add(percentage);
}
while (!error && columnsPercentageEnumerator.MoveNext())
{
int percentage = columnsPercentageEnumerator.Current.GetInt32();
if (percentage <= 0)
{
error = true;
break;
}
columnsPercentage.Add(percentage);
}
int i = 0;
JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray();
int[,] cellChildMap = new int[rows, columns];
if (cellChildMapRows.Count() != rows)
{
error = true;
}
while (!error && cellChildMapRows.MoveNext())
{
int j = 0;
JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray();
if (cellChildMapRowElems.Count() != columns)
{
error = true;
break;
}
while (cellChildMapRowElems.MoveNext())
{
cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32();
}
i++;
}
if (error)
{
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
_deletedCustomModels.Add(Guid.Parse(uuid).ToString().ToUpper());
continue;
}
_customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap));
}
else if (type.Equals(CanvasJsonTag))
{
int lastWorkAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32();
int lastWorkAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32();
JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray();
IList<Int32Rect> zones = new List<Int32Rect>();
bool error = false;
if (lastWorkAreaWidth <= 0 || lastWorkAreaHeight <= 0)
{
error = true;
}
while (!error && zonesEnumerator.MoveNext())
{
int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32();
int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32();
int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32();
int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32();
if (width <= 0 || height <= 0)
{
error = true;
break;
}
zones.Add(new Int32Rect(x, y, width, height));
}
if (error)
{
ShowExceptionMessageBox(string.Format(ErrorLayoutMalformedData, name));
_deletedCustomModels.Add(Guid.Parse(uuid).ToString().ToUpper());
continue;
}
_customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, lastWorkAreaWidth, lastWorkAreaHeight));
}
}
inputStream.Close();
}
catch (Exception ex)
{
ShowExceptionMessageBox(ErrorLoadingCustomLayouts, ex);
return new ObservableCollection<LayoutModel>();
}
App.FancyZonesEditorIO.ParseLayouts(ref _customModels, ref _deletedCustomModels);
return _customModels;
}
private static ObservableCollection<LayoutModel> _customModels = null;
private static List<string> _deletedCustomModels = new List<string>();
private static List<JsonElement> _createdCustomLayouts = new List<JsonElement>();
// Callbacks that the base LayoutModel makes to derived types
protected abstract void PersistData();
@ -369,83 +200,18 @@ namespace FancyZonesEditor.Models
Apply();
}
private struct ActiveZoneSetWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
}
private struct AppliedZoneSet
{
public string DeviceId { get; set; }
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
public bool EditorShowSpacing { get; set; }
public int EditorSpacing { get; set; }
public int EditorZoneCount { get; set; }
public int EditorSensitivityRadius { get; set; }
}
public void Apply()
{
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
};
MainWindowSettingsModel settings = ((App)App.Current).MainWindowSettings;
settings.ResetAppliedModel();
IsApplied = true;
switch (Type)
{
case LayoutType.Focus:
activeZoneSet.Type = FocusJsonTag;
break;
case LayoutType.Rows:
activeZoneSet.Type = RowsJsonTag;
break;
case LayoutType.Columns:
activeZoneSet.Type = ColumnsJsonTag;
break;
case LayoutType.Grid:
activeZoneSet.Type = GridJsonTag;
break;
case LayoutType.PriorityGrid:
activeZoneSet.Type = PriorityGridJsonTag;
break;
case LayoutType.Custom:
activeZoneSet.Type = CustomJsonTag;
break;
}
// update settings
App.Overlay.CurrentLayoutSettings.ZonesetUuid = Uuid;
App.Overlay.CurrentLayoutSettings.Type = Type;
Settings settings = ((App)Application.Current).ZoneSettings;
AppliedZoneSet zoneSet = new AppliedZoneSet
{
DeviceId = Settings.UniqueKey,
ActiveZoneset = activeZoneSet,
EditorShowSpacing = settings.ShowSpacing,
EditorSpacing = settings.Spacing,
EditorZoneCount = settings.ZoneCount,
EditorSensitivityRadius = settings.SensitivityRadius,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
string jsonString = JsonSerializer.Serialize(zoneSet, options);
FileSystem.File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{
ShowExceptionMessageBox(ErrorApplyingLayout, ex);
}
// update temp file
App.FancyZonesEditorIO.SerializeAppliedLayouts();
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
public class LayoutSettings
{
public static bool DefaultShowSpacing => true;
public static int DefaultSpacing => 16;
public static int DefaultZoneCount => 3;
public static int DefaultSensitivityRadius => 20;
public string DeviceId { get; set; } = string.Empty;
public string ZonesetUuid { get; set; } = string.Empty;
public LayoutType Type { get; set; } = LayoutType.PriorityGrid;
public bool ShowSpacing { get; set; } = DefaultShowSpacing;
public int Spacing { get; set; } = DefaultSpacing;
public int ZoneCount { get; set; } = DefaultZoneCount;
public int SensitivityRadius { get; set; } = DefaultSensitivityRadius;
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace FancyZonesEditor.Models
{
public enum LayoutType
{
Blank = -1,
Focus,
Columns,
Rows,
Grid,
PriorityGrid,
Custom,
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@ -6,10 +6,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.IO.Abstractions;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Windows;
using FancyZonesEditor.Models;
@ -18,30 +15,16 @@ namespace FancyZonesEditor
// Settings
// These are the configuration settings used by the rest of the editor
// Other UIs in the editor will subscribe to change events on the properties to stay up to date as these properties change
public class Settings : INotifyPropertyChanged
public class MainWindowSettingsModel : INotifyPropertyChanged
{
private enum CmdArgs
private enum DeviceIdParts
{
WorkAreaSize = 1,
PowerToysPID,
}
private enum WorkAreaCmdArgElements
{
X = 0,
Y,
Name = 0,
Width,
Height,
VirtualDesktopId,
}
private enum ParseDeviceMode
{
Prod,
Debug,
}
private static readonly IFileSystem _fileSystem = new FileSystem();
private static CanvasLayoutModel _blankCustomModel;
private readonly CanvasLayoutModel _focusModel;
private readonly GridLayoutModel _rowsModel;
@ -63,33 +46,9 @@ namespace FancyZonesEditor
public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones";
public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath;
private const string ZonesSettingsFile = "\\Microsoft\\PowerToys\\FancyZones\\zones-settings.json";
private const string ActiveZoneSetsTmpFileName = "FancyZonesActiveZoneSets.json";
private const string AppliedZoneSetsTmpFileName = "FancyZonesAppliedZoneSets.json";
private const string DeletedCustomZoneSetsTmpFileName = "FancyZonesDeletedCustomZoneSets.json";
private const string LayoutTypeBlankStr = "blank";
private const string NullUuidStr = "null";
// DeviceInfo JSON tags
private const string DeviceIdJsonTag = "device-id";
private const string ActiveZoneSetJsonTag = "active-zoneset";
private const string UuidJsonTag = "uuid";
private const string TypeJsonTag = "type";
private const string EditorShowSpacingJsonTag = "editor-show-spacing";
private const string EditorSpacingJsonTag = "editor-spacing";
private const string EditorZoneCountJsonTag = "editor-zone-count";
private const string EditorSensitivityRadiusJsonTag = "editor-sensitivity-radius";
private const string FocusJsonTag = "focus";
private const string ColumnsJsonTag = "columns";
private const string RowsJsonTag = "rows";
private const string GridJsonTag = "grid";
private const string PriorityGridJsonTag = "priority-grid";
private const string CustomJsonTag = "custom";
private const string DebugMode = "Debug";
// hard coded data for all the "Priority Grid" configurations that are unique to "Grid"
private static readonly byte[][] _priorityData = new byte[][]
{
@ -124,19 +83,8 @@ namespace FancyZonesEditor
}
}
public Settings()
public MainWindowSettingsModel()
{
string tmpDirPath = _fileSystem.Path.GetTempPath();
ActiveZoneSetTmpFile = tmpDirPath + ActiveZoneSetsTmpFileName;
AppliedZoneSetTmpFile = tmpDirPath + AppliedZoneSetsTmpFileName;
DeletedCustomZoneSetsTmpFile = tmpDirPath + DeletedCustomZoneSetsTmpFileName;
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
FancyZonesSettingsFile = localAppDataDir + ZonesSettingsFile;
ParseCommandLineArgs();
// Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid
DefaultModels = new List<LayoutModel>(5);
_focusModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Focus, LayoutType.Focus);
@ -164,7 +112,7 @@ namespace FancyZonesEditor
_blankCustomModel = new CanvasLayoutModel(Properties.Resources.Custom_Layout_Create_New, LayoutType.Blank);
UpdateLayoutModels();
UpdateTemplateLayoutModels();
}
// ZoneCount - number of zones selected in the picker window
@ -172,82 +120,79 @@ namespace FancyZonesEditor
{
get
{
return _zoneCount;
return App.Overlay.CurrentLayoutSettings.ZoneCount;
}
set
{
if (_zoneCount != value)
if (App.Overlay.CurrentLayoutSettings.ZoneCount != value)
{
_zoneCount = value;
UpdateLayoutModels();
FirePropertyChanged();
App.Overlay.CurrentLayoutSettings.ZoneCount = value;
UpdateTemplateLayoutModels();
FirePropertyChanged(nameof(ZoneCount));
}
}
}
private int _zoneCount;
// Spacing - how much space in between zones of the grid do you want
public int Spacing
{
get
{
return _spacing;
return App.Overlay.CurrentLayoutSettings.Spacing;
}
set
{
if (_spacing != value)
value = Math.Max(0, value);
if (App.Overlay.CurrentLayoutSettings.Spacing != value)
{
_spacing = Math.Max(MaxNegativeSpacing, value);
FirePropertyChanged();
App.Overlay.CurrentLayoutSettings.Spacing = value;
UpdateTemplateLayoutModels();
FirePropertyChanged(nameof(Spacing));
}
}
}
private int _spacing;
// ShowSpacing - is the Spacing value used or ignored?
public bool ShowSpacing
{
get
{
return _showSpacing;
return App.Overlay.CurrentLayoutSettings.ShowSpacing;
}
set
{
if (_showSpacing != value)
if (App.Overlay.CurrentLayoutSettings.ShowSpacing != value)
{
_showSpacing = value;
FirePropertyChanged();
App.Overlay.CurrentLayoutSettings.ShowSpacing = value;
UpdateTemplateLayoutModels();
FirePropertyChanged(nameof(ShowSpacing));
}
}
}
private bool _showSpacing;
// SensitivityRadius - how much space inside the zone to highlight the adjacent zone too
public int SensitivityRadius
{
get
{
return _sensitivityRadius;
return App.Overlay.CurrentLayoutSettings.SensitivityRadius;
}
set
{
if (_sensitivityRadius != value)
value = Math.Max(0, value);
if (App.Overlay.CurrentLayoutSettings.SensitivityRadius != value)
{
_sensitivityRadius = Math.Max(0, value);
FirePropertyChanged();
App.Overlay.CurrentLayoutSettings.SensitivityRadius = value;
UpdateTemplateLayoutModels();
FirePropertyChanged(nameof(SensitivityRadius));
}
}
}
private int _sensitivityRadius;
// IsShiftKeyPressed - is the shift key currently being held down
public bool IsShiftKeyPressed
{
@ -261,7 +206,7 @@ namespace FancyZonesEditor
if (_isShiftKeyPressed != value)
{
_isShiftKeyPressed = value;
FirePropertyChanged();
FirePropertyChanged(nameof(IsShiftKeyPressed));
}
}
}
@ -281,41 +226,16 @@ namespace FancyZonesEditor
if (_isCtrlKeyPressed != value)
{
_isCtrlKeyPressed = value;
FirePropertyChanged();
FirePropertyChanged(nameof(IsCtrlKeyPressed));
}
}
}
private bool _isCtrlKeyPressed;
public static Rect WorkArea { get; private set; }
public static List<Rect> UsedWorkAreas { get; private set; }
public static string UniqueKey { get; private set; }
public static string ActiveZoneSetUUid { get; private set; }
public static LayoutType ActiveZoneSetLayoutType { get; private set; }
public static string ActiveZoneSetTmpFile { get; private set; }
public static string AppliedZoneSetTmpFile { get; private set; }
public static string DeletedCustomZoneSetsTmpFile { get; private set; }
public static string FancyZonesSettingsFile { get; private set; }
public static int PowerToysPID
{
get { return _powerToysPID; }
}
private static int _powerToysPID;
// UpdateLayoutModels
// Update the five default layouts based on the new ZoneCount
private void UpdateLayoutModels()
private void UpdateTemplateLayoutModels()
{
// Update the "Focus" Default Layout
_focusModel.Zones.Clear();
@ -328,9 +248,13 @@ namespace FancyZonesEditor
// If changing focus layout zones size and/or increment,
// same change should be applied in ZoneSet.cpp (ZoneSet::CalculateFocusLayout)
Int32Rect focusZoneRect = new Int32Rect(100, 100, (int)(WorkArea.Width * 0.4), (int)(WorkArea.Height * 0.4));
int focusRectXIncrement = (ZoneCount <= 1) ? 0 : 50;
int focusRectYIncrement = (ZoneCount <= 1) ? 0 : 50;
var workingArea = App.Overlay.WorkArea;
int topLeftCoordinate = (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(100); // TODO: replace magic numbers
int width = (int)(workingArea.Width * 0.4);
int height = (int)(workingArea.Height * 0.4);
Int32Rect focusZoneRect = new Int32Rect(topLeftCoordinate, topLeftCoordinate, width, height);
int focusRectXIncrement = (ZoneCount <= 1) ? 0 : (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(50);
int focusRectYIncrement = (ZoneCount <= 1) ? 0 : (int)App.Overlay.ScaleCoordinateWithCurrentMonitorDpi(50);
for (int i = 0; i < ZoneCount; i++)
{
@ -422,128 +346,6 @@ namespace FancyZonesEditor
}
}
private void ParseDeviceInfoData(ParseDeviceMode mode = ParseDeviceMode.Prod)
{
try
{
string layoutType = LayoutTypeBlankStr;
ActiveZoneSetUUid = NullUuidStr;
JsonElement jsonObject = default(JsonElement);
if (_fileSystem.File.Exists(Settings.ActiveZoneSetTmpFile))
{
Stream inputStream = _fileSystem.File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open);
jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement;
inputStream.Close();
UniqueKey = jsonObject.GetProperty(DeviceIdJsonTag).GetString();
ActiveZoneSetUUid = jsonObject.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString();
layoutType = jsonObject.GetProperty(ActiveZoneSetJsonTag).GetProperty(TypeJsonTag).GetString();
}
if (mode == ParseDeviceMode.Debug || ActiveZoneSetUUid == NullUuidStr || layoutType == LayoutTypeBlankStr)
{
// Default or there is no active layout on current device
ActiveZoneSetLayoutType = LayoutType.Focus;
_showSpacing = true;
_spacing = 16;
_zoneCount = 3;
_sensitivityRadius = 20;
}
else
{
switch (layoutType)
{
case FocusJsonTag:
ActiveZoneSetLayoutType = LayoutType.Focus;
break;
case ColumnsJsonTag:
ActiveZoneSetLayoutType = LayoutType.Columns;
break;
case RowsJsonTag:
ActiveZoneSetLayoutType = LayoutType.Rows;
break;
case GridJsonTag:
ActiveZoneSetLayoutType = LayoutType.Grid;
break;
case PriorityGridJsonTag:
ActiveZoneSetLayoutType = LayoutType.PriorityGrid;
break;
case CustomJsonTag:
ActiveZoneSetLayoutType = LayoutType.Custom;
break;
}
_showSpacing = jsonObject.GetProperty(EditorShowSpacingJsonTag).GetBoolean();
_spacing = jsonObject.GetProperty(EditorSpacingJsonTag).GetInt32();
_zoneCount = jsonObject.GetProperty(EditorZoneCountJsonTag).GetInt32();
_sensitivityRadius = jsonObject.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32();
}
}
catch (Exception ex)
{
LayoutModel.ShowExceptionMessageBox(Properties.Resources.Error_Parsing_Device_Info, ex);
}
}
private void ParseCommandLineArgs()
{
WorkArea = SystemParameters.WorkArea;
UsedWorkAreas = new List<Rect> { WorkArea };
string[] args = Environment.GetCommandLineArgs();
if (args.Length == 2)
{
if (args[1].Equals(DebugMode))
{
ParseDeviceInfoData(ParseDeviceMode.Debug);
}
else
{
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
}
else if (args.Length == 3)
{
UsedWorkAreas.Clear();
foreach (var singleMonitorString in args[(int)CmdArgs.WorkAreaSize].Split('/'))
{
var parsedLocation = singleMonitorString.Split('_');
if (parsedLocation.Length != 4)
{
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
var x = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.X]);
var y = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Y]);
var width = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Width]);
var height = int.Parse(parsedLocation[(int)WorkAreaCmdArgElements.Height]);
Rect thisMonitor = new Rect(x, y, width, height);
if (UsedWorkAreas.Count == 0)
{
WorkArea = thisMonitor;
}
else
{
WorkArea = Rect.Union(WorkArea, thisMonitor);
}
UsedWorkAreas.Add(thisMonitor);
}
int.TryParse(args[(int)CmdArgs.PowerToysPID], out _powerToysPID);
ParseDeviceInfoData();
}
else
{
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
}
public IList<LayoutModel> DefaultModels { get; }
public static ObservableCollection<LayoutModel> CustomModels
@ -567,6 +369,120 @@ namespace FancyZonesEditor
return model.Type != LayoutType.Custom;
}
public LayoutModel UpdateSelectedLayoutModel()
{
UpdateTemplateLayoutModels();
ResetAppliedModel();
ResetSelectedModel();
LayoutModel foundModel = null;
LayoutSettings currentApplied = App.Overlay.CurrentLayoutSettings;
// set new layout
if (currentApplied.Type == LayoutType.Custom)
{
foreach (LayoutModel model in MainWindowSettingsModel.CustomModels)
{
if ("{" + model.Guid.ToString().ToUpper() + "}" == currentApplied.ZonesetUuid.ToUpper())
{
// found match
foundModel = model;
break;
}
}
}
else
{
foreach (LayoutModel model in DefaultModels)
{
if (model.Type == currentApplied.Type)
{
// found match
foundModel = model;
break;
}
}
}
if (foundModel == null)
{
foundModel = DefaultModels[0];
}
foundModel.IsSelected = true;
foundModel.IsApplied = true;
FirePropertyChanged(nameof(IsCustomLayoutActive));
return foundModel;
}
public void ResetSelectedModel()
{
foreach (LayoutModel model in CustomModels)
{
if (model.IsSelected)
{
model.IsSelected = false;
break;
}
}
foreach (LayoutModel model in DefaultModels)
{
if (model.IsSelected)
{
model.IsSelected = false;
break;
}
}
}
public void ResetAppliedModel()
{
foreach (LayoutModel model in CustomModels)
{
if (model.IsApplied)
{
model.IsApplied = false;
break;
}
}
foreach (LayoutModel model in DefaultModels)
{
if (model.IsApplied)
{
model.IsApplied = false;
break;
}
}
}
public void UpdateDesktopDependantProperties(LayoutSettings prevSettings)
{
UpdateTemplateLayoutModels();
if (prevSettings.ZoneCount != ZoneCount)
{
FirePropertyChanged(nameof(ZoneCount));
}
if (prevSettings.Spacing != Spacing)
{
FirePropertyChanged(nameof(Spacing));
}
if (prevSettings.ShowSpacing != ShowSpacing)
{
FirePropertyChanged(nameof(ShowSpacing));
}
if (prevSettings.SensitivityRadius != SensitivityRadius)
{
FirePropertyChanged(nameof(SensitivityRadius));
}
}
// implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;

View File

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor.Models
{
public class Monitor
{
public LayoutOverlayWindow Window { get; private set; }
public LayoutSettings Settings { get; set; }
public Device Device { get; set; }
public Monitor(Rect bounds, Rect workArea, bool primary)
{
Window = new LayoutOverlayWindow();
Settings = new LayoutSettings();
Device = new Device(bounds, workArea, primary);
if (App.DebugMode)
{
long milliseconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
PropertyInfo[] properties = typeof(Brushes).GetProperties();
Window.Opacity = 0.5;
Window.Background = (Brush)properties[milliseconds % properties.Length].GetValue(null, null);
}
Window.Left = workArea.X;
Window.Top = workArea.Y;
Window.Width = workArea.Width;
Window.Height = workArea.Height;
}
public Monitor(string id, int dpi, Rect bounds, Rect workArea, bool primary)
: this(bounds, workArea, primary)
{
Device = new Device(id, dpi, bounds, workArea, primary);
}
public void Scale(double scaleFactor)
{
Device.Scale(scaleFactor);
var workArea = Device.WorkAreaRect;
Window.Left = workArea.X;
Window.Top = workArea.Y;
Window.Width = workArea.Width;
Window.Height = workArea.Height;
}
}
}

View File

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using FancyZonesEditor.ViewModels;
namespace FancyZonesEditor.Utils
{
public class MonitorInfoModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MonitorInfoModel(int index, int height, int width, int dpi, bool selected = false)
{
Index = index;
ScreenBoundsHeight = height;
ScreenBoundsWidth = width;
DPI = dpi;
Selected = selected;
}
public int Index { get; set; }
public int ScreenBoundsHeight { get; set; }
public double DisplayHeight
{
get
{
return ScreenBoundsHeight * MonitorViewModel.DesktopPreviewMultiplier;
}
}
public int ScreenBoundsWidth { get; set; }
public double DisplayWidth
{
get
{
return ScreenBoundsWidth * MonitorViewModel.DesktopPreviewMultiplier;
}
}
public int DPI { get; set; }
public string Dimensions
{
get
{
if (App.DebugMode)
{
var rect = App.Overlay.Monitors[Index - 1].Device.WorkAreaRect;
return "Screen: (" + rect.X + ", " + rect.Y + "); (" + rect.Width + ", " + rect.Height + ")";
}
else
{
return ScreenBoundsWidth + " x " + ScreenBoundsHeight;
}
}
}
public bool Selected
{
get
{
return _selected;
}
set
{
if (_selected == value)
{
return;
}
_selected = value;
OnPropertyChanged(nameof(Selected));
}
}
private bool _selected;
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@ -0,0 +1,397 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
public class Overlay
{
private MainWindow _mainWindow;
private LayoutPreview _layoutPreview;
private UserControl _editor;
public List<Monitor> Monitors { get; private set; }
public Rect WorkArea
{
get
{
if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count)
{
return Monitors[CurrentDesktop].Device.WorkAreaRect;
}
return default(Rect);
}
}
public LayoutSettings CurrentLayoutSettings
{
get
{
if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count)
{
return Monitors[CurrentDesktop].Settings;
}
return new LayoutSettings();
}
}
public Window CurrentLayoutWindow
{
get
{
if (Monitors.Count > 0 && CurrentDesktop < Monitors.Count)
{
return Monitors[CurrentDesktop].Window;
}
return default(Window);
}
}
public List<Rect> WorkAreas { get; private set; }
public object CurrentDataContext
{
get
{
return _dataContext;
}
set
{
_dataContext = value;
CurrentLayoutWindow.DataContext = value;
}
}
private object _dataContext;
public int DesktopsCount
{
get
{
return Monitors.Count;
}
}
public int CurrentDesktop
{
get
{
return _currentDesktop;
}
set
{
if (value != _currentDesktop)
{
if (value < 0 || value >= DesktopsCount)
{
return;
}
var prevSettings = CurrentLayoutSettings;
_currentDesktop = value;
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
if (settings != null)
{
settings.ResetAppliedModel();
settings.UpdateDesktopDependantProperties(prevSettings);
}
Update();
}
}
}
private int _currentDesktop = 0;
public bool SpanZonesAcrossMonitors
{
get
{
return _spanZonesAcrossMonitors;
}
set
{
_spanZonesAcrossMonitors = value;
if (_spanZonesAcrossMonitors)
{
Rect workArea = default(Rect);
Rect bounds = default(Rect);
foreach (Monitor monitor in Monitors)
{
workArea = Rect.Union(workArea, monitor.Device.WorkAreaRect);
bounds = Rect.Union(bounds, monitor.Device.ScaledBounds);
}
Monitors.Clear();
Monitors.Add(new Monitor(bounds, workArea, true));
}
}
}
private bool _spanZonesAcrossMonitors;
public bool MultiMonitorMode
{
get
{
return DesktopsCount > 1 && !SpanZonesAcrossMonitors;
}
}
public Overlay()
{
WorkAreas = new List<Rect>();
Monitors = new List<Monitor>();
var screens = System.Windows.Forms.Screen.AllScreens;
foreach (System.Windows.Forms.Screen screen in screens)
{
Rect bounds = new Rect(screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height);
Rect workArea = new Rect(screen.WorkingArea.X, screen.WorkingArea.Y, screen.WorkingArea.Width, screen.WorkingArea.Height);
Add(bounds, workArea, screen.Primary);
}
}
public void Show()
{
_layoutPreview = new LayoutPreview
{
IsActualSize = true,
Opacity = 0.5,
};
ShowLayout();
OpenMainWindow();
}
public void ShowLayout()
{
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
CurrentDataContext = settings.UpdateSelectedLayoutModel();
var window = CurrentLayoutWindow;
window.Content = _layoutPreview;
window.DataContext = CurrentDataContext;
if (_layoutPreview != null)
{
_layoutPreview.UpdatePreview();
}
for (int i = 0; i < DesktopsCount; i++)
{
Monitors[i].Window.Show();
}
}
public void OpenEditor(LayoutModel model)
{
_layoutPreview = null;
if (CurrentDataContext is GridLayoutModel)
{
_editor = new GridEditor();
}
else if (CurrentDataContext is CanvasLayoutModel)
{
_editor = new CanvasEditor();
}
CurrentLayoutWindow.Content = _editor;
EditorWindow window;
bool isGrid = false;
if (model is GridLayoutModel)
{
window = new GridEditorWindow();
isGrid = true;
}
else
{
window = new CanvasEditorWindow();
}
window.Owner = Monitors[App.Overlay.CurrentDesktop].Window;
window.DataContext = model;
window.Show();
if (isGrid)
{
(window as GridEditorWindow).NameTextBox().Focus();
}
window.LeftWindowCommands = null;
window.RightWindowCommands = null;
}
public void CloseEditor()
{
_editor = null;
_layoutPreview = new LayoutPreview
{
IsActualSize = true,
Opacity = 0.5,
};
CurrentLayoutWindow.Content = _layoutPreview;
OpenMainWindow();
}
public void CloseLayoutWindow()
{
for (int i = 0; i < DesktopsCount; i++)
{
Monitors[i].Window.Close();
}
}
public double ScaleCoordinateWithCurrentMonitorDpi(double coordinate)
{
if (Monitors.Count == 0)
{
return coordinate;
}
double minimalDpi = Monitors[0].Device.Dpi;
foreach (Monitor monitor in Monitors)
{
if (minimalDpi > monitor.Device.Dpi)
{
minimalDpi = monitor.Device.Dpi;
}
}
if (minimalDpi == 0 || Monitors[CurrentDesktop].Device.Dpi == 0)
{
return coordinate;
}
double scaleFactor = minimalDpi / Monitors[CurrentDesktop].Device.Dpi;
return Math.Round(coordinate * scaleFactor);
}
private void Update()
{
CloseLayout();
if (_mainWindow != null)
{
_mainWindow.Update();
}
ShowLayout();
}
private void CloseLayout()
{
var window = CurrentLayoutWindow;
window.Content = null;
window.DataContext = null;
}
private void OpenMainWindow()
{
if (_mainWindow == null)
{
_mainWindow = new MainWindow(SpanZonesAcrossMonitors, WorkArea);
}
// reset main window owner to keep it on the top
_mainWindow.Owner = CurrentLayoutWindow;
_mainWindow.ShowActivated = true;
_mainWindow.Topmost = true;
_mainWindow.Show();
_mainWindow.LeftWindowCommands = null;
_mainWindow.RightWindowCommands = null;
// window is set to topmost to make sure it shows on top of PowerToys settings page
// we can reset topmost flag now
_mainWindow.Topmost = false;
}
private void Add(Rect bounds, Rect workArea, bool primary)
{
var monitor = new Monitor(bounds, workArea, primary);
bool inserted = false;
var workAreaRect = workArea;
for (int i = 0; i < Monitors.Count && !inserted; i++)
{
var rect = Monitors[i].Device.WorkAreaRect;
if (workAreaRect.Left < rect.Left && (workAreaRect.Top <= rect.Top || workAreaRect.Top == 0))
{
Monitors.Insert(i, monitor);
inserted = true;
}
else if (workAreaRect.Left == rect.Left && workAreaRect.Top < rect.Top)
{
Monitors.Insert(i, monitor);
inserted = true;
}
}
if (!inserted)
{
Monitors.Add(monitor);
}
}
public Int32Rect[] GetZoneRects()
{
if (_editor != null)
{
if (_editor is GridEditor gridEditor)
{
return ZoneRectsFromPanel(gridEditor.PreviewPanel);
}
else
{
// CanvasEditor
return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview);
}
}
else
{
// One of the predefined zones (neither grid or canvas editor used).
return _layoutPreview.GetZoneRects();
}
}
private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel)
{
// TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info
int count = previewPanel.Children.Count;
Int32Rect[] zones = new Int32Rect[count];
for (int i = 0; i < count; i++)
{
FrameworkElement child = (FrameworkElement)previewPanel.Children[i];
Point topLeft = child.TransformToAncestor(previewPanel).Transform(default);
zones[i].X = (int)topLeft.X;
zones[i].Y = (int)topLeft.Y;
zones[i].Width = (int)child.ActualWidth;
zones[i].Height = (int)child.ActualHeight;
}
return zones;
}
}
}

View File

@ -105,6 +105,15 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Close.
/// </summary>
public static string Close {
get {
return ResourceManager.GetString("Close", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error logged to .
/// </summary>
@ -186,6 +195,24 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Error applying layout.
/// </summary>
public static string Error_Applying_Layout {
get {
return ResourceManager.GetString("Error_Applying_Layout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor Exception Handler.
/// </summary>
public static string Error_Exception_Message_Box_Title {
get {
return ResourceManager.GetString("Error_Exception_Message_Box_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor arguments are invalid..
/// </summary>
@ -195,6 +222,24 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Layout &apos;{0}&apos; has malformed data.
/// </summary>
public static string Error_Layout_Malformed_Data {
get {
return ResourceManager.GetString("Error_Layout_Malformed_Data", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error loading custom layouts.
/// </summary>
public static string Error_Loading_Custom_Layouts {
get {
return ResourceManager.GetString("Error_Loading_Custom_Layouts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor Error.
/// </summary>
@ -204,6 +249,15 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Match not found ({0}).
/// </summary>
public static string Error_Monitor_Match_Not_Found {
get {
return ResourceManager.GetString("Error_Monitor_Match_Not_Found", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor should not be run as standalone application..
/// </summary>
@ -222,6 +276,33 @@ namespace FancyZonesEditor.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Error persisting custom layout.
/// </summary>
public static string Error_Persisting_Custom_Layout {
get {
return ResourceManager.GetString("Error_Persisting_Custom_Layout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please report the bug to .
/// </summary>
public static string Error_Report {
get {
return ResourceManager.GetString("Error_Report", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error serializing deleted layouts.
/// </summary>
public static string Error_Serializing_Deleted_Layouts {
get {
return ResourceManager.GetString("Error_Serializing_Deleted_Layouts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor.
/// </summary>

View File

@ -226,4 +226,31 @@
<data name="Tab_Item_Templates" xml:space="preserve">
<value>Templates tab selected, press ctrl + tab to switch to Custom</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close</value>
</data>
<data name="Error_Applying_Layout" xml:space="preserve">
<value>Error applying layout</value>
</data>
<data name="Error_Layout_Malformed_Data" xml:space="preserve">
<value>Layout '{0}' has malformed data</value>
</data>
<data name="Error_Loading_Custom_Layouts" xml:space="preserve">
<value>Error loading custom layouts</value>
</data>
<data name="Error_Persisting_Custom_Layout" xml:space="preserve">
<value>Error persisting custom layout</value>
</data>
<data name="Error_Serializing_Deleted_Layouts" xml:space="preserve">
<value>Error serializing deleted layouts</value>
</data>
<data name="Error_Exception_Message_Box_Title" xml:space="preserve">
<value>FancyZones Editor Exception Handler</value>
</data>
<data name="Error_Report" xml:space="preserve">
<value>Please report the bug to </value>
</data>
<data name="Error_Monitor_Match_Not_Found" xml:space="preserve">
<value>Match not found ({0})</value>
</data>
</root>

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace FancyZonesEditor.Utils
{
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace FancyZonesEditor.Utils
{
public static class EventRaiser
{
public static void Raise(this EventHandler handler, object sender)
{
handler?.Invoke(sender, EventArgs.Empty);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
{
handler?.Invoke(sender, new EventArgs<T>(value));
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T value)
where T : EventArgs
{
handler?.Invoke(sender, value);
}
public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
{
handler?.Invoke(sender, value);
}
}
}

View File

@ -0,0 +1,678 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Windows;
using FancyZonesEditor.Models;
namespace FancyZonesEditor.Utils
{
public class FancyZonesEditorIO
{
// Non-localizable strings: JSON tags
private const string AppliedZonesetsJsonTag = "applied-zonesets";
private const string DeviceIdJsonTag = "device-id";
private const string ActiveZoneSetJsonTag = "active-zoneset";
private const string UuidJsonTag = "uuid";
private const string TypeJsonTag = "type";
private const string EditorShowSpacingJsonTag = "editor-show-spacing";
private const string EditorSpacingJsonTag = "editor-spacing";
private const string EditorZoneCountJsonTag = "editor-zone-count";
private const string EditorSensitivityRadiusJsonTag = "editor-sensitivity-radius";
private const string FocusJsonTag = "focus";
private const string ColumnsJsonTag = "columns";
private const string RowsJsonTag = "rows";
private const string GridJsonTag = "grid";
private const string PriorityGridJsonTag = "priority-grid";
private const string CustomJsonTag = "custom";
private const string NameJsonTag = "name";
private const string CustomZoneSetsJsonTag = "custom-zone-sets";
private const string InfoJsonTag = "info";
private const string RowsPercentageJsonTag = "rows-percentage";
private const string ColumnsPercentageJsonTag = "columns-percentage";
private const string CellChildMapJsonTag = "cell-child-map";
private const string ZonesJsonTag = "zones";
private const string CanvasJsonTag = "canvas";
private const string RefWidthJsonTag = "ref-width";
private const string RefHeightJsonTag = "ref-height";
private const string XJsonTag = "X";
private const string YJsonTag = "Y";
private const string WidthJsonTag = "width";
private const string HeightJsonTag = "height";
// Non-localizable strings: Files
private const string ZonesSettingsFile = "\\Microsoft\\PowerToys\\FancyZones\\zones-settings.json";
private const string ActiveZoneSetsTmpFileName = "FancyZonesActiveZoneSets.json";
private const string AppliedZoneSetsTmpFileName = "FancyZonesAppliedZoneSets.json";
private const string DeletedCustomZoneSetsTmpFileName = "FancyZonesDeletedCustomZoneSets.json";
private readonly IFileSystem _fileSystem = new FileSystem();
private JsonSerializerOptions _options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
public string ActiveZoneSetTmpFile { get; private set; }
public string AppliedZoneSetTmpFile { get; private set; }
public string DeletedCustomZoneSetsTmpFile { get; private set; }
public string FancyZonesSettingsFile { get; private set; }
private enum CmdArgs
{
PowerToysPID = 0,
SpanZones,
TargetMonitorId,
MonitorsCount,
MonitorId,
DPI,
MonitorLeft,
MonitorTop,
}
private struct NativeMonitorData
{
public string Id { get; set; }
public int Dpi { get; set; }
public int X { get; set; }
public int Y { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.Append("ID: ");
sb.AppendLine(Id);
sb.Append("DPI: ");
sb.AppendLine(Dpi.ToString());
sb.Append("X: ");
sb.AppendLine(X.ToString());
sb.Append("Y: ");
sb.AppendLine(Y.ToString());
return sb.ToString();
}
}
private struct ActiveZoneSetWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
}
private struct AppliedZoneSet
{
public string DeviceId { get; set; }
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
public bool EditorShowSpacing { get; set; }
public int EditorSpacing { get; set; }
public int EditorZoneCount { get; set; }
public int EditorSensitivityRadius { get; set; }
}
private struct AppliedZonesetsToDesktops
{
public List<AppliedZoneSet> AppliedZonesets { get; set; }
}
private struct DeletedCustomZoneSetsWrapper
{
public List<string> DeletedCustomZoneSets { get; set; }
}
private struct CreatedCustomZoneSetsWrapper
{
public List<JsonElement> CreatedCustomZoneSets { get; set; }
}
public FancyZonesEditorIO()
{
string tmpDirPath = _fileSystem.Path.GetTempPath();
ActiveZoneSetTmpFile = tmpDirPath + ActiveZoneSetsTmpFileName;
AppliedZoneSetTmpFile = tmpDirPath + AppliedZoneSetsTmpFileName;
DeletedCustomZoneSetsTmpFile = tmpDirPath + DeletedCustomZoneSetsTmpFileName;
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
FancyZonesSettingsFile = localAppDataDir + ZonesSettingsFile;
}
// All strings in this function shouldn't be localized.
public static void ParseCommandLineArguments()
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length < 2 && !App.DebugMode)
{
MessageBox.Show(Properties.Resources.Error_Not_Standalone_App, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
try
{
/*
* Divider: /
* Parts:
* (1) Process id
* (2) Span zones across monitors
* (3) Monitor id where the Editor should be opened
* (4) Monitors count
*
* Data for each monitor:
* (5) Monitor id
* (6) DPI
* (7) monitor left
* (8) monitor top
* ...
*/
var argsParts = args[1].Split('/');
// Process ID
App.PowerToysPID = int.Parse(argsParts[(int)CmdArgs.PowerToysPID]);
// Span zones across monitors
App.Overlay.SpanZonesAcrossMonitors = int.Parse(argsParts[(int)CmdArgs.SpanZones]) == 1;
if (!App.Overlay.SpanZonesAcrossMonitors)
{
// Target monitor id
string targetMonitorName = argsParts[(int)CmdArgs.TargetMonitorId];
// Monitors count
int count = int.Parse(argsParts[(int)CmdArgs.MonitorsCount]);
if (count != App.Overlay.DesktopsCount)
{
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
double primaryMonitorDPI = 96f;
double minimalUsedMonitorDPI = double.MaxValue;
// Parse the native monitor data
List<NativeMonitorData> nativeMonitorData = new List<NativeMonitorData>();
const int monitorArgsCount = 4;
for (int i = 0; i < count; i++)
{
var nativeData = default(NativeMonitorData);
nativeData.Id = argsParts[(int)CmdArgs.MonitorId + (i * monitorArgsCount)];
nativeData.Dpi = int.Parse(argsParts[(int)CmdArgs.DPI + (i * monitorArgsCount)]);
nativeData.X = int.Parse(argsParts[(int)CmdArgs.MonitorLeft + (i * monitorArgsCount)]);
nativeData.Y = int.Parse(argsParts[(int)CmdArgs.MonitorTop + (i * monitorArgsCount)]);
nativeMonitorData.Add(nativeData);
if (nativeData.X == 0 && nativeData.Y == 0)
{
primaryMonitorDPI = nativeData.Dpi;
}
if (minimalUsedMonitorDPI > nativeData.Dpi)
{
minimalUsedMonitorDPI = nativeData.Dpi;
}
}
var monitors = App.Overlay.Monitors;
double identifyScaleFactor = minimalUsedMonitorDPI / primaryMonitorDPI;
double scaleFactor = 96f / primaryMonitorDPI;
// Update monitors data
foreach (Monitor monitor in monitors)
{
bool matchFound = false;
monitor.Scale(scaleFactor);
double scaledBoundX = (int)(monitor.Device.UnscaledBounds.X * identifyScaleFactor);
double scaledBoundY = (int)(monitor.Device.UnscaledBounds.Y * identifyScaleFactor);
foreach (NativeMonitorData nativeData in nativeMonitorData)
{
// Can't do an exact match since the rounding algorithm used by the framework is different from ours
if (scaledBoundX >= (nativeData.X - 1) && scaledBoundX <= (nativeData.X + 1) &&
scaledBoundY >= (nativeData.Y - 1) && scaledBoundY <= (nativeData.Y + 1))
{
monitor.Device.Id = nativeData.Id;
monitor.Device.Dpi = nativeData.Dpi;
matchFound = true;
break;
}
}
if (matchFound == false)
{
MessageBox.Show(string.Format(Properties.Resources.Error_Monitor_Match_Not_Found, monitor.Device.UnscaledBounds.ToString()));
}
}
// Set active desktop
for (int i = 0; i < monitors.Count; i++)
{
var monitor = monitors[i];
if (monitor.Device.Id == targetMonitorName)
{
App.Overlay.CurrentDesktop = i;
break;
}
}
}
}
catch (Exception)
{
MessageBox.Show(Properties.Resources.Error_Invalid_Arguments, Properties.Resources.Error_Message_Box_Title);
((App)Application.Current).Shutdown();
}
}
public void ParseDeviceInfoData()
{
try
{
JsonElement jsonObject = default(JsonElement);
if (_fileSystem.File.Exists(ActiveZoneSetTmpFile))
{
Stream inputStream = _fileSystem.File.Open(ActiveZoneSetTmpFile, FileMode.Open);
jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement;
inputStream.Close();
JsonElement json = jsonObject.GetProperty(AppliedZonesetsJsonTag);
int layoutId = 0;
for (int i = 0; i < json.GetArrayLength() && layoutId < App.Overlay.DesktopsCount; i++)
{
var zonesetData = json[i];
string deviceId = zonesetData.GetProperty(DeviceIdJsonTag).GetString();
string currentLayoutType = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(TypeJsonTag).GetString();
LayoutType type = JsonTagToLayoutType(currentLayoutType);
if (!App.Overlay.SpanZonesAcrossMonitors)
{
var monitors = App.Overlay.Monitors;
for (int monitorIndex = 0; monitorIndex < monitors.Count; monitorIndex++)
{
if (monitors[monitorIndex].Device.Id == deviceId)
{
monitors[monitorIndex].Settings = new LayoutSettings
{
DeviceId = deviceId,
ZonesetUuid = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString(),
ShowSpacing = zonesetData.GetProperty(EditorShowSpacingJsonTag).GetBoolean(),
Spacing = zonesetData.GetProperty(EditorSpacingJsonTag).GetInt32(),
Type = type,
ZoneCount = zonesetData.GetProperty(EditorZoneCountJsonTag).GetInt32(),
SensitivityRadius = zonesetData.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32(),
};
break;
}
}
}
else
{
bool isLayoutMultiMonitor = deviceId.StartsWith("FancyZones#MultiMonitorDevice");
if (isLayoutMultiMonitor)
{
// one zoneset for all desktops
App.Overlay.Monitors[App.Overlay.CurrentDesktop].Settings = new LayoutSettings
{
DeviceId = deviceId,
ZonesetUuid = zonesetData.GetProperty(ActiveZoneSetJsonTag).GetProperty(UuidJsonTag).GetString(),
ShowSpacing = zonesetData.GetProperty(EditorShowSpacingJsonTag).GetBoolean(),
Spacing = zonesetData.GetProperty(EditorSpacingJsonTag).GetInt32(),
Type = type,
ZoneCount = zonesetData.GetProperty(EditorZoneCountJsonTag).GetInt32(),
SensitivityRadius = zonesetData.GetProperty(EditorSensitivityRadiusJsonTag).GetInt32(),
};
break;
}
}
}
}
}
catch (Exception ex)
{
App.ShowExceptionMessageBox(Properties.Resources.Error_Parsing_Device_Info, ex);
}
}
public void ParseLayouts(ref ObservableCollection<LayoutModel> custom, ref List<string> deleted)
{
try
{
Stream inputStream = _fileSystem.File.Open(FancyZonesSettingsFile, FileMode.Open);
JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default);
JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty(CustomZoneSetsJsonTag).EnumerateArray();
while (customZoneSetsEnumerator.MoveNext())
{
var current = customZoneSetsEnumerator.Current;
string name = current.GetProperty(NameJsonTag).GetString();
string type = current.GetProperty(TypeJsonTag).GetString();
string uuid = current.GetProperty(UuidJsonTag).GetString();
var info = current.GetProperty(InfoJsonTag);
if (type.Equals(GridJsonTag))
{
bool error = false;
int rows = info.GetProperty(RowsJsonTag).GetInt32();
int columns = info.GetProperty(ColumnsJsonTag).GetInt32();
List<int> rowsPercentage = new List<int>(rows);
JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty(RowsPercentageJsonTag).EnumerateArray();
List<int> columnsPercentage = new List<int>(columns);
JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty(ColumnsPercentageJsonTag).EnumerateArray();
if (rows <= 0 || columns <= 0 || rowsPercentageEnumerator.Count() != rows || columnsPercentageEnumerator.Count() != columns)
{
error = true;
}
while (!error && rowsPercentageEnumerator.MoveNext())
{
int percentage = rowsPercentageEnumerator.Current.GetInt32();
if (percentage <= 0)
{
error = true;
break;
}
rowsPercentage.Add(percentage);
}
while (!error && columnsPercentageEnumerator.MoveNext())
{
int percentage = columnsPercentageEnumerator.Current.GetInt32();
if (percentage <= 0)
{
error = true;
break;
}
columnsPercentage.Add(percentage);
}
int i = 0;
JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty(CellChildMapJsonTag).EnumerateArray();
int[,] cellChildMap = new int[rows, columns];
if (cellChildMapRows.Count() != rows)
{
error = true;
}
while (!error && cellChildMapRows.MoveNext())
{
int j = 0;
JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray();
if (cellChildMapRowElems.Count() != columns)
{
error = true;
break;
}
while (cellChildMapRowElems.MoveNext())
{
cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32();
}
i++;
}
if (error)
{
App.ShowExceptionMessageBox(string.Format(Properties.Resources.Error_Layout_Malformed_Data, name));
deleted.Add(Guid.Parse(uuid).ToString().ToUpper());
continue;
}
custom.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap));
}
else if (type.Equals(CanvasJsonTag))
{
int workAreaWidth = info.GetProperty(RefWidthJsonTag).GetInt32();
int workAreaHeight = info.GetProperty(RefHeightJsonTag).GetInt32();
JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty(ZonesJsonTag).EnumerateArray();
IList<Int32Rect> zones = new List<Int32Rect>();
bool error = false;
if (workAreaWidth <= 0 || workAreaHeight <= 0)
{
error = true;
}
while (!error && zonesEnumerator.MoveNext())
{
int x = zonesEnumerator.Current.GetProperty(XJsonTag).GetInt32();
int y = zonesEnumerator.Current.GetProperty(YJsonTag).GetInt32();
int width = zonesEnumerator.Current.GetProperty(WidthJsonTag).GetInt32();
int height = zonesEnumerator.Current.GetProperty(HeightJsonTag).GetInt32();
if (width <= 0 || height <= 0)
{
error = true;
break;
}
zones.Add(new Int32Rect(x, y, width, height));
}
if (error)
{
App.ShowExceptionMessageBox(string.Format(Properties.Resources.Error_Layout_Malformed_Data, name));
deleted.Add(Guid.Parse(uuid).ToString().ToUpper());
continue;
}
custom.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, zones, workAreaWidth, workAreaHeight));
}
}
inputStream.Close();
}
catch (Exception ex)
{
App.ShowExceptionMessageBox(Properties.Resources.Error_Loading_Custom_Layouts, ex);
}
}
public void SerializeAppliedLayouts()
{
AppliedZonesetsToDesktops applied = new AppliedZonesetsToDesktops { };
applied.AppliedZonesets = new List<AppliedZoneSet>();
foreach (var monitor in App.Overlay.Monitors)
{
LayoutSettings zoneset = monitor.Settings;
if (zoneset.ZonesetUuid.Length == 0)
{
continue;
}
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
{
Uuid = zoneset.ZonesetUuid,
};
activeZoneSet.Type = LayoutTypeToJsonTag(zoneset.Type);
applied.AppliedZonesets.Add(new AppliedZoneSet
{
DeviceId = zoneset.DeviceId,
ActiveZoneset = activeZoneSet,
EditorShowSpacing = zoneset.ShowSpacing,
EditorSpacing = zoneset.Spacing,
EditorZoneCount = zoneset.ZoneCount,
EditorSensitivityRadius = zoneset.SensitivityRadius,
});
}
try
{
string jsonString = JsonSerializer.Serialize(applied, _options);
_fileSystem.File.WriteAllText(ActiveZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{
App.ShowExceptionMessageBox(Properties.Resources.Error_Applying_Layout, ex);
}
}
public void SerializeDeletedCustomZoneSets(List<string> models)
{
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
{
DeletedCustomZoneSets = models,
};
try
{
string jsonString = JsonSerializer.Serialize(deletedLayouts, _options);
_fileSystem.File.WriteAllText(DeletedCustomZoneSetsTmpFile, jsonString);
}
catch (Exception ex)
{
App.ShowExceptionMessageBox(Properties.Resources.Error_Serializing_Deleted_Layouts, ex);
}
}
public void SerializeCreatedCustomZonesets(List<JsonElement> models)
{
CreatedCustomZoneSetsWrapper layouts = new CreatedCustomZoneSetsWrapper
{
CreatedCustomZoneSets = models,
};
try
{
string jsonString = JsonSerializer.Serialize(layouts, _options);
_fileSystem.File.WriteAllText(AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{
App.ShowExceptionMessageBox(Properties.Resources.Error_Persisting_Custom_Layout, ex);
}
}
private LayoutType JsonTagToLayoutType(string tag)
{
LayoutType type = LayoutType.Blank;
switch (tag)
{
case FocusJsonTag:
type = LayoutType.Focus;
break;
case ColumnsJsonTag:
type = LayoutType.Columns;
break;
case RowsJsonTag:
type = LayoutType.Rows;
break;
case GridJsonTag:
type = LayoutType.Grid;
break;
case PriorityGridJsonTag:
type = LayoutType.PriorityGrid;
break;
case CustomJsonTag:
type = LayoutType.Custom;
break;
}
return type;
}
private string LayoutTypeToJsonTag(LayoutType type)
{
switch (type)
{
case LayoutType.Focus:
return FocusJsonTag;
case LayoutType.Rows:
return RowsJsonTag;
case LayoutType.Columns:
return ColumnsJsonTag;
case LayoutType.Grid:
return GridJsonTag;
case LayoutType.PriorityGrid:
return PriorityGridJsonTag;
case LayoutType.Custom:
return CustomJsonTag;
}
return string.Empty;
}
private static string ParsingCmdArgsErrorReport(string args, int count, string targetMonitorName, List<NativeMonitorData> monitorData, List<Monitor> monitors)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Command-line arguments:");
sb.AppendLine();
sb.AppendLine(args);
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Parsed command-line arguments:");
sb.AppendLine();
sb.Append("Span zones across monitors: ");
sb.AppendLine(App.Overlay.SpanZonesAcrossMonitors.ToString());
sb.Append("Monitors count: ");
sb.AppendLine(count.ToString());
sb.Append("Target monitor: ");
sb.AppendLine(targetMonitorName);
sb.AppendLine();
sb.AppendLine(" # Per monitor data:");
sb.AppendLine();
foreach (NativeMonitorData data in monitorData)
{
sb.AppendLine(data.ToString());
}
sb.AppendLine();
sb.AppendLine("```");
sb.AppendLine(" ## Monitors discovered:");
sb.AppendLine();
foreach (Monitor m in monitors)
{
sb.AppendLine(m.Device.ToString());
}
return sb.ToString();
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace FancyZonesEditor.Utils
{
public class MonitorChangedEventArgs : EventArgs
{
public int LastMonitor { get; }
public MonitorChangedEventArgs(int lastMonitor)
{
LastMonitor = lastMonitor;
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
namespace FancyZonesEditor.Utils
{
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
// Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
// associated views should be enabled whenever a command is invoked
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
CanExecuteChangedInternal -= value;
}
}
private event EventHandler CanExecuteChangedInternal;
public void RaiseCanExecuteChanged()
{
CanExecuteChangedInternal.Invoke(this, null);
/*CanExecuteChangedInternal.Raise(this);*/
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
namespace FancyZonesEditor.Utils
{
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute)
: this(execute, null)
{
_execute = execute;
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor.ViewModels
{
public class MonitorViewModel : INotifyPropertyChanged
{
private const int MaxPreviewDisplaySize = 160;
private const int MinPreviewDisplaySize = 98;
public event PropertyChangedEventHandler PropertyChanged;
public delegate void MonitorChangedEventHandler(MonitorChangedEventArgs args);
public ObservableCollection<MonitorInfoModel> MonitorInfoForViewModel { get; set; }
public static double DesktopPreviewMultiplier { get; private set; }
public Visibility DesktopsPanelVisibility
{
get
{
return App.Overlay.MultiMonitorMode ? Visibility.Visible : Visibility.Collapsed;
}
}
public RelayCommand AddCommand { get; set; }
public RelayCommand DeleteCommand { get; set; }
public RelayCommand<MonitorInfoModel> SelectCommand { get; set; }
public MonitorViewModel()
{
SelectCommand = new RelayCommand<MonitorInfoModel>(SelectCommandExecute, SelectCommandCanExecute);
MonitorInfoForViewModel = new ObservableCollection<MonitorInfoModel>();
double maxDimension = 0, minDimension = double.MaxValue;
int i = 1;
foreach (var monitor in App.Overlay.Monitors)
{
Device device = monitor.Device;
var bounds = device.ScaledBounds;
maxDimension = System.Math.Max(System.Math.Max(maxDimension, bounds.Height), bounds.Width);
minDimension = System.Math.Min(System.Math.Min(minDimension, bounds.Height), bounds.Width);
MonitorInfoForViewModel.Add(new MonitorInfoModel(i, (int)bounds.Height, (int)bounds.Width, device.Dpi, App.Overlay.CurrentDesktop == i - 1));
i++;
}
double maxMultiplier = MaxPreviewDisplaySize / maxDimension;
double minMultiplier = MinPreviewDisplaySize / minDimension;
DesktopPreviewMultiplier = (minMultiplier + maxMultiplier) / 2;
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool SelectCommandCanExecute(MonitorInfoModel monitorInfo)
{
return true;
}
private void SelectCommandExecute(MonitorInfoModel monitorInfo)
{
MonitorInfoForViewModel[App.Overlay.CurrentDesktop].Selected = false;
MonitorInfoForViewModel[monitorInfo.Index - 1].Selected = true;
App.Overlay.CurrentDesktop = monitorInfo.Index - 1;
}
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">System</dpiAwareness>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -592,133 +592,99 @@ void FancyZones::ToggleEditor() noexcept
m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr));
}
HMONITOR monitor{};
HWND foregroundWindow{};
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen;
POINT currentCursorPos{};
if (use_cursorpos_editor_startupscreen)
{
GetCursorPos(&currentCursorPos);
monitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY);
}
else
{
foregroundWindow = GetForegroundWindow();
monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTOPRIMARY);
}
if (!monitor)
{
return;
}
winrt::com_ptr<IZoneWindow> zoneWindow;
std::shared_lock readLock(m_lock);
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
HMONITOR targetMonitor{};
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen;
if (use_cursorpos_editor_startupscreen)
{
zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, NULL);
POINT currentCursorPos{};
GetCursorPos(&currentCursorPos);
targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY);
}
else
{
zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor);
targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY);
}
if (!zoneWindow)
if (!targetMonitor)
{
return;
}
std::wstring editorLocation;
/*
* Divider: /
* Parts:
* (1) Process id
* (2) Span zones across monitors
* (3) Monitor id where the Editor should be opened
* (4) Monitors count
*
* Data for each monitor:
* (5) Monitor id
* (6) DPI
* (7) monitor left
* (8) monitor top
* ...
*/
std::wstring params;
const std::wstring divider = L"/";
params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
const bool spanZonesAcrossMonitors = m_settings->GetSettings()->spanZonesAcrossMonitors;
params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */
std::vector<std::pair<HMONITOR, MONITORINFOEX>> allMonitors;
allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>();
bool showDpiWarning = false;
int prevDpiX = -1, prevDpiY = -1;
std::wstring monitorsData;
for (auto& monitor : allMonitors)
{
std::vector<std::pair<HMONITOR, RECT>> allMonitors;
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
} })
.wait();
UINT currentDpi = 0;
for (const auto& monitor : allMonitors)
auto monitorId = FancyZonesUtils::GenerateMonitorId(monitor.second, monitor.first, m_currentDesktopId);
if (monitor.first == targetMonitor)
{
params += *monitorId + divider; /* Monitor id where the Editor should be opened */
}
if (monitorId.has_value())
{
monitorsData += std::move(*monitorId) + divider; /* Monitor id */
UINT dpiX = 0;
UINT dpiY = 0;
if (GetDpiForMonitor(monitor.first, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK)
{
if (currentDpi == 0)
monitorsData += std::to_wstring(dpiX) + divider; /* DPI */
if (spanZonesAcrossMonitors && prevDpiX != -1 && (prevDpiX != dpiX || prevDpiY != dpiY))
{
currentDpi = dpiX;
continue;
showDpiWarning = true;
}
if (currentDpi != dpiX)
{
MessageBoxW(NULL,
GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
MB_OK | MB_ICONWARNING);
break;
}
}
}
for (auto& [monitor, workArea] : allMonitors)
{
const auto x = workArea.left;
const auto y = workArea.top;
const auto width = workArea.right - workArea.left;
const auto height = workArea.bottom - workArea.top;
std::wstring editorLocationPart =
std::to_wstring(x) + L"_" +
std::to_wstring(y) + L"_" +
std::to_wstring(width) + L"_" +
std::to_wstring(height);
prevDpiX = dpiX;
prevDpiY = dpiY;
}
if (editorLocation.empty())
{
editorLocation = std::move(editorLocationPart);
}
else
{
editorLocation += L'/';
editorLocation += editorLocationPart;
}
monitorsData += std::to_wstring(monitor.second.rcMonitor.left) + divider;
monitorsData += std::to_wstring(monitor.second.rcMonitor.top) + divider;
}
}
else
params += std::to_wstring(allMonitors.size()) + divider; /* Monitors count */
params += monitorsData;
if (showDpiWarning)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
GetMonitorInfo(monitor, &mi);
} })
.wait();
const auto x = mi.rcWork.left;
const auto y = mi.rcWork.top;
const auto width = mi.rcWork.right - mi.rcWork.left;
const auto height = mi.rcWork.bottom - mi.rcWork.top;
editorLocation =
std::to_wstring(x) + L"_" +
std::to_wstring(y) + L"_" +
std::to_wstring(width) + L"_" +
std::to_wstring(height);
MessageBoxW(NULL,
GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
MB_OK | MB_ICONWARNING);
}
const auto& fancyZonesData = FancyZonesDataInstance();
if (!fancyZonesData.SerializeDeviceInfoToTmpFile(zoneWindow->UniqueId()))
{
return;
}
const std::wstring params =
/*1*/ editorLocation + L" " +
/*2*/ L"\"" + std::to_wstring(GetCurrentProcessId()) + L"\"";
fancyZonesData.SerializeDeviceInfoToTmpFile(m_currentDesktopId);
SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@ -930,11 +896,11 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId) n
if (monitor)
{
uniqueId = ZoneWindowUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get());
uniqueId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get());
}
else
{
uniqueId = ZoneWindowUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get());
uniqueId = FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get());
}
std::wstring parentId{};

View File

@ -478,47 +478,41 @@ void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const FancyZ
}
}
bool FancyZonesData::SerializeDeviceInfoToTmpFile(const std::wstring& uniqueId) const
void FancyZonesData::SerializeDeviceInfoToTmpFile(const GUID& currentVirtualDesktop) const
{
const auto deviceInfo = FindDeviceInfo(uniqueId);
if (!deviceInfo.has_value())
{
return false;
}
JSONHelpers::DeviceInfoJSON deviceInfoJson{ uniqueId, *deviceInfo };
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoJson, activeZoneSetTmpFileName);
return true;
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, currentVirtualDesktop, activeZoneSetTmpFileName);
}
void FancyZonesData::ParseDataFromTmpFiles()
{
ParseDeviceInfoFromTmpFile(activeZoneSetTmpFileName);
ParseDeletedCustomZoneSetsFromTmpFile(deletedCustomZoneSetsTmpFileName);
ParseCustomZoneSetFromTmpFile(appliedZoneSetTmpFileName);
ParseCustomZoneSetsFromTmpFile(appliedZoneSetTmpFileName);
SaveFancyZonesData();
}
void FancyZonesData::ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath)
{
std::scoped_lock lock{ dataLock };
const auto& deviceInfo = JSONHelpers::ParseDeviceInfoFromTmpFile(tmpFilePath);
const auto& appliedZonesets = JSONHelpers::ParseDeviceInfoFromTmpFile(tmpFilePath);
if (deviceInfo)
if (appliedZonesets)
{
deviceInfoMap[deviceInfo->deviceId] = std::move(deviceInfo->data);
for (const auto& zoneset : *appliedZonesets)
{
deviceInfoMap[zoneset.first] = std::move(zoneset.second);
}
}
}
void FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath)
void FancyZonesData::ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath)
{
std::scoped_lock lock{ dataLock };
const auto& customZoneSet = JSONHelpers::ParseCustomZoneSetFromTmpFile(tmpFilePath);
const auto& customZoneSets = JSONHelpers::ParseCustomZoneSetsFromTmpFile(tmpFilePath);
if (customZoneSet)
for (const auto& zoneSet : customZoneSets)
{
customZoneSetsMap[customZoneSet->uuid] = std::move(customZoneSet->data);
customZoneSetsMap[zoneSet.uuid] = zoneSet.data;
}
}

View File

@ -15,6 +15,7 @@
namespace FancyZonesDataTypes
{
struct ZoneSetData;
struct DeviceIdData;
struct DeviceInfoData;
struct CustomZoneSetData;
struct AppZoneHistoryData;
@ -71,7 +72,7 @@ public:
void SetActiveZoneSet(const std::wstring& deviceId, const FancyZonesDataTypes::ZoneSetData& zoneSet);
bool SerializeDeviceInfoToTmpFile(const std::wstring& uniqueId) const;
void SerializeDeviceInfoToTmpFile(const GUID& currentVirtualDesktop) const;
void ParseDataFromTmpFiles();
json::JsonObject GetPersistFancyZonesJSON();
@ -113,7 +114,7 @@ private:
}
#endif
void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath);
void ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath);
void ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath);
void ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath);
void RemoveDesktopAppZoneHistory(const std::wstring& desktopId);

View File

@ -108,6 +108,15 @@ namespace FancyZonesDataTypes
std::vector<size_t> zoneIndexSet;
};
struct DeviceIdData
{
std::wstring deviceName;
int width;
int height;
GUID virtualDesktopId;
std::wstring monitorId;
};
struct DeviceInfoData
{
ZoneSetData activeZoneSet;

View File

@ -15,12 +15,14 @@
namespace NonLocalizable
{
const wchar_t ActiveZoneSetStr[] = L"active-zoneset";
const wchar_t AppliedZonesets[] = L"applied-zonesets";
const wchar_t AppPathStr[] = L"app-path";
const wchar_t AppZoneHistoryStr[] = L"app-zone-history";
const wchar_t CanvasStr[] = L"canvas";
const wchar_t CellChildMapStr[] = L"cell-child-map";
const wchar_t ColumnsPercentageStr[] = L"columns-percentage";
const wchar_t ColumnsStr[] = L"columns";
const wchar_t CreatedCustomZoneSets[] = L"created-custom-zone-sets";
const wchar_t CustomZoneSetsStr[] = L"custom-zone-sets";
const wchar_t DeletedCustomZoneSetsStr[] = L"deleted-custom-zone-sets";
const wchar_t DeviceIdStr[] = L"device-id";
@ -445,6 +447,64 @@ namespace JSONHelpers
}
}
json::JsonObject AppliedZonesetsJSON::ToJson(const TDeviceInfoMap& deviceInfoMap)
{
json::JsonObject result{};
json::JsonArray array;
for (const auto& info : deviceInfoMap)
{
JSONHelpers::DeviceInfoJSON deviceInfoJson{ info.first, info.second };
array.Append(JSONHelpers::DeviceInfoJSON::ToJson(deviceInfoJson));
}
result.SetNamedValue(NonLocalizable::AppliedZonesets, array);
return result;
}
json::JsonObject AppliedZonesetsJSON::ToJson(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop)
{
json::JsonObject result{};
json::JsonArray array;
for (const auto& info : deviceInfoMap)
{
std::optional<FancyZonesDataTypes::DeviceIdData> id = FancyZonesUtils::ParseDeviceId(info.first);
if (id.has_value() && id->virtualDesktopId == currentVirtualDesktop)
{
JSONHelpers::DeviceInfoJSON deviceInfoJson{ info.first, info.second };
array.Append(JSONHelpers::DeviceInfoJSON::ToJson(deviceInfoJson));
}
}
result.SetNamedValue(NonLocalizable::AppliedZonesets, array);
return result;
}
std::optional<TDeviceInfoMap> AppliedZonesetsJSON::FromJson(const json::JsonObject& json)
{
try
{
std::unordered_map<std::wstring, FancyZonesDataTypes::DeviceInfoData> appliedZonesets;
auto zonesets = json.GetNamedArray(NonLocalizable::AppliedZonesets);
for (const auto& zoneset : zonesets)
{
std::optional<DeviceInfoJSON> device = DeviceInfoJSON::FromJson(zoneset.GetObjectW());
if (device.has_value())
{
appliedZonesets.insert(std::make_pair(device->deviceId, device->data));
}
}
return appliedZonesets;
}
catch (const winrt::hresult_error&)
{
return std::nullopt;
}
}
json::JsonObject GetPersistFancyZonesJSON(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName)
{
auto result = json::from_file(zonesSettingsFileName);
@ -603,20 +663,34 @@ namespace JSONHelpers
return customZoneSetsJSON;
}
void SerializeDeviceInfoToTmpFile(const JSONHelpers::DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath)
void SerializeDeviceInfoToTmpFile(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop, std::wstring_view tmpFilePath)
{
json::JsonObject deviceInfoJson = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
json::to_file(tmpFilePath, deviceInfoJson);
json::to_file(tmpFilePath, JSONHelpers::AppliedZonesetsJSON::ToJson(deviceInfoMap, currentVirtualDesktop));
}
std::optional<DeviceInfoJSON> ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath)
void SerializeCustomZoneSetsToTmpFile(const TCustomZoneSetsMap& customZoneSetsMap, std::wstring_view tmpFilePath)
{
std::optional<DeviceInfoJSON> result{ std::nullopt };
json::JsonObject result{};
json::JsonArray array;
for (const auto& zoneSet : customZoneSetsMap)
{
CustomZoneSetJSON json{ zoneSet.first, zoneSet.second };
array.Append(JSONHelpers::CustomZoneSetJSON::ToJson(json));
}
result.SetNamedValue(NonLocalizable::CreatedCustomZoneSets, array);
json::to_file(tmpFilePath, result);
}
std::optional<TDeviceInfoMap> ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath)
{
std::optional<TDeviceInfoMap> result{ std::nullopt };
if (std::filesystem::exists(tmpFilePath))
{
if (auto zoneSetJson = json::from_file(tmpFilePath); zoneSetJson.has_value())
{
if (auto deviceInfo = JSONHelpers::DeviceInfoJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value())
if (auto deviceInfo = JSONHelpers::AppliedZonesetsJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value())
{
result = std::move(deviceInfo);
}
@ -627,24 +701,27 @@ namespace JSONHelpers
return result;
}
std::optional<CustomZoneSetJSON> ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath)
std::vector<CustomZoneSetJSON> ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath)
{
std::optional<CustomZoneSetJSON> result{ std::nullopt };
std::vector<CustomZoneSetJSON> result;
if (std::filesystem::exists(tmpFilePath))
{
try
{
if (auto customZoneSetJson = json::from_file(tmpFilePath); customZoneSetJson.has_value())
{
if (auto customZoneSet = JSONHelpers::CustomZoneSetJSON::FromJson(customZoneSetJson.value()); customZoneSet.has_value())
auto zoneSetArray = customZoneSetJson.value().GetNamedArray(NonLocalizable::CreatedCustomZoneSets);
for (const auto& zoneSet : zoneSetArray)
{
result = std::move(customZoneSet);
if (auto customZoneSet = JSONHelpers::CustomZoneSetJSON::FromJson(zoneSet.GetObjectW()); customZoneSet.has_value())
{
result.emplace_back(std::move(*customZoneSet));
}
}
}
}
catch (const winrt::hresult_error&)
{
result = std::nullopt;
}
DeleteTmpFile(tmpFilePath);
@ -676,4 +753,4 @@ namespace JSONHelpers
return result;
}
}
}

View File

@ -46,7 +46,6 @@ namespace JSONHelpers
static std::optional<AppZoneHistoryJSON> FromJson(const json::JsonObject& zoneSet);
};
struct DeviceInfoJSON
{
std::wstring deviceId;
@ -60,6 +59,13 @@ namespace JSONHelpers
using TDeviceInfoMap = std::unordered_map<std::wstring, FancyZonesDataTypes::DeviceInfoData>;
using TCustomZoneSetsMap = std::unordered_map<std::wstring, FancyZonesDataTypes::CustomZoneSetData>;
struct AppliedZonesetsJSON
{
static json::JsonObject ToJson(const TDeviceInfoMap& deviceInfoMap);
static json::JsonObject ToJson(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop);
static std::optional<TDeviceInfoMap> FromJson(const json::JsonObject& json);
};
json::JsonObject GetPersistFancyZonesJSON(const std::wstring& zonesSettingsFileName, const std::wstring& appZoneHistoryFileName);
void SaveFancyZonesData(const std::wstring& zonesSettingsFileName,
const std::wstring& appZoneHistoryFileName,
@ -76,8 +82,13 @@ namespace JSONHelpers
TCustomZoneSetsMap ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON);
json::JsonArray SerializeCustomZoneSets(const TCustomZoneSetsMap& customZoneSetsMap);
void SerializeDeviceInfoToTmpFile(const JSONHelpers::DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath);
std::optional<DeviceInfoJSON> ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath);
std::optional<CustomZoneSetJSON> ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath);
void SerializeDeviceInfoToTmpFile(const TDeviceInfoMap& deviceInfoMap, const GUID& currentVirtualDesktop, std::wstring_view tmpFilePath);
std::optional<TDeviceInfoMap> ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath);
std::vector<CustomZoneSetJSON> ParseCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath);
std::vector<std::wstring> ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath);
#if defined(UNIT_TESTS)
void SerializeCustomZoneSetsToTmpFile(const TCustomZoneSetsMap& customZoneSetsMap, std::wstring_view tmpFilePath);
#endif
}

View File

@ -25,44 +25,6 @@ namespace NonLocalizable
using namespace FancyZonesUtils;
namespace ZoneWindowUtils
{
std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& deviceId, const std::wstring& virtualDesktopId)
{
MONITORINFOEXW mi;
mi.cbSize = sizeof(mi);
if (!virtualDesktopId.empty() && GetMonitorInfo(monitor, &mi))
{
Rect const monitorRect(mi.rcMonitor);
// Unique identifier format: <parsed-device-id>_<width>_<height>_<virtual-desktop-id>
return ParseDeviceId(deviceId) +
L'_' +
std::to_wstring(monitorRect.width()) +
L'_' +
std::to_wstring(monitorRect.height()) +
L'_' +
virtualDesktopId;
}
return {};
}
std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId)
{
std::wstring result{ ZonedWindowProperties::MultiMonitorDeviceID };
RECT combinedResolution = GetAllMonitorsCombinedRect<&MONITORINFO::rcMonitor>();
result += L'_';
result += std::to_wstring(combinedResolution.right - combinedResolution.left);
result += L'_';
result += std::to_wstring(combinedResolution.bottom - combinedResolution.top);
result += L'_';
result += virtualDesktopId;
return result;
}
}
struct ZoneWindow : public winrt::implements<ZoneWindow, IZoneWindow>
{
public:

View File

@ -2,12 +2,6 @@
#include "FancyZones.h"
#include "lib/ZoneSet.h"
namespace ZoneWindowUtils
{
std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& devideId, const std::wstring& virtualDesktopId);
std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId);
}
/**
* Class representing single work area, which is defined by monitor and virtual desktop.
*/

View File

@ -9,6 +9,8 @@
#include <sstream>
#include <complex>
#include <fancyzones/lib/FancyZonesDataTypes.h>
// Non-Localizable strings
namespace NonLocalizable
{
@ -40,7 +42,7 @@ namespace
namespace FancyZonesUtils
{
std::wstring ParseDeviceId(const std::wstring& deviceId)
std::wstring TrimDeviceId(const std::wstring& deviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
@ -63,7 +65,95 @@ namespace FancyZonesUtils
return defaultDeviceId;
}
}
std::optional<FancyZonesDataTypes::DeviceIdData> ParseDeviceId(const std::wstring& str)
{
FancyZonesDataTypes::DeviceIdData data;
std::wstring temp;
std::wstringstream wss(str);
/*
Important fix for device info that contains a '_' in the name:
1. first search for '#'
2. Then split the remaining string by '_'
*/
// Step 1: parse the name until the #, then to the '_'
if (str.find(L'#') != std::string::npos)
{
std::getline(wss, temp, L'#');
data.deviceName = temp;
if (!std::getline(wss, temp, L'_'))
{
return std::nullopt;
}
data.deviceName += L"#" + temp;
}
else if(std::getline(wss, temp, L'_') && !temp.empty())
{
data.deviceName = temp;
}
else
{
return std::nullopt;
}
// Step 2: parse the rest of the id
std::vector<std::wstring> parts;
while (std::getline(wss, temp, L'_'))
{
parts.push_back(temp);
}
if (parts.size() != 3 && parts.size() != 4)
{
return std::nullopt;
}
/*
Refer to ZoneWindowUtils::GenerateUniqueId parts contain:
1. monitor id [string]
2. width of device [int]
3. height of device [int]
4. virtual desktop id (GUID) [string]
*/
try
{
for (const auto& c : parts[0])
{
std::stoi(std::wstring(&c));
}
for (const auto& c : parts[1])
{
std::stoi(std::wstring(&c));
}
data.width = std::stoi(parts[0]);
data.height = std::stoi(parts[1]);
}
catch (const std::exception&)
{
return std::nullopt;
}
if (!SUCCEEDED(CLSIDFromString(parts[2].c_str(), &data.virtualDesktopId)))
{
return std::nullopt;
}
if (parts.size() == 4)
{
data.monitorId = parts[3]; //could be empty
}
return data;
}
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
{
@ -457,6 +547,71 @@ namespace FancyZonesUtils
return true;
}
std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& deviceId, const std::wstring& virtualDesktopId)
{
MONITORINFOEXW mi;
mi.cbSize = sizeof(mi);
if (!virtualDesktopId.empty() && GetMonitorInfo(monitor, &mi))
{
Rect const monitorRect(mi.rcMonitor);
// Unique identifier format: <parsed-device-id>_<width>_<height>_<virtual-desktop-id>
return TrimDeviceId(deviceId) +
L'_' +
std::to_wstring(monitorRect.width()) +
L'_' +
std::to_wstring(monitorRect.height()) +
L'_' +
virtualDesktopId;
}
return {};
}
std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId)
{
std::wstring result{ ZonedWindowProperties::MultiMonitorDeviceID };
RECT combinedResolution = GetAllMonitorsCombinedRect<&MONITORINFO::rcMonitor>();
result += L'_';
result += std::to_wstring(combinedResolution.right - combinedResolution.left);
result += L'_';
result += std::to_wstring(combinedResolution.bottom - combinedResolution.top);
result += L'_';
result += virtualDesktopId;
return result;
}
std::optional<std::wstring> GenerateMonitorId(MONITORINFOEX mi, HMONITOR monitor, const GUID& virtualDesktopId)
{
DISPLAY_DEVICE displayDevice = { sizeof(displayDevice) };
PCWSTR deviceId = nullptr;
bool validMonitor = true;
if (EnumDisplayDevices(mi.szDevice, 0, &displayDevice, 1))
{
if (displayDevice.DeviceID[0] != L'\0')
{
deviceId = displayDevice.DeviceID;
}
}
if (!deviceId)
{
deviceId = GetSystemMetrics(SM_REMOTESESSION) ?
L"\\\\?\\DISPLAY#REMOTEDISPLAY#" :
L"\\\\?\\DISPLAY#LOCALDISPLAY#";
}
wil::unique_cotaskmem_string vdId;
if (SUCCEEDED(StringFromCLSID(virtualDesktopId, &vdId)))
{
return GenerateUniqueId(monitor, deviceId, vdId.get());
}
return std::nullopt;
}
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept
{
using complex = std::complex<double>;

View File

@ -3,6 +3,11 @@
#include "gdiplus.h"
#include <common/string_utils.h>
namespace FancyZonesDataTypes
{
struct DeviceIdData;
}
namespace FancyZonesUtils
{
struct Rect
@ -131,6 +136,28 @@ namespace FancyZonesUtils
return result;
}
template<RECT MONITORINFO::*member>
std::vector<std::pair<HMONITOR, MONITORINFOEX>> GetAllMonitorInfo()
{
using result_t = std::vector<std::pair<HMONITOR, MONITORINFOEX>>;
result_t result;
auto enumMonitors = [](HMONITOR monitor, HDC hdc, LPRECT pRect, LPARAM param) -> BOOL {
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
result_t& result = *reinterpret_cast<result_t*>(param);
if (GetMonitorInfo(monitor, &mi))
{
result.push_back({ monitor, mi });
}
return TRUE;
};
EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast<LPARAM>(&result));
return result;
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
@ -157,8 +184,6 @@ namespace FancyZonesUtils
return result;
}
std::wstring ParseDeviceId(const std::wstring& deviceId);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
void SizeWindowToRect(HWND window, RECT rect) noexcept;
@ -174,6 +199,13 @@ namespace FancyZonesUtils
void RestoreWindowOrigin(HWND window) noexcept;
bool IsValidGuid(const std::wstring& str);
std::wstring GenerateUniqueId(HMONITOR monitor, const std::wstring& devideId, const std::wstring& virtualDesktopId);
std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId);
std::optional<std::wstring> GenerateMonitorId(MONITORINFOEX mi, HMONITOR monitor, const GUID& virtualDesktopId);
std::wstring TrimDeviceId(const std::wstring& deviceId);
std::optional<FancyZonesDataTypes::DeviceIdData> ParseDeviceId(const std::wstring& deviceId);
bool IsValidDeviceId(const std::wstring& str);
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept;

View File

@ -938,6 +938,92 @@ namespace FancyZonesUnitTests
}
};
TEST_CLASS(AppliedZonesetsUnitTests)
{
TEST_METHOD(SingleDevice)
{
const std::wstring deviceId = L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1539}";
const std::wstring zoneUuid = L"{33A2B101-06E0-437B-A61E-CDBECF502906}";
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
DeviceInfoData data{ ZoneSetData{ zoneUuid, type }, true, 10, 4 };
TDeviceInfoMap expected;
expected.insert(std::make_pair(deviceId, data));
json::JsonObject json = AppliedZonesetsJSON::ToJson(expected);
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsTrue(actual.has_value());
Assert::AreEqual(expected.size(), actual->size());
for (const auto& exp : expected)
{
Assert::IsTrue(actual->contains(exp.first));
const auto act = actual->find(exp.first);
Assert::AreEqual(exp.second.zoneCount, act->second.zoneCount);
Assert::AreEqual(exp.second.showSpacing, act->second.showSpacing);
Assert::AreEqual(exp.second.spacing, act->second.spacing);
Assert::AreEqual(exp.second.activeZoneSet.uuid, act->second.activeZoneSet.uuid);
Assert::AreEqual((int)exp.second.activeZoneSet.type, (int)act->second.activeZoneSet.type);
}
}
TEST_METHOD (MultipleDevices)
{
TDeviceInfoMap expected;
expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1539}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Columns }, true, 10, 4 }));
expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1538}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502905}", ZoneSetLayoutType::Rows }, false, 8, 5 }));
expected.insert(std::make_pair(L"AOC2460#4&fe3a015&0&UID65793_1920_1200_{39B25DD2-130D-4B5D-8851-4791D66B1537}", DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502904}", ZoneSetLayoutType::Grid }, true, 9, 6 }));
json::JsonObject json = AppliedZonesetsJSON::ToJson(expected);
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsTrue(actual.has_value());
Assert::AreEqual(expected.size(), actual->size());
for (const auto& exp : expected)
{
Assert::IsTrue(actual->contains(exp.first));
const auto act = actual->find(exp.first);
Assert::AreEqual(exp.second.zoneCount, act->second.zoneCount);
Assert::AreEqual(exp.second.showSpacing, act->second.showSpacing);
Assert::AreEqual(exp.second.spacing, act->second.spacing);
Assert::AreEqual(exp.second.activeZoneSet.uuid, act->second.activeZoneSet.uuid);
Assert::AreEqual((int)exp.second.activeZoneSet.type, (int)act->second.activeZoneSet.type);
}
}
TEST_METHOD (FromJsonNoDeviceId)
{
json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": [{\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{81B9FCD3-88CA-4B21-A681-5D1129A1527F}\",\"type\": \"grid\"},\"editor-show-spacing\": true,\"editor-spacing\": 5,\"editor-zone-count\": 4},{\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{8110E0D5-4815-4A35-A5AC-DF82A65FF58B}\",\"type\": \"priority-grid\"},\"editor-show-spacing\": false,\"editor-spacing\": 6,\"editor-zone-count\": 2}]}");
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsTrue(actual.has_value());
Assert::IsTrue(actual->empty());
}
TEST_METHOD (FromInvalidJsonNotArray)
{
json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": {\"device-id\": \"\",\"active-zoneset\": {\"uuid\": \"{81B9FCD3-88CA-4B21-A681-5D1129A1527F}\",\"type\": \"grid\"},\"editor-show-spacing\": true,\"editor-spacing\": 5,\"editor-zone-count\": 4}}");
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (FromEmptyJson)
{
json::JsonObject json = json::JsonObject::Parse(L"{}");
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (FromEmptyDeviceArray)
{
json::JsonObject json = json::JsonObject::Parse(L"{\"applied-zonesets\": []}");
auto actual = AppliedZonesetsJSON::FromJson(json);
Assert::IsTrue(actual.has_value());
Assert::IsTrue(actual->empty());
}
};
TEST_CLASS (FancyZonesDataUnitTests)
{
private:
@ -947,6 +1033,8 @@ namespace FancyZonesUnitTests
const json::JsonValue m_defaultCustomDeviceValue = json::JsonValue::Parse(m_defaultCustomDeviceStr);
const json::JsonObject m_defaultCustomDeviceObj = json::JsonObject::Parse(m_defaultCustomDeviceStr);
GUID m_defaultVDId;
HINSTANCE m_hInst{};
FancyZonesData& m_fzData = FancyZonesDataInstance();
@ -964,6 +1052,10 @@ namespace FancyZonesUnitTests
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_fzData.clear_data();
std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName));
auto guid = Helpers::StringToGuid(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}");
Assert::IsTrue(guid.has_value());
m_defaultVDId = *guid;
}
TEST_METHOD_CLEANUP(CleanUp)
@ -1098,15 +1190,18 @@ namespace FancyZonesUnitTests
{
FancyZonesData data;
data.SetSettingsModulePath(m_moduleName);
DeviceInfoJSON deviceInfo{ L"default_device_id", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } };
TDeviceInfoMap deviceInfoMap;
DeviceInfoData deviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 };
deviceInfoMap.insert(std::make_pair(m_defaultDeviceId, deviceInfoData));
const std::wstring path = data.zonesSettingsFileName + L".test_tmp";
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, path);
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_defaultVDId, path);
bool actualFileExists = std::filesystem::exists(path);
Assert::IsTrue(actualFileExists);
auto expectedData = DeviceInfoJSON::ToJson(deviceInfo);
auto expectedData = AppliedZonesetsJSON::ToJson(deviceInfoMap);
auto actualSavedData = json::from_file(path);
std::filesystem::remove(path); //clean up before compare asserts
@ -1118,10 +1213,15 @@ namespace FancyZonesUnitTests
{
FancyZonesData data;
data.SetSettingsModulePath(m_moduleName);
const std::wstring deviceId = m_defaultDeviceId;
DeviceInfoJSON expected{ deviceId, DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 } };
DeviceInfoData expected{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 };
TDeviceInfoMap expectedDeviceInfoMap;
expectedDeviceInfoMap.insert(std::make_pair(deviceId, expected));
const std::wstring path = data.zonesSettingsFileName + L".test_tmp";
JSONHelpers::SerializeDeviceInfoToTmpFile(expected, path);
JSONHelpers::SerializeDeviceInfoToTmpFile(expectedDeviceInfoMap, m_defaultVDId, path);
data.ParseDeviceInfoFromTmpFile(path);
@ -1136,11 +1236,11 @@ namespace FancyZonesUnitTests
Assert::AreEqual((size_t)1, devices.size());
auto actual = devices.find(deviceId)->second;
Assert::AreEqual(expected.data.showSpacing, actual.showSpacing);
Assert::AreEqual(expected.data.spacing, actual.spacing);
Assert::AreEqual(expected.data.zoneCount, actual.zoneCount);
Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual.activeZoneSet.type);
Assert::AreEqual(expected.data.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str());
Assert::AreEqual(expected.showSpacing, actual.showSpacing);
Assert::AreEqual(expected.spacing, actual.spacing);
Assert::AreEqual(expected.zoneCount, actual.zoneCount);
Assert::AreEqual((int)expected.activeZoneSet.type, (int)actual.activeZoneSet.type);
Assert::AreEqual(expected.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str());
}
TEST_METHOD (DeviceInfoReadTempNonexistent)
@ -1525,9 +1625,12 @@ namespace FancyZonesUnitTests
const std::wstring deviceId = L"default_device_id";
{
DeviceInfoJSON deviceInfo{ deviceId, DeviceInfoData{ ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 } };
TDeviceInfoMap deviceInfoMap;
DeviceInfoData deviceInfoData { ZoneSetData{ L"{33A2B101-06E0-437B-A61E-CDBECF502906}", ZoneSetLayoutType::Custom }, true, 16, 3 };
deviceInfoMap.insert(std::make_pair(deviceId, deviceInfoData));
const std::wstring deviceInfoPath = m_fzData.zonesSettingsFileName + L".device_info_tmp";
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath);
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_defaultVDId, deviceInfoPath);
m_fzData.ParseDeviceInfoFromTmpFile(deviceInfoPath);
std::filesystem::remove(deviceInfoPath);
@ -1540,13 +1643,18 @@ namespace FancyZonesUnitTests
.rowsPercents = { 10000 },
.columnsPercents = { 2500, 5000, 2500 },
.cellChildMap = { { 0, 1, 2 } } }));
CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } };
CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid };
CustomZoneSetJSON expected{ uuid, zoneSetData };
FancyZonesData data;
data.SetSettingsModulePath(m_moduleName);
const std::wstring path = data.zonesSettingsFileName + L".test_tmp";
json::to_file(path, CustomZoneSetJSON::ToJson(expected));
m_fzData.ParseCustomZoneSetFromTmpFile(path);
TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(uuid, zoneSetData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, path);
m_fzData.ParseCustomZoneSetsFromTmpFile(path);
bool actualFileExists = std::filesystem::exists(path);
if (actualFileExists)
@ -1572,7 +1680,7 @@ namespace FancyZonesUnitTests
const std::wstring path = m_fzData.zonesSettingsFileName + L".test_tmp";
const std::wstring deviceId = L"default_device_id";
m_fzData.ParseCustomZoneSetFromTmpFile(path);
m_fzData.ParseCustomZoneSetsFromTmpFile(path);
auto devices = m_fzData.GetDeviceInfoMap();
Assert::AreEqual((size_t)0, devices.size());
}

View File

@ -40,28 +40,143 @@ namespace FancyZonesUnitTests
TEST_CLASS(UtilUnitTests)
{
TEST_METHOD(TestParseDeviceId)
TEST_METHOD (TestTrimDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
const std::wstring actual = ParseDeviceId(input);
const std::wstring actual = TrimDeviceId(input);
const std::wstring expected = L"DELA026#5&10a58c63&0&UID16777488";
Assert::AreEqual(expected, actual);
}
TEST_METHOD(TestParseInvalidDeviceId)
TEST_METHOD(TestTrimInvalidDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring input = L"AnInvalidDeviceId";
const std::wstring actual = ParseDeviceId(input);
const std::wstring actual = TrimDeviceId(input);
const std::wstring expected = L"FallbackDevice";
Assert::AreEqual(expected, actual);
}
TEST_METHOD(TestParseDeviceId01)
{
const std::wstring input = L"AOC0001#5&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}";
GUID guid;
const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}";
CLSIDFromString(expectedGuidStr, &guid);
const FancyZonesDataTypes::DeviceIdData expected{ L"AOC0001#5&37ac4db&0&UID160002", 1536, 960, guid };
const auto actual = ParseDeviceId(input);
Assert::IsTrue(actual.has_value());
Assert::AreEqual(expected.deviceName, actual->deviceName);
Assert::AreEqual(expected.height, actual->height);
Assert::AreEqual(expected.width, actual->width);
Assert::AreEqual(expected.monitorId, actual->monitorId);
wil::unique_cotaskmem_string actualGuidStr;
StringFromCLSID(actual->virtualDesktopId, &actualGuidStr);
Assert::AreEqual(expectedGuidStr, actualGuidStr.get());
}
TEST_METHOD (TestParseDeviceId02)
{
const std::wstring input = L"AOC0001#5&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}_monitorId";
GUID guid;
const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}";
CLSIDFromString(expectedGuidStr, &guid);
const FancyZonesDataTypes::DeviceIdData expected{ L"AOC0001#5&37ac4db&0&UID160002", 1536, 960, guid, L"monitorId" };
const auto actual = ParseDeviceId(input);
Assert::IsTrue(actual.has_value());
Assert::AreEqual(expected.deviceName, actual->deviceName);
Assert::AreEqual(expected.height, actual->height);
Assert::AreEqual(expected.width, actual->width);
Assert::AreEqual(expected.monitorId, actual->monitorId);
wil::unique_cotaskmem_string actualGuidStr;
StringFromCLSID(actual->virtualDesktopId, &actualGuidStr);
Assert::AreEqual(expectedGuidStr, actualGuidStr.get());
}
TEST_METHOD (TestParseDeviceId03)
{
// difference with previous tests is in the device name: there is no # symbol
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}";
GUID guid;
const auto expectedGuidStr = L"{E0A2904E-889C-4532-95B1-28FE15C16F66}";
CLSIDFromString(expectedGuidStr, &guid);
const FancyZonesDataTypes::DeviceIdData expected{ L"AOC00015&37ac4db&0&UID160002", 1536, 960, guid };
const auto actual = ParseDeviceId(input);
Assert::IsTrue(actual.has_value());
Assert::AreEqual(expected.deviceName, actual->deviceName);
Assert::AreEqual(expected.height, actual->height);
Assert::AreEqual(expected.width, actual->width);
Assert::AreEqual(expected.monitorId, actual->monitorId);
wil::unique_cotaskmem_string actualGuidStr;
StringFromCLSID(actual->virtualDesktopId, &actualGuidStr);
Assert::AreEqual(expectedGuidStr, actualGuidStr.get());
}
TEST_METHOD (TestParseDeviceIdInvalid01)
{
// no width or height
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_{E0A2904E-889C-4532-95B1-28FE15C16F66}";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (TestParseDeviceIdInvalid02)
{
// no width and height
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_{E0A2904E-889C-4532-95B1-28FE15C16F66}_monitorId";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (TestParseDeviceIdInvalid03)
{
// no guid
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (TestParseDeviceIdInvalid04)
{
// invalid guid
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_1536960_{asdf}";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (TestParseDeviceIdInvalid05)
{
// invalid width/height
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_15a6_960_{E0A2904E-889C-4532-95B1-28FE15C16F66}";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD (TestParseDeviceIdInvalid06)
{
// changed order
const std::wstring input = L"AOC00015&37ac4db&0&UID160002_15a6_960_monitorId_{E0A2904E-889C-4532-95B1-28FE15C16F66}";
const auto actual = ParseDeviceId(input);
Assert::IsFalse(actual.has_value());
}
TEST_METHOD(TestMonitorOrdering01)
{
// Three horizontally arranged monitors, bottom aligned, with increasing sizes

View File

@ -172,3 +172,14 @@ std::wstring Helpers::CreateGuidString()
return L"";
}
std::optional<GUID> Helpers::StringToGuid(const std::wstring& str)
{
GUID guid;
if (CLSIDFromString(str.c_str(), &guid) == S_OK)
{
return guid;
}
return std::nullopt;
}

View File

@ -57,6 +57,7 @@ namespace Helpers
{
std::wstring GuidToString(const GUID& guid);
std::wstring CreateGuidString();
std::optional<GUID> StringToGuid(const std::wstring& str);
}
template<>

View File

@ -2,6 +2,7 @@
#include "lib\FancyZonesData.h"
#include "lib\FancyZonesDataTypes.h"
#include "lib\JsonHelpers.h"
#include "lib\VirtualDesktopUtils.h"
#include "lib\ZoneSet.h"
#include <filesystem>
@ -1048,9 +1049,14 @@ namespace FancyZonesUnitTests
//prepare device data
{
const std::wstring zoneUuid = L"default_device_id";
JSONHelpers::DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } };
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }));
GUID virtualDesktopId{};
Assert::IsTrue(VirtualDesktopUtils::GetCurrentVirtualDesktopId(&virtualDesktopId), L"Cannot create virtual desktop id");
const std::wstring deviceInfoPath = FancyZonesDataInstance().zonesSettingsFileName + L".device_info_tmp";
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath);
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, virtualDesktopId, deviceInfoPath);
FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath);
std::filesystem::remove(deviceInfoPath);
@ -1060,10 +1066,14 @@ namespace FancyZonesUnitTests
wil::unique_cotaskmem_string uuid;
Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid));
const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } };
JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } };
json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected));
CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Canvas, info };
JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), zoneSetData };
JSONHelpers::TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(uuid.get(), zoneSetData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, m_path);
Assert::IsTrue(std::filesystem::exists(m_path));
FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path);
FancyZonesDataInstance().ParseCustomZoneSetsFromTmpFile(m_path);
//test
const int spacing = 10;
@ -1083,9 +1093,14 @@ namespace FancyZonesUnitTests
//prepare device data
{
const std::wstring zoneUuid = L"default_device_id";
JSONHelpers::DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } };
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }));
GUID virtualDesktopId{};
Assert::IsTrue(VirtualDesktopUtils::GetCurrentVirtualDesktopId(&virtualDesktopId), L"Cannot create virtual desktop id");
const std::wstring deviceInfoPath = FancyZonesDataInstance().zonesSettingsFileName + L".device_info_tmp";
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath);
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, virtualDesktopId, deviceInfoPath);
FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath);
std::filesystem::remove(deviceInfoPath);
@ -1100,10 +1115,14 @@ namespace FancyZonesUnitTests
.rowsPercents = { 10000 },
.columnsPercents = { 2500, 5000, 2500 },
.cellChildMap = { { 0, 1, 2 } } }));
JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } };
json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected));
CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid };
JSONHelpers::CustomZoneSetJSON expected{ uuid.get(), zoneSetData };
JSONHelpers::TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(uuid.get(), zoneSetData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, m_path);
Assert::IsTrue(std::filesystem::exists(m_path));
FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path);
FancyZonesDataInstance().ParseCustomZoneSetsFromTmpFile(m_path);
const int spacing = 10;
const int zoneCount = grid.rows() * grid.columns();

View File

@ -69,7 +69,8 @@ namespace FancyZonesUnitTests
HINSTANCE m_hInst{};
HMONITOR m_monitor{};
MONITORINFO m_monitorInfo{};
MONITORINFOEX m_monitorInfo{};
GUID m_virtualDesktopGuid{};
FancyZonesData& m_fancyZonesData = FancyZonesDataInstance();
@ -102,6 +103,10 @@ namespace FancyZonesUnitTests
m_fancyZonesData.SetSettingsModulePath(L"FancyZonesUnitTests");
m_fancyZonesData.clear_data();
auto guid = Helpers::StringToGuid(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}");
Assert::IsTrue(guid.has_value());
m_virtualDesktopGuid = *guid;
}
TEST_METHOD_CLEANUP(Cleanup)
@ -154,7 +159,7 @@ namespace FancyZonesUnitTests
TEST_METHOD(CreateZoneWindowNoDeviceId)
{
// Generate unique id without device id
std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, {}, m_virtualDesktopId);
std::wstring uniqueId = FancyZonesUtils::GenerateUniqueId(m_monitor, {}, m_virtualDesktopId);
auto zoneWindow = MakeZoneWindow(winrt::make_self<MockZoneWindowHost>().get(), m_hInst, m_monitor, uniqueId, {});
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
@ -172,7 +177,7 @@ namespace FancyZonesUnitTests
TEST_METHOD(CreateZoneWindowNoDesktopId)
{
// Generate unique id without virtual desktop id
std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, m_deviceId, {});
std::wstring uniqueId = FancyZonesUtils::GenerateUniqueId(m_monitor, m_deviceId, {});
auto zoneWindow = MakeZoneWindow(winrt::make_self<MockZoneWindowHost>().get(), m_hInst, m_monitor, uniqueId, {});
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
@ -219,9 +224,9 @@ namespace FancyZonesUnitTests
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
const auto expectedZoneSet = ZoneSetData{ Helpers::CreateGuidString(), type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
json::to_file(activeZoneSetTempPath, json);
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data));
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath);
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
@ -248,18 +253,20 @@ namespace FancyZonesUnitTests
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
json::to_file(activeZoneSetTempPath, json);
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data));
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath);
const auto info = CanvasLayoutInfo{
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
};
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData });
json::to_file(appliedZoneSetTempPath, customZoneJson);
JSONHelpers::TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(customSetGuid, customZoneData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath);
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath);
//temp file read on initialization
auto actual = MakeZoneWindow(winrt::make_self<MockZoneWindowHost>().get(), m_hInst, m_monitor, m_uniqueId.str(), {});
@ -285,9 +292,9 @@ namespace FancyZonesUnitTests
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
json::to_file(activeZoneSetTempPath, json);
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data));
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath);
const auto info = CanvasLayoutInfo{
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
@ -295,7 +302,9 @@ namespace FancyZonesUnitTests
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
const auto customZoneSet = JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData };
auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(customZoneSet);
json::to_file(appliedZoneSetTempPath, customZoneJson);
JSONHelpers::TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(customSetGuid, customZoneData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath);
//save same zone as deleted
json::JsonObject deletedCustomZoneSets = {};
@ -306,7 +315,7 @@ namespace FancyZonesUnitTests
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath);
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath);
//temp file read on initialization
auto actual = MakeZoneWindow(winrt::make_self<MockZoneWindowHost>().get(), m_hInst, m_monitor, m_uniqueId.str(), {});
@ -331,9 +340,9 @@ namespace FancyZonesUnitTests
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
json::to_file(activeZoneSetTempPath, json);
JSONHelpers::TDeviceInfoMap deviceInfoMap;
deviceInfoMap.insert(std::make_pair(m_uniqueId.str(), data));
JSONHelpers::SerializeDeviceInfoToTmpFile(deviceInfoMap, m_virtualDesktopGuid, activeZoneSetTempPath);
const auto info = CanvasLayoutInfo{
100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } }
@ -341,7 +350,9 @@ namespace FancyZonesUnitTests
const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info };
const auto customZoneSet = JSONHelpers::CustomZoneSetJSON{ customSetGuid, customZoneData };
auto customZoneJson = JSONHelpers::CustomZoneSetJSON::ToJson(customZoneSet);
json::to_file(appliedZoneSetTempPath, customZoneJson);
JSONHelpers::TCustomZoneSetsMap customZoneSets;
customZoneSets.insert(std::make_pair(customSetGuid, customZoneData));
JSONHelpers::SerializeCustomZoneSetsToTmpFile(customZoneSets, appliedZoneSetTempPath);
//save different zone as deleted
json::JsonObject deletedCustomZoneSets = {};
@ -353,7 +364,7 @@ namespace FancyZonesUnitTests
m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath);
m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath);
m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath);
m_fancyZonesData.ParseCustomZoneSetsFromTmpFile(appliedZoneSetTempPath);
//temp file read on initialization
auto actual = MakeZoneWindow(winrt::make_self<MockZoneWindowHost>().get(), m_hInst, m_monitor, m_uniqueId.str(), {});

View File

@ -0,0 +1,9 @@
<Application x:Class="FancyZonesEditor_DPI_netcore_test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_netcore_test"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace FancyZonesEditor_DPI_netcore_test
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.DpiAwareness" Version="6.7.30328" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30621.155
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor_DPI_netcore_test", "FancyZonesEditor_DPI_netcore_test.csproj", "{1764AC88-EE10-4613-AB77-F213EF8043D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1764AC88-EE10-4613-AB77-F213EF8043D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1764AC88-EE10-4613-AB77-F213EF8043D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1764AC88-EE10-4613-AB77-F213EF8043D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1764AC88-EE10-4613-AB77-F213EF8043D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F95C5E3B-3EC1-42AC-A24C-6A9350B72807}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,15 @@
<Window x:Class="FancyZonesEditor_DPI_netcore_test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_netcore_test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Closing="Window_Closing">
<Grid>
<ListView x:Name="MonitorList" Margin="16">
</ListView>
</Grid>
</Window>

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using Microsoft.VisualStudio.Utilities;
namespace FancyZonesEditor_DPI_netcore_test
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
struct ScreenInfo
{
public int MonitorDPI { get; set; }
public double WindowDPI { get; set; }
public Rect Resolution { get; set; }
public Rect WorkArea { get; set; }
public override string ToString()
{
var resolution = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", Resolution.Left, Resolution.Top, Resolution.Width, Resolution.Height);
var workArea = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", WorkArea.Left, WorkArea.Top, WorkArea.Width, WorkArea.Height);
return "Monitor DPI: " + MonitorDPI + " - Resolution: " + resolution + " - WorkArea: " + workArea;
}
}
private List<OverlayWindow> workAreaWindows;
public MainWindow()
{
InitializeComponent();
double primaryMonitorDPI = 96f;
var colors = new Brush[] { Brushes.Green, Brushes.Blue, Brushes.Red };
var screens = System.Windows.Forms.Screen.AllScreens;
List<ScreenInfo> screenInfoList = new List<ScreenInfo>();
workAreaWindows = new List<OverlayWindow>();
var monitors = MonitorsInfo.GetMonitors();
for (int i = 0; i < screens.Length; i++)
{
if (screens[i].Primary)
{
double monitorDPI;
DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI);
primaryMonitorDPI = monitorDPI;
break;
}
}
for (int i = 0; i < screens.Length; i++)
{
var monitor = monitors[i];
ScreenInfo screenInfo = new ScreenInfo();
var window = new OverlayWindow
{
Opacity = 0.8,
Background = colors[i % colors.Length],
BorderBrush = Brushes.White,
BorderThickness = new Thickness(4, 4, 4, 4)
};
// get monitor dpi
double monitorDPI;
DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI);
screenInfo.MonitorDPI = (int)monitorDPI;
// resolution
screenInfo.Resolution = new Rect(monitor.MonitorInfo.monitor.left, monitor.MonitorInfo.monitor.top,
monitor.MonitorInfo.monitor.width, monitor.MonitorInfo.monitor.height);
// work area
Rect workedArea = new Rect(monitor.MonitorInfo.work.left, monitor.MonitorInfo.work.top,
monitor.MonitorInfo.work.width, monitor.MonitorInfo.work.height);
double scalePosition = 96f / primaryMonitorDPI;
workedArea.X *= scalePosition;
workedArea.Y *= scalePosition;
double scaleSize = 96f / monitorDPI;
workedArea.Width *= scaleSize;
workedArea.Height *= scaleSize;
screenInfo.WorkArea = workedArea;
screenInfo.WindowDPI = window.GetDpiX();
screenInfoList.Add(screenInfo);
// open window
window.Left = workedArea.X;
window.Top = workedArea.Y;
window.Width = workedArea.Width;
window.Height = workedArea.Height;
workAreaWindows.Add(window);
window.Show();
}
MonitorList.ItemsSource = screenInfoList;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
foreach (OverlayWindow window in workAreaWindows)
{
window.Close();
}
}
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace FancyZonesEditor_DPI_netcore_test
{
public class MonitorsInfo
{
/// <summary>
/// Rectangle
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int width
{
get
{
return right - left;
}
}
public int height
{
get
{
return bottom - top;
}
}
}
/// <summary>
/// Monitor information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public uint size;
public RECT monitor;
public RECT work;
public uint flags;
}
/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor,
ref RECT lprcMonitor, IntPtr dwData);
/// <summary>
/// Enumerates through the display monitors.
/// </summary>
/// <param name="hdc">A handle to a display device context that defines the visible region of interest.</param>
/// <param name="lprcClip">A pointer to a RECT structure that specifies a clipping rectangle.</param>
/// <param name="lpfnEnum">A pointer to a MonitorEnumProc application-defined callback function.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
MonitorEnumDelegate lpfnEnum, IntPtr dwData);
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <param name="hmon">A handle to the display monitor of interest.</param>
/// <param name="mi">A pointer to a MONITORINFO instance created by this method.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi);
/// <summary>
/// Monitor information with handle interface.
/// </summary>
public interface IMonitorInfoWithHandle
{
IntPtr MonitorHandle { get; }
MONITORINFO MonitorInfo { get; }
}
/// <summary>
/// Monitor information with handle.
/// </summary>
public class MonitorInfoWithHandle : IMonitorInfoWithHandle
{
/// <summary>
/// Gets the monitor handle.
/// </summary>
/// <value>
/// The monitor handle.
/// </value>
public IntPtr MonitorHandle { get; private set; }
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <value>
/// The monitor information.
/// </value>
public MONITORINFO MonitorInfo { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="MonitorInfoWithHandle"/> class.
/// </summary>
/// <param name="monitorHandle">The monitor handle.</param>
/// <param name="monitorInfo">The monitor information.</param>
public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo)
{
MonitorHandle = monitorHandle;
MonitorInfo = monitorInfo;
}
}
/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
{
var mi = new MONITORINFO();
mi.size = (uint)Marshal.SizeOf(mi);
GetMonitorInfo(hMonitor, ref mi);
// Add to monitor info
_monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi));
return true;
}
/// <summary>
/// Gets the monitors.
/// </summary>
/// <returns></returns>
public static MonitorInfoWithHandle[] GetMonitors()
{
_monitorInfos = new List<MonitorInfoWithHandle>();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);
return _monitorInfos.ToArray();
}
private static List<MonitorInfoWithHandle> _monitorInfos;
}
}

View File

@ -0,0 +1,14 @@
<Window x:Class="FancyZonesEditor_DPI_netcore_test.OverlayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_netcore_test"
mc:Ignorable="d"
Title="OverlayWindow" Height="450" Width="800"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent">
</Window>

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace FancyZonesEditor_DPI_netcore_test
{
/// <summary>
/// Interaction logic for OverlayWindow.xaml
/// </summary>
public partial class OverlayWindow : Window
{
public OverlayWindow()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30621.155
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor_DPI_test", "FancyZonesEditor_DPI_test\FancyZonesEditor_DPI_test.csproj", "{B1708F5E-78F8-4646-86B5-0E83E3C79860}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B1708F5E-78F8-4646-86B5-0E83E3C79860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1708F5E-78F8-4646-86B5-0E83E3C79860}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1708F5E-78F8-4646-86B5-0E83E3C79860}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1708F5E-78F8-4646-86B5-0E83E3C79860}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6C181B9-2BFF-4057-89D2-9BFDEBCC87F6}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<AppContextSwitchOverrides value = "Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>
</configuration>

View File

@ -0,0 +1,9 @@
<Application x:Class="FancyZonesEditor_DPI_test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_test"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace FancyZonesEditor_DPI_test
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B1708F5E-78F8-4646-86B5-0E83E3C79860}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>FancyZonesEditor_DPI_test</RootNamespace>
<AssemblyName>FancyZonesEditor_DPI_test</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.DpiAwareness, Version=6.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.VisualStudio.DpiAwareness.6.7.30328\lib\net46\Microsoft.VisualStudio.DpiAwareness.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="OverlayWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="MonitorInfo.cs" />
<Compile Include="OverlayWindow.xaml.cs">
<DependentUpon>OverlayWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.manifest" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,15 @@
<Window x:Class="FancyZonesEditor_DPI_test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_test"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Closing="Window_Closing">
<Grid>
<ListView x:Name="MonitorList" Margin="16">
</ListView>
</Grid>
</Window>

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using Microsoft.VisualStudio.Utilities;
namespace FancyZonesEditor_DPI_test
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
struct ScreenInfo
{
public int MonitorDPI { get; set; }
public double WindowDPI { get; set; }
public Rect Resolution { get; set; }
public Rect WorkArea { get; set; }
public override string ToString()
{
var resolution = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", Resolution.Left, Resolution.Top, Resolution.Width, Resolution.Height);
var workArea = String.Format("X:{0,5}, Y:{1,5}, W:{2,5}, H:{3,5}", WorkArea.Left, WorkArea.Top, WorkArea.Width, WorkArea.Height);
return "Monitor DPI: " + MonitorDPI + " - Resolution: " + resolution + " - WorkArea: " + workArea;
}
}
private List<OverlayWindow> workAreaWindows;
public MainWindow()
{
InitializeComponent();
double primaryMonitorDPI = 96f;
var colors = new Brush[] { Brushes.Green, Brushes.Blue, Brushes.Red };
var screens = System.Windows.Forms.Screen.AllScreens;
List<ScreenInfo> screenInfoList = new List<ScreenInfo>();
workAreaWindows = new List<OverlayWindow>();
var monitors = MonitorsInfo.GetMonitors();
for (int i = 0; i < screens.Length; i++)
{
if (screens[i].Primary)
{
double monitorDPI;
DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI);
primaryMonitorDPI = monitorDPI;
break;
}
}
for (int i = 0; i < screens.Length; i++)
{
var monitor = monitors[i];
ScreenInfo screenInfo = new ScreenInfo();
var window = new OverlayWindow
{
Opacity = 0.8,
Background = colors[i % colors.Length],
BorderBrush = Brushes.White,
BorderThickness = new Thickness(4, 4, 4, 4)
};
// get monitor dpi
double monitorDPI;
DpiAwareness.GetMonitorDpi(monitors[i].MonitorHandle, out monitorDPI, out monitorDPI);
screenInfo.MonitorDPI = (int)monitorDPI;
// screen resolution
screenInfo.Resolution = new Rect(monitor.MonitorInfo.monitor.left, monitor.MonitorInfo.monitor.top,
monitor.MonitorInfo.monitor.width, monitor.MonitorInfo.monitor.height);
// work area
Rect workedArea = new Rect(monitor.MonitorInfo.work.left, monitor.MonitorInfo.work.top,
monitor.MonitorInfo.work.width, monitor.MonitorInfo.work.height);
double scaleFactor = 96f / primaryMonitorDPI;
workedArea.X *= scaleFactor;
workedArea.Y *= scaleFactor;
workedArea.Width *= scaleFactor;
workedArea.Height *= scaleFactor;
screenInfo.WorkArea = workedArea;
screenInfo.WindowDPI = window.GetDpiX();
screenInfoList.Add(screenInfo);
// open window
window.Left = workedArea.X;
window.Top = workedArea.Y;
window.Width = workedArea.Width;
window.Height = workedArea.Height;
workAreaWindows.Add(window);
window.Show();
}
MonitorList.ItemsSource = screenInfoList;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
foreach (OverlayWindow window in workAreaWindows)
{
window.Close();
}
}
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace FancyZonesEditor_DPI_test
{
class MonitorsInfo
{
/// <summary>
/// Rectangle
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int width
{
get
{
return right - left;
}
}
public int height
{
get
{
return bottom - top;
}
}
}
/// <summary>
/// Monitor information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public uint size;
public RECT monitor;
public RECT work;
public uint flags;
}
/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor,
ref RECT lprcMonitor, IntPtr dwData);
/// <summary>
/// Enumerates through the display monitors.
/// </summary>
/// <param name="hdc">A handle to a display device context that defines the visible region of interest.</param>
/// <param name="lprcClip">A pointer to a RECT structure that specifies a clipping rectangle.</param>
/// <param name="lpfnEnum">A pointer to a MonitorEnumProc application-defined callback function.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the MonitorEnumProc function.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
MonitorEnumDelegate lpfnEnum, IntPtr dwData);
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <param name="hmon">A handle to the display monitor of interest.</param>
/// <param name="mi">A pointer to a MONITORINFO instance created by this method.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool GetMonitorInfo(IntPtr hmon, ref MONITORINFO mi);
/// <summary>
/// Monitor information with handle interface.
/// </summary>
public interface IMonitorInfoWithHandle
{
IntPtr MonitorHandle { get; }
MONITORINFO MonitorInfo { get; }
}
/// <summary>
/// Monitor information with handle.
/// </summary>
public class MonitorInfoWithHandle : IMonitorInfoWithHandle
{
/// <summary>
/// Gets the monitor handle.
/// </summary>
/// <value>
/// The monitor handle.
/// </value>
public IntPtr MonitorHandle { get; private set; }
/// <summary>
/// Gets the monitor information.
/// </summary>
/// <value>
/// The monitor information.
/// </value>
public MONITORINFO MonitorInfo { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="MonitorInfoWithHandle"/> class.
/// </summary>
/// <param name="monitorHandle">The monitor handle.</param>
/// <param name="monitorInfo">The monitor information.</param>
public MonitorInfoWithHandle(IntPtr monitorHandle, MONITORINFO monitorInfo)
{
MonitorHandle = monitorHandle;
MonitorInfo = monitorInfo;
}
}
/// <summary>
/// Monitor Enum Delegate
/// </summary>
/// <param name="hMonitor">A handle to the display monitor.</param>
/// <param name="hdcMonitor">A handle to a device context.</param>
/// <param name="lprcMonitor">A pointer to a RECT structure.</param>
/// <param name="dwData">Application-defined data that EnumDisplayMonitors passes directly to the enumeration function.</param>
/// <returns></returns>
public static bool MonitorEnum(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData)
{
var mi = new MONITORINFO();
mi.size = (uint)Marshal.SizeOf(mi);
GetMonitorInfo(hMonitor, ref mi);
// Add to monitor info
_monitorInfos.Add(new MonitorInfoWithHandle(hMonitor, mi));
return true;
}
/// <summary>
/// Gets the monitors.
/// </summary>
/// <returns></returns>
public static MonitorInfoWithHandle[] GetMonitors()
{
_monitorInfos = new List<MonitorInfoWithHandle>();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnum, IntPtr.Zero);
return _monitorInfos.ToArray();
}
private static List<MonitorInfoWithHandle> _monitorInfos;
}
}

View File

@ -0,0 +1,14 @@
<Window x:Class="FancyZonesEditor_DPI_test.OverlayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FancyZonesEditor_DPI_test"
mc:Ignorable="d"
Title="OverlayWindow" Height="450" Width="800"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
DpiChanged="Window_DpiChanged"/>

View File

@ -0,0 +1,22 @@
using System;
using System.Windows;
using System.Windows.Media;
namespace FancyZonesEditor_DPI_test
{
/// <summary>
/// Interaction logic for OverlayWindow.xaml
/// </summary>
public partial class OverlayWindow : Window
{
public OverlayWindow()
{
InitializeComponent();
}
private void Window_DpiChanged(object sender, DpiChangedEventArgs e)
{
}
}
}

View File

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FancyZonesEditor_DPI_test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FancyZonesEditor_DPI_test")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FancyZonesEditor_DPI_test.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FancyZonesEditor_DPI_test.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FancyZonesEditor_DPI_test.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">System</dpiAwareness>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.VisualStudio.DpiAwareness" version="6.7.30328" targetFramework="net472" />
</packages>