Add template deletion functionality to FancyZones Editor

- Created LayoutTypeDeletableToVisibilityConverter to show delete button for templates (except Blank)
- Updated MainWindow.xaml to use new converter for delete buttons and menu items
- Modified LayoutModel.Delete() to handle both custom and template layout deletion
- Changed TemplateModels from IList to ObservableCollection for UI updates
- Updated all index-based TemplateModels access to use LINQ queries
- Enhanced DefaultLayoutsModel.Reset() to handle missing templates gracefully
- Added fallback logic when templates are deleted

Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-10-25 06:56:07 +00:00
parent 5723f6b998
commit 69a554cb79
6 changed files with 106 additions and 42 deletions

View File

@@ -0,0 +1,28 @@
// 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.Globalization;
using System.Windows;
using System.Windows.Data;
using FancyZonesEditor.Models;
namespace FancyZonesEditor.Converters
{
public class LayoutTypeDeletableToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Allow deletion for custom layouts and all template layouts except Blank
LayoutType type = (LayoutType)value;
return (type == LayoutType.Custom || (type != LayoutType.Blank && type != LayoutType.Custom)) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}

View File

@@ -33,6 +33,7 @@
<Converters:LayoutTypeCustomToVisibilityConverter x:Key="LayoutTypeCustomToVisibilityConverter" />
<Converters:LayoutTypeTemplateToVisibilityConverter x:Key="LayoutTypeTemplateToVisibilityConverter" />
<Converters:LayoutModelTypeBlankToVisibilityConverter x:Key="LayoutModelTypeBlankToVisibilityConverter" />
<Converters:LayoutTypeDeletableToVisibilityConverter x:Key="LayoutTypeDeletableToVisibilityConverter" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<ContextMenu x:Key="LayoutContextMenu" Visibility="{Binding Path=Type, Converter={StaticResource LayoutModelTypeBlankToVisibilityConverter}}">
@@ -68,11 +69,11 @@
<ui:FontIcon Glyph="&#xE8C8;" />
</MenuItem.Icon>
</MenuItem>
<Separator Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeCustomToVisibilityConverter}}" />
<Separator Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeDeletableToVisibilityConverter}}" />
<MenuItem
Click="DeleteLayout_Click"
Header="{x:Static props:Resources.Delete}"
Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeCustomToVisibilityConverter}}">
Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeDeletableToVisibilityConverter}}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xE74D;" />
</MenuItem.Icon>
@@ -437,7 +438,7 @@
Click="DeleteLayout_Click"
Style="{StaticResource IconOnlyButtonStyle}"
ToolTip="{x:Static props:Resources.Delete}"
Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeCustomToVisibilityConverter}}">
Visibility="{Binding Path=Type, Converter={StaticResource LayoutTypeDeletableToVisibilityConverter}}">
<Button.Content>
<TextBlock
AutomationProperties.Name="{x:Static props:Resources.Delete}"

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace FancyZonesEditor.Models
@@ -23,27 +24,39 @@ namespace FancyZonesEditor.Models
public void Reset(MonitorConfigurationType type)
{
LayoutModel defaultLayout = null;
switch (type)
{
case MonitorConfigurationType.Horizontal:
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.PriorityGrid], type);
// Try to get PriorityGrid, fallback to first available template or Blank
defaultLayout = MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == LayoutType.PriorityGrid)
?? MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type != LayoutType.Blank)
?? MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == LayoutType.Blank);
break;
case MonitorConfigurationType.Vertical:
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.Rows], type);
// Try to get Rows, fallback to first available template or Blank
defaultLayout = MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == LayoutType.Rows)
?? MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type != LayoutType.Blank)
?? MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == LayoutType.Blank);
break;
}
if (defaultLayout != null)
{
Set(defaultLayout, type);
}
}
public void Reset(string uuid)
{
if (Layouts[MonitorConfigurationType.Horizontal].Uuid == uuid)
{
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.PriorityGrid], MonitorConfigurationType.Horizontal);
Reset(MonitorConfigurationType.Horizontal);
}
if (Layouts[MonitorConfigurationType.Vertical].Uuid == uuid)
{
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.Rows], MonitorConfigurationType.Vertical);
Reset(MonitorConfigurationType.Vertical);
}
}

View File

@@ -335,7 +335,7 @@ namespace FancyZonesEditor.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Removes this Layout from the registry and the loaded CustomModels list
// Removes this Layout from the registry and the loaded CustomModels list or TemplateModels list
public void Delete()
{
var customModels = MainWindowSettingsModel.CustomModels;
@@ -353,6 +353,16 @@ namespace FancyZonesEditor.Models
{
customModels.RemoveAt(i);
}
else
{
// Try to remove from template models if it's a template layout
var templateModels = MainWindowSettingsModel.TemplateModels;
i = templateModels.IndexOf(this);
if (i != -1)
{
templateModels.RemoveAt(i);
}
}
}
public void RestoreTo(LayoutModel layout)

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using FancyZonesEditor.Models;
@@ -51,11 +52,11 @@ namespace FancyZonesEditor
TemplateZoneCount = 0,
SensitivityRadius = 0,
};
TemplateModels.Insert((int)LayoutType.Blank, blankModel);
TemplateModels.Add(blankModel);
var focusModel = new CanvasLayoutModel(Properties.Resources.Template_Layout_Focus, LayoutType.Focus);
focusModel.InitTemplateZones();
TemplateModels.Insert((int)LayoutType.Focus, focusModel);
TemplateModels.Add(focusModel);
var columnsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Columns, LayoutType.Columns)
{
@@ -63,7 +64,7 @@ namespace FancyZonesEditor
RowPercents = new List<int>(1) { GridLayoutModel.GridMultiplier },
};
columnsModel.InitTemplateZones();
TemplateModels.Insert((int)LayoutType.Columns, columnsModel);
TemplateModels.Add(columnsModel);
var rowsModel = new GridLayoutModel(Properties.Resources.Template_Layout_Rows, LayoutType.Rows)
{
@@ -71,15 +72,15 @@ namespace FancyZonesEditor
ColumnPercents = new List<int>(1) { GridLayoutModel.GridMultiplier },
};
rowsModel.InitTemplateZones();
TemplateModels.Insert((int)LayoutType.Rows, rowsModel);
TemplateModels.Add(rowsModel);
var gridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Grid, LayoutType.Grid);
gridModel.InitTemplateZones();
TemplateModels.Insert((int)LayoutType.Grid, gridModel);
TemplateModels.Add(gridModel);
var priorityGridModel = new GridLayoutModel(Properties.Resources.Template_Layout_Priority_Grid, LayoutType.PriorityGrid);
priorityGridModel.InitTemplateZones();
TemplateModels.Insert((int)LayoutType.PriorityGrid, priorityGridModel);
TemplateModels.Add(priorityGridModel);
// set default layouts
DefaultLayouts.Set(rowsModel, MonitorConfigurationType.Vertical);
@@ -130,11 +131,11 @@ namespace FancyZonesEditor
{
get
{
return TemplateModels[(int)LayoutType.Blank];
return TemplateModels.FirstOrDefault(m => m.Type == LayoutType.Blank);
}
}
public static IList<LayoutModel> TemplateModels { get; } = new List<LayoutModel>(6);
public static ObservableCollection<LayoutModel> TemplateModels { get; } = new ObservableCollection<LayoutModel>();
public static ObservableCollection<LayoutModel> CustomModels
{

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Windows;
@@ -648,15 +649,18 @@ namespace FancyZonesEditor.Utils
// replace deleted layout with the Blank layout
if (!existingLayout)
{
LayoutModel blankLayout = MainWindowSettingsModel.TemplateModels[(int)LayoutType.Blank];
settings.ZonesetUuid = blankLayout.Uuid;
settings.Type = blankLayout.Type;
settings.ZoneCount = blankLayout.TemplateZoneCount;
settings.SensitivityRadius = blankLayout.SensitivityRadius;
LayoutModel blankLayout = MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == LayoutType.Blank);
if (blankLayout != null)
{
settings.ZonesetUuid = blankLayout.Uuid;
settings.Type = blankLayout.Type;
settings.ZoneCount = blankLayout.TemplateZoneCount;
settings.SensitivityRadius = blankLayout.SensitivityRadius;
// grid layout settings, just resetting them
settings.ShowSpacing = false;
settings.Spacing = 0;
// grid layout settings, just resetting them
settings.ShowSpacing = false;
settings.Spacing = 0;
}
}
bool unused = true;
@@ -748,18 +752,21 @@ namespace FancyZonesEditor.Utils
foreach (var wrapper in templateLayouts)
{
LayoutType type = JsonTagToLayoutType(wrapper.Type);
LayoutModel layout = MainWindowSettingsModel.TemplateModels[(int)type];
LayoutModel layout = MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == type);
layout.SensitivityRadius = wrapper.SensitivityRadius;
layout.TemplateZoneCount = wrapper.ZoneCount;
if (layout is GridLayoutModel grid)
if (layout != null)
{
grid.ShowSpacing = wrapper.ShowSpacing;
grid.Spacing = wrapper.Spacing;
}
layout.SensitivityRadius = wrapper.SensitivityRadius;
layout.TemplateZoneCount = wrapper.ZoneCount;
layout.InitTemplateZones();
if (layout is GridLayoutModel grid)
{
grid.ShowSpacing = wrapper.ShowSpacing;
grid.Spacing = wrapper.Spacing;
}
layout.InitTemplateZones();
}
}
return true;
@@ -807,17 +814,21 @@ namespace FancyZonesEditor.Utils
else
{
LayoutType layoutType = JsonTagToLayoutType(layout.Layout.Type);
defaultLayoutModel = MainWindowSettingsModel.TemplateModels[(int)layoutType];
defaultLayoutModel.TemplateZoneCount = layout.Layout.ZoneCount;
defaultLayoutModel.SensitivityRadius = layout.Layout.SensitivityRadius;
if (defaultLayoutModel is GridLayoutModel gridDefaultLayoutModel)
defaultLayoutModel = MainWindowSettingsModel.TemplateModels.FirstOrDefault(m => m.Type == layoutType);
if (defaultLayoutModel != null)
{
gridDefaultLayoutModel.ShowSpacing = layout.Layout.ShowSpacing;
gridDefaultLayoutModel.Spacing = layout.Layout.Spacing;
}
defaultLayoutModel.TemplateZoneCount = layout.Layout.ZoneCount;
defaultLayoutModel.SensitivityRadius = layout.Layout.SensitivityRadius;
MainWindowSettingsModel.DefaultLayouts.Set(defaultLayoutModel, type);
if (defaultLayoutModel is GridLayoutModel gridDefaultLayoutModel)
{
gridDefaultLayoutModel.ShowSpacing = layout.Layout.ShowSpacing;
gridDefaultLayoutModel.Spacing = layout.Layout.Spacing;
}
MainWindowSettingsModel.DefaultLayouts.Set(defaultLayoutModel, type);
}
}
if (defaultLayoutModel != null)