| File: System\Windows\Forms\Controls\ToolStrips\StatusStrip.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; using System.Windows.Forms.Layout; namespace System.Windows.Forms; [SRDescription(nameof(SR.DescriptionStatusStrip))] public partial class StatusStrip : ToolStrip { private const AnchorStyles AllAnchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Top; private const AnchorStyles HorizontalAnchor = AnchorStyles.Left | AnchorStyles.Right; private const AnchorStyles VerticalAnchor = AnchorStyles.Top | AnchorStyles.Bottom; private BitVector32 _state; private static readonly int s_stateSizingGrip = BitVector32.CreateMask(); private static readonly int s_stateCalledSpringTableLayout = BitVector32.CreateMask(s_stateSizingGrip); private const int GripWidth = 15; private const int GripHeight = 22; private RightToLeftLayoutGrip? _rtlLayoutGrip; private Orientation _lastOrientation = Orientation.Horizontal; public StatusStrip() { SuspendLayout(); CanOverflow = false; LayoutStyle = ToolStripLayoutStyle.Table; RenderMode = ToolStripRenderMode.System; GripStyle = ToolStripGripStyle.Hidden; SetStyle(ControlStyles.ResizeRedraw, true); Stretch = true; _state[s_stateSizingGrip] = true; ResumeLayout(true); } [DefaultValue(false)] [SRDescription(nameof(SR.ToolStripCanOverflowDescr))] [SRCategory(nameof(SR.CatLayout))] [Browsable(false)] public new bool CanOverflow { get => base.CanOverflow; set => base.CanOverflow = value; } protected override bool DefaultShowItemToolTips { get { return false; } } protected override Size DefaultSize { get { return new Size(200, 22); } } protected override Padding DefaultPadding { get { if (Orientation == Orientation.Horizontal) { int gripPaddingWidth = SizeGripBounds.Width < GripWidth + 2 ? GripWidth + 2 : SizeGripBounds.Width; return RightToLeft == RightToLeft.No ? new Padding(1, 0, gripPaddingWidth, 0) : new Padding(gripPaddingWidth, 0, 1, 0); } else { // vertical // the difference in symmetry here is that the grip does not actually rotate, it remains the same height it // was before, so the DisplayRectangle needs to shrink up by its height. int gripPaddingHeight = SizeGripBounds.Height < DefaultSize.Height ? DefaultSize.Height : SizeGripBounds.Height; return new Padding(1, 3, 1, gripPaddingHeight); } } } protected override DockStyle DefaultDock { get { return DockStyle.Bottom; } } [DefaultValue(DockStyle.Bottom)] public override DockStyle Dock { get => base.Dock; set => base.Dock = value; } [DefaultValue(ToolStripGripStyle.Hidden)] public new ToolStripGripStyle GripStyle { get => base.GripStyle; set => base.GripStyle = value; } [DefaultValue(ToolStripLayoutStyle.Table)] public new ToolStripLayoutStyle LayoutStyle { get => base.LayoutStyle; set => base.LayoutStyle = value; } // we do some custom stuff with padding to accommodate size grip. // changing this is not supported at DT [Browsable(false)] public new Padding Padding { get => base.Padding; set => base.Padding = value; } [Browsable(false)] public new event EventHandler? PaddingChanged { add => base.PaddingChanged += value; remove => base.PaddingChanged -= value; } private Control RTLGrip { get { _rtlLayoutGrip ??= new RightToLeftLayoutGrip(); return _rtlLayoutGrip; } } [DefaultValue(false)] [SRDescription(nameof(SR.ToolStripShowItemToolTipsDescr))] [SRCategory(nameof(SR.CatBehavior))] public new bool ShowItemToolTips { get => base.ShowItemToolTips; set => base.ShowItemToolTips = value; } // return whether we should paint the sizing grip. private bool ShowSizingGrip { get { if (SizingGrip && IsHandleCreated) { if (DesignMode) { return true; // we don't care about the state of VS. } HWND rootHwnd = PInvoke.GetAncestor(this, GET_ANCESTOR_FLAGS.GA_ROOT); if (!rootHwnd.IsNull) { return !PInvoke.IsZoomed(rootHwnd); } } return false; } } [SRCategory(nameof(SR.CatAppearance))] [DefaultValue(true)] [SRDescription(nameof(SR.StatusStripSizingGripDescr))] public bool SizingGrip { get { return _state[s_stateSizingGrip]; } set { if (value != _state[s_stateSizingGrip]) { _state[s_stateSizingGrip] = value; EnsureRightToLeftGrip(); Invalidate(true); } } } [Browsable(false)] public Rectangle SizeGripBounds { get { if (!SizingGrip) { return Rectangle.Empty; } Size statusStripSize = Size; Rectangle clientRect = ClientRectangle; int scaledGripHeight = ScaleHelper.ScaleToDpi(GripHeight, DeviceDpi); int scaleGripWidth = ScaleHelper.ScaleToDpi(GripWidth, DeviceDpi); scaledGripHeight = Math.Min(scaledGripHeight, clientRect.Height); return RightToLeft == RightToLeft.Yes ? new Rectangle( x: 0, y: statusStripSize.Height - scaledGripHeight, width: scaleGripWidth, height: scaledGripHeight) : new Rectangle( x: statusStripSize.Width - scaleGripWidth, y: statusStripSize.Height - scaledGripHeight, width: scaleGripWidth, height: scaledGripHeight); } } [DefaultValue(true)] [SRCategory(nameof(SR.CatLayout))] [SRDescription(nameof(SR.ToolStripStretchDescr))] public new bool Stretch { get => base.Stretch; set => base.Stretch = value; } private TableLayoutSettings TableLayoutSettings { get { return (TableLayoutSettings)LayoutSettings!; } } protected override AccessibleObject CreateAccessibilityInstance() { return new StatusStripAccessibleObject(this); } protected internal override ToolStripItem CreateDefaultItem(string? text, Image? image, EventHandler? onClick) { return new ToolStripStatusLabel(text, image, onClick); } protected override void Dispose(bool disposing) { if (disposing) { _rtlLayoutGrip?.Dispose(); _rtlLayoutGrip = null; } base.Dispose(disposing); } // in RTL, we parent a transparent control over the grip to support mirroring. private void EnsureRightToLeftGrip() { if (SizingGrip && RightToLeft == RightToLeft.Yes) { RTLGrip.Bounds = SizeGripBounds; if (!Controls.Contains(RTLGrip)) { if (Controls is ReadOnlyControlCollection controlCollection) { controlCollection.AddInternal(RTLGrip); } } } else if (_rtlLayoutGrip is not null) { if (Controls.Contains(_rtlLayoutGrip)) { if (Controls is ReadOnlyControlCollection controlCollection) { controlCollection.RemoveInternal(_rtlLayoutGrip); } _rtlLayoutGrip.Dispose(); _rtlLayoutGrip = null; } } } internal override Size GetPreferredSizeCore(Size proposedSize) { if (LayoutStyle == ToolStripLayoutStyle.Table) { if (proposedSize.Width == 1) { proposedSize.Width = int.MaxValue; } if (proposedSize.Height == 1) { proposedSize.Height = int.MaxValue; } return Orientation == Orientation.Horizontal ? GetPreferredSizeHorizontal(this, proposedSize) + Padding.Size : GetPreferredSizeVertical(this) + Padding.Size; } return base.GetPreferredSizeCore(proposedSize); } protected override void OnPaintBackground(PaintEventArgs e) { base.OnPaintBackground(e); if (ShowSizingGrip) { Renderer.DrawStatusStripSizingGrip(new ToolStripRenderEventArgs(e.Graphics, this)); } } protected override void OnLayout(LayoutEventArgs levent) { _state[s_stateCalledSpringTableLayout] = false; bool inDisplayedItemCollection = false; ToolStripItem? item = levent.AffectedComponent as ToolStripItem; int itemCount = DisplayedItems.Count; if (item is not null) { inDisplayedItemCollection = DisplayedItems.Contains(item); } if (LayoutStyle == ToolStripLayoutStyle.Table) { OnSpringTableLayoutCore(); } base.OnLayout(levent); if (itemCount != DisplayedItems.Count || (item is not null && (inDisplayedItemCollection != DisplayedItems.Contains(item)))) { // calling OnLayout has changed the displayed items collection // the SpringTableLayoutCore requires the count of displayed items to // be accurate. // - so we need to perform layout again. if (LayoutStyle == ToolStripLayoutStyle.Table) { OnSpringTableLayoutCore(); base.OnLayout(levent); } } EnsureRightToLeftGrip(); } internal override void ResetRenderMode() { RenderMode = ToolStripRenderMode.System; } internal override bool ShouldSerializeRenderMode() { // We should NEVER serialize custom. return (RenderMode is not ToolStripRenderMode.System and not ToolStripRenderMode.Custom); } internal override bool SupportsUiaProviders => true; protected override void SetDisplayedItems() { if (_state[s_stateCalledSpringTableLayout]) { // shove all items that don't fit one pixel outside the displayed region Rectangle displayRect = DisplayRectangle; Point noMansLand = displayRect.Location; noMansLand.X += ClientSize.Width + 1; noMansLand.Y += ClientSize.Height + 1; bool overflow = false; Rectangle lastItemBounds = Rectangle.Empty; ToolStripItem? lastItem = null; for (int i = 0; i < Items.Count; i++) { ToolStripItem item = Items[i]; // using spring layout we can get into a situation where there's extra items which arent // visible. if (overflow || ((IArrangedElement)item).ParticipatesInLayout) { if (overflow || (SizingGrip && item.Bounds.IntersectsWith(SizeGripBounds))) { // if the item collides with the size grip, set the location to nomansland. SetItemLocation(item, noMansLand); item.SetPlacement(ToolStripItemPlacement.None); } } else if (lastItem is not null && (lastItemBounds.IntersectsWith(item.Bounds))) { // if it overlaps the previous element, set the location to nomansland. SetItemLocation(item, noMansLand); item.SetPlacement(ToolStripItemPlacement.None); } else if (item.Bounds.Width == 1) { if (item is ToolStripStatusLabel panel && panel.Spring) { // once we get down to one pixel, there can always be a one pixel // distribution problem with the TLP - there's usually a spare one around. // so set this off to nomansland as well. SetItemLocation(item, noMansLand); item.SetPlacement(ToolStripItemPlacement.None); } } if (item.Bounds.Location != noMansLand) { // set the next item to inspect for collisions lastItem = item; lastItemBounds = lastItem.Bounds; } else { // we can't fit an item, everything else after it should not be displayed if (((IArrangedElement)item).ParticipatesInLayout) { overflow = true; } } } } base.SetDisplayedItems(); } /// <summary> /// Override this function if you want to do custom table layouts for the /// StatusStrip. The default layoutstyle is tablelayout, and we need to play /// with the row/column styles /// </summary> protected virtual void OnSpringTableLayoutCore() { if (LayoutStyle == ToolStripLayoutStyle.Table) { _state[s_stateCalledSpringTableLayout] = true; SuspendLayout(); if (_lastOrientation != Orientation) { TableLayoutSettings settings = TableLayoutSettings; settings.RowCount = 0; settings.ColumnCount = 0; settings.ColumnStyles.Clear(); settings.RowStyles.Clear(); } _lastOrientation = Orientation; if (Orientation == Orientation.Horizontal) { // // Horizontal layout // TableLayoutSettings.GrowStyle = TableLayoutPanelGrowStyle.AddColumns; int originalColumnCount = TableLayoutSettings.ColumnStyles.Count; // iterate through the elements which are going to be displayed. for (int i = 0; i < DisplayedItems.Count; i++) { if (i >= originalColumnCount) { // add if it's necessary. TableLayoutSettings.ColumnStyles.Add(new ColumnStyle()); } // determine if we "spring" or "autosize" the column bool spring = (DisplayedItems[i] is ToolStripStatusLabel panel && panel.Spring); DisplayedItems[i].Anchor = (spring) ? AllAnchor : VerticalAnchor; // spring is achieved by using 100% as the column style ColumnStyle colStyle = TableLayoutSettings.ColumnStyles[i]; colStyle.Width = 100; // this width is ignored in AutoSize. colStyle.SizeType = (spring) ? SizeType.Percent : SizeType.AutoSize; } if (TableLayoutSettings.RowStyles.Count is > 1 or 0) { TableLayoutSettings.RowStyles.Clear(); TableLayoutSettings.RowStyles.Add(new RowStyle()); } TableLayoutSettings.RowCount = 1; TableLayoutSettings.RowStyles[0].SizeType = SizeType.Absolute; TableLayoutSettings.RowStyles[0].Height = Math.Max(0, DisplayRectangle.Height); TableLayoutSettings.ColumnCount = DisplayedItems.Count + 1; // add an extra cell so it fills the remaining space // don't remove the extra column styles, just set them back to autosize. for (int i = DisplayedItems.Count; i < TableLayoutSettings.ColumnStyles.Count; i++) { TableLayoutSettings.ColumnStyles[i].SizeType = SizeType.AutoSize; } } else { // // Vertical layout // TableLayoutSettings.GrowStyle = TableLayoutPanelGrowStyle.AddRows; int originalRowCount = TableLayoutSettings.RowStyles.Count; // iterate through the elements which are going to be displayed. for (int i = 0; i < DisplayedItems.Count; i++) { if (i >= originalRowCount) { // add if it's necessary. TableLayoutSettings.RowStyles.Add(new RowStyle()); } // determine if we "spring" or "autosize" the row bool spring = (DisplayedItems[i] is ToolStripStatusLabel panel && panel.Spring); DisplayedItems[i].Anchor = (spring) ? AllAnchor : HorizontalAnchor; // spring is achieved by using 100% as the row style RowStyle rowStyle = TableLayoutSettings.RowStyles[i]; rowStyle.Height = 100; // this width is ignored in AutoSize. rowStyle.SizeType = (spring) ? SizeType.Percent : SizeType.AutoSize; } TableLayoutSettings.ColumnCount = 1; if (TableLayoutSettings.ColumnStyles.Count is > 1 or 0) { TableLayoutSettings.ColumnStyles.Clear(); TableLayoutSettings.ColumnStyles.Add(new ColumnStyle()); } TableLayoutSettings.ColumnCount = 1; TableLayoutSettings.ColumnStyles[0].SizeType = SizeType.Absolute; TableLayoutSettings.ColumnStyles[0].Width = Math.Max(0, DisplayRectangle.Width); TableLayoutSettings.RowCount = DisplayedItems.Count + 1; // add an extra cell so it fills the remaining space // don't remove the extra column styles, just set them back to autosize. for (int i = DisplayedItems.Count; i < TableLayoutSettings.RowStyles.Count; i++) { TableLayoutSettings.RowStyles[i].SizeType = SizeType.AutoSize; } } ResumeLayout(false); } } protected override void WndProc(ref Message m) { if ((m.Msg == (int)PInvokeCore.WM_NCHITTEST) && SizingGrip) { // if we're within the grip bounds tell windows // that we're the bottom right of the window. Rectangle sizeGripBounds = SizeGripBounds; if (sizeGripBounds.Contains(PointToClient(PARAM.ToPoint(m.LParamInternal)))) { HWND rootHwnd = PInvoke.GetAncestor(this, GET_ANCESTOR_FLAGS.GA_ROOT); // if the main window isn't maximized - we should paint a resize grip. // double check that we're at the bottom right hand corner of the window. if (!rootHwnd.IsNull && !PInvoke.IsZoomed(rootHwnd)) { // get the client area of the topmost window. If we're next to the edge then // the sizing grip is valid. PInvokeCore.GetClientRect(rootHwnd, out RECT rootHwndClientArea); // map the size grip FROM statusStrip coords TO the toplevel window coords. Point gripLocation = RightToLeft == RightToLeft.Yes ? new Point(SizeGripBounds.Left, SizeGripBounds.Bottom) : new Point(SizeGripBounds.Right, SizeGripBounds.Bottom); PInvokeCore.MapWindowPoints(this, rootHwnd, ref gripLocation); int deltaBottomEdge = Math.Abs(rootHwndClientArea.bottom - gripLocation.Y); int deltaRightEdge = Math.Abs(rootHwndClientArea.right - gripLocation.X); if (RightToLeft != RightToLeft.Yes) { if ((deltaRightEdge + deltaBottomEdge) < 2) { m.ResultInternal = (LRESULT)(nint)PInvoke.HTBOTTOMRIGHT; return; } } } } } base.WndProc(ref m); } // special transparent mirrored window which says it's the bottom left of the form. private class RightToLeftLayoutGrip : Control { public RightToLeftLayoutGrip() { SetStyle(ControlStyles.SupportsTransparentBackColor, true); BackColor = Color.Transparent; } protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_LAYOUTRTL; return cp; } } protected override void WndProc(ref Message m) { if (m.MsgInternal == PInvokeCore.WM_NCHITTEST) { if (ClientRectangle.Contains(PointToClient(PARAM.ToPoint(m.LParamInternal)))) { m.ResultInternal = (LRESULT)(nint)PInvoke.HTBOTTOMLEFT; return; } } base.WndProc(ref m); } } }