From c6697c5b53d335d1484c296f425a77438fd1fc97 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Tue, 3 Sep 2024 09:30:45 +0200 Subject: [PATCH] Refactor window menu composition: simplify by using WPF patterns. --- ILSpy/Docking/PaneCollection.cs | 21 ++- ILSpy/Util/MenuService.cs | 275 +++++++++----------------------- 2 files changed, 89 insertions(+), 207 deletions(-) diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs index 72b1b851be..c0a2c5a913 100644 --- a/ILSpy/Docking/PaneCollection.cs +++ b/ILSpy/Docking/PaneCollection.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -25,16 +26,14 @@ namespace ICSharpCode.ILSpy.Docking { - public class PaneCollection : INotifyCollectionChanged, ICollection + public class PaneCollection : INotifyCollectionChanged, IList where T : PaneModel, new() { - private ObservableCollection observableCollection = new ObservableCollection(); + private readonly ObservableCollection observableCollection = []; - public event NotifyCollectionChangedEventHandler CollectionChanged; - - public PaneCollection() - { - observableCollection.CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e); + public event NotifyCollectionChangedEventHandler CollectionChanged { + add => observableCollection.CollectionChanged += value; + remove => observableCollection.CollectionChanged -= value; } public void Add(T item = null) @@ -46,7 +45,6 @@ public void Add(T item = null) item.IsVisible = true; item.IsActive = true; } - public int Count => observableCollection.Count; public bool IsReadOnly => false; public void Clear() => observableCollection.Clear(); @@ -55,5 +53,12 @@ public void Add(T item = null) public bool Remove(T item) => observableCollection.Remove(item); public IEnumerator GetEnumerator() => observableCollection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); + int IList.IndexOf(T item) => observableCollection.IndexOf(item); + void IList.Insert(int index, T item) => throw new NotImplementedException("Only Add is supported"); + void IList.RemoveAt(int index) => observableCollection.RemoveAt(index); + T IList.this[int index] { + get => observableCollection[index]; + set => observableCollection[index] = value; + } } } diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs index 414787f951..fafb500b53 100644 --- a/ILSpy/Util/MenuService.cs +++ b/ILSpy/Util/MenuService.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; @@ -14,6 +12,8 @@ using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Composition; +using TomsToolbox.ObservableCollections; +using TomsToolbox.Wpf.Converters; namespace ICSharpCode.ILSpy.Util { @@ -111,193 +111,18 @@ public void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) { var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - var separatorBeforeTools = new Separator(); - var separatorBeforeDocuments = new Separator(); + var defaultItems = windowMenuItem.Items.Cast().ToArray(); - windowMenuItem.Items.Add(separatorBeforeTools); - windowMenuItem.Items.Add(separatorBeforeDocuments); + windowMenuItem.Items.Clear(); var dock = DockWorkspace.Instance; - dock.ToolPanes.CollectionChanged += ToolsChanged; - dock.TabPages.CollectionChanged += TabsChanged; - MessageBus.Subscribers += ActiveTabPageChanged; + var toolItems = dock.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); + var tabItems = dock.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dock)); - ToolsChanged(dock.ToolPanes, new(NotifyCollectionChangedAction.Reset)); - TabsChanged(dock.TabPages, new(NotifyCollectionChangedAction.Reset)); + var allItems = new ObservableCompositeCollection(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); - void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (ToolPaneModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (ToolPaneModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[0]; - item.Tag = null; - windowMenuItem.Items.RemoveAt(i); - endIndex--; - } - insertionIndex = endIndex; - foreach (ToolPaneModel pane in dock.ToolPanes) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(ToolPaneModel pane) - { - MenuItem menuItem = new(); - menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); - menuItem.Header = pane.Title; - menuItem.Tag = pane; - var shortcutKey = pane.ShortcutKey; - if (shortcutKey != null) - { - inputBindings.Add(new(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); - } - if (!string.IsNullOrEmpty(pane.Icon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(pane, pane.Icon) - }; - } - - return menuItem; - } - } - - void TabsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.Count; - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (TabPageModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (TabPageModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - pane.PropertyChanged -= TabPageChanged; - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - windowMenuItem.Items.RemoveAt(i); - ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; - endIndex--; - } - insertionIndex = endIndex; - foreach (TabPageModel pane in dock.TabPages) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(TabPageModel pane) - { - MenuItem menuItem = new(); - menuItem.Command = new TabPageCommand(pane); - menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; - menuItem.Tag = pane; - menuItem.IsCheckable = true; - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - - return menuItem; - } - } - - void TabPageChanged(object sender, PropertyChangedEventArgs e) - { - var windowMenu = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenu.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag == sender) - { - string title = ((TabPageModel)sender).Title; - menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; - } - } - } - - void ActiveTabPageChanged(object sender, EventArgs e) - { - foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) - { - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - } - } - } + windowMenuItem.ItemsSource = allItems; } public void InitToolbar(ToolBar toolBar) @@ -311,7 +136,7 @@ public void InitToolbar(ToolBar toolBar) { foreach (var command in commandGroup) { - toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command)); openPos++; } } @@ -319,7 +144,7 @@ public void InitToolbar(ToolBar toolBar) { foreach (var command in commandGroup) { - toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(openPos++, CreateToolbarItem(command)); } } else @@ -327,33 +152,85 @@ public void InitToolbar(ToolBar toolBar) toolBar.Items.Add(new Separator()); foreach (var command in commandGroup) { - toolBar.Items.Add(MakeToolbarItem(command)); + toolBar.Items.Add(CreateToolbarItem(command)); } } } } - Button MakeToolbarItem(IExport command) + public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) + { + InitMainMenu(mainMenu); + InitWindowMenu(mainMenu, inputBindings); + InitToolbar(toolBar); + } + + static object CreateMenuItem(TabPageModel pane, DockWorkspace dock) + { + var header = new TextBlock { + MaxWidth = 200, + TextTrimming = TextTrimming.CharacterEllipsis + }; + + header.SetBinding(TextBlock.TextProperty, new Binding(nameof(pane.Title)) { + Source = pane + }); + + MenuItem menuItem = new() { + Command = new TabPageCommand(pane), + Header = header, + Tag = pane, + IsCheckable = true + }; + + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) { + Source = dock, + ConverterParameter = pane, + Converter = BinaryOperationConverter.Equality + }); + + return menuItem; + } + + static object CreateMenuItem(ToolPaneModel pane, InputBindingCollection inputBindings) + { + MenuItem menuItem = new() { + Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId), + Header = pane.Title, + Tag = pane + }; + var shortcutKey = pane.ShortcutKey; + if (shortcutKey != null) + { + inputBindings.Add(new(menuItem.Command, shortcutKey)); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); + } + if (!string.IsNullOrEmpty(pane.Icon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(pane, pane.Icon) + }; + } + + return menuItem; + } + + static Button CreateToolbarItem(IExport command) { return new() { Style = ThemeManager.Current.CreateToolBarButtonStyle(), Command = CommandWrapper.Unwrap(command.Value), - ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), - Tag = command.Metadata.Tag, + ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata?.ToolTip), + Tag = command.Metadata?.Tag, Content = new Image { Width = 16, Height = 16, - Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) + Source = Images.Load(command.Value, command.Metadata?.ToolbarIcon) } }; } - - public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) - { - InitMainMenu(mainMenu); - InitWindowMenu(mainMenu, inputBindings); - InitToolbar(toolBar); - } } }