| File: System\Windows\Forms\Internal\WinFormsUtils.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.ComponentModel; using System.Drawing; using System.Globalization; using System.Text; namespace System.Windows.Forms; internal sealed partial class WindowsFormsUtils { public const ContentAlignment AnyRightAlign = ContentAlignment.TopRight | ContentAlignment.MiddleRight | ContentAlignment.BottomRight; public const ContentAlignment AnyLeftAlign = ContentAlignment.TopLeft | ContentAlignment.MiddleLeft | ContentAlignment.BottomLeft; public const ContentAlignment AnyTopAlign = ContentAlignment.TopLeft | ContentAlignment.TopCenter | ContentAlignment.TopRight; public const ContentAlignment AnyBottomAlign = ContentAlignment.BottomLeft | ContentAlignment.BottomCenter | ContentAlignment.BottomRight; public const ContentAlignment AnyMiddleAlign = ContentAlignment.MiddleLeft | ContentAlignment.MiddleCenter | ContentAlignment.MiddleRight; public const ContentAlignment AnyCenterAlign = ContentAlignment.TopCenter | ContentAlignment.MiddleCenter | ContentAlignment.BottomCenter; /// <summary> /// The GetMessagePos function retrieves the cursor position for the last message /// retrieved by the GetMessage function. /// </summary> public static Point LastCursorPoint { get { int lastXY = (int)PInvoke.GetMessagePos(); return new Point(PARAM.SignedLOWORD(lastXY), PARAM.SignedHIWORD(lastXY)); } } /// <summary> /// If you want to know if a piece of text contains one and only one & /// this is your function. If you have a character "t" and want match it to &Text /// Control.IsMnemonic is a better bet. /// </summary> public static bool ContainsMnemonic([NotNullWhen(true)] string? text) { if (text is not null) { int textLength = text.Length; int firstAmpersand = text.IndexOf('&', 0); if (firstAmpersand >= 0 && firstAmpersand <= /*second to last char=*/textLength - 2) { // we found one ampersand and it's either the first character // or the second to last character // or a character in between // We're so close! make sure we don't have a double ampersand now. int secondAmpersand = text.IndexOf('&', firstAmpersand + 1); if (secondAmpersand == -1) { // didn't find a second one in the string. return true; } } } return false; } internal static Rectangle ConstrainToScreenWorkingAreaBounds(Rectangle bounds) { return ConstrainToBounds(Screen.GetWorkingArea(bounds), bounds); } /// <summary> /// Given a rectangle, constrain it to fit onto the current screen. /// </summary> internal static Rectangle ConstrainToScreenBounds(Rectangle bounds) { return ConstrainToBounds(Screen.FromRectangle(bounds).Bounds, bounds); } internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds) { // use screen instead of SystemInformation.WorkingArea for better multimon support. if (!constrainingBounds.Contains(bounds)) { // make sure size does not exceed working area. bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height)); // X calculations // // scooch so it will fit on the screen. if (bounds.Right > constrainingBounds.Right) { // its too far to the right. bounds.X = constrainingBounds.Right - bounds.Width; } else if (bounds.Left < constrainingBounds.Left) { // its too far to the left. bounds.X = constrainingBounds.Left; } // Y calculations // // scooch so it will fit on the screen. if (bounds.Bottom > constrainingBounds.Bottom) { // its too far to the bottom. bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height; } else if (bounds.Top < constrainingBounds.Top) { // its too far to the top. bounds.Y = constrainingBounds.Top; } } return bounds; } /// <summary> /// Adds an extra & to to the text so that "Fish & Chips" can be displayed on a menu item /// without underlining anything. /// Fish & Chips --> Fish && Chips /// </summary> internal static string? EscapeTextWithAmpersands(string? text) { if (text is null) { return null; } int index = text.IndexOf('&'); if (index == -1) { return text; } StringBuilder str = new(text.Length); str.Append(text.AsSpan(0, index)); for (; index < text.Length; ++index) { if (text[index] == '&') { str.Append('&'); } str.Append(text[index]); } return str.ToString(); } /// <summary> /// Helper function for generating information about a particular control. /// </summary> internal static string GetControlInformation(HWND hwnd) { if (hwnd.IsNull) { return "Handle is null"; } #if DEBUG string windowText = PInvokeCore.GetWindowText(hwnd); string typeOfControl = "Unknown"; string nameOfControl = ""; Control? c = Control.FromHandle(hwnd); if (c is not null) { typeOfControl = c.GetType().ToString(); if (!string.IsNullOrEmpty(c.Name)) { nameOfControl = c.Name; } else { nameOfControl = "Unknown"; // Add some extra debug info for ToolStripDropDowns. if (c is ToolStripDropDown dd && dd.OwnerItem is not null) { nameOfControl += $"{Environment.NewLine}\tOwnerItem: {dd.OwnerItem}"; } } } return $""" {windowText} Type: {typeOfControl} Name: {nameOfControl} """; #else return string.Empty; #endif } /// <summary> /// Retrieves the mnemonic from a given string, or zero if no mnemonic. /// As used by the Control.Mnemonic to get mnemonic from Control.Text. /// </summary> public static char GetMnemonic(string? text, bool convertToUpperCase) { char mnemonic = '\0'; if (text is not null) { int len = text.Length; for (int i = 0; i < len - 1; i++) { if (text[i] == '&') { if (text[i + 1] == '&') { // we have an escaped &, so we need to skip it. i++; continue; } if (convertToUpperCase) { mnemonic = char.ToUpper(text[i + 1], CultureInfo.CurrentCulture); } else { mnemonic = char.ToLower(text[i + 1], CultureInfo.CurrentCulture); } break; } } } return mnemonic; } /// <summary> /// Strips all keyboard mnemonic prefixes from a given string, eg. turning "He&lp" into "Help". /// </summary> /// <remarks> /// <para> /// Note: Be careful not to call this multiple times on the same string, otherwise you'll turn /// something like "Fi&sh && Chips" into "Fish & Chips" on the first call, and then "Fish Chips" /// on the second call. /// </para> /// </remarks> [return: NotNullIfNotNull(nameof(text))] public static string? TextWithoutMnemonics(string? text) { if (text is null) { return null; } int index = text.IndexOf('&'); if (index == -1) { return text; } StringBuilder str = new(text.Length); str.Append(text.AsSpan(0, index)); for (; index < text.Length; ++index) { if (text[index] == '&') { // Skip this & and copy the next character instead index++; } if (index < text.Length) { str.Append(text[index]); } } return str.ToString(); } /// <summary> /// Translates a point from one control's coordinate system to the other. /// </summary> /// <remarks> /// <para> /// Same as controlTo.PointToClient(controlFrom.PointToScreen(point)), but more slightly more performant. /// </para> /// </remarks> public static Point TranslatePoint(Point point, Control fromControl, Control toControl) { PInvokeCore.MapWindowPoints(fromControl, toControl, ref point); return point; } /// <summary> /// Compares the strings using invariant culture for Turkish-I support. Returns true if they match. /// /// If your strings are symbolic (returned from APIs, not from user) the following calls /// are faster than this method: /// /// String.Equals(s1, s2, StringComparison.Ordinal) /// String.Equals(s1, s2, StringComparison.OrdinalIgnoreCase) /// </summary> public static bool SafeCompareStrings(string? string1, string? string2, bool ignoreCase) { if ((string1 is null) || (string2 is null)) { // if either key is null, we should return false return false; } // Because String.Compare returns an ordering, it can not terminate early if lengths are not the same. // Also, equivalent characters can be encoded in different byte sequences, so it can not necessarily // terminate on the first byte which doesn't match. Hence this optimization. if (string1.Length != string2.Length) { return false; } return string.Compare(string1, string2, ignoreCase, CultureInfo.InvariantCulture) == 0; } public static string GetComponentName(IComponent component, string? defaultNameValue) { Debug.Assert(component is not null, "component passed here cannot be null"); if (string.IsNullOrEmpty(defaultNameValue)) { return component.Site?.Name ?? string.Empty; } else { return defaultNameValue; } } }