diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.MapWindowPoints.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.MapWindowPoints.cs index d06086edad0..b3c3288441e 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.MapWindowPoints.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.MapWindowPoints.cs @@ -35,6 +35,13 @@ public static int MapWindowPoints(HandleRef hWndFrom, HandleRef hWndTo, ref Poin return result; } + public static int MapWindowPoints(IntPtr hWndFrom, IHandle hWndTo, ref Point lpPoints, uint cPoints) + { + int result = MapWindowPoints(hWndFrom, hWndTo.Handle, ref lpPoints, cPoints); + GC.KeepAlive(hWndTo); + return result; + } + [DllImport(Libraries.User32, ExactSpelling = true)] public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref RECT lpPoints, uint cPoints); diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs index 8e8bb6f224d..5640bd62d5c 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs @@ -74,11 +74,11 @@ internal abstract class UiaTextProvider : ITextProvider public abstract void SetSelection(int start, int end); - public ES GetEditStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (ES)GetWindowLong(new HandleRef(null, hWnd), GWL.STYLE) : ES.LEFT; + public ES GetEditStyle(IHandle hWnd) => (ES)GetWindowLong(hWnd, GWL.STYLE); - public WS_EX GetWindowExStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (WS_EX)GetWindowLong(new HandleRef(null, hWnd), GWL.EXSTYLE) : WS_EX.LEFT; + public WS_EX GetWindowExStyle(IHandle hWnd) => (WS_EX)GetWindowLong(hWnd, GWL.EXSTYLE); - public WS GetWindowStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (WS)GetWindowLong(new HandleRef(this, hWnd), GWL.STYLE) : WS.DISABLED; + public WS GetWindowStyle(IHandle hWnd) => (WS)GetWindowLong(hWnd, GWL.STYLE); public double[] RectListToDoubleArray(List rectArray) { diff --git a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs index e51465e329f..216cbbd7230 100644 --- a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs +++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs @@ -22,7 +22,7 @@ public void UiaTextProvider_GetEditStyle_ContainsMultilineStyle_ForMultilineText style: WS.OVERLAPPED | WS.VISIBLE); Mock providerMock = new Mock(MockBehavior.Strict); - ES actual = providerMock.Object.GetEditStyle(textBox.Handle); + ES actual = providerMock.Object.GetEditStyle(textBox); Assert.True(actual.HasFlag(ES.MULTILINE)); } @@ -35,19 +35,10 @@ public void UiaTextProvider_GetEditStyle_DoesntContainMultilineStyle_ForSingleli style: WS.OVERLAPPED | WS.VISIBLE); Mock providerMock = new Mock(MockBehavior.Strict); - ES actual = providerMock.Object.GetEditStyle(textBox.Handle); + ES actual = providerMock.Object.GetEditStyle(textBox); Assert.False(actual.HasFlag(ES.MULTILINE)); } - [StaFact] - public void UiaTextProvider_GetEditStyle_ReturnsLeft_WithoutHandle() - { - Mock providerMock = new Mock(MockBehavior.Strict); - - ES actual = providerMock.Object.GetEditStyle(IntPtr.Zero); - Assert.Equal(ES.LEFT, actual); - } - [StaFact] public void UiaTextProvider_GetWindowStyle_ContainsVisible() { @@ -56,19 +47,10 @@ public void UiaTextProvider_GetWindowStyle_ContainsVisible() style: WS.OVERLAPPED | WS.VISIBLE); Mock providerMock = new Mock(MockBehavior.Strict); - WS actual = providerMock.Object.GetWindowStyle(textBox.Handle); + WS actual = providerMock.Object.GetWindowStyle(textBox); Assert.True(actual.HasFlag(WS.VISIBLE)); } - [StaFact] - public void UiaTextProvider_GetWindowStyle_ReturnsDisabled_WithoutHandle() - { - Mock providerMock = new Mock(MockBehavior.Strict); - - WS actual = providerMock.Object.GetWindowStyle(IntPtr.Zero); - Assert.Equal(WS.DISABLED, actual); - } - [StaFact] public void UiaTextProvider_GetWindowExStyle_ContainsClientedge() { @@ -76,18 +58,10 @@ public void UiaTextProvider_GetWindowExStyle_ContainsClientedge() style: WS.OVERLAPPED | WS.VISIBLE); Mock providerMock = new Mock(MockBehavior.Strict); - WS_EX actual = providerMock.Object.GetWindowExStyle(textBox.Handle); + WS_EX actual = providerMock.Object.GetWindowExStyle(textBox); Assert.True(actual.HasFlag(WS_EX.CLIENTEDGE)); } - [StaFact] - public void UiaTextProvider_GetWindowExStyle_ReturnsLeft_WithoutHandle() - { - Mock providerMock = new Mock(MockBehavior.Strict); - WS_EX actual = providerMock.Object.GetWindowExStyle(IntPtr.Zero); - Assert.Equal(WS_EX.LEFT, actual); - } - [StaFact] public void UiaTextProvider_RectArrayToDoubleArray_ReturnsCorrectValue() { diff --git a/src/System.Windows.Forms/src/PublicAPI.Shipped.txt b/src/System.Windows.Forms/src/PublicAPI.Shipped.txt index 713e94cda3e..3860947a837 100644 --- a/src/System.Windows.Forms/src/PublicAPI.Shipped.txt +++ b/src/System.Windows.Forms/src/PublicAPI.Shipped.txt @@ -10720,7 +10720,7 @@ System.Windows.Forms.ScrollProperties.ScrollProperties(System.Windows.Forms.Scro ~System.Windows.Forms.ToolStripControlHost.Control.get -> System.Windows.Forms.Control ~System.Windows.Forms.ToolStripControlHost.ToolStripControlHost(System.Windows.Forms.Control c) -> void ~System.Windows.Forms.ToolStripControlHost.ToolStripControlHost(System.Windows.Forms.Control c, string name) -> void -~System.Windows.Forms.ToolStripControlHost.ToolStripHostedControlAccessibleObject.ToolStripHostedControlAccessibleObject(System.Windows.Forms.Control toolStripHostedControl, System.Windows.Forms.ToolStripControlHost toolStripControlHost) -> void +System.Windows.Forms.ToolStripControlHost.ToolStripHostedControlAccessibleObject.ToolStripHostedControlAccessibleObject(System.Windows.Forms.Control! toolStripHostedControl, System.Windows.Forms.ToolStripControlHost? toolStripControlHost) -> void ~System.Windows.Forms.ToolStripDropDown.ContextMenuStrip.get -> System.Windows.Forms.ContextMenuStrip ~System.Windows.Forms.ToolStripDropDown.ContextMenuStrip.set -> void ~System.Windows.Forms.ToolStripDropDown.OverflowButton.get -> System.Windows.Forms.ToolStripOverflowButton diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs index 6deaf4b1061..f93b7452258 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs @@ -78,7 +78,7 @@ public TextBoxBaseUiaTextProvider(TextBoxBase owner) // Convert screen to client coordinates. // (Essentially ScreenToClient but MapWindowPoints accounts for window mirroring using WS_EX_LAYOUTRTL.) - if (MapWindowPoints(new HandleRef(null, IntPtr.Zero), new HandleRef(this, _owningTextBoxBase.Handle), ref clientLocation, 1) == 0) + if (MapWindowPoints(default, _owningTextBoxBase, ref clientLocation, 1) == 0) { return new UiaTextRange(new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject), this, 0, 0); } @@ -177,7 +177,7 @@ public override bool IsScrollable public override int LinesCount => _owningTextBoxBase.IsHandleCreated - ? (int)(long)SendMessageW(new HandleRef(this, _owningTextBoxBase.Handle), (WM)EM.GETLINECOUNT) + ? (int)(long)SendMessageW(_owningTextBoxBase, (WM)EM.GETLINECOUNT) : -1; public override int LinesPerPage @@ -222,17 +222,17 @@ public override int TextLength public override WS_EX WindowExStyle => _owningTextBoxBase.IsHandleCreated - ? GetWindowExStyle(_owningTextBoxBase.Handle) + ? GetWindowExStyle(_owningTextBoxBase) : WS_EX.LEFT; public override WS WindowStyle => _owningTextBoxBase.IsHandleCreated - ? GetWindowStyle(_owningTextBoxBase.Handle) + ? GetWindowStyle(_owningTextBoxBase) : WS.OVERLAPPED; public override ES EditStyle => _owningTextBoxBase.IsHandleCreated - ? GetEditStyle(_owningTextBoxBase.Handle) + ? GetEditStyle(_owningTextBoxBase) : ES.LEFT; public override int GetLineFromCharIndex(int charIndex) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripControlHost.ToolStripHostedControlAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripControlHost.ToolStripHostedControlAccessibleObject.cs index 30f936929ab..210f0d09b0d 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripControlHost.ToolStripHostedControlAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripControlHost.ToolStripHostedControlAccessibleObject.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using static Interop; namespace System.Windows.Forms @@ -17,7 +15,7 @@ public partial class ToolStripControlHost /// public class ToolStripHostedControlAccessibleObject : Control.ControlAccessibleObject { - private ToolStripControlHost _toolStripControlHost; + private ToolStripControlHost? _toolStripControlHost; private Control _toolStripHostedControl; /// @@ -25,13 +23,13 @@ public class ToolStripHostedControlAccessibleObject : Control.ControlAccessibleO /// /// The ToolStrip control hosted in the ToolStripControlHost container. /// The ToolStripControlHost container which hosts the control. - public ToolStripHostedControlAccessibleObject(Control toolStripHostedControl, ToolStripControlHost toolStripControlHost) : base(toolStripHostedControl) + public ToolStripHostedControlAccessibleObject(Control toolStripHostedControl, ToolStripControlHost? toolStripControlHost) : base(toolStripHostedControl) { _toolStripControlHost = toolStripControlHost; _toolStripHostedControl = toolStripHostedControl; } - internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot + internal override UiaCore.IRawElementProviderFragmentRoot? FragmentRoot { get { @@ -46,7 +44,7 @@ internal override UiaCore.IRawElementProviderFragmentRoot FragmentRoot } } - internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.NavigateDirection direction) + internal override UiaCore.IRawElementProviderFragment? FragmentNavigate(UiaCore.NavigateDirection direction) { if (_toolStripHostedControl != null && _toolStripControlHost != null) @@ -63,7 +61,7 @@ internal override UiaCore.IRawElementProviderFragment FragmentNavigate(UiaCore.N return base.FragmentNavigate(direction); } - internal override object GetPropertyValue(UiaCore.UIA propertyID) + internal override object? GetPropertyValue(UiaCore.UIA propertyID) { switch (propertyID) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControl.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControl.cs new file mode 100644 index 00000000000..9e8aa0626f1 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControl.cs @@ -0,0 +1,292 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Drawing; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class ToolStripTextBox + { + private class ToolStripTextBoxControl : TextBox + { + private bool _mouseIsOver; + private bool _isFontSet = true; + private bool _alreadyHooked; + + public ToolStripTextBoxControl() + { + // required to make the text box height match the combo. + Font = ToolStripManager.DefaultFont; + _isFontSet = false; + } + + // returns the distance from the client rect to the upper left hand corner of the control + private RECT AbsoluteClientRECT + { + get + { + RECT rect = new RECT(); + CreateParams cp = CreateParams; + + AdjustWindowRectEx(ref rect, cp.Style, false, cp.ExStyle); + + // the coordinates we get back are negative, we need to translate this back to positive. + int offsetX = -rect.left; // one to get back to 0,0, another to translate + int offsetY = -rect.top; + + // fetch the client rect, then apply the offset. + User32.GetClientRect(new HandleRef(this, Handle), ref rect); + + rect.left += offsetX; + rect.right += offsetX; + rect.top += offsetY; + rect.bottom += offsetY; + + return rect; + } + } + private Rectangle AbsoluteClientRectangle + { + get + { + RECT rect = AbsoluteClientRECT; + return Rectangle.FromLTRB(rect.top, rect.top, rect.right, rect.bottom); + } + } + + private ProfessionalColorTable ColorTable + { + get + { + if (Owner != null) + { + if (Owner.Renderer is ToolStripProfessionalRenderer renderer) + { + return renderer.ColorTable; + } + } + return ProfessionalColors.ColorTable; + } + } + + private bool IsPopupTextBox + { + get + { + return ((BorderStyle == BorderStyle.Fixed3D) && + (Owner != null && (Owner.Renderer is ToolStripProfessionalRenderer))); + } + } + + internal bool MouseIsOver + { + get { return _mouseIsOver; } + set + { + if (_mouseIsOver != value) + { + _mouseIsOver = value; + if (!Focused) + { + InvalidateNonClient(); + } + } + } + } + + public override Font Font + { + get => base.Font; + set + { + base.Font = value; + _isFontSet = ShouldSerializeFont(); + } + } + + public ToolStripTextBox? Owner { get; set; } + + internal override bool SupportsUiaProviders => true; + + private unsafe void InvalidateNonClient() + { + if (!IsPopupTextBox) + { + return; + } + + RECT absoluteClientRectangle = AbsoluteClientRECT; + + // Get the total client area, then exclude the client by using XOR + using var hTotalRegion = new Gdi32.RegionScope(0, 0, Width, Height); + using var hClientRegion = new Gdi32.RegionScope( + absoluteClientRectangle.left, + absoluteClientRectangle.top, + absoluteClientRectangle.right, + absoluteClientRectangle.bottom); + using var hNonClientRegion = new Gdi32.RegionScope(0, 0, 0, 0); + + Gdi32.CombineRgn(hNonClientRegion, hTotalRegion, hClientRegion, Gdi32.RGN.XOR); + + // Call RedrawWindow with the region. + User32.RedrawWindow( + new HandleRef(this, Handle), + null, + hNonClientRegion, + User32.RDW.INVALIDATE | User32.RDW.ERASE | User32.RDW.UPDATENOW + | User32.RDW.ERASENOW | User32.RDW.FRAME); + } + + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + InvalidateNonClient(); + } + + protected override void OnLostFocus(EventArgs e) + { + base.OnLostFocus(e); + InvalidateNonClient(); + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + MouseIsOver = true; + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + MouseIsOver = false; + } + + private void HookStaticEvents(bool hook) + { + if (hook) + { + if (!_alreadyHooked) + { + try + { + SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + finally + { + _alreadyHooked = true; + } + } + } + else if (_alreadyHooked) + { + try + { + SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); + } + finally + { + _alreadyHooked = false; + } + } + } + + private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + if (e.Category == UserPreferenceCategory.Window) + { + if (!_isFontSet) + { + Font = ToolStripManager.DefaultFont; + } + } + } + + protected override void OnVisibleChanged(EventArgs e) + { + base.OnVisibleChanged(e); + if (!Disposing && !IsDisposed) + { + HookStaticEvents(Visible); + } + } + + protected override AccessibleObject CreateAccessibilityInstance() + { + return new ToolStripTextBoxControlAccessibleObject(this, Owner); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + HookStaticEvents(false); + } + base.Dispose(disposing); + } + + private void WmNCPaint(ref Message m) + { + if (!IsPopupTextBox) + { + base.WndProc(ref m); + return; + } + + // Paint over the edges of the text box. + + // Note that GetWindowDC just calls GetDCEx with DCX_WINDOW | DCX_USESTYLE. + + using var hdc = new User32.GetDcScope(m.HWnd, IntPtr.Zero, User32.DCX.WINDOW | User32.DCX.USESTYLE); + if (hdc.IsNull) + { + throw new Win32Exception(); + } + + // Don't set the clipping region based on the WParam - windows seems to take out the two pixels intended for the non-client border. + + Color outerBorderColor = (MouseIsOver || Focused) ? ColorTable.TextBoxBorder : BackColor; + Color innerBorderColor = BackColor; + + if (!Enabled) + { + outerBorderColor = SystemColors.ControlDark; + innerBorderColor = SystemColors.Control; + } + + using Graphics g = hdc.CreateGraphics(); + Rectangle clientRect = AbsoluteClientRectangle; + + // Could have set up a clip and fill-rectangled, thought this would be faster. + using var brush = innerBorderColor.GetCachedSolidBrushScope(); + g.FillRectangle(brush, 0, 0, Width, clientRect.Top); // top border + g.FillRectangle(brush, 0, 0, clientRect.Left, Height); // left border + g.FillRectangle(brush, 0, clientRect.Bottom, Width, Height - clientRect.Height); // bottom border + g.FillRectangle(brush, clientRect.Right, 0, Width - clientRect.Right, Height); // right border + + // Paint the outside rect. + using var pen = outerBorderColor.GetCachedPenScope(); + g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1); + + // We've handled WM_NCPAINT. + m.Result = IntPtr.Zero; + } + protected override void WndProc(ref Message m) + { + if (m.Msg == (int)User32.WM.NCPAINT) + { + WmNCPaint(ref m); + return; + } + else + { + base.WndProc(ref m); + } + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObject.cs new file mode 100644 index 00000000000..626a5fc4a2d --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObject.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using static System.Windows.Forms.TextBoxBase; +using static Interop; + +namespace System.Windows.Forms +{ + public partial class ToolStripTextBox + { + private class ToolStripTextBoxControlAccessibleObject : ToolStripHostedControlAccessibleObject + { + private readonly TextBoxBase _owningTextBoxBase; + private readonly TextBoxBaseUiaTextProvider _textProvider; + + public ToolStripTextBoxControlAccessibleObject(TextBox toolStripHostedControl, ToolStripControlHost? toolStripControlHost) : base(toolStripHostedControl, toolStripControlHost) + { + _owningTextBoxBase = toolStripHostedControl; + _textProvider = new TextBoxBaseUiaTextProvider(toolStripHostedControl); + UseTextProviders(_textProvider, _textProvider); + } + + internal override object? GetPropertyValue(UiaCore.UIA propertyID) => + propertyID switch + { + UiaCore.UIA.ControlTypePropertyId => UiaCore.UIA.EditControlTypeId, + UiaCore.UIA.NamePropertyId => Name, + UiaCore.UIA.IsTextPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.TextPatternId), + UiaCore.UIA.IsTextPattern2AvailablePropertyId => IsPatternSupported(UiaCore.UIA.TextPattern2Id), + UiaCore.UIA.IsValuePatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.ValuePatternId), + _ => base.GetPropertyValue(propertyID) + }; + + internal override bool IsPatternSupported(UiaCore.UIA patternId) => + patternId switch + { + UiaCore.UIA.ValuePatternId => true, + UiaCore.UIA.TextPatternId => true, + UiaCore.UIA.TextPattern2Id => true, + _ => base.IsPatternSupported(patternId) + }; + + internal override bool IsReadOnly => _owningTextBoxBase.ReadOnly; + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.cs index 5cc6f8f5787..6bea036ed21 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ToolStripTextBox.cs @@ -10,13 +10,12 @@ using System.Runtime.InteropServices; using System.Windows.Forms.Design; using System.Windows.Forms.Layout; -using Microsoft.Win32; using static Interop; namespace System.Windows.Forms { [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.MenuStrip | ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.ContextMenuStrip)] - public class ToolStripTextBox : ToolStripControlHost + public partial class ToolStripTextBox : ToolStripControlHost { internal static readonly object s_eventTextBoxTextAlignChanged = new object(); internal static readonly object s_eventAcceptsTabChanged = new object(); @@ -502,311 +501,5 @@ public event EventHandler TextBoxTextAlignChanged public void SelectAll() { TextBox.SelectAll(); } public void Undo() { TextBox.Undo(); } #endregion - private class ToolStripTextBoxControl : TextBox - { - private bool _mouseIsOver; - private bool _isFontSet = true; - private bool _alreadyHooked; - - public ToolStripTextBoxControl() - { - // required to make the text box height match the combo. - Font = ToolStripManager.DefaultFont; - _isFontSet = false; - } - - // returns the distance from the client rect to the upper left hand corner of the control - private RECT AbsoluteClientRECT - { - get - { - RECT rect = new RECT(); - CreateParams cp = CreateParams; - - AdjustWindowRectEx(ref rect, cp.Style, false, cp.ExStyle); - - // the coordinates we get back are negative, we need to translate this back to positive. - int offsetX = -rect.left; // one to get back to 0,0, another to translate - int offsetY = -rect.top; - - // fetch the client rect, then apply the offset. - User32.GetClientRect(new HandleRef(this, Handle), ref rect); - - rect.left += offsetX; - rect.right += offsetX; - rect.top += offsetY; - rect.bottom += offsetY; - - return rect; - } - } - private Rectangle AbsoluteClientRectangle - { - get - { - RECT rect = AbsoluteClientRECT; - return Rectangle.FromLTRB(rect.top, rect.top, rect.right, rect.bottom); - } - } - - private ProfessionalColorTable ColorTable - { - get - { - if (Owner != null) - { - if (Owner.Renderer is ToolStripProfessionalRenderer renderer) - { - return renderer.ColorTable; - } - } - return ProfessionalColors.ColorTable; - } - } - - private bool IsPopupTextBox - { - get - { - return ((BorderStyle == BorderStyle.Fixed3D) && - (Owner != null && (Owner.Renderer is ToolStripProfessionalRenderer))); - } - } - - internal bool MouseIsOver - { - get { return _mouseIsOver; } - set - { - if (_mouseIsOver != value) - { - _mouseIsOver = value; - if (!Focused) - { - InvalidateNonClient(); - } - } - } - } - - public override Font Font - { - get => base.Font; - set - { - base.Font = value; - _isFontSet = ShouldSerializeFont(); - } - } - - public ToolStripTextBox Owner { get; set; } - - internal override bool SupportsUiaProviders => true; - - private unsafe void InvalidateNonClient() - { - if (!IsPopupTextBox) - { - return; - } - - RECT absoluteClientRectangle = AbsoluteClientRECT; - - // Get the total client area, then exclude the client by using XOR - using var hTotalRegion = new Gdi32.RegionScope(0, 0, Width, Height); - using var hClientRegion = new Gdi32.RegionScope( - absoluteClientRectangle.left, - absoluteClientRectangle.top, - absoluteClientRectangle.right, - absoluteClientRectangle.bottom); - using var hNonClientRegion = new Gdi32.RegionScope(0, 0, 0, 0); - - Gdi32.CombineRgn(hNonClientRegion, hTotalRegion, hClientRegion, Gdi32.RGN.XOR); - - // Call RedrawWindow with the region. - User32.RedrawWindow( - new HandleRef(this, Handle), - null, - hNonClientRegion, - User32.RDW.INVALIDATE | User32.RDW.ERASE | User32.RDW.UPDATENOW - | User32.RDW.ERASENOW | User32.RDW.FRAME); - } - - protected override void OnGotFocus(EventArgs e) - { - base.OnGotFocus(e); - InvalidateNonClient(); - } - - protected override void OnLostFocus(EventArgs e) - { - base.OnLostFocus(e); - InvalidateNonClient(); - } - - protected override void OnMouseEnter(EventArgs e) - { - base.OnMouseEnter(e); - MouseIsOver = true; - } - - protected override void OnMouseLeave(EventArgs e) - { - base.OnMouseLeave(e); - MouseIsOver = false; - } - - private void HookStaticEvents(bool hook) - { - if (hook) - { - if (!_alreadyHooked) - { - try - { - SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); - } - finally - { - _alreadyHooked = true; - } - } - } - else if (_alreadyHooked) - { - try - { - SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(OnUserPreferenceChanged); - } - finally - { - _alreadyHooked = false; - } - } - } - - private void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) - { - if (e.Category == UserPreferenceCategory.Window) - { - if (!_isFontSet) - { - Font = ToolStripManager.DefaultFont; - } - } - } - - protected override void OnVisibleChanged(EventArgs e) - { - base.OnVisibleChanged(e); - if (!Disposing && !IsDisposed) - { - HookStaticEvents(Visible); - } - } - - protected override AccessibleObject CreateAccessibilityInstance() - { - return new ToolStripTextBoxControlAccessibleObject(this, Owner); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - HookStaticEvents(false); - } - base.Dispose(disposing); - } - - private void WmNCPaint(ref Message m) - { - if (!IsPopupTextBox) - { - base.WndProc(ref m); - return; - } - - // Paint over the edges of the text box. - - // Note that GetWindowDC just calls GetDCEx with DCX_WINDOW | DCX_USESTYLE. - - using var hdc = new User32.GetDcScope(m.HWnd, IntPtr.Zero, User32.DCX.WINDOW | User32.DCX.USESTYLE); - if (hdc.IsNull) - { - throw new Win32Exception(); - } - - // Don't set the clipping region based on the WParam - windows seems to take out the two pixels intended for the non-client border. - - Color outerBorderColor = (MouseIsOver || Focused) ? ColorTable.TextBoxBorder : BackColor; - Color innerBorderColor = BackColor; - - if (!Enabled) - { - outerBorderColor = SystemColors.ControlDark; - innerBorderColor = SystemColors.Control; - } - - using Graphics g = hdc.CreateGraphics(); - Rectangle clientRect = AbsoluteClientRectangle; - - // Could have set up a clip and fill-rectangled, thought this would be faster. - using var brush = innerBorderColor.GetCachedSolidBrushScope(); - g.FillRectangle(brush, 0, 0, Width, clientRect.Top); // top border - g.FillRectangle(brush, 0, 0, clientRect.Left, Height); // left border - g.FillRectangle(brush, 0, clientRect.Bottom, Width, Height - clientRect.Height); // bottom border - g.FillRectangle(brush, clientRect.Right, 0, Width - clientRect.Right, Height); // right border - - // Paint the outside rect. - using var pen = outerBorderColor.GetCachedPenScope(); - g.DrawRectangle(pen, 0, 0, Width - 1, Height - 1); - - // We've handled WM_NCPAINT. - m.Result = IntPtr.Zero; - } - protected override void WndProc(ref Message m) - { - if (m.Msg == (int)User32.WM.NCPAINT) - { - WmNCPaint(ref m); - return; - } - else - { - base.WndProc(ref m); - } - } - } - - private class ToolStripTextBoxControlAccessibleObject : ToolStripHostedControlAccessibleObject - { - public ToolStripTextBoxControlAccessibleObject(Control toolStripHostedControl, ToolStripControlHost toolStripControlHost) : base(toolStripHostedControl, toolStripControlHost) - { - } - - internal override object GetPropertyValue(UiaCore.UIA propertyID) - { - switch (propertyID) - { - case UiaCore.UIA.ControlTypePropertyId: - return UiaCore.UIA.EditControlTypeId; - case UiaCore.UIA.NamePropertyId: - return Name; - } - - return base.GetPropertyValue(propertyID); - } - - internal override bool IsPatternSupported(UiaCore.UIA patternId) - { - if (patternId == UiaCore.UIA.ValuePatternId) - { - return true; - } - - return base.IsPatternSupported(patternId); - } - } } } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObjectTests.cs new file mode 100644 index 00000000000..e17f69c7fe5 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolStripTextBox.ToolStripTextBoxControlAccessibleObjectTests.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Xunit; +using static System.Windows.Forms.Control; +using static Interop; + +namespace System.Windows.Forms.Tests +{ + public class ToolStripTextBox_ToolStripTextBoxControlAccessibleObjectTests : IClassFixture + { + [WinFormsFact] + public void ToolStripTextBoxControlAccessibleObject_ctor_default() + { + using ToolStripTextBox toolStripTextBox = new ToolStripTextBox(); + TextBox textBox = toolStripTextBox.TextBox; + Type type = toolStripTextBox.GetType().GetNestedType("ToolStripTextBoxControlAccessibleObject", BindingFlags.NonPublic); + Assert.NotNull(type); + ControlAccessibleObject accessibleObject = (ControlAccessibleObject)Activator.CreateInstance(type, textBox, toolStripTextBox); + Assert.Equal(textBox, accessibleObject.Owner); + } + + [WinFormsFact] + public void ToolStripTextBoxControlAccessibleObject_ctor_ThrowsException_IfOwnerIsNull() + { + using ToolStripTextBox toolStripTextBox = new ToolStripTextBox(); + TextBox textBox = toolStripTextBox.TextBox; + Type type = toolStripTextBox.GetType().GetNestedType("ToolStripTextBoxControlAccessibleObject", BindingFlags.NonPublic); + Assert.NotNull(type); + Assert.Throws(() => Activator.CreateInstance(type, (Control)null, toolStripTextBox)); + } + + [WinFormsTheory] + [InlineData(true)] + [InlineData(false)] + public void ToolStripTextBoxControlAccessibleObject_IsReadOnly_IsExpected(bool readOnly) + { + using ToolStripTextBox toolStripTextBox = new ToolStripTextBox(); + TextBox textBox = toolStripTextBox.TextBox; + textBox.ReadOnly = readOnly; + AccessibleObject accessibleObject = textBox.AccessibilityObject; + Assert.Equal(readOnly, accessibleObject.IsReadOnly); + } + + [WinFormsTheory] + [InlineData((int)UiaCore.UIA.IsTextPatternAvailablePropertyId)] + [InlineData((int)UiaCore.UIA.IsTextPattern2AvailablePropertyId)] + [InlineData((int)UiaCore.UIA.IsValuePatternAvailablePropertyId)] + public void ToolStripTextBoxControlAccessibleObject_GetPropertyValue_PatternsSuported(int propertyID) + { + using ToolStripTextBox toolStripTextBox = new ToolStripTextBox(); + TextBox textBox = toolStripTextBox.TextBox; + AccessibleObject accessibleObject = textBox.AccessibilityObject; + Assert.True((bool)accessibleObject.GetPropertyValue((UiaCore.UIA)propertyID)); + } + + [WinFormsTheory] + [InlineData((int)UiaCore.UIA.ValuePatternId)] + [InlineData((int)UiaCore.UIA.TextPatternId)] + [InlineData((int)UiaCore.UIA.TextPattern2Id)] + public void ToolStripTextBoxControlAccessibleObject_IsPatternSupported_PatternsSuported(int patternId) + { + using ToolStripTextBox toolStripTextBox = new ToolStripTextBox(); + TextBox textBox = toolStripTextBox.TextBox; + AccessibleObject accessibleObject = textBox.AccessibilityObject; + Assert.True(accessibleObject.IsPatternSupported((UiaCore.UIA)patternId)); + } + } +}