using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using DelftTools.Controls.Swf; using DelftTools.Shell.Core; using DelftTools.Shell.Gui; using DelftTools.Shell.Gui.Forms; using DelftTools.Utils; using DelftTools.Utils.Aop; using DelftTools.Utils.Collections; using DelftTools.Utils.Collections.Extensions; using DelftTools.Utils.Interop; using DelftTools.Utils.Reflection; using DeltaShell.Core; using DeltaShell.Gui.Forms.OptionsDialog; using DeltaShell.Gui.Properties; using Fluent; using log4net; using Microsoft.Win32; using Xceed.Wpf.AvalonDock; using Xceed.Wpf.AvalonDock.Controls; using Xceed.Wpf.AvalonDock.Layout; using Xceed.Wpf.AvalonDock.Layout.Serialization; using Xceed.Wpf.AvalonDock.Themes; using Application = System.Windows.Forms.Application; using Button = Fluent.Button; using Cursors = System.Windows.Input.Cursors; using IWin32Window = System.Windows.Forms.IWin32Window; using MessageBox = DelftTools.Controls.Swf.MessageBox; namespace DeltaShell.Gui.Forms.MainWindow { /// /// Main windows of Delta Shell /// public partial class MainWindow : IMainWindow, IDisposable, IWin32Window, ISynchronizeInvoke { private static readonly ILog log = LogManager.GetLogger(typeof(MainWindow)); private static Form synchronizationForm; /// /// Remember last active contextual tab per view. /// private readonly IDictionary lastActiveContextTabNamePerViewType = new Dictionary(); private bool resetDefaultLayout; private MessageWindow.MessageWindow messageWindow; private PropertyGridView.PropertyGridView propertyGrid; private WindowInteropHelper windowInteropHelper; private IEnumerable ribbonCommandHandlers; /// /// This is used when user selects non-contextual tab explicitly. Then we won't activate contextual tab on the next view activation. /// private bool activateContextualTab; /// /// Used when contextual tab was activated and we switch back to view which does not support contextual tabs. /// private string lastNonContextualTab; public MainWindow() { InitializeComponent(); windowInteropHelper = new WindowInteropHelper(this); ModalHelper.MainWindow = this; InitializeInvokeRequired(); App.RunDeltaShell(this); log.Info(Properties.Resources.MainWindow_MainWindow_Main_window_created_); } public MainWindow(DeltaShellGui gui) { DeltaShellApplication.SetLanguageAndRegionalSettions(Settings.Default); Gui = gui; InitializeComponent(); windowInteropHelper = new WindowInteropHelper(this); ModalHelper.MainWindow = this; InitializeInvokeRequired(); log.Info(Properties.Resources.MainWindow_MainWindow_Main_window_created_); } public DeltaShellGui Gui { get; set; } public bool IsWindowDisposed { get; private set; } public bool Enabled { get { return IsEnabled; } set { IsEnabled = value; NativeWin32.EnableWindow(new HandleRef(this, windowInteropHelper.Handle), value); // prevents resize etc } } public DockingManager DockingManager { get { return dockingManager; } } public IMessageWindow MessageWindow { get { return messageWindow; } } public bool Visible { get { return IsVisible; } set { if (value) { if (IsVisible) { return; } Show(); } else { if (!IsVisible) { return; } Hide(); } } } public string StatusBarMessage { get { return StatusMessageTextBlock.Text; } set { StatusMessageTextBlock.Text = value; } } public IProjectExplorer ProjectExplorer { get { return Gui.ToolWindowViews.OfType().FirstOrDefault(); } } public IPropertyGrid PropertyGrid { get { return propertyGrid; } } public bool InvokeRequired { get { return !Dispatcher.CheckAccess(); } } public IntPtr Handle { get { return windowInteropHelper.Handle; } } public void SubscribeToGui() { if (Gui != null) { Gui.ToolWindowViews.CollectionChanged += ToolWindowViews_CollectionChanged; Gui.DocumentViews.ActiveViewChanged += DocumentViewsOnActiveViewChanged; Gui.DocumentViews.ActiveViewChanging += DocumentViewsOnActiveViewChanging; } } public void UnsubscribeFromGui() { if (Gui != null) { Gui.ToolWindowViews.CollectionChanged -= ToolWindowViews_CollectionChanged; Gui.DocumentViews.ActiveViewChanged -= DocumentViewsOnActiveViewChanged; Gui.DocumentViews.ActiveViewChanging -= DocumentViewsOnActiveViewChanging; } } public void RestoreLayout() { if (Assembly.GetEntryAssembly() == null) // API, not a real exe { return; // make sure we don't mess layout } OnLoadLayout("normal"); RestoreWindowAppearance(); } public void SaveLayout() { if (Settings.Default.autosaveWindowLayout) { SaveWindowAppearance(); OnSaveLayout("normal"); } } public void InitializeToolWindows() { InitMessagesWindowOrActivate(); InitPropertiesWindowAndActivate(); } public void SuspendLayout() {} public void ResumeLayout() {} public void InitPropertiesWindowAndActivate() { if ((propertyGrid == null) || (propertyGrid.IsDisposed)) { propertyGrid = new PropertyGridView.PropertyGridView(Gui); } propertyGrid.Text = Properties.Resources.Properties; propertyGrid.Data = propertyGrid.GetObjectProperties(Gui.Selection); Gui.ToolWindowViews.Add(propertyGrid, ViewLocation.Right | ViewLocation.Bottom); Gui.ToolWindowViews.ActiveView = null; Gui.ToolWindowViews.ActiveView = Gui.MainWindow.PropertyGrid; } public void ShowStartPage(bool checkUseSettings = true) { if (!checkUseSettings || Convert.ToBoolean(Gui.Application.UserSettings["showStartPage"], CultureInfo.InvariantCulture)) { log.Info(Properties.Resources.MainWindow_ShowStartPage_Adding_welcome_page____); OpenStartPage(); } } public void ClearDocumentTabs() { foreach (var contentToClose in dockingManager.Layout.Descendents().OfType().Where(d => (d.Parent is LayoutDocumentPane || d.Parent is LayoutDocumentFloatingWindow)).ToArray()) { if (!contentToClose.CanClose) { continue; } if (contentToClose is LayoutDocument) { CloseContent(contentToClose as LayoutDocument); } else if (contentToClose is LayoutAnchorable) { CloseContent(contentToClose as LayoutAnchorable); } } foreach (var child in LayoutDocumentPaneGroup.Children.OfType()) { child.Children.Clear(); } while (LayoutDocumentPaneGroup.Children.Count != 1) { LayoutDocumentPaneGroup.Children.RemoveAt(0); } } public void Dispose() { Close(); Close(); if (IsWindowDisposed) { return; } // TODO: add dispose code IsWindowDisposed = true; if (Equals(InvokeRequiredInfo.SynchronizeObject, this)) { InvokeRequiredInfo.SynchronizeObject = null; } if (dockingManager.AutoHideWindow != null) { var m = typeof(LayoutAutoHideWindowControl).GetField("_manager", BindingFlags.Instance | BindingFlags.NonPublic); m.SetValue(dockingManager.AutoHideWindow, null); dockingManager.AutoHideWindow.Dispose(); } Content = null; if (Ribbon != null) { foreach (var tab in Ribbon.Tabs) { foreach (var group in tab.Groups) { group.Items.Clear(); } tab.Groups.Clear(); } Ribbon.Tabs.Clear(); Ribbon = null; } dockingManager = null; if (propertyGrid != null) { propertyGrid.Dispose(); propertyGrid = null; } if (messageWindow != null) { messageWindow.OnError -= MessageWindowOnError; messageWindow.Dispose(); messageWindow = null; } // I pulled this code from some internet sources combined with the reflector to remove a well-known leak var handlers = typeof(SystemEvents).GetField("_handlers", BindingFlags.Static | BindingFlags.NonPublic) .GetValue(null); var upcHandler = typeof(SystemEvents).GetField("OnUserPreferenceChangedEvent", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); var eventLockObject = typeof(SystemEvents).GetField("eventLockObject", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); lock (eventLockObject) { var upcHandlerList = (IList) ((IDictionary) handlers)[upcHandler]; for (int i = upcHandlerList.Count - 1; i >= 0; i--) { var target = (Delegate) upcHandlerList[i].GetType().GetField("_delegate", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(upcHandlerList[i]); upcHandlerList.RemoveAt(i); } } ribbonCommandHandlers = null; windowInteropHelper = null; Gui = null; // Dispatcher.InvokeShutdown(); //System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown(); } public void ValidateItems() { if (Gui == null) { return; } ValidateMainWindowRibbonItems(); if (ribbonCommandHandlers == null) { return; } // activate all context-specific groups (ValidateItems implementations should activate them) foreach (var tabGroup in Ribbon.ContextualGroups) { var showGroup = false; foreach (var tabItem in tabGroup.Items) { var tabItemVisible = ribbonCommandHandlers.Any(h => h.IsContextualTabVisible(tabGroup.Name, tabItem.Name)); tabItem.Visibility = tabItemVisible ? Visibility.Visible : Visibility.Collapsed; if (tabItemVisible && !showGroup) { showGroup = true; } } tabGroup.Visibility = showGroup ? Visibility.Visible : Visibility.Collapsed; } foreach (var ribbonCommandHandler in ribbonCommandHandlers) { ribbonCommandHandler.ValidateItems(); } foreach (var ribbonGroupBox in Ribbon.Tabs.SelectMany(tab => tab.Groups)) { // Colapse all groups without visible items ribbonGroupBox.Visibility = ribbonGroupBox.Items.OfType().All(e => e.Visibility == Visibility.Collapsed || e is Separator) ? Visibility.Collapsed : Visibility.Visible; } } public void SetWaitCursorOn() { Mouse.OverrideCursor = Cursors.Wait; } public void SetWaitCursorOff() { Mouse.OverrideCursor = null; } public IAsyncResult BeginInvoke(Delegate method, object[] args) { Dispatcher.BeginInvoke(method, args); return null; } public object EndInvoke(IAsyncResult result) { throw new NotImplementedException(); } public object Invoke(Delegate method, object[] args) { return Dispatcher.Invoke(method, args); } private void ToolWindowViews_CollectionChanged(object sender, NotifyCollectionChangingEventArgs e) { if (e.Action != NotifyCollectionChangeAction.Remove) { return; } if (e.Item == propertyGrid) { propertyGrid = null; } if (e.Item == MessageWindow) { messageWindow = null; } } private void DocumentViewsOnActiveViewChanging(object sender, ActiveViewChangeEventArgs e) { if (Ribbon.SelectedTabItem != null && !Ribbon.SelectedTabItem.IsContextual) { lastNonContextualTab = Ribbon.SelectedTabItem.Header.ToString(); } // remember active contextual tab per view type, when view is activated back - activate contextual item if (Ribbon.SelectedTabItem != null && e.OldView != null) { if (Ribbon.SelectedTabItem.IsContextual) { lastActiveContextTabNamePerViewType[e.OldView.GetType()] = Ribbon.SelectedTabItem.Header.ToString(); activateContextualTab = true; } else { // user has clicked on non-contextual tab before switching active view if (lastActiveContextTabNamePerViewType.ContainsKey(e.OldView.GetType())) { activateContextualTab = false; } } } else { activateContextualTab = true; } } private void DocumentViewsOnActiveViewChanged(object sender, ActiveViewChangeEventArgs e) { // activate contextual tab which was active for this view type if (activateContextualTab && Ribbon.SelectedTabItem != null && e.View != null && Ribbon.Tabs.Any(t => t.IsContextual && t.Visibility == Visibility.Visible)) { string lastActiveTabForActiveView; if (lastActiveContextTabNamePerViewType.TryGetValue(e.View.GetType(), out lastActiveTabForActiveView)) { var tab = Ribbon.Tabs.First(t => t.Header.ToString() == lastActiveTabForActiveView); if (tab.IsVisible) { Ribbon.SelectedTabItem = tab; } } else // activate first contextual group tab { var tab = Ribbon.Tabs.FirstOrDefault(t => t.IsContextual && t.Visibility == Visibility.Visible); Ribbon.SelectedTabItem = tab; } } else if (!string.IsNullOrEmpty(lastNonContextualTab)) // reactivate last non-contextual tab { var tab = Ribbon.Tabs.First(t => t.Header.ToString() == lastNonContextualTab); Ribbon.SelectedTabItem = tab; } } private void InitializeInvokeRequired() { if (Assembly.GetEntryAssembly() != null) // HACK: when assembly is non-empty - we run from real exe (not test) { if (InvokeRequiredInfo.SynchronizeObject == null) { InvokeRequiredInfo.SynchronizeObject = this; InvokeRequiredInfo.WaitMethod = Application.DoEvents; } return; // uses MainWindow } // use static form for synchronization if (synchronizationForm == null) { synchronizationForm = new Form { ShowInTaskbar = false, WindowState = FormWindowState.Minimized }; var handle = synchronizationForm.Handle; //force get handle synchronizationForm.Show(); } if (InvokeRequiredInfo.SynchronizeObject == null) { InvokeRequiredInfo.SynchronizeObject = synchronizationForm; InvokeRequiredInfo.WaitMethod = Application.DoEvents; } } private void AddRecentlyOpenedProjectsToFileMenu() { var mruList = Settings.Default["mruList"] as StringCollection; foreach (var recent in mruList) { AddNewMruItem(recent, false); } } private void AddNewMruItem(string path, bool putOnTop = true) { var newItem = new TabItem { Header = path }; newItem.MouseDoubleClick += (sender, args) => { try { Gui.CommandHandler.TryOpenExistingWTIProject(path); RecentProjectsTabControl.Items.Remove(newItem); RecentProjectsTabControl.Items.Insert(1, newItem); } catch (Exception) { //remove item from the list if it cannot be retrieved from file RecentProjectsTabControl.Items.Remove(newItem); log.WarnFormat("{0} {1}", Properties.Resources.MainWindow_AddNewMruItem_Can_t_open_project, path); } finally { CommitMruToSettings(); ValidateItems(); Menu.IsOpen = false; } }; if (RecentProjectsTabControl.Items.OfType().Any(i => Equals(i.Header, path))) { return; } if (putOnTop) { RecentProjectsTabControl.Items.Insert(1, newItem); } else { RecentProjectsTabControl.Items.Add(newItem); } } private void CommitMruToSettings() { var mruList = (StringCollection) Settings.Default["mruList"]; mruList.Clear(); foreach (TabItem item in RecentProjectsTabControl.Items) { if (item is SeparatorTabItem) //header { continue; } mruList.Add(item.Header.ToString()); } } private void OnLayoutRootPropertyChanged(object sender, PropertyChangedEventArgs e) {} private void ValidateMainWindowRibbonItems() { if (Gui.ToolWindowViews != null) { ButtonShowMessages.IsChecked = Gui.ToolWindowViews.Contains(MessageWindow); ButtonShowProperties.IsChecked = Gui.ToolWindowViews.Contains(PropertyGrid); } var appHasProject = (Gui.Application.Project != null); var isActivityRunning = Gui.Application.IsActivityRunning(); // filemenu items dependent on existence of project and if processes are running ButtonMenuFileNewProject.IsEnabled = !isActivityRunning; ButtonMenuFileOpenProject.IsEnabled = !isActivityRunning; ButtonMenuFileSaveProject.IsEnabled = appHasProject && !isActivityRunning; ButtonMenuFileSaveProjectAs.IsEnabled = appHasProject && !isActivityRunning; ButtonMenuFileCloseProject.IsEnabled = appHasProject && !isActivityRunning; } private void InitMessagesWindowOrActivate() { if (messageWindow == null || messageWindow.IsDisposed) { if (messageWindow != null && messageWindow.IsDisposed) { messageWindow.OnError -= MessageWindowOnError; } messageWindow = new MessageWindow.MessageWindow { Text = Properties.Resources.Messages }; messageWindow.OnError += MessageWindowOnError; } if (Gui == null || Gui.ToolWindowViews == null) { return; } if (Gui.ToolWindowViews.Contains(messageWindow)) { Gui.ToolWindowViews.ActiveView = messageWindow; return; } Gui.ToolWindowViews.Add(messageWindow, ViewLocation.Bottom); messageWindow.Visible = true; //doesn't always work (eg, remains false) } [InvokeRequired] private void MessageWindowOnError(object sender, EventArgs e) { // activates messageWindow when error occurs InitMessagesWindowOrActivate(); } private void OnFileSaveClicked(object sender, RoutedEventArgs e) { MessageBox.Show("Not implemented yet."); return; // Original code: //var saveProject = Gui.CommandHandler.SaveProject(); //OnAfterProjectSaveOrOpen(saveProject); } private void OnFileSaveAsClicked(object sender, RoutedEventArgs e) { MessageBox.Show("Not implemented yet."); return; // Original code: //var saveProject = Gui.CommandHandler.SaveProjectAs(); //OnAfterProjectSaveOrOpen(saveProject); } private void OnAfterProjectSaveOrOpen(bool actionSuccesful) { if (actionSuccesful) { AddNewMruItem(Gui.Application.ProjectFilePath); CommitMruToSettings(); } ValidateItems(); } private void OnFileOpenClicked(object sender, RoutedEventArgs e) { MessageBox.Show("Not implemented yet."); return; // Original code: //var succesful = Gui.CommandHandler.TryOpenExistingWTIProject(); //OnAfterProjectSaveOrOpen(succesful); } private void OnFileCloseClicked(object sender, RoutedEventArgs e) { Gui.CommandHandler.TryCloseWTIProject(); ValidateItems(); } private void OnFileNewClicked(object sender, RoutedEventArgs e) { Gui.CommandHandler.TryCreateNewWTIProject(); ValidateItems(); } private void OnFileExitClicked(object sender, RoutedEventArgs e) { Gui.Exit(); } private string GetLayoutFilePath(string perspective) { string localUserSettingsDirectory = Gui.Application.GetUserSettingsDirectoryPath(); string layoutFileName = GetLayoutFileName(perspective); return Path.Combine(localUserSettingsDirectory, layoutFileName); } private static string GetLayoutFileName(string perspective) { return "WindowLayout_" + perspective + ".xml"; } private void OnLoadLayout(string perspective) { string layoutFilePath = GetLayoutFilePath(perspective); if (!File.Exists(layoutFilePath)) { // try to load the default file directly from the current directory string layoutFileName = GetLayoutFileName(perspective); UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase); string path = Uri.UnescapeDataString(uri.Path); string assemblyDir = Path.GetDirectoryName(path); layoutFilePath = Path.Combine(assemblyDir, layoutFileName); if (!File.Exists(layoutFilePath)) { return; } } var layoutSerializer = new XmlLayoutSerializer(dockingManager); //Here I've implemented the LayoutSerializationCallback just to show // a way to feed layout desarialization with content loaded at runtime //Actually I could in this case let AvalonDock to attach the contents //from current layout using the content ids //LayoutSerializationCallback should anyway be handled to attach contents //not currently loaded layoutSerializer.LayoutSerializationCallback += (s, e) => { var c = e.Content; /* if (e.Model.ContentId == FileStatsViewModel.ToolContentId) e.Content = Workspace.This.FileStats; else if (!string.IsNullOrWhiteSpace(e.Model.ContentId) && File.Exists(e.Model.ContentId)) e.Content = Workspace.This.Open(e.Model.ContentId); */ }; try { layoutSerializer.Deserialize(layoutFilePath); } catch (InvalidOperationException) { // the xml was not valid. Too bad. log.Warn(Properties.Resources.MainWindow_OnLoadLayout_Could_not_load_the_requested_dock_layout__The_settings_are_invalid_and_will_be_reset_to_the_default_state_); return; } // load ribbon state var ribbonSettingsPath = layoutFilePath + ".ribbon"; if (!File.Exists(ribbonSettingsPath)) { return; } try { var settings = File.ReadAllText(ribbonSettingsPath).Split('|'); if (settings.Length != 2) { return; } var ribbonStateSettings = settings[0].Split(','); Ribbon.IsMinimized = bool.Parse(ribbonStateSettings[0]); Ribbon.ShowQuickAccessToolBarAboveRibbon = bool.Parse(ribbonStateSettings[1]); // disabled LoadState because the ribbon elements are not correctly detected during Ribbon.LoadState, // resulting in disappearing quickaccess items //Ribbon.LoadState(ribbonStream); } catch (Exception) { log.Warn(Properties.Resources.MainWindow_OnLoadLayout_Could_not_restore_the_ribbon_state__The_settings_are_invalid_and_will_be_reset_to_the_default_state_); } } private void OnSaveLayout(string perspective) { string layoutFilePath = GetLayoutFilePath(perspective); var ribbonLayoutFilePath = layoutFilePath + ".ribbon"; if (resetDefaultLayout) { if (File.Exists(layoutFilePath)) { File.Delete(layoutFilePath); } if (File.Exists(ribbonLayoutFilePath)) { File.Delete(ribbonLayoutFilePath); } return; // Do not save when resetting } var layoutSerializer = new XmlLayoutSerializer(dockingManager); layoutSerializer.Serialize(layoutFilePath); var ribbonStream = new FileStream(ribbonLayoutFilePath, FileMode.Create); Ribbon.SaveState(ribbonStream); } private void RestoreWindowAppearance() { WindowStartupLocation = WindowStartupLocation.Manual; var x = Settings.Default.MainWindow_X; var y = Settings.Default.MainWindow_Y; var width = Settings.Default.MainWindow_Width; var height = Settings.Default.MainWindow_Height; var rec = new Rectangle(x, y, width, height); if (!IsVisibleOnAnyScreen(rec)) { width = Screen.PrimaryScreen.Bounds.Width - 200; height = Screen.PrimaryScreen.Bounds.Height - 200; } var fs = Settings.Default.MainWindow_FullScreen; Width = width; Height = height; if (fs) { WindowState = WindowState.Maximized; } } private bool IsVisibleOnAnyScreen(Rectangle rect) { foreach (Screen screen in Screen.AllScreens) { if (screen.WorkingArea.IntersectsWith(rect)) { return true; } } return false; } private void SaveWindowAppearance() { if (WindowState == WindowState.Maximized) { Settings.Default.MainWindow_FullScreen = true; } else { Settings.Default.MainWindow_Width = (int) Width; Settings.Default.MainWindow_Height = (int) Height; Settings.Default.MainWindow_FullScreen = false; } Settings.Default.Save(); } private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { AddRecentlyOpenedProjectsToFileMenu(); SetColorTheme((string) Gui.Application.UserSettings["colorTheme"]); FileManualButton.IsEnabled = File.Exists(ConfigurationManager.AppSettings["manualFileName"]); // Enable as soon as relevant/implemented AboutButton.IsEnabled = false; LicenseButton.IsEnabled = false; FeedbackButton.IsEnabled = false; UpdateMainWindowRibbonElements(); UpdateRibbonExtensions(); ValidateItems(); } private void UpdateMainWindowRibbonElements() { resetUIButton.ToolTip = new ScreenTip { Title = Properties.Resources.MainWindow_UpdateMainWindowRibbonElements_Reset_layout__restart_, Text = Properties.Resources.MainWindow_UpdateMainWindowRibbonElements_When_this_option_is_turned_on__the_default_layout_will_be_used_when_restarting_DeltaShell_, MaxWidth = 250 }; } private void UpdateRibbonExtensions() { // get all ribbon controls ribbonCommandHandlers = Gui.Plugins.Where(p => p.RibbonCommandHandler != null).OrderBy(p => p.Name).Select(p => p.RibbonCommandHandler).ToArray(); foreach (var ribbonExtension in ribbonCommandHandlers) { var ribbonControl = (Ribbon) ribbonExtension.GetRibbonControl(); // fill contextual groups from plugins foreach (var group in ribbonControl.ContextualGroups) { if (Ribbon.ContextualGroups.Any(g => g.Name == group.Name)) { continue; } Ribbon.ContextualGroups.Add(group); } // fill tabs from plugins foreach (var tab in ribbonControl.Tabs) { var existingTab = Ribbon.Tabs.FirstOrDefault(t => t.Header.Equals(tab.Header)); if (existingTab == null) // add new tab { Ribbon.Tabs.Add(tab); existingTab = tab; } else { if (!string.IsNullOrEmpty(tab.ReduceOrder)) { existingTab.ReduceOrder += "," + tab.ReduceOrder; // Naive implementation; Can cause duplicates to occur } foreach (var group in tab.Groups) { var existingGroup = existingTab.Groups.FirstOrDefault(g => g.Header.Equals(group.Header)); if (existingGroup == null) // add new group { var newGroup = new RibbonGroupBox { Header = group.Header, Name = group.Name // Ensure ReduceOrder is working properly }; // Set KeyTip for keyboard navigation: var keys = KeyTip.GetKeys(group); if (!string.IsNullOrEmpty(keys)) { KeyTip.SetKeys(newGroup, keys); } existingTab.Groups.Add(newGroup); existingGroup = newGroup; } // Set group icon if not yet set: if (existingGroup.Icon == null && group.Icon != null) { existingGroup.Icon = group.Icon; } foreach (var item in group.Items.Cast().ToArray()) { // HACK: remember and restore button size (looks like a bug in Fluent) var iconSize = RibbonControlSize.Small; if (item is Button) { var button = (Button) item; iconSize = button.Size; } group.Items.Remove(item); existingGroup.Items.Add(item); if (item is Button) { var button = existingGroup.Items.OfType