/// <summary> /// When items are added to the tabs collection, we need to ensure that the <see cref="_parentWindow" />'s minimum width is set so that we can display at /// least each tab and its close buttons. /// </summary> /// <param name="sender">List of tabs in the <see cref="_parentWindow" />.</param> /// <param name="e">Arguments associated with the event.</param> private void Tabs_CollectionModified(object sender, NotifyCollectionChangedEventArgs e) { ListWithEvents <TitleBarTabItem> tabs = (ListWithEvents <TitleBarTabItem>)sender; if (tabs.Count == 0) { return; } int minimumWidth = tabs.Sum( tab => (tab.Active ? _activeLeftSideImage.Width : _inactiveLeftSideImage.Width) + (tab.Active ? _activeRightSideImage.Width : _inactiveRightSideImage.Width) + (tab.ShowCloseButton ? tab.CloseButtonArea.Width + CloseButtonMarginLeft : 0)); minimumWidth += OverlapWidth; minimumWidth += (_parentWindow.ControlBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MinimizeBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MaximizeBox ? SystemInformation.CaptionButtonSize.Width : 0) + (ShowAddButton ? _addButtonImage.Width + AddButtonMarginLeft + AddButtonMarginRight : 0); _parentWindow.MinimumSize = new Size(minimumWidth, 0); }
/// <summary>Renders the list of <paramref name="tabs" /> to the screen using the given <paramref name="graphicsContext" />.</summary> /// <param name="tabs">List of tabs that we are to render.</param> /// <param name="graphicsContext">Graphics context that we should use while rendering.</param> /// <param name="cursor">Current location of the cursor on the screen.</param> /// <param name="forceRedraw">Flag indicating whether or not the redraw should be forced.</param> /// <param name="offset">Offset within <paramref name="graphicsContext" /> that the tabs should be rendered.</param> public virtual void Render(ListWithEvents <TitleBarTabItem> tabs, Graphics graphicsContext, Point offset, Point cursor, bool forceRedraw = false) { if (_suspendRendering) { return; } if (tabs == null || tabs.Count == 0) { return; } Point screenCoordinates = _parentWindow.PointToScreen(_parentWindow.ClientRectangle.Location); // Calculate the maximum tab area, excluding the add button and any minimize/maximize/close buttons in the window _maxTabArea.Location = new Point(SystemInformation.BorderSize.Width + offset.X + screenCoordinates.X, offset.Y + screenCoordinates.Y); _maxTabArea.Width = (_parentWindow.ClientRectangle.Width - offset.X - (ShowAddButton ? _addButtonImage.Width + AddButtonMarginLeft + AddButtonMarginRight : 0) - (tabs.Count() * OverlapWidth) - (_parentWindow.ControlBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MinimizeBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MaximizeBox ? SystemInformation.CaptionButtonSize.Width : 0)); _maxTabArea.Height = _activeCenterImage.Height; // Get the width of the content area for each tab by taking the parent window's client width, subtracting the left and right border widths and the // add button area (if applicable) and then dividing by the number of tabs int tabContentWidth = Math.Min(_activeCenterImage.Width, Convert.ToInt32(Math.Floor(Convert.ToDouble(_maxTabArea.Width / tabs.Count())))); // Determine if we need to redraw the TabImage properties for each tab by seeing if the content width that we calculated above is equal to content // width we had in the previous rendering pass bool redraw = (tabContentWidth != _tabContentWidth || forceRedraw); if (redraw) { _tabContentWidth = tabContentWidth; } int i = tabs.Count - 1; List <Tuple <TitleBarTabItem, Rectangle> > activeTabs = new List <Tuple <TitleBarTabItem, Rectangle> >(); // Render the background image if (_background != null) { graphicsContext.DrawImage(_background, offset.X, offset.Y, _parentWindow.Width, _activeCenterImage.Height); } int selectedIndex = tabs.FindIndex(t => t.Active); if (selectedIndex != -1) { Rectangle tabArea = new Rectangle( SystemInformation.BorderSize.Width + offset.X + (selectedIndex * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)), offset.Y, tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width, _activeCenterImage.Height); if (IsTabRepositioning && _tabClickOffset != null) { // Make sure that the user doesn't move the tab past the beginning of the list or the outside of the window tabArea.X = cursor.X - _tabClickOffset.Value; tabArea.X = Math.Max(SystemInformation.BorderSize.Width + offset.X, tabArea.X); tabArea.X = Math.Min( SystemInformation.BorderSize.Width + (_parentWindow.WindowState == FormWindowState.Maximized ? _parentWindow.ClientRectangle.Width - (_parentWindow.ControlBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MinimizeBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MaximizeBox ? SystemInformation.CaptionButtonSize.Width : 0) : _parentWindow.ClientRectangle.Width) - tabArea.Width, tabArea.X); int dropIndex = 0; // Figure out which slot the active tab is being "dropped" over if (tabArea.X - SystemInformation.BorderSize.Width - offset.X - TabRepositionDragDistance > 0) { dropIndex = Math.Min( Convert.ToInt32( Math.Round( Convert.ToDouble(tabArea.X - SystemInformation.BorderSize.Width - offset.X - TabRepositionDragDistance) / Convert.ToDouble(tabArea.Width - OverlapWidth))), tabs.Count - 1); } // If the tab has been moved over another slot, move the tab object in the window's tab list if (dropIndex != selectedIndex) { TitleBarTabItem tab = tabs[selectedIndex]; _parentWindow.Tabs.SuppressEvents(); _parentWindow.Tabs.Remove(tab); _parentWindow.Tabs.Insert(dropIndex, tab); _parentWindow.Tabs.ResumeEvents(); } } activeTabs.Add(new Tuple <TitleBarTabItem, Rectangle>(tabs[selectedIndex], tabArea)); } // Loop through the tabs in reverse order since we need the ones farthest on the left to overlap those to their right foreach (TitleBarTabItem tab in ((IEnumerable <TitleBarTabItem>)tabs).Reverse()) { Rectangle tabArea = new Rectangle( SystemInformation.BorderSize.Width + offset.X + (i * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)), offset.Y, tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width, _activeCenterImage.Height); // If we need to redraw the tab image, null out the property so that it will be recreated in the call to Render() below if (redraw) { tab.TabImage = null; } // In this first pass, we only render the inactive tabs since we need the active tabs to show up on top of everything else if (!tab.Active) { Render(graphicsContext, tab, tabArea, cursor); } i--; } // In the second pass, render all of the active tabs identified in the previous pass foreach (Tuple <TitleBarTabItem, Rectangle> tab in activeTabs) { Render(graphicsContext, tab.Item1, tab.Item2, cursor); } _previousTabCount = tabs.Count; // Render the add tab button to the screen if (ShowAddButton && !IsTabRepositioning) { _addButtonArea = new Rectangle( (_previousTabCount * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)) + _activeRightSideImage.Width + AddButtonMarginLeft + offset.X, AddButtonMarginTop + offset.Y, _addButtonImage.Width, _addButtonImage.Height); bool cursorOverAddButton = IsOverAddButton(cursor); graphicsContext.DrawImage( cursorOverAddButton ? _addButtonHoverImage : _addButtonImage, _addButtonArea, 0, 0, cursorOverAddButton ? _addButtonHoverImage.Width : _addButtonImage.Width, cursorOverAddButton ? _addButtonHoverImage.Height : _addButtonImage.Height, GraphicsUnit.Pixel); } }