From e86ca21c812cd9e2d4e959a7a941acb3cc6a4c6f Mon Sep 17 00:00:00 2001 From: Klaus Loeffelmann Date: Fri, 14 Aug 2026 09:31:13 -0700 Subject: [PATCH 01/12] Revert NodeLeading and related logic from TreeView. --- .../PublicAPI.Unshipped.txt | 4 - src/System.Windows.Forms/Resources/SR.resx | 6 - .../Resources/xlf/SR.cs.xlf | 10 - .../Resources/xlf/SR.de.xlf | 10 - .../Resources/xlf/SR.es.xlf | 10 - .../Resources/xlf/SR.fr.xlf | 10 - .../Resources/xlf/SR.it.xlf | 10 - .../Resources/xlf/SR.ja.xlf | 10 - .../Resources/xlf/SR.ko.xlf | 10 - .../Resources/xlf/SR.pl.xlf | 10 - .../Resources/xlf/SR.pt-BR.xlf | 10 - .../Resources/xlf/SR.ru.xlf | 10 - .../Resources/xlf/SR.tr.xlf | 10 - .../Resources/xlf/SR.zh-Hans.xlf | 10 - .../Resources/xlf/SR.zh-Hant.xlf | 10 - .../TreeView/TreeView.SuspendMutation.cs | 2 +- .../Forms/Controls/TreeView/TreeView.cs | 256 ++-------------- .../System/Windows/Forms/TreeViewTests.cs | 281 ------------------ 18 files changed, 21 insertions(+), 658 deletions(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 1b299c8b911..3e9bcdd6360 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -25,10 +25,6 @@ System.Windows.Forms.SystemVisualSettingsChangedEventArgs.OldSettings.get -> Sys System.Windows.Forms.SystemVisualSettingsChangedEventArgs System.Windows.Forms.SystemVisualSettingsChangedEventHandler virtual System.Windows.Forms.SystemVisualSettingsChangedEventHandler.Invoke(object? sender, System.Windows.Forms.SystemVisualSettingsChangedEventArgs! e) -> void -System.Windows.Forms.TreeView.NodeLeading.get -> float -System.Windows.Forms.TreeView.NodeLeading.set -> void -System.Windows.Forms.TreeView.NodeLeadingChanged -> System.EventHandler? -virtual System.Windows.Forms.TreeView.OnNodeLeadingChanged(System.EventArgs! e) -> void System.Windows.Forms.ControlMutationExtensions static System.Windows.Forms.ControlMutationExtensions.SuspendPainting(this System.Windows.Forms.ISupportSuspendPainting! target) -> System.IDisposable! static System.Windows.Forms.ControlMutationExtensions.SuspendPainting(this System.Windows.Forms.ISupportSuspendPainting! target, System.Func! suspendLayoutContainerFilter) -> System.IDisposable! diff --git a/src/System.Windows.Forms/Resources/SR.resx b/src/System.Windows.Forms/Resources/SR.resx index d3a89671107..7b33b7618cf 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -6337,9 +6337,6 @@ Stack trace where the illegal operation occurred was: The color of the lines that connect the nodes of the TreeView. - - The uniform row-height leading factor applied to all nodes. - Occurs when a node is clicked with the mouse. @@ -6355,9 +6352,6 @@ Stack trace where the illegal operation occurred was: The sorting comparer for the TreeView. - - Occurs when the value of the NodeLeading property changes. - The string delimiter used for the path returned by a node's FullPath property. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf index 3516b3584de..7048c4940ae 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf @@ -11059,11 +11059,6 @@ Trasování zásobníku, kde došlo k neplatné operaci: Barva čar spojujících uzly ovládacího prvku TreeView - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Vyvolá se při kliknutí myší na uzel. @@ -11089,11 +11084,6 @@ Trasování zásobníku, kde došlo k neplatné operaci: Kořenové uzly v ovládacím prvku TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Oddělovač řetězců použitý pro cesty vracené vlastností FullPath uzlu. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf index 1b1a7896269..135b5077458 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf @@ -11059,11 +11059,6 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Die Farbe der Linien, die die Knoten der TreeView verbinden. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Tritt auf, wenn mit der Maus auf einen Knoten geklickt wird. @@ -11089,11 +11084,6 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Die Stammknoten im TreeView-Steuerelement. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Das Zeichenfolgen-Trennzeichen für den Pfad, der von der FullPath-Eigenschaft eines Knotens zurückgegeben wird. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf index 650b5dc8f1c..0ff0c395387 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf @@ -11059,11 +11059,6 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: Color de las líneas que conectan los nodos en TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Tiene lugar cuando se hace clic con el mouse en un nodo. @@ -11089,11 +11084,6 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: Nodos raíz en el control TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Delimitador de cadena utilizado para la ruta de acceso devuelta por la propiedad FullPath de un nodo. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf index 762393d64b1..06ff6ad95ed 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf @@ -11059,11 +11059,6 @@ Cette opération non conforme s'est produite sur la trace de la pile : La couleur des lignes qui connectent les nœuds du TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Se produit lors d'un clic de souris sur un nœud. @@ -11089,11 +11084,6 @@ Cette opération non conforme s'est produite sur la trace de la pile : Les nœuds racine dans le contrôle TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Le délimiteur de chaîne utilisé pour le chemin d'accès retourné par la propriété FullPath d'un nœud. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf index 3c34571e6df..1cf670682a4 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf @@ -11059,11 +11059,6 @@ Analisi dello stack dove si è verificata l'operazione non valida: Il colore delle righe che connettono i nodi del controllo TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Generato quando si fa clic con il mouse su un nodo. @@ -11089,11 +11084,6 @@ Analisi dello stack dove si è verificata l'operazione non valida: I nodi radice nel controllo TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Il delimitatore di stringa utilizzato per il percorso restituito dalla proprietà FullPath di un nodo. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf index 05f5f03a962..617f5222089 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf @@ -11059,11 +11059,6 @@ Stack trace where the illegal operation occurred was: ツリー ビューのノード間を接続する線の色です。 - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. ノードがマウスでクリックされたときに発生します。 @@ -11089,11 +11084,6 @@ Stack trace where the illegal operation occurred was: TreeView コントロールのルートのノードです。 - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. ノードの FullPath プロパティにより返されるパスで使用する、文字列の区切り文字です。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf index 06b9e480dcb..02d54abd10f 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf @@ -11059,11 +11059,6 @@ Stack trace where the illegal operation occurred was: TreeView의 노드를 연결하는 줄의 색입니다. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. 마우스로 노드를 클릭할 때 발생합니다. @@ -11089,11 +11084,6 @@ Stack trace where the illegal operation occurred was: TreeView 컨트롤의 루트 노드입니다. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. 노드의 FullPath 속성으로 반환된 경로에 사용되는 문자열 구분 기호입니다. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf index 5f0edef73e8..edd92bf4d3c 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf @@ -11059,11 +11059,6 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Kolor linii łączących węzły elementu TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Występuje, gdy węzeł zostanie kliknięty przy użyciu myszy. @@ -11089,11 +11084,6 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Węzły główne w formancie TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Ogranicznik ciągu używany w ścieżce zwracanej przez właściwość FullPath węzła. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf index 94b1dd779a0..b4b0b95002b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf @@ -11059,11 +11059,6 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: A cor das linhas que conectam os nós de TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Ocorre quando o usuário clica com o mouse em um nó. @@ -11089,11 +11084,6 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: Nós raiz no controle TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. O delimitador de cadeia de caracteres usado para o caminho retornado pela propriedade FullPath de um nó. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf index 31f2fc77cf5..c2cc58299ee 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf @@ -11059,11 +11059,6 @@ Stack trace where the illegal operation occurred was: Цвет линий, соединяющих узлы в TreeView. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Возникает при щелчке мыши на узле. @@ -11089,11 +11084,6 @@ Stack trace where the illegal operation occurred was: Корневые узлы в элементе управления TreeView. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Разделитель строк в пути, возвращаемом свойством FullPath узла. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf index 2388df399e0..bec94a1e2d7 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf @@ -11059,11 +11059,6 @@ Geçersiz işlemin gerçekleştiği yığın izi: TreeView'in düğümlerini bağlayan çizgilerin rengi. - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. Öğe fareyle tıklatıldığında gerçekleşir. @@ -11089,11 +11084,6 @@ Geçersiz işlemin gerçekleştiği yığın izi: TreeView denetiminde kök düğümler. - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. Düğümün FullPath özelliği tarafından döndürülen yol için kullanılan dize sınırlayıcı. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf index 3f81edc8ec1..11e042ab8a5 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf @@ -11059,11 +11059,6 @@ Stack trace where the illegal operation occurred was: 连接树视图节点的线条的颜色。 - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. 用鼠标单击节点时发生。 @@ -11089,11 +11084,6 @@ Stack trace where the illegal operation occurred was: TreeView 控件中的根节点。 - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. 用于由节点的 FullPath 属性返回的路径的字符串分隔符。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf index 72b32f99c14..afb3ff9a6b6 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf @@ -11059,11 +11059,6 @@ Stack trace where the illegal operation occurred was: 連接樹狀檢視節點的線條色彩。 - - The uniform row-height leading factor applied to all nodes. - The uniform row-height leading factor applied to all nodes. - - Occurs when a node is clicked with the mouse. 以滑鼠按一下節點時發生。 @@ -11089,11 +11084,6 @@ Stack trace where the illegal operation occurred was: TreeView 控制項中的根節點。 - - Occurs when the value of the NodeLeading property changes. - Occurs when the value of the NodeLeading property changes. - - The string delimiter used for the path returned by a node's FullPath property. 節點 FullPath 屬性用來傳回路徑的字串分隔符號。 diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.SuspendMutation.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.SuspendMutation.cs index 1c738bb8360..8923621088d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.SuspendMutation.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.SuspendMutation.cs @@ -18,7 +18,7 @@ protected override void EndSuspendPaintingCore() { if (EndSuspendPaintingScope()) { - EndUpdate(invalidate: !IsRecursiveInvalidateAfterSuspendPaintingRequested); + EndUpdateInternal(invalidate: !IsRecursiveInvalidateAfterSuspendPaintingRequested); } } #endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs index 779bfa58231..70a54a573c4 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -43,7 +43,6 @@ public partial class TreeView : Control private TreeViewEventHandler? _onAfterSelect; private ItemDragEventHandler? _onItemDrag; private TreeNodeMouseHoverEventHandler? _onNodeMouseHover; - private EventHandler? _onNodeLeadingChanged; private EventHandler? _onRightToLeftLayoutChanged; internal TreeNode? _selectedNode; @@ -79,7 +78,6 @@ public partial class TreeView : Control private Collections.Specialized.BitVector32 _treeViewState; // see TREEVIEWSTATE_ constants above private static bool s_isScalingInitialized; - private static readonly int s_nodeLeadingProperty = PropertyStore.CreateKey(); private static Size? s_stateImageSize; private static Size? StateImageSize { @@ -119,30 +117,6 @@ internal ImageList.Indexer SelectedImageIndexer } } - private bool RequiresNonEvenHeightStyle - { - get - { -#if NET11_0_OR_GREATER - return _setOddHeight || UsesNodeLeading; -#else - return _setOddHeight; -#endif - } - } - - private bool UsesNodeLeading - { - get - { -#if NET11_0_OR_GREATER - return NodeLeading != 0.0f; -#else - return false; -#endif - } - } - private ImageList? _imageList; private int _indent = -1; private int _itemHeight = -1; @@ -196,11 +170,6 @@ public TreeView() SetStyle(ControlStyles.UserPaint, false); SetStyle(ControlStyles.StandardClick, false); SetStyle(ControlStyles.UseTextForAccessibility, false); - -#if NET11_0_OR_GREATER - FontChanged += HandleNodeLeadingMetricChanged; - DpiChangedAfterParent += HandleNodeLeadingMetricChanged; -#endif } internal override void ReleaseUiaProvider(HWND handle) @@ -397,7 +366,7 @@ protected override CreateParams CreateParams cp.Style |= (int)PInvoke.TVS_FULLROWSELECT; } - if (RequiresNonEvenHeightStyle) + if (_setOddHeight) { cp.Style |= (int)PInvoke.TVS_NONEVENHEIGHT; } @@ -791,79 +760,6 @@ public int Indent } } -#if NET11_0_OR_GREATER - /// - /// Gets or sets the per-node vertical leading factor applied on top of the live height. - /// Default 0.0f selects legacy behavior. - /// - /// - /// A non-negative . 0.0f (default) selects legacy behavior. - /// 1.0f opts in to the honest row-height calculation. Values greater than 1.0f add extra leading. - /// - /// - /// - /// This property applies uniformly to all nodes in the control. It is not a per-node knob, and per-node row - /// heights are not supported by the native control. - /// - /// - /// 0.0f keeps the legacy behavior, including the managed FontHeight + 3 - /// estimate and the default even-height rounding. 1.0f opts in to a row height derived from the live - /// height with TVS_NONEVENHEIGHT enabled. Other positive values scale the - /// honest base for denser or airier rows. - /// - /// - /// The factor is dimensionless. The resulting pixels scale with , whose height already - /// carries the current DPI, so the factor itself does not scale with DPI. - /// - /// - /// "Leading" (pronounced "ledding") is a typesetting term from the strips of lead metal once placed between - /// lines of type to add vertical spacing; it is unrelated to leading or guiding. - /// - /// - /// - /// The value is negative or not finite. - /// - [SRCategory(nameof(SR.CatAppearance))] - [DefaultValue(0.0f)] - [SRDescription(nameof(SR.TreeViewNodeLeadingDescr))] - public float NodeLeading - { - get => Properties.GetValueOrDefault(s_nodeLeadingProperty, 0.0f); - set - { - if (!float.IsFinite(value)) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - ArgumentOutOfRangeException.ThrowIfNegative(value); - - float oldValue = NodeLeading; - if (oldValue != value) - { - bool oldUsesNodeLeading = oldValue != 0.0f; - bool newUsesNodeLeading = value != 0.0f; - - Properties.AddOrRemoveValue(s_nodeLeadingProperty, value, defaultValue: 0.0f); - - if (IsHandleCreated) - { - if (oldUsesNodeLeading != newUsesNodeLeading) - { - RecreateHandle(); - } - else if (newUsesNodeLeading) - { - ApplyItemHeightFromCurrentSettings(); - } - } - - OnNodeLeadingChanged(EventArgs.Empty); - } - } - } -#endif - /// /// The height of every item in the tree view, in pixels. /// @@ -873,18 +769,6 @@ public int ItemHeight { get { -#if NET11_0_OR_GREATER - if (UsesNodeLeading) - { - if (IsHandleCreated) - { - return (int)PInvokeCore.SendMessage(this, PInvoke.TVM_GETITEMHEIGHT); - } - - return GetNodeLeadingItemHeight(); - } -#endif - if (_itemHeight != -1) { return _itemHeight; @@ -914,28 +798,21 @@ public int ItemHeight _itemHeight = value; if (IsHandleCreated) { - if (UsesNodeLeading) - { - ApplyItemHeightFromCurrentSettings(); - } - else + if (_itemHeight % 2 != 0) { - if (_itemHeight % 2 != 0) + _setOddHeight = true; + try { - _setOddHeight = true; - try - { - RecreateHandle(); - } - finally - { - _setOddHeight = false; - } + RecreateHandle(); + } + finally + { + _setOddHeight = false; } - - PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)value); - _itemHeight = (int)PInvokeCore.SendMessage(this, PInvoke.TVM_GETITEMHEIGHT); } + + PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)value); + _itemHeight = (int)PInvokeCore.SendMessage(this, PInvoke.TVM_GETITEMHEIGHT); } } } @@ -1604,19 +1481,6 @@ public event EventHandler? RightToLeftLayoutChanged remove => _onRightToLeftLayoutChanged -= value; } -#if NET11_0_OR_GREATER - /// - /// Occurs when the value of changes. - /// - [SRCategory(nameof(SR.CatPropertyChanged))] - [SRDescription(nameof(SR.TreeViewOnNodeLeadingChangedDescr))] - public event EventHandler? NodeLeadingChanged - { - add => _onNodeLeadingChanged += value; - remove => _onNodeLeadingChanged -= value; - } -#endif - /// /// Disables redrawing of the tree view. A call to beginUpdate() must be /// balanced by a following call to endUpdate(). Following a call to @@ -1705,9 +1569,10 @@ protected override void Dispose(bool disposing) /// beginUpdate(), any redrawing caused by operations performed on the /// combo box is deferred until the call to endUpdate(). /// - public void EndUpdate() => EndUpdate(invalidate: true); - - private void EndUpdate(bool invalidate) => EndUpdateInternal(invalidate); + public void EndUpdate() + { + EndUpdateInternal(); + } /// /// Expands all nodes at the root level. @@ -1931,16 +1796,6 @@ private void StateImageListChangedHandle(object? sender, EventArgs e) } } -#if NET11_0_OR_GREATER - private void HandleNodeLeadingMetricChanged(object? sender, EventArgs e) - { - if (UsesNodeLeading) - { - ApplyItemHeightFromCurrentSettings(); - } - } -#endif - /// /// Overridden to handle RETURN key. /// @@ -2075,7 +1930,10 @@ protected override void OnHandleCreated(EventArgs e) PInvokeCore.SendMessage(this, PInvoke.TVM_SETINDENT, (WPARAM)_indent); } - ApplyItemHeightFromCurrentSettings(); + if (_itemHeight != -1) + { + PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)ItemHeight); + } // Essentially we are setting the width to be infinite so that the // TreeView never thinks it needs a scrollbar when the first node is created @@ -2113,7 +1971,6 @@ protected override void OnHandleCreated(EventArgs e) flags); } } - finally { _treeViewState[TREEVIEWSTATE_stopResizeWindowMsgs] = false; @@ -2479,23 +2336,6 @@ protected virtual void OnRightToLeftLayoutChanged(EventArgs e) _onRightToLeftLayoutChanged?.Invoke(this, e); } -#if NET11_0_OR_GREATER - /// - /// Raises the event. - /// - /// An that contains the event data. - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnNodeLeadingChanged(EventArgs e) - { - if (GetAnyDisposingInHierarchy()) - { - return; - } - - _onNodeLeadingChanged?.Invoke(this, e); - } -#endif - // Refresh the nodes by clearing the tree and adding the nodes back again // private void RefreshNodes() @@ -2526,13 +2366,6 @@ private void ResetItemHeight() RecreateHandle(); } -#if NET11_0_OR_GREATER - /// - /// This resets the node leading factor to the legacy default. - /// - private void ResetNodeLeading() => NodeLeading = 0.0f; -#endif - /// /// Retrieves true if the indent should be persisted in code gen. /// @@ -2543,10 +2376,6 @@ private void ResetItemHeight() /// private bool ShouldSerializeItemHeight() => (_itemHeight != -1); -#if NET11_0_OR_GREATER - private bool ShouldSerializeNodeLeading() => Properties.ContainsKey(s_nodeLeadingProperty); -#endif - private bool ShouldSerializeSelectedImageIndex() { if (_imageList is not null) @@ -2567,51 +2396,6 @@ private bool ShouldSerializeImageIndex() return ImageIndex != ImageList.Indexer.DefaultIndex; } - private void ApplyItemHeightFromCurrentSettings() - { - if (!IsHandleCreated) - { - return; - } - -#if NET11_0_OR_GREATER - if (UsesNodeLeading) - { - int itemHeight = GetNodeLeadingItemHeight(); - if ((int)PInvokeCore.SendMessage(this, PInvoke.TVM_GETITEMHEIGHT) != itemHeight) - { - PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)itemHeight); - } - - return; - } -#endif - - if (_itemHeight != -1) - { - PInvokeCore.SendMessage(this, PInvoke.TVM_SETITEMHEIGHT, (WPARAM)ItemHeight); - } - } - -#if NET11_0_OR_GREATER - private int GetNodeLeadingItemHeight() - { - double itemHeight = Math.Ceiling(NodeLeading * Font.Height); - - if (itemHeight < 1) - { - return 1; - } - - if (itemHeight >= short.MaxValue) - { - return short.MaxValue - 1; - } - - return (int)itemHeight; - } -#endif - /// /// Updated the sorted order /// diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TreeViewTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TreeViewTests.cs index 7dff39ca7ef..230d7b81490 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TreeViewTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TreeViewTests.cs @@ -2731,279 +2731,6 @@ public void ItemHeight_Get_ReturnsExpected(Font font, bool checkBoxes, TreeViewD Assert.Equal(expectedHeight, treeView.ItemHeight); } -#if NET11_0_OR_GREATER - public static IEnumerable NodeLeading_Set_TestData() - { - yield return new object[] { 0.25f }; - yield return new object[] { 1.0f }; - yield return new object[] { 1.25f }; - } - - public static IEnumerable NodeLeading_SetWithHandle_TestData() - { - yield return new object[] { 1.0f }; - yield return new object[] { 1.25f }; - } - - public static IEnumerable NodeLeading_SetInvalid_TestData() - { - yield return new object[] { -0.1f }; - yield return new object[] { float.NaN }; - yield return new object[] { float.PositiveInfinity }; - yield return new object[] { float.NegativeInfinity }; - } - - [WinFormsFact] - public void TreeView_NodeLeading_DefaultValue() - { - using SubTreeView control = new(); - - Assert.Equal(0.0f, control.NodeLeading); - Assert.Equal(0, control.CreateParams.Style & (int)PInvoke.TVS_NONEVENHEIGHT); - Assert.False(control.IsHandleCreated); - } - - [WinFormsTheory] - [MemberData(nameof(ItemHeight_Get_TestData))] - public void TreeView_NodeLeadingDefault_ItemHeightGet_ReturnsLegacyExpected(Font font, bool checkBoxes, TreeViewDrawMode drawMode, int expectedHeight) - { - using TreeView control = new() - { - Font = font, - CheckBoxes = checkBoxes, - DrawMode = drawMode, - NodeLeading = 0.0f - }; - - Assert.Equal(expectedHeight, control.ItemHeight); - Assert.False(control.IsHandleCreated); - } - - [WinFormsTheory] - [MemberData(nameof(NodeLeading_Set_TestData))] - public void TreeView_NodeLeading_Set_GetReturnsExpected(float value) - { - using SubTreeView control = new() - { - NodeLeading = value - }; - - Assert.Equal(value, control.NodeLeading); - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, value), control.ItemHeight); - Assert.Equal((int)PInvoke.TVS_NONEVENHEIGHT, control.CreateParams.Style & (int)PInvoke.TVS_NONEVENHEIGHT); - Assert.False(control.IsHandleCreated); - - control.NodeLeading = value; - Assert.Equal(value, control.NodeLeading); - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, value), control.ItemHeight); - Assert.False(control.IsHandleCreated); - } - - [WinFormsTheory] - [MemberData(nameof(NodeLeading_SetWithHandle_TestData))] - public void TreeView_NodeLeading_SetWithHandle_GetReturnsExpected(float value) - { - using SubTreeView control = new(); - Assert.NotEqual(IntPtr.Zero, control.Handle); - int invalidatedCallCount = 0; - control.Invalidated += (sender, e) => invalidatedCallCount++; - int styleChangedCallCount = 0; - control.StyleChanged += (sender, e) => styleChangedCallCount++; - int createdCallCount = 0; - control.HandleCreated += (sender, e) => createdCallCount++; - - control.NodeLeading = value; - Assert.Equal(value, control.NodeLeading); - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, value), control.ItemHeight); - Assert.Equal((int)PInvoke.TVS_NONEVENHEIGHT, control.CreateParams.Style & (int)PInvoke.TVS_NONEVENHEIGHT); - Assert.True(control.IsHandleCreated); - Assert.Equal(0, invalidatedCallCount); - Assert.Equal(0, styleChangedCallCount); - Assert.Equal(1, createdCallCount); - - control.NodeLeading = value; - Assert.Equal(value, control.NodeLeading); - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, value), control.ItemHeight); - Assert.True(control.IsHandleCreated); - Assert.Equal(0, invalidatedCallCount); - Assert.Equal(0, styleChangedCallCount); - Assert.Equal(1, createdCallCount); - } - - [WinFormsTheory] - [MemberData(nameof(NodeLeading_SetInvalid_TestData))] - public void TreeView_NodeLeading_SetInvalid_ThrowsArgumentOutOfRangeException(float value) - { - using TreeView control = new(); - Assert.Throws("value", () => control.NodeLeading = value); - } - - [WinFormsFact] - public void TreeView_NodeLeading_SetWithHandler_CallsNodeLeadingChanged() - { - using TreeView control = new(); - int callCount = 0; - EventHandler handler = (sender, e) => - { - Assert.Same(control, sender); - Assert.Same(EventArgs.Empty, e); - callCount++; - }; - - control.NodeLeadingChanged += handler; - - control.NodeLeading = 1.0f; - Assert.Equal(1, callCount); - - control.NodeLeading = 1.0f; - Assert.Equal(1, callCount); - - control.NodeLeading = 1.25f; - Assert.Equal(2, callCount); - - control.NodeLeadingChanged -= handler; - control.NodeLeading = 0.0f; - Assert.Equal(2, callCount); - } - - [WinFormsTheory] - [NewAndDefaultData] - public void TreeView_OnNodeLeadingChanged_Invoke_CallsNodeLeadingChanged(EventArgs eventArgs) - { - using SubTreeView control = new(); - int callCount = 0; - EventHandler handler = (sender, e) => - { - Assert.Same(control, sender); - Assert.Same(eventArgs, e); - callCount++; - }; - - control.NodeLeadingChanged += handler; - control.OnNodeLeadingChanged(eventArgs); - Assert.Equal(1, callCount); - Assert.False(control.IsHandleCreated); - - control.NodeLeadingChanged -= handler; - control.OnNodeLeadingChanged(eventArgs); - Assert.Equal(1, callCount); - Assert.False(control.IsHandleCreated); - } - - [WinFormsFact] - public void TreeView_NodeLeading_PrecedesItemHeightWithHandle() - { - using SubTreeView control = new() - { - ItemHeight = 42 - }; - Assert.NotEqual(IntPtr.Zero, control.Handle); - int createdCallCount = 0; - control.HandleCreated += (sender, e) => createdCallCount++; - - control.NodeLeading = 1.0f; - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, 1.0f), control.ItemHeight); - Assert.Equal((int)PInvoke.TVS_NONEVENHEIGHT, control.CreateParams.Style & (int)PInvoke.TVS_NONEVENHEIGHT); - Assert.Equal(1, createdCallCount); - - control.ItemHeight = 24; - Assert.Equal(GetExpectedNodeLeadingItemHeight(control.Font, 1.0f), control.ItemHeight); - Assert.Equal(1, createdCallCount); - - control.NodeLeading = 0.0f; - Assert.Equal(24, control.ItemHeight); - Assert.Equal(0, control.CreateParams.Style & (int)PInvoke.TVS_NONEVENHEIGHT); - Assert.Equal(2, createdCallCount); - } - - [WinFormsFact] - public void TreeView_NodeLeading_SetFontWithHandle_UpdatesItemHeight() - { - using Font initialFont = new(FontFamily.GenericSansSerif, 8.0f); - using Font updatedFont = new(FontFamily.GenericSansSerif, 20.0f); - using TreeView control = new() - { - Font = initialFont, - NodeLeading = 1.25f - }; - Assert.NotEqual(IntPtr.Zero, control.Handle); - Assert.Equal(GetExpectedNodeLeadingItemHeight(initialFont, 1.25f), control.ItemHeight); - - control.Font = updatedFont; - Assert.Equal(GetExpectedNodeLeadingItemHeight(updatedFont, 1.25f), control.ItemHeight); - } - - [WinFormsFact] - public void TreeView_NodeLeading_OnDpiChangedAfterParentWithHandle_UpdatesItemHeight() - { - using SubTreeView control = new() - { - NodeLeading = 1.25f - }; - Assert.NotEqual(IntPtr.Zero, control.Handle); - int expectedItemHeight = GetExpectedNodeLeadingItemHeight(control.Font, control.NodeLeading); - - PInvokeCore.SendMessage( - control, - PInvoke.TVM_SETITEMHEIGHT, - (WPARAM)(expectedItemHeight + 1)); - Assert.Equal(expectedItemHeight + 1, control.ItemHeight); - - control.OnDpiChangedAfterParent(EventArgs.Empty); - - Assert.Equal(expectedItemHeight, control.ItemHeight); - } - - [WinFormsFact] - public void TreeView_ResetNodeLeading_Invoke_Success() - { - using TreeView treeView = new() - { - NodeLeading = 1.25f - }; - - var accessor = treeView.TestAccessor; - accessor.Dynamic.ResetNodeLeading(); - - Assert.Equal(0.0f, treeView.NodeLeading); - } - - [WinFormsFact] - public void TreeView_ShouldSerializeNodeLeading_Invoke_ReturnsExpected() - { - using TreeView treeView = new(); - - var accessor = treeView.TestAccessor; - bool result = accessor.Dynamic.ShouldSerializeNodeLeading(); - Assert.False(result); - - treeView.NodeLeading = 1.25f; - result = accessor.Dynamic.ShouldSerializeNodeLeading(); - Assert.True(result); - - accessor.Dynamic.ResetNodeLeading(); - result = accessor.Dynamic.ShouldSerializeNodeLeading(); - Assert.False(result); - } - - private static int GetExpectedNodeLeadingItemHeight(Font font, float nodeLeading) - { - double itemHeight = Math.Ceiling(nodeLeading * font.Height); - - if (itemHeight < 1) - { - return 1; - } - - if (itemHeight >= short.MaxValue) - { - return short.MaxValue - 1; - } - - return (int)itemHeight; - } -#endif - public static IEnumerable ItemHeight_Set_TestData() { yield return new object[] { -1, Control.DefaultFont.Height + 3 }; @@ -7924,10 +7651,6 @@ private class SubTreeView : TreeView public new void OnDrawNode(DrawTreeNodeEventArgs e) => base.OnDrawNode(e); -#if NET11_0_OR_GREATER - public new void OnDpiChangedAfterParent(EventArgs e) => base.OnDpiChangedAfterParent(e); -#endif - public new void OnItemDrag(ItemDragEventArgs e) => base.OnItemDrag(e); public new void OnKeyDown(KeyEventArgs e) => base.OnKeyDown(e); @@ -7948,10 +7671,6 @@ private class SubTreeView : TreeView public new void OnNodeMouseDoubleClick(TreeNodeMouseClickEventArgs e) => base.OnNodeMouseDoubleClick(e); -#if NET11_0_OR_GREATER - public new void OnNodeLeadingChanged(EventArgs e) => base.OnNodeLeadingChanged(e); -#endif - public new void OnNodeMouseHover(TreeNodeMouseHoverEventArgs e) => base.OnNodeMouseHover(e); public new void OnRightToLeftLayoutChanged(EventArgs e) => base.OnRightToLeftLayoutChanged(e); From abfb9ba166477e0975c728fb57dd3941abebcb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20L=C3=B6ffelmann?= Date: Tue, 19 May 2026 15:11:44 -0700 Subject: [PATCH 02/12] Move component code files to component folder. --- src/System.Windows.Forms.Primitives/src/NativeMethods.txt | 3 +++ .../Forms/{ => Components}/ErrorProvider/ErrorBlinkStyle.cs | 0 .../Forms/{ => Components}/ErrorProvider/ErrorIconAlignment.cs | 0 .../ErrorProvider.ControlItem.ControlItemAccessibleObject.cs | 0 .../ErrorProvider/ErrorProvider.ControlItem.cs | 0 .../ErrorProvider/ErrorProvider.ErrorProviderStates.cs | 0 .../ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs | 0 .../ErrorProvider/ErrorProvider.ErrorWindow.cs | 0 .../{ => Components}/ErrorProvider/ErrorProvider.IconRegion.cs | 0 .../Forms/{ => Components}/ErrorProvider/ErrorProvider.cs | 0 .../System/Windows/Forms/{Help => Components}/HelpProvider.cs | 0 .../Forms/{Controls => Components}/ImageList/ColorDepth.cs | 0 .../ImageList/ImageList.ImageCollection.ImageInfo.cs | 0 .../ImageList/ImageList.ImageCollection.cs | 0 .../{Controls => Components}/ImageList/ImageList.Indexer.cs | 0 .../ImageList/ImageList.NativeImageList.cs | 0 .../{Controls => Components}/ImageList/ImageList.Original.cs | 0 .../ImageList/ImageList.OriginalOptions.cs | 0 .../Forms/{Controls => Components}/ImageList/ImageList.cs | 0 .../{Controls => Components}/ImageList/ImageListConverter.cs | 0 .../{Controls => Components}/ImageList/ImageListStreamer.cs | 0 .../ImageList/RelatedImageListAttribute.cs | 0 .../System/Windows/Forms/{ => Components}/Timer.cs | 0 23 files changed, 3 insertions(+) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorBlinkStyle.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorIconAlignment.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.ControlItem.ControlItemAccessibleObject.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.ControlItem.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.ErrorProviderStates.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.ErrorWindow.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.IconRegion.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/ErrorProvider/ErrorProvider.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Help => Components}/HelpProvider.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ColorDepth.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.ImageCollection.ImageInfo.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.ImageCollection.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.Indexer.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.NativeImageList.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.Original.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.OriginalOptions.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageList.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageListConverter.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/ImageListStreamer.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{Controls => Components}/ImageList/RelatedImageListAttribute.cs (100%) rename src/System.Windows.Forms/System/Windows/Forms/{ => Components}/Timer.cs (100%) diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index b1a36cfacdd..9828b18cad8 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -517,6 +517,9 @@ PD_RESULT_* PostQuitMessage PostQuitMessage PostThreadMessage +PowerCreateRequest +PowerClearRequest +PowerSetRequest PRF_* PROGRESS_CLASS PROPERTYKEY diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorBlinkStyle.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorBlinkStyle.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorBlinkStyle.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorBlinkStyle.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorIconAlignment.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorIconAlignment.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorIconAlignment.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorIconAlignment.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ControlItem.ControlItemAccessibleObject.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ControlItem.ControlItemAccessibleObject.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ControlItem.ControlItemAccessibleObject.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ControlItem.ControlItemAccessibleObject.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ControlItem.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ControlItem.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ControlItem.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ControlItem.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorProviderStates.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorProviderStates.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorProviderStates.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorProviderStates.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorWindow.ErrorWindowAccessibleObject.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorWindow.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorWindow.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.ErrorWindow.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.ErrorWindow.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.IconRegion.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.IconRegion.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.IconRegion.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.IconRegion.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/ErrorProvider/ErrorProvider.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ErrorProvider/ErrorProvider.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Help/HelpProvider.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/HelpProvider.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Help/HelpProvider.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/HelpProvider.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ColorDepth.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ColorDepth.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ColorDepth.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ColorDepth.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.ImageCollection.ImageInfo.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.ImageCollection.ImageInfo.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.ImageCollection.ImageInfo.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.ImageCollection.ImageInfo.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.ImageCollection.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.ImageCollection.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.ImageCollection.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.ImageCollection.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.Indexer.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.Indexer.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.Indexer.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.Indexer.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.NativeImageList.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.NativeImageList.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.Original.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.Original.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.Original.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.Original.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.OriginalOptions.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.OriginalOptions.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.OriginalOptions.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.OriginalOptions.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageList.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageList.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageListConverter.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageListConverter.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageListConverter.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageListConverter.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageListStreamer.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/ImageListStreamer.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/RelatedImageListAttribute.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/RelatedImageListAttribute.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Controls/ImageList/RelatedImageListAttribute.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/ImageList/RelatedImageListAttribute.cs diff --git a/src/System.Windows.Forms/System/Windows/Forms/Timer.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/Timer.cs similarity index 100% rename from src/System.Windows.Forms/System/Windows/Forms/Timer.cs rename to src/System.Windows.Forms/System/Windows/Forms/Components/Timer.cs From ff98ab9b25e5698053bbae969b94b1edaabc97ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20L=C3=B6ffelmann?= Date: Tue, 19 May 2026 15:12:12 -0700 Subject: [PATCH 03/12] Introduce KioskModeManager component. * Refactor KioskModeManager component and introduce WakeUp-Events incl.. new WakeUpEventArgs. --- src/BuildAssist/BuildAssist.msbuildproj | 2 +- .../src/NativeMethods.txt | 2 + .../PublicAPI.Unshipped.txt | 41 + src/System.Windows.Forms/Resources/SR.resx | 41 +- .../Resources/xlf/SR.cs.xlf | 65 + .../Resources/xlf/SR.de.xlf | 65 + .../Resources/xlf/SR.es.xlf | 65 + .../Resources/xlf/SR.fr.xlf | 65 + .../Resources/xlf/SR.it.xlf | 65 + .../Resources/xlf/SR.ja.xlf | 65 + .../Resources/xlf/SR.ko.xlf | 65 + .../Resources/xlf/SR.pl.xlf | 65 + .../Resources/xlf/SR.pt-BR.xlf | 65 + .../Resources/xlf/SR.ru.xlf | 65 + .../Resources/xlf/SR.tr.xlf | 65 + .../Resources/xlf/SR.zh-Hans.xlf | 65 + .../Resources/xlf/SR.zh-Hant.xlf | 65 + .../KioskModeManager/KioskModeManager.cs | 1145 +++++++++++++++++ .../KioskModeWakeupEventArgs.cs | 36 + .../KioskModeWakeupEventHandler.cs | 14 + .../KioskModeManager/KioskModeWakeupSource.cs | 35 + .../Windows/Forms/KioskModeManagerTests.cs | 423 ++++++ 22 files changed, 2582 insertions(+), 2 deletions(-) create mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs create mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs create mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs create mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs create mode 100644 src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs diff --git a/src/BuildAssist/BuildAssist.msbuildproj b/src/BuildAssist/BuildAssist.msbuildproj index 46c1bcc9109..fa0f6af0028 100644 --- a/src/BuildAssist/BuildAssist.msbuildproj +++ b/src/BuildAssist/BuildAssist.msbuildproj @@ -41,7 +41,7 @@ System.Windows.Form virtual System.Windows.Forms.Control.VisualStylesMode.set -> void override System.Windows.Forms.PrintPreviewControl.ForeColor.get -> System.Drawing.Color override System.Windows.Forms.PrintPreviewControl.ForeColor.set -> void +System.Windows.Forms.KioskModeManager +System.Windows.Forms.KioskModeManager.ContainerControl.get -> System.Windows.Forms.ContainerControl? +System.Windows.Forms.KioskModeManager.ContainerControl.set -> void +System.Windows.Forms.KioskModeManager.ContainerControlChanged -> System.EventHandler? +override System.Windows.Forms.KioskModeManager.Dispose(bool disposing) -> void +System.Windows.Forms.KioskModeManager.EscapeExitsFullScreen.get -> bool +System.Windows.Forms.KioskModeManager.EscapeExitsFullScreen.set -> void +System.Windows.Forms.KioskModeManager.FullScreen.get -> bool +System.Windows.Forms.KioskModeManager.FullScreen.set -> void +System.Windows.Forms.KioskModeManager.FullScreenChanged -> System.EventHandler? +System.Windows.Forms.KioskModeManager.HideTaskbar.get -> bool +System.Windows.Forms.KioskModeManager.HideTaskbar.set -> void +System.Windows.Forms.KioskModeManager.KioskModeManager() -> void +System.Windows.Forms.KioskModeManager.KioskModeManager(System.ComponentModel.IContainer! container) -> void +System.Windows.Forms.KioskModeManager.MousePointerAutoHideDelay.get -> int +System.Windows.Forms.KioskModeManager.MousePointerAutoHideDelay.set -> void +virtual System.Windows.Forms.KioskModeManager.OnContainerControlChanged(System.EventArgs! e) -> void +virtual System.Windows.Forms.KioskModeManager.OnFullScreenChanged(System.EventArgs! e) -> void +virtual System.Windows.Forms.KioskModeManager.OnWakeup(System.Windows.Forms.KioskModeWakeupEventArgs! e) -> void +override System.Windows.Forms.KioskModeManager.Site.get -> System.ComponentModel.ISite? +override System.Windows.Forms.KioskModeManager.Site.set -> void +System.Windows.Forms.KioskModeManager.SuppressPowerSaving.get -> bool +System.Windows.Forms.KioskModeManager.SuppressPowerSaving.set -> void +System.Windows.Forms.KioskModeManager.ToggleFullScreen() -> void +System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.get -> System.Windows.Forms.Keys +System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.set -> void +System.Windows.Forms.KioskModeManager.TopMostInFullScreen.get -> bool +System.Windows.Forms.KioskModeManager.TopMostInFullScreen.set -> void +System.Windows.Forms.KioskModeManager.Wakeup -> System.Windows.Forms.KioskModeWakeupEventHandler? +System.Windows.Forms.KioskModeManager.WakeUpCommand.get -> System.Windows.Input.ICommand? +System.Windows.Forms.KioskModeManager.WakeUpCommand.set -> void +System.Windows.Forms.KioskModeWakeupEventArgs +System.Windows.Forms.KioskModeWakeupEventArgs.KioskModeWakeupEventArgs(System.Windows.Forms.KioskModeWakeupSource source) -> void +System.Windows.Forms.KioskModeWakeupEventArgs.Source.get -> System.Windows.Forms.KioskModeWakeupSource +System.Windows.Forms.KioskModeWakeupEventHandler +virtual System.Windows.Forms.KioskModeWakeupEventHandler.Invoke(object? sender, System.Windows.Forms.KioskModeWakeupEventArgs! e) -> void +System.Windows.Forms.KioskModeWakeupSource +System.Windows.Forms.KioskModeWakeupSource.Keyboard = 0 -> System.Windows.Forms.KioskModeWakeupSource +System.Windows.Forms.KioskModeWakeupSource.Mouse = 1 -> System.Windows.Forms.KioskModeWakeupSource +System.Windows.Forms.KioskModeWakeupSource.PowerResume = 2 -> System.Windows.Forms.KioskModeWakeupSource +System.Windows.Forms.KioskModeWakeupSource.Session = 3 -> System.Windows.Forms.KioskModeWakeupSource diff --git a/src/System.Windows.Forms/Resources/SR.resx b/src/System.Windows.Forms/Resources/SR.resx index 7b33b7618cf..e9b9f061661 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -2691,6 +2691,45 @@ To replace this default dialog please handle the DataError event. Manages a collection of images that are typically used by other controls such as ListView, TreeView, or ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + + The container whose containing form is controlled by the kiosk mode manager. + + + Occurs when the ContainerControl property value changes. + + + Indicates whether pressing Escape exits fullscreen mode. + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + Occurs when the fullscreen state changes. + + + Indicates whether fullscreen mode covers the Windows taskbar. + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + Indicates whether Windows should keep the display and system awake while this component is active. + + + The key that toggles between fullscreen and restored mode. + + + Indicates whether the form is topmost while fullscreen. + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + The command that is executed whenever the kiosk experience is woken. + Provides run-time information or descriptive text for a control. @@ -7069,4 +7108,4 @@ Stack trace where the illegal operation occurred was: The FormScreenCaptureMode property can only be changed on top-level Forms with their TopLevel property set to true. - \ No newline at end of file + diff --git a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf index 7048c4940ae..7632faf1b54 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf @@ -4549,6 +4549,11 @@ Chcete-li nahradit toto výchozí dialogové okno, nastavte popisovač události Spravuje kolekci obrázků, které standardně používají ovládací prvky, jako například ListView, TreeView nebo ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Poskytuje běhové informace nebo popisný text pro ovládací prvek. @@ -6144,6 +6149,66 @@ Trasování zásobníku, kde došlo k neplatné operaci: Kombinace klíčů je neplatná. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Umožňuje automatické zpracování textu, který je delší než šířka ovládacího prvku popisku. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf index 135b5077458..3a38493cfd0 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf @@ -4549,6 +4549,11 @@ Behandeln Sie das DataError-Ereignis, um dieses Standarddialogfeld zu ersetzen.< Verwaltet eine Sammlung von Bildern, die in der Regel von anderen Steuerelementen wie ListView, TreeView oder ToolStrip verwendet werden. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Stellt Laufzeitinformationen oder deskriptiven Text für ein Steuerelement bereit. @@ -6144,6 +6149,66 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Ungültige Schlüsselkombination. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Aktiviert die automatische Behandlung von Text, der über die Breite des Bezeichnungssteuerelements hinausgeht. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf index 0ff0c395387..68aabae5632 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf @@ -4549,6 +4549,11 @@ Para reemplazar este cuadro de diálogo predeterminado controle el evento DataEr Controla una colección de imágenes que suelen utilizar otros controles como ListView, TreeView o ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Proporciona información en tiempo de ejecución o texto descriptivo para un control. @@ -6144,6 +6149,66 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: La combinación de teclas no es válida. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Permite el control automático del texto que se extiende más allá del ancho del control de la etiqueta. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf index 06ff6ad95ed..74d3eb7e7fc 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf @@ -4549,6 +4549,11 @@ Pour remplacer cette boîte de dialogue par défaut, traitez l'événement DataE Gère une collection d'images qui sont généralement utilisées par d'autres contrôles tels que ListView, TreeView et ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Fournit des informations d'exécution ou un texte descriptif pour un contrôle. @@ -6144,6 +6149,66 @@ Cette opération non conforme s'est produite sur la trace de la pile : La combinaison de touches n'est pas valide. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Active la gestion automatique du texte qui dépasse les limites de largeur du contrôle label. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf index 1cf670682a4..9f85ac4c131 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf @@ -4549,6 +4549,11 @@ Per sostituire questa finestra di dialogo, gestisci l'evento DataError. Gestisce una raccolta di immagini utilizzate in genere da altri controlli, quali ListView, TreeView o ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Fornisce informazioni di runtime o testo descrittivo per un controllo. @@ -6144,6 +6149,66 @@ Analisi dello stack dove si è verificata l'operazione non valida: Combinazione di tasti non valida. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Consente la gestione automatica del testo che non è contenuto all'interno della larghezza del controllo etichetta. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf index 617f5222089..b9b4ef99004 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf @@ -4549,6 +4549,11 @@ To replace this default dialog please handle the DataError event. ListView、TreeView、または ToolStrip のような他のコントロールが通常使用するイメージのコレクションを管理します。 + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. コントロールの実行時の情報または説明用のテキストを提供します。 @@ -6144,6 +6149,66 @@ Stack trace where the illegal operation occurred was: キーの組み合わせが有効ではありません。 + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. ラベル コントロールの幅を越えるテキストの自動処理を有効にします。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf index 02d54abd10f..faa7da7714a 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf @@ -4549,6 +4549,11 @@ To replace this default dialog please handle the DataError event. ListView, TreeView 또는 ToolStrip과 같은 다른 컨트롤에서 일반적으로 사용하는 이미지 컬렉션을 관리합니다. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. 컨트롤에 대한 설명 텍스트나 런타임 정보를 제공합니다. @@ -6144,6 +6149,66 @@ Stack trace where the illegal operation occurred was: 키 조합이 잘못되었습니다. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. 레이블 컨트롤의 너비보다 큰 텍스트를 자동으로 처리합니다. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf index edd92bf4d3c..9a390fee7be 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf @@ -4549,6 +4549,11 @@ Aby zamienić to domyślne okno dialogowe, obsłuż zdarzenie DataError.Zarządza kolekcją obrazów, które są zazwyczaj używane przez inne formanty, takie jak ListView, TreeView lub ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Wyświetla informacje o wykonaniu lub tekst opisowy dla formantu. @@ -6144,6 +6149,66 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Kombinacja kluczy jest nieprawidłowa. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Włącza automatyczną obsługę tekstu, który mieści się w szerokości formantu etykiety. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf index b4b0b95002b..feee155ef4e 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf @@ -4549,6 +4549,11 @@ Para substituir a caixa de diálogo padrão, manipule o evento DataError.Gerencia uma coleção de imagens geralmente usadas por outros controles, como ListView, TreeView ou ToolStrip. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Fornece informações em tempo de execução ou texto descritivo para um controle. @@ -6144,6 +6149,66 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: A combinação de chave não é válida. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Permite a manipulação automática de texto que ultrapassa a largura do controle de rótulo. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf index c2cc58299ee..afc78fd9de2 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf @@ -4549,6 +4549,11 @@ To replace this default dialog please handle the DataError event. Управляет коллекцией изображений, которые обычно используются другими элементами управления (например, ListView, TreeView или ToolStrip). + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Предоставляет элементу управления текст описания либо информацию во время выполнения. @@ -6144,6 +6149,66 @@ Stack trace where the illegal operation occurred was: Недопустимая комбинация ключа. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Включает автоматическую обработку текста, выходящего за пределы ширины элемента управления с подписью. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf index bec94a1e2d7..a056935ac7d 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf @@ -4549,6 +4549,11 @@ Bu varsayılan iletişim kutusunu değiştirmek için, lütfen DataError olayın Genellikle ListView, TreeView veya ToolStrip gibi diğer denetimler tarafından kullanılan resim koleksiyonunu düzenler. + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. Bir denetim için çalışma zamanı bilgileri veya açıklayıcı metin sağlar. @@ -6144,6 +6149,66 @@ Geçersiz işlemin gerçekleştiği yığın izi: Anahtar birleşimi geçerli değil. + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. Etiket denetimi genişliğini aşan metnin otomatik işlenmesini sağlar. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf index 11e042ab8a5..6e3341c1c73 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf @@ -4549,6 +4549,11 @@ To replace this default dialog please handle the DataError event. 管理通常由其他控件(如 ListView、TreeView 或 ToolStrip)使用的图像集合。 + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. 为控件提供运行时信息或说明性文字。 @@ -6144,6 +6149,66 @@ Stack trace where the illegal operation occurred was: 组合键无效。 + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. 启用对扩展到标签控件宽度以外的文本的自动处理。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf index afb3ff9a6b6..5750ea7d965 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf @@ -4549,6 +4549,11 @@ To replace this default dialog please handle the DataError event. 管理通常由其他控制項 (例如 ListView、TreeView 或 ToolStrip) 使用的影像集合。 + + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Provides run-time information or descriptive text for a control. 提供控制項的執行階段資訊或描述文字。 @@ -6144,6 +6149,66 @@ Stack trace where the illegal operation occurred was: 組合鍵無效。 + + Occurs when the ContainerControl property value changes. + Occurs when the ContainerControl property value changes. + + + + The container whose containing form is controlled by the kiosk mode manager. + The container whose containing form is controlled by the kiosk mode manager. + + + + Indicates whether pressing Escape exits fullscreen mode. + Indicates whether pressing Escape exits fullscreen mode. + + + + Gets or sets a value indicating whether the resolved form is currently fullscreen. + Gets or sets a value indicating whether the resolved form is currently fullscreen. + + + + Occurs when the fullscreen state changes. + Occurs when the fullscreen state changes. + + + + Indicates whether fullscreen mode covers the Windows taskbar. + Indicates whether fullscreen mode covers the Windows taskbar. + + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + + + The key that toggles between fullscreen and restored mode. + The key that toggles between fullscreen and restored mode. + + + + Indicates whether the form is topmost while fullscreen. + Indicates whether the form is topmost while fullscreen. + + + + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. + + + + The command that is executed whenever the kiosk experience is woken. + The command that is executed whenever the kiosk experience is woken. + + Enables the automatic handling of text that extends beyond the width of the label control. 啟用自動處理,以處理超出標籤控制項寬度的文件。 diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs new file mode 100644 index 00000000000..30bf82ff0b8 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -0,0 +1,1145 @@ +// 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.ComponentModel.Design; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Input; +using Windows.Win32.System.Power; +using Windows.Win32.System.Threading; + +namespace System.Windows.Forms; + +#if NET11_0_OR_GREATER +/// +/// Manages common kiosk-mode behavior for a WinForms form. +/// +/// +/// +/// is a component that can be dropped on a +/// form or on a user control at design time. Set +/// to the owning container and the component resolves the containing +/// when fullscreen behavior is needed. +/// +/// +/// The component can make the resolved form fullscreen, optionally cover the +/// taskbar, keep the form topmost while fullscreen, suppress display and +/// system sleep, hide the mouse pointer after inactivity, and notify the +/// application when user or system activity wakes the kiosk experience. +/// +/// +/// +/// +/// public partial class MainForm : Form +/// { +/// private readonly KioskModeManager _kioskModeManager; +/// +/// public MainForm() +/// { +/// InitializeComponent(); +/// +/// _kioskModeManager = new KioskModeManager +/// { +/// ContainerControl = this, +/// HideTaskbar = true, +/// TopMostInFullScreen = true, +/// MousePointerAutoHideDelay = 3000, +/// SuppressPowerSaving = true +/// }; +/// +/// _kioskModeManager.Wakeup += (sender, e) => +/// { +/// if (e.Source == KioskModeWakeupSource.Mouse) +/// { +/// // Refresh the kiosk surface after mouse activity. +/// } +/// }; +/// +/// if (!_kioskModeManager.FullScreen) +/// { +/// _kioskModeManager.ToggleFullScreen(); +/// } +/// } +/// } +/// +/// +[DefaultProperty(nameof(ToggleFullScreenKey))] +[ToolboxItemFilter("System.Windows.Forms")] +[SRDescription(nameof(SR.DescriptionKioskModeManager))] +public class KioskModeManager : Component, ISupportInitialize +{ + private const uint PowerRequestContextVersion = 0; + private const POWER_REQUEST_CONTEXT_FLAGS PowerRequestContextSimpleString = (POWER_REQUEST_CONTEXT_FLAGS)0x00000001; + private const uint NotifyForThisSession = 0; + private const nint PbtApmResumeSuspend = 0x0007; + private const nint PbtApmResumeAutomatic = 0x0012; + private const nint WtsConsoleConnect = 0x0001; + private const nint WtsRemoteConnect = 0x0003; + private const nint WtsSessionLogon = 0x0005; + private const nint WtsSessionUnlock = 0x0008; + private const string PowerRequestReason = "WinForms KioskModeManager: Preventing screen saver and sleep"; + + private static readonly object s_containerControlChangedEvent = new(); + private static readonly object s_fullScreenChangedEvent = new(); + private static readonly object s_wakeupEvent = new(); + + private ContainerControl? _containerControl; + private bool _containerControlExplicitlySet; + private Form? _targetForm; + private bool _isFullScreen; + private bool _pendingFullScreen; + private bool _initializing; + private bool _isCursorHidden; + private KioskModeMessageFilter? _messageFilter; + private KioskModeFormObserver? _formObserver; + private Timer? _mousePointerAutoHideTimer; + + private FormBorderStyle _savedBorderStyle; + private FormWindowState _savedWindowState; + private Rectangle _savedBounds; + private bool _savedTopMost; + + private bool _hideTaskbar; + private bool _topMostInFullScreen; + private Keys _toggleFullScreenKey = Keys.F11; + private bool _escapeExitsFullScreen = true; + private bool _suppressPowerSaving; + private int _mousePointerAutoHideDelay; + private ICommand? _wakeUpCommand; + + private HANDLE _powerRequestHandle; + + /// + /// Initializes a new instance of the class. + /// + public KioskModeManager() + { + } + + /// + /// Initializes a new instance of the class + /// and adds it to the specified container. + /// + /// The container that owns the component. + /// + /// is . + /// + public KioskModeManager(IContainer container) + { + ArgumentNullException.ThrowIfNull(container); + container.Add(this); + } + + /// + /// Gets or sets the associated with the component. + /// + /// + /// + /// When the component is sited at design time and + /// has not been set explicitly, the component resolves the root component of the + /// designer host (typically the owning or ) + /// and assigns it as the . An explicitly assigned + /// is never overwritten. + /// + /// + public override ISite? Site + { + get => base.Site; + set + { + base.Site = value; + + if (value is not null + && !_containerControlExplicitlySet + && value.GetService(typeof(IDesignerHost)) is IDesignerHost host + && host.RootComponent is ContainerControl root) + { + SetContainerControl(root, isExplicitAssignment: false); + } + } + } + + /// + /// Gets or sets the container whose containing form is controlled by this + /// component. + /// + /// + /// A that is either a or + /// a child container that can resolve a parent form; otherwise, + /// . + /// + /// + /// + /// This property intentionally uses rather + /// than . A can be placed + /// on a at design time, and that user control + /// can later be hosted by a form. Restricting the property to + /// would make that design-time scenario inconsistent. + /// + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerContainerControlDescr))] + [DefaultValue(null)] + public ContainerControl? ContainerControl + { + get => _containerControl; + set => SetContainerControl(value, isExplicitAssignment: true); + } + + /// + /// Occurs when the value of changes. + /// + [SRCategory(nameof(SR.CatPropertyChanged))] + [SRDescription(nameof(SR.KioskModeManagerContainerControlChangedDescr))] + public event EventHandler? ContainerControlChanged + { + add => Events.AddHandler(s_containerControlChangedEvent, value); + remove => Events.RemoveHandler(s_containerControlChangedEvent, value); + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void OnContainerControlChanged(EventArgs e) + { + if (Events[s_containerControlChangedEvent] is EventHandler handler) + { + handler(this, e); + } + } + + /// + /// Gets or sets a value indicating whether fullscreen mode covers the + /// Windows taskbar. + /// + /// + /// to size the form to the complete screen bounds; + /// to use normal maximized form behavior. + /// The default is . + /// + /// + /// + /// When this property is , the form is maximized + /// and Windows keeps the taskbar available according to the user's shell + /// settings. When this property is , the form is + /// sized to the screen bounds so the kiosk surface covers the taskbar. + /// + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerHideTaskbarDescr))] + [DefaultValue(false)] + public bool HideTaskbar + { + get => _hideTaskbar; + set + { + if (_hideTaskbar == value) + { + return; + } + + _hideTaskbar = value; + + if (_isFullScreen && _targetForm is not null) + { + ApplyFullScreen(_targetForm); + } + } + } + + /// + /// Gets or sets a value indicating whether the form is topmost while + /// fullscreen. + /// + /// + /// to set while + /// fullscreen; otherwise, . The default is + /// . + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerTopMostInFullScreenDescr))] + [DefaultValue(false)] + public bool TopMostInFullScreen + { + get => _topMostInFullScreen; + set + { + if (_topMostInFullScreen == value) + { + return; + } + + _topMostInFullScreen = value; + + if (_isFullScreen && _targetForm is not null) + { + _targetForm.TopMost = value; + } + } + } + + /// + /// Gets or sets the key that toggles fullscreen mode. + /// + /// + /// A value that is handled without modifiers. The + /// default is . + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerToggleFullScreenKeyDescr))] + [DefaultValue(Keys.F11)] + public Keys ToggleFullScreenKey + { + get => _toggleFullScreenKey; + set + { + if (_toggleFullScreenKey == value) + { + return; + } + + _toggleFullScreenKey = value; + } + } + + /// + /// Gets or sets a value indicating whether pressing Escape exits + /// fullscreen mode. + /// + /// + /// to exit fullscreen mode when Escape is pressed + /// without modifiers; otherwise, . The default is + /// . + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerEscapeExitsFullScreenDescr))] + [DefaultValue(true)] + public bool EscapeExitsFullScreen + { + get => _escapeExitsFullScreen; + set + { + if (_escapeExitsFullScreen == value) + { + return; + } + + _escapeExitsFullScreen = value; + } + } + + /// + /// Gets or sets a value indicating whether the component requests that + /// Windows keep the display and system awake. + /// + /// + /// to request that Windows suppress display sleep + /// and system sleep; otherwise, . The default is + /// . + /// + /// + /// + /// This property uses the Windows power request APIs. It prevents ordinary + /// display and system idle sleep while enabled, but it does not override + /// every system policy and does not configure wake timers, Wake-on-LAN, or + /// voice activation. + /// + /// + /// + /// Windows could not create or activate the power request. + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerSuppressPowerSavingDescr))] + [DefaultValue(false)] + public bool SuppressPowerSaving + { + get => _suppressPowerSaving; + set + { + if (_suppressPowerSaving == value) + { + return; + } + + _suppressPowerSaving = value; + UpdatePowerRequest(); + } + } + + /// + /// Gets or sets the amount of time, in milliseconds, before the mouse + /// pointer is hidden while fullscreen. + /// + /// + /// The inactivity delay in milliseconds. A value of 0 disables automatic + /// pointer hiding. The default is 0. + /// + /// + /// + /// The timer is active only while the component is in fullscreen mode. + /// When the user moves the mouse after the component hid the pointer, the + /// pointer is shown immediately and the timer starts again. + /// + /// + /// + /// is less than 0. + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerMousePointerAutoHideDelayDescr))] + [DefaultValue(0)] + public int MousePointerAutoHideDelay + { + get => _mousePointerAutoHideDelay; + set + { + ArgumentOutOfRangeException.ThrowIfNegative(value); + + if (_mousePointerAutoHideDelay == value) + { + return; + } + + _mousePointerAutoHideDelay = value; + + if (value == 0) + { + StopMousePointerAutoHideTimer(); + ShowMousePointerIfHidden(); + } + else if (_isFullScreen) + { + RestartMousePointerAutoHideTimer(); + } + } + } + + /// + /// Gets or sets a value indicating whether the resolved form is currently + /// fullscreen. + /// + /// + /// if the component has placed the form in + /// fullscreen mode; otherwise, . + /// + /// + /// + /// Setting this property to enters fullscreen mode and + /// setting it to restores the previously saved form + /// state. The property is bindable so it can participate in two-way data + /// binding. + /// + /// + [SRCategory(nameof(SR.CatBehavior))] + [SRDescription(nameof(SR.KioskModeManagerFullScreenDescr))] + [Bindable(true)] + [DefaultValue(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool FullScreen + { + get => _pendingFullScreen || _isFullScreen; + set + { + if (!value) + { + _pendingFullScreen = false; + ExitFullScreen(); + } + else if (_initializing || DesignMode || !EnterFullScreen()) + { + _pendingFullScreen = true; + } + else + { + _pendingFullScreen = false; + } + } + } + + /// + /// Occurs when the fullscreen state changes. + /// + [SRCategory(nameof(SR.CatPropertyChanged))] + [SRDescription(nameof(SR.KioskModeManagerFullScreenChangedDescr))] + public event EventHandler? FullScreenChanged + { + add => Events.AddHandler(s_fullScreenChangedEvent, value); + remove => Events.RemoveHandler(s_fullScreenChangedEvent, value); + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void OnFullScreenChanged(EventArgs e) + { + if (Events[s_fullScreenChangedEvent] is EventHandler handler) + { + handler(this, e); + } + } + + /// + /// Occurs when keyboard, mouse, power resume, or session activity wakes + /// the kiosk experience. + /// + /// + /// + /// The event reports activity that the component can observe on the UI + /// thread or through Windows power/session notifications. It does not mean + /// that the component caused the computer to wake from sleep, and it does + /// not configure voice wake, network wake, or wake timers. + /// + /// + [SRCategory(nameof(SR.CatAction))] + [SRDescription(nameof(SR.KioskModeManagerWakeupDescr))] + public event KioskModeWakeupEventHandler? Wakeup + { + add => Events.AddHandler(s_wakeupEvent, value); + remove => Events.RemoveHandler(s_wakeupEvent, value); + } + + /// + /// Raises the event. + /// + /// A that contains the event data. + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected virtual void OnWakeup(KioskModeWakeupEventArgs e) + { + if (Events[s_wakeupEvent] is KioskModeWakeupEventHandler handler) + { + handler(this, e); + } + + if (_wakeUpCommand is not null) + { + // The wakeup source is passed as its string name so the bound command stays + // independent of the WinForms-specific enum type. + string parameter = e.Source.ToString(); + if (_wakeUpCommand.CanExecute(parameter)) + { + _wakeUpCommand.Execute(parameter); + } + } + } + + /// + /// Gets or sets the command that is executed whenever the + /// event is raised. + /// + /// + /// An to execute on every wakeup, or + /// to execute no command. The default is . + /// + /// + /// + /// The command is executed with the wakeup source name (the result of + /// .) as the command + /// parameter, keeping the command independent of the WinForms-specific enum type. + /// + /// + [SRCategory(nameof(SR.CatAction))] + [SRDescription(nameof(SR.KioskModeManagerWakeUpCommandDescr))] + [Bindable(true)] + [DefaultValue(null)] + public ICommand? WakeUpCommand + { + get => _wakeUpCommand; + set => _wakeUpCommand = value; + } + + /// + /// Places the resolved form in fullscreen mode. + /// + /// + /// +/// The component saves the form's border style, window state, bounds, and +/// topmost state before applying fullscreen mode. Call +/// again to restore the saved state. + /// + /// + private bool EnterFullScreen() + { + if (_isFullScreen || _initializing || DesignMode) + { + return _isFullScreen; + } + + Form? targetForm = ResolveTargetForm(); + if (targetForm is null) + { + return false; + } + + _savedBorderStyle = targetForm.FormBorderStyle; + _savedWindowState = targetForm.WindowState; + _savedBounds = targetForm.WindowState == FormWindowState.Normal + ? targetForm.Bounds + : targetForm.RestoreBounds; + _savedTopMost = targetForm.TopMost; + + ApplyFullScreen(targetForm); + SetFullScreenState(true); + _pendingFullScreen = false; + RestartMousePointerAutoHideTimer(); + return true; + } + + /// + /// Restores the form state that was saved when fullscreen mode was + /// entered. + /// + private void ExitFullScreen() + { + if (!_isFullScreen || _targetForm is null) + { + return; + } + + StopMousePointerAutoHideTimer(); + ShowMousePointerIfHidden(); + + _targetForm.FormBorderStyle = _savedBorderStyle; + _targetForm.TopMost = _savedTopMost; + _targetForm.WindowState = FormWindowState.Normal; + _targetForm.Bounds = _savedBounds; + _targetForm.WindowState = _savedWindowState; + + SetFullScreenState(false); + } + + /// + /// Updates the fullscreen state and raises + /// when the value changes. + /// + /// The new fullscreen state. + private void SetFullScreenState(bool value) + { + if (_isFullScreen == value) + { + return; + } + + _isFullScreen = value; + OnFullScreenChanged(EventArgs.Empty); + } + + /// + /// Toggles the resolved form between fullscreen and restored mode. + /// + /// + /// + /// Use to determine the current state before + /// calling this method when the application needs an explicit target + /// state. + /// + /// + public void ToggleFullScreen() + => FullScreen = !FullScreen; + + void ISupportInitialize.BeginInit() + { + _initializing = true; + } + + void ISupportInitialize.EndInit() + { + _initializing = false; + + AttachToForm(); + UpdatePowerRequest(); + + if (_pendingFullScreen) + { + FullScreen = true; + } + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + _pendingFullScreen = false; + ExitFullScreen(); + DetachFromForm(); + _mousePointerAutoHideTimer?.Dispose(); + _mousePointerAutoHideTimer = null; + ReleasePowerRequest(); + } + + base.Dispose(disposing); + } + + private void SetContainerControl(ContainerControl? value, bool isExplicitAssignment) + { + if (_containerControl == value) + { + if (isExplicitAssignment) + { + _containerControlExplicitlySet = true; + } + + return; + } + + if (_isFullScreen) + { + _pendingFullScreen = false; + ExitFullScreen(); + } + + if (value is null) + { + _pendingFullScreen = false; + } + + DetachFromForm(); + _containerControl = value; + if (isExplicitAssignment) + { + _containerControlExplicitlySet = true; + } + + AttachToForm(); + OnContainerControlChanged(EventArgs.Empty); + } + + private void AttachToForm() + { + if (_initializing || DesignMode || _containerControl is null) + { + return; + } + + EnsureMessageMonitoring(); + ResolveTargetForm(); + if (_pendingFullScreen && _targetForm is not null) + { + EnterFullScreen(); + } + } + + private void DetachFromForm() + { + if (_messageFilter is not null) + { + Application.RemoveMessageFilter(_messageFilter); + _messageFilter = null; + } + + _formObserver?.Detach(); + StopMousePointerAutoHideTimer(); + ShowMousePointerIfHidden(); + _targetForm = null; + } + + private Form? ResolveTargetForm() + { + // ContainerControl intentionally supports both Form and UserControl + // design-time placement. FindForm handles the UserControl case. + Form? targetForm = _containerControl as Form ?? _containerControl?.FindForm(); + if (!ReferenceEquals(_targetForm, targetForm)) + { + _targetForm = targetForm; + UpdateFormObserver(); + } + + return _targetForm; + } + + private void EnsureMessageMonitoring() + { + // IMessageFilter observes input for child controls without changing + // Form.KeyPreview or installing global keyboard/mouse hooks. + if (_messageFilter is null) + { + _messageFilter = new KioskModeMessageFilter(this); + Application.AddMessageFilter(_messageFilter); + } + } + + private void ApplyFullScreen(Form form) + { + // Apply fullscreen from a normal state so bounds and border changes do + // not operate on Windows' maximized window rectangle. + form.WindowState = FormWindowState.Normal; + form.FormBorderStyle = FormBorderStyle.None; + + if (_topMostInFullScreen) + { + form.TopMost = true; + } + + if (_hideTaskbar) + { + Screen screen = Screen.FromControl(form); + form.Bounds = screen.Bounds; + } + else + { + form.WindowState = FormWindowState.Maximized; + } + } + + private void UpdateFormObserver() + { + if (DesignMode) + { + return; + } + + _formObserver ??= new KioskModeFormObserver(this); + _formObserver.Attach(_targetForm); + } + + private void UpdatePowerRequest() + { + if (_initializing || DesignMode) + { + return; + } + + if (_suppressPowerSaving) + { + CreatePowerRequest(); + } + else + { + ReleasePowerRequest(); + } + } + + private void ProcessPowerBroadcast(WPARAM powerEvent) + { + nint value = (nint)powerEvent.Value; + if (value is PbtApmResumeAutomatic or PbtApmResumeSuspend) + { + RaiseWakeup(KioskModeWakeupSource.PowerResume); + } + } + + private void ProcessSessionChange(WPARAM sessionEvent) + { + nint value = (nint)sessionEvent.Value; + if (value is WtsConsoleConnect or WtsRemoteConnect or WtsSessionLogon or WtsSessionUnlock) + { + RaiseWakeup(KioskModeWakeupSource.Session); + } + } + + private void RestartMousePointerAutoHideTimer() + { + // Use a WinForms timer so cursor changes happen on the UI thread that + // receives the input messages restarting this timer. + if (!_isFullScreen || _mousePointerAutoHideDelay == 0) + { + return; + } + + _mousePointerAutoHideTimer ??= new Timer(); + _mousePointerAutoHideTimer.Stop(); + _mousePointerAutoHideTimer.Interval = _mousePointerAutoHideDelay; + _mousePointerAutoHideTimer.Tick -= OnMousePointerAutoHideTimerTick; + _mousePointerAutoHideTimer.Tick += OnMousePointerAutoHideTimerTick; + _mousePointerAutoHideTimer.Start(); + } + + private void StopMousePointerAutoHideTimer() + { + _mousePointerAutoHideTimer?.Stop(); + } + + private void OnMousePointerAutoHideTimerTick(object? sender, EventArgs e) + { + StopMousePointerAutoHideTimer(); + + if (!_isCursorHidden) + { + Cursor.Hide(); + _isCursorHidden = true; + } + } + + private void ProcessKeyboardActivity(Keys keyData, Keys modifiers) + { + // Any keyboard input wakes the kiosk experience, even when it does not + // match one of the configured fullscreen control keys. + RaiseWakeup(KioskModeWakeupSource.Keyboard); + + Keys keyCode = keyData & Keys.KeyCode; + + if (keyCode == _toggleFullScreenKey && modifiers == Keys.None) + { + ToggleFullScreen(); + } + else if (keyCode == Keys.Escape && modifiers == Keys.None + && _escapeExitsFullScreen && _isFullScreen) + { + ExitFullScreen(); + } + } + + private void ProcessMouseActivity() + { + ShowMousePointerIfHidden(); + RestartMousePointerAutoHideTimer(); + RaiseWakeup(KioskModeWakeupSource.Mouse); + } + + private void ShowMousePointerIfHidden() + { + // Cursor.Hide and Cursor.Show are counter-based. Only balance hides + // performed by this component. + if (!_isCursorHidden) + { + return; + } + + Cursor.Show(); + _isCursorHidden = false; + } + + private void RaiseWakeup(KioskModeWakeupSource source) + => OnWakeup(new KioskModeWakeupEventArgs(source)); + + private unsafe void CreatePowerRequest() + { + // PowerClearRequest clears the active request, but only CloseHandle + // releases the native HANDLE returned by PowerCreateRequest. + if (!_powerRequestHandle.IsNull) + { + return; + } + + REASON_CONTEXT context = new() + { + Version = PowerRequestContextVersion, + Flags = PowerRequestContextSimpleString, + }; + + nint reasonString = Marshal.StringToHGlobalUni(PowerRequestReason); + + try + { + context.Reason.SimpleReasonString = (PWSTR)(char*)reasonString; + _powerRequestHandle = PInvoke.PowerCreateRequest(in context); + } + finally + { + Marshal.FreeHGlobal(reasonString); + } + + if (_powerRequestHandle.IsNull) + { + throw new Win32Exception(); + } + + try + { + if (!PInvoke.PowerSetRequest(_powerRequestHandle, POWER_REQUEST_TYPE.PowerRequestDisplayRequired) + || !PInvoke.PowerSetRequest(_powerRequestHandle, POWER_REQUEST_TYPE.PowerRequestSystemRequired)) + { + throw new Win32Exception(); + } + } + catch + { + ReleasePowerRequest(); + throw; + } + } + + private void ReleasePowerRequest() + { + if (_powerRequestHandle.IsNull) + { + return; + } + + PInvoke.PowerClearRequest(_powerRequestHandle, POWER_REQUEST_TYPE.PowerRequestDisplayRequired); + PInvoke.PowerClearRequest(_powerRequestHandle, POWER_REQUEST_TYPE.PowerRequestSystemRequired); + PInvoke.CloseHandle(_powerRequestHandle); + _powerRequestHandle = default; + } + + private bool IsMessageFromMonitoredControl(HWND hwnd) + { + if (_targetForm is not null + && !_targetForm.IsDisposed + && (hwnd == _targetForm.HWND || PInvoke.IsChild(_targetForm, hwnd))) + { + return true; + } + + return _containerControl is not null + && !_containerControl.IsDisposed + && _containerControl.IsHandleCreated + && (hwnd == _containerControl.HWND || PInvoke.IsChild(_containerControl, hwnd)); + } + + private bool ProcessMessage(Message message) + { + if (_containerControl is null || DesignMode) + { + return false; + } + + if (_targetForm is null || _targetForm.IsDisposed) + { + ResolveTargetForm(); + if (_pendingFullScreen && _targetForm is not null) + { + EnterFullScreen(); + } + } + + if (!IsMessageFromMonitoredControl(message.HWND)) + { + return false; + } + + if (message.MsgInternal == PInvokeCore.WM_KEYDOWN + || message.MsgInternal == PInvokeCore.WM_SYSKEYDOWN) + { + ProcessKeyboardActivity((Keys)(nint)message.WParamInternal, Control.ModifierKeys & Keys.Modifiers); + } + else if (message.MsgInternal >= PInvokeCore.WM_MOUSEFIRST + && message.MsgInternal <= PInvokeCore.WM_MOUSELAST) + { + ProcessMouseActivity(); + } + + return false; + } + + /// + /// Observes keyboard and mouse messages that belong to the target form. + /// + private sealed class KioskModeMessageFilter : IMessageFilter + { + private readonly KioskModeManager _owner; + + public KioskModeMessageFilter(KioskModeManager owner) + { + _owner = owner; + } + + public bool PreFilterMessage(ref Message m) + => _owner.ProcessMessage(m); + } + + /// + /// Observes power and session messages that belong to the target form. + /// + private sealed class KioskModeFormObserver : NativeWindow + { + private readonly KioskModeManager _owner; + private Form? _form; + private bool _sessionNotificationsRegistered; + + public KioskModeFormObserver(KioskModeManager owner) + { + _owner = owner; + } + + public void Attach(Form? form) + { + if (ReferenceEquals(_form, form)) + { + return; + } + + Detach(); + + if (form is null || form.IsDisposed) + { + return; + } + + _form = form; + form.HandleCreated += OnFormHandleCreated; + form.HandleDestroyed += OnFormHandleDestroyed; + + if (form.IsHandleCreated) + { + OnFormHandleCreated(form, EventArgs.Empty); + } + } + + public void Detach() + { + if (_form is not null) + { + _form.HandleCreated -= OnFormHandleCreated; + _form.HandleDestroyed -= OnFormHandleDestroyed; + _form = null; + } + + UnregisterSessionNotifications(); + if (!HWND.IsNull) + { + ReleaseHandle(); + } + } + + private void OnFormHandleCreated(object? sender, EventArgs e) + { + if (_form is null || _form.IsDisposed || !_form.IsHandleCreated) + { + return; + } + + AssignHandle(_form.HWND); + RegisterSessionNotifications(); + } + + private void OnFormHandleDestroyed(object? sender, EventArgs e) + { + UnregisterSessionNotifications(); + if (!HWND.IsNull) + { + ReleaseHandle(); + } + } + + private void RegisterSessionNotifications() + { + if (_sessionNotificationsRegistered || HWND.IsNull) + { + return; + } + + _sessionNotificationsRegistered = PInvoke.WTSRegisterSessionNotification(HWND, NotifyForThisSession); + } + + private void UnregisterSessionNotifications() + { + if (!_sessionNotificationsRegistered) + { + return; + } + + PInvoke.WTSUnRegisterSessionNotification(HWND); + _sessionNotificationsRegistered = false; + } + + protected override void WndProc(ref Message m) + { + if (m.MsgInternal == PInvokeCore.WM_POWERBROADCAST) + { + _owner.ProcessPowerBroadcast(m.WParamInternal); + } + else if (m.MsgInternal == PInvokeCore.WM_WTSSESSION_CHANGE) + { + _owner.ProcessSessionChange(m.WParamInternal); + } + + base.WndProc(ref m); + } + } +} +#endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs new file mode 100644 index 00000000000..1ad14d4fcc2 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Forms; + +#if NET11_0_OR_GREATER +/// +/// Provides data for the event. +/// +/// +/// +/// The property identifies the kind of activity the +/// component observed. Applications can use this value to distinguish direct +/// user activity, such as mouse or keyboard input, from system activity such +/// as power resume or session unlock. +/// +/// +public class KioskModeWakeupEventArgs : EventArgs +{ + /// + /// Initializes a new instance of the + /// class. + /// + /// The source of the wakeup notification. + public KioskModeWakeupEventArgs(KioskModeWakeupSource source) + { + SourceGenerated.EnumValidator.Validate(source, nameof(source)); + Source = source; + } + + /// + /// Gets the source of the wakeup notification. + /// + public KioskModeWakeupSource Source { get; } +} +#endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs new file mode 100644 index 00000000000..b2784bb3b3c --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Forms; + +#if NET11_0_OR_GREATER +/// +/// Represents the method that will handle the +/// event. +/// +/// The source of the event. +/// A that contains the event data. +public delegate void KioskModeWakeupEventHandler(object? sender, KioskModeWakeupEventArgs e); +#endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs new file mode 100644 index 00000000000..6c9a05784c4 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Forms; + +#if NET11_0_OR_GREATER +/// +/// Specifies the source of a +/// notification. +/// +public enum KioskModeWakeupSource +{ + /// + /// The wakeup notification was caused by keyboard activity. + /// + Keyboard = 0, + + /// + /// The wakeup notification was caused by mouse activity. + /// + Mouse = 1, + + /// + /// The wakeup notification was caused by the system resuming from a low + /// power state. + /// + PowerResume = 2, + + /// + /// The wakeup notification was caused by a Windows session activity, such + /// as logon or unlock. + /// + Session = 3, +} +#endif diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs new file mode 100644 index 00000000000..c5d3adcdff4 --- /dev/null +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Windows.Input; +using Moq; + +namespace System.Windows.Forms.Tests; + +public class KioskModeManagerTests +{ + [WinFormsFact] + public void KioskModeManager_Ctor_Default() + { + using SubKioskModeManager manager = new(); + + Assert.Null(manager.Container); + Assert.Null(manager.ContainerControl); + Assert.False(manager.DesignMode); + Assert.True(manager.EscapeExitsFullScreen); + Assert.False(manager.HideTaskbar); + Assert.False(manager.FullScreen); + Assert.Equal(0, manager.MousePointerAutoHideDelay); + Assert.Null(manager.Site); + Assert.False(manager.SuppressPowerSaving); + Assert.Equal(Keys.F11, manager.ToggleFullScreenKey); + Assert.False(manager.TopMostInFullScreen); + } + + [WinFormsFact] + public void KioskModeManager_Ctor_IContainer() + { + using Container container = new(); + using KioskModeManager manager = new(container); + + Assert.Same(container, manager.Container); + Assert.NotNull(manager.Site); + } + + [WinFormsFact] + public void KioskModeManager_Ctor_NullContainer_ThrowsArgumentNullException() + { + Assert.Throws("container", () => new KioskModeManager(null)); + } + + [WinFormsFact] + public void KioskModeManager_ContainerControl_Set_GetReturnsExpected() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + Assert.Same(form, manager.ContainerControl); + + manager.ContainerControl = form; + Assert.Same(form, manager.ContainerControl); + + manager.ContainerControl = null; + Assert.Null(manager.ContainerControl); + } + + [WinFormsFact] + public void KioskModeManager_ContainerControl_SetWithHandler_CallsContainerControlChanged() + { + using Form form = new(); + using KioskModeManager manager = new(); + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(manager, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + + manager.ContainerControlChanged += handler; + manager.ContainerControl = form; + Assert.Equal(1, callCount); + + manager.ContainerControl = form; + Assert.Equal(1, callCount); + + manager.ContainerControl = null; + Assert.Equal(2, callCount); + + manager.ContainerControlChanged -= handler; + manager.ContainerControl = form; + Assert.Equal(2, callCount); + } + + [WinFormsTheory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3000)] + public void KioskModeManager_MousePointerAutoHideDelay_Set_GetReturnsExpected(int value) + { + using KioskModeManager manager = new() + { + MousePointerAutoHideDelay = value + }; + + Assert.Equal(value, manager.MousePointerAutoHideDelay); + + manager.MousePointerAutoHideDelay = value; + Assert.Equal(value, manager.MousePointerAutoHideDelay); + } + + [WinFormsFact] + public void KioskModeManager_MousePointerAutoHideDelay_SetNegative_ThrowsArgumentOutOfRangeException() + { + using KioskModeManager manager = new(); + + Assert.Throws("value", () => manager.MousePointerAutoHideDelay = -1); + } + + [WinFormsTheory] + [EnumData] + public void KioskModeWakeupEventArgs_Ctor_Source(KioskModeWakeupSource source) + { + KioskModeWakeupEventArgs eventArgs = new(source); + + Assert.Equal(source, eventArgs.Source); + } + + [WinFormsTheory] + [InvalidEnumData] + public void KioskModeWakeupEventArgs_Ctor_InvalidSource_ThrowsInvalidEnumArgumentException(KioskModeWakeupSource source) + { + Assert.Throws("source", () => new KioskModeWakeupEventArgs(source)); + } + + [WinFormsTheory] + [EnumData] + public void KioskModeManager_OnWakeup_Invoke_CallsWakeup(KioskModeWakeupSource source) + { + using SubKioskModeManager manager = new(); + KioskModeWakeupEventArgs eventArgs = new(source); + int callCount = 0; + KioskModeWakeupEventHandler handler = (sender, e) => + { + Assert.Same(manager, sender); + Assert.Same(eventArgs, e); + Assert.Equal(source, e.Source); + callCount++; + }; + + manager.Wakeup += handler; + manager.OnWakeup(eventArgs); + Assert.Equal(1, callCount); + + manager.Wakeup -= handler; + manager.OnWakeup(eventArgs); + Assert.Equal(1, callCount); + } + + [WinFormsFact] + public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + FormBorderStyle = FormBorderStyle.FixedDialog, + WindowState = FormWindowState.Normal, + TopMost = false + }; + + using KioskModeManager manager = new() + { + ContainerControl = form, + HideTaskbar = false, + TopMostInFullScreen = true + }; + + manager.ToggleFullScreen(); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + Assert.True(form.TopMost); + Assert.Equal(FormWindowState.Maximized, form.WindowState); + + manager.ToggleFullScreen(); + + Assert.False(manager.FullScreen); + Assert.Equal(FormBorderStyle.FixedDialog, form.FormBorderStyle); + Assert.False(form.TopMost); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); + } + + [WinFormsFact] + public void KioskModeManager_ToggleFullScreen_UserControlContainer_UsesParentForm() + { + using Form form = new(); + using UserControl userControl = new(); + form.Controls.Add(userControl); + using KioskModeManager manager = new() + { + ContainerControl = userControl + }; + + manager.ToggleFullScreen(); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + } + + [WinFormsFact] + public void KioskModeManager_ToggleFullScreen_UserControlContainerParentedAfterSet_UsesParentForm() + { + using Form form = new(); + using UserControl userControl = new(); + using KioskModeManager manager = new() + { + ContainerControl = userControl + }; + + form.Controls.Add(userControl); + manager.ToggleFullScreen(); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + } + + [WinFormsFact] + public void KioskModeManager_ContainerControl_SetWhileFullScreen_RestoresPreviousForm() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + FormBorderStyle = FormBorderStyle.FixedDialog, + WindowState = FormWindowState.Normal + }; + + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + manager.ToggleFullScreen(); + Assert.True(manager.FullScreen); + + manager.ContainerControl = null; + + Assert.False(manager.FullScreen); + Assert.Equal(FormBorderStyle.FixedDialog, form.FormBorderStyle); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); + } + + [WinFormsFact] + public void KioskModeManager_ContainerControl_Set_DoesNotChangeFormKeyPreview() + { + using Form form = new() + { + KeyPreview = false + }; + + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + Assert.False(form.KeyPreview); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_SetTrueThenFalse_EntersAndExitsFullScreen() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + FormBorderStyle = FormBorderStyle.FixedDialog, + WindowState = FormWindowState.Normal + }; + + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + manager.FullScreen = true; + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + + manager.FullScreen = false; + + Assert.False(manager.FullScreen); + Assert.Equal(FormBorderStyle.FixedDialog, form.FormBorderStyle); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_SetWithHandler_CallsFullScreenChanged() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(manager, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + + manager.FullScreenChanged += handler; + + manager.FullScreen = true; + Assert.Equal(1, callCount); + + manager.FullScreen = true; + Assert.Equal(1, callCount); + + manager.FullScreen = false; + Assert.Equal(2, callCount); + + manager.FullScreenChanged -= handler; + manager.FullScreen = true; + Assert.Equal(2, callCount); + } + + [WinFormsTheory] + [EnumData] + public void KioskModeManager_WakeUpCommand_ExecutedOnWakeup_WithSourceName(KioskModeWakeupSource source) + { + using SubKioskModeManager manager = new(); + TestCommand command = new(); + manager.WakeUpCommand = command; + + Assert.Same(command, manager.WakeUpCommand); + + manager.OnWakeup(new KioskModeWakeupEventArgs(source)); + + Assert.Equal(1, command.ExecuteCount); + Assert.Equal(source.ToString(), command.LastParameter); + } + + [WinFormsFact] + public void KioskModeManager_WakeUpCommand_Null_DoesNotThrowOnWakeup() + { + using SubKioskModeManager manager = new(); + + Assert.Null(manager.WakeUpCommand); + + manager.OnWakeup(new KioskModeWakeupEventArgs(KioskModeWakeupSource.Keyboard)); + } + + [WinFormsFact] + public void KioskModeManager_Site_Set_AssignsRootComponentAsContainerControl() + { + using Form form = new(); + Mock host = new(); + host.Setup(h => h.RootComponent).Returns(form); + + Mock site = new(); + site.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(host.Object); + + using KioskModeManager manager = new(); + manager.Site = site.Object; + + Assert.Same(form, manager.ContainerControl); + } + + [WinFormsFact] + public void KioskModeManager_Site_Set_DoesNotOverwriteExplicitContainerControl() + { + using Form explicitForm = new(); + using Form rootForm = new(); + Mock host = new(); + host.Setup(h => h.RootComponent).Returns(rootForm); + + Mock site = new(); + site.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(host.Object); + + using KioskModeManager manager = new() + { + ContainerControl = explicitForm + }; + + manager.Site = site.Object; + + Assert.Same(explicitForm, manager.ContainerControl); + } + + private class TestCommand : ICommand + { + public int ExecuteCount { get; private set; } + + public object LastParameter { get; private set; } + + public event EventHandler CanExecuteChanged + { + add { } + remove { } + } + + public bool CanExecute(object parameter) => true; + + public void Execute(object parameter) + { + ExecuteCount++; + LastParameter = parameter; + } + } + + private class SubKioskModeManager : KioskModeManager + { + public new bool DesignMode => base.DesignMode; + + public new void OnWakeup(KioskModeWakeupEventArgs e) + => base.OnWakeup(e); + } +} From b3980cf6a9ae7e5248e486beb8d2f405fe07d46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20L=C3=B6ffelmann?= Date: Wed, 1 Jul 2026 17:39:18 -0700 Subject: [PATCH 04/12] Expand KioskModeManager tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Windows/Forms/KioskModeManagerTests.cs | 272 +++++++++++++++++- 1 file changed, 271 insertions(+), 1 deletion(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index c5d3adcdff4..6c3f1c2d652 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -11,6 +11,7 @@ namespace System.Windows.Forms.Tests; +#if NET11_0_OR_GREATER public class KioskModeManagerTests { [WinFormsFact] @@ -329,6 +330,50 @@ public void KioskModeManager_FullScreen_SetWithHandler_CallsFullScreenChanged() Assert.Equal(2, callCount); } + [WinFormsTheory] + [NewAndDefaultData] + public void KioskModeManager_OnContainerControlChanged_Invoke_CallsContainerControlChanged(EventArgs eventArgs) + { + using SubKioskModeManager manager = new(); + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(manager, sender); + Assert.Same(eventArgs, e); + callCount++; + }; + + manager.ContainerControlChanged += handler; + manager.OnContainerControlChanged(eventArgs); + Assert.Equal(1, callCount); + + manager.ContainerControlChanged -= handler; + manager.OnContainerControlChanged(eventArgs); + Assert.Equal(1, callCount); + } + + [WinFormsTheory] + [NewAndDefaultData] + public void KioskModeManager_OnFullScreenChanged_Invoke_CallsFullScreenChanged(EventArgs eventArgs) + { + using SubKioskModeManager manager = new(); + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(manager, sender); + Assert.Same(eventArgs, e); + callCount++; + }; + + manager.FullScreenChanged += handler; + manager.OnFullScreenChanged(eventArgs); + Assert.Equal(1, callCount); + + manager.FullScreenChanged -= handler; + manager.OnFullScreenChanged(eventArgs); + Assert.Equal(1, callCount); + } + [WinFormsTheory] [EnumData] public void KioskModeManager_WakeUpCommand_ExecutedOnWakeup_WithSourceName(KioskModeWakeupSource source) @@ -341,6 +386,8 @@ public void KioskModeManager_WakeUpCommand_ExecutedOnWakeup_WithSourceName(Kiosk manager.OnWakeup(new KioskModeWakeupEventArgs(source)); + Assert.Equal(1, command.CanExecuteCount); + Assert.Equal(source.ToString(), command.LastCanExecuteParameter); Assert.Equal(1, command.ExecuteCount); Assert.Equal(source.ToString(), command.LastParameter); } @@ -355,6 +402,24 @@ public void KioskModeManager_WakeUpCommand_Null_DoesNotThrowOnWakeup() manager.OnWakeup(new KioskModeWakeupEventArgs(KioskModeWakeupSource.Keyboard)); } + [WinFormsFact] + public void KioskModeManager_WakeUpCommand_CanExecuteFalse_DoesNotExecute() + { + using SubKioskModeManager manager = new(); + TestCommand command = new() + { + CanExecuteResult = false + }; + + manager.WakeUpCommand = command; + manager.OnWakeup(new KioskModeWakeupEventArgs(KioskModeWakeupSource.Mouse)); + + Assert.Equal(1, command.CanExecuteCount); + Assert.Equal(KioskModeWakeupSource.Mouse.ToString(), command.LastCanExecuteParameter); + Assert.Equal(0, command.ExecuteCount); + Assert.Null(command.LastParameter); + } + [WinFormsFact] public void KioskModeManager_Site_Set_AssignsRootComponentAsContainerControl() { @@ -392,8 +457,201 @@ public void KioskModeManager_Site_Set_DoesNotOverwriteExplicitContainerControl() Assert.Same(explicitForm, manager.ContainerControl); } + [WinFormsFact] + public void KioskModeManager_Site_SetWithExplicitNull_DoesNotAssignRootComponent() + { + using Form form = new(); + Mock host = new(); + host.Setup(h => h.RootComponent).Returns(form); + + Mock site = new(); + site.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(host.Object); + + using KioskModeManager manager = new() + { + ContainerControl = null + }; + + manager.Site = site.Object; + + Assert.Null(manager.ContainerControl); + } + + [WinFormsFact] + public void KioskModeManager_Site_SetWithoutExplicitContainerControl_UpdatesResolvedRootComponent() + { + using Form firstForm = new(); + using Form secondForm = new(); + + Mock firstHost = new(); + firstHost.Setup(h => h.RootComponent).Returns(firstForm); + Mock secondHost = new(); + secondHost.Setup(h => h.RootComponent).Returns(secondForm); + + Mock firstSite = new(); + firstSite.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(firstHost.Object); + Mock secondSite = new(); + secondSite.Setup(s => s.GetService(typeof(IDesignerHost))).Returns(secondHost.Object); + + using KioskModeManager manager = new(); + manager.Site = firstSite.Object; + Assert.Same(firstForm, manager.ContainerControl); + + manager.Site = secondSite.Object; + Assert.Same(secondForm, manager.ContainerControl); + } + + [WinFormsFact] + public void KioskModeManager_BeginInit_FullScreenSet_DoesNotEnterUntilEndInit() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + FormBorderStyle = FormBorderStyle.FixedDialog, + WindowState = FormWindowState.Normal + }; + + using KioskModeManager manager = new(); + ISupportInitialize supportInitialize = manager; + + supportInitialize.BeginInit(); + manager.ContainerControl = form; + manager.FullScreen = true; + + Assert.Equal(FormBorderStyle.FixedDialog, form.FormBorderStyle); + Assert.Equal(FormWindowState.Normal, form.WindowState); + + supportInitialize.EndInit(); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + Assert.Equal(FormWindowState.Maximized, form.WindowState); + } + + [WinFormsFact] + public void KioskModeManager_DisposeWhileFullScreen_RestoresExpected() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + FormBorderStyle = FormBorderStyle.FixedDialog, + WindowState = FormWindowState.Normal, + TopMost = false + }; + + KioskModeManager manager = new() + { + ContainerControl = form, + TopMostInFullScreen = true, + FullScreen = true + }; + manager.Dispose(); + + Assert.Equal(FormBorderStyle.FixedDialog, form.FormBorderStyle); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); + Assert.False(form.TopMost); + } + + [WinFormsFact] + public void KioskModeManager_ProcessMessage_KeyDownThenKeyUp_TogglesOnce() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + Message keyDownMessage = Message.Create(form.Handle, (int)PInvokeCore.WM_KEYDOWN, (nint)Keys.F11, 0); + Message keyUpMessage = Message.Create(form.Handle, (int)PInvokeCore.WM_KEYUP, (nint)Keys.F11, 0); + + manager.TestAccessor.Dynamic.ProcessMessage(keyDownMessage); + Assert.True(manager.FullScreen); + + manager.TestAccessor.Dynamic.ProcessMessage(keyUpMessage); + Assert.True(manager.FullScreen); + } + + [WinFormsFact] + public void KioskModeManager_ProcessMessage_MouseMove_CallsWakeup() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + int callCount = 0; + KioskModeWakeupEventArgs lastEventArgs = null; + manager.Wakeup += (sender, e) => + { + Assert.Same(manager, sender); + callCount++; + lastEventArgs = e; + }; + + Message message = Message.Create(form.Handle, (int)PInvokeCore.WM_MOUSEMOVE, 0, 0); + manager.TestAccessor.Dynamic.ProcessMessage(message); + + Assert.Equal(1, callCount); + Assert.NotNull(lastEventArgs); + Assert.Equal(KioskModeWakeupSource.Mouse, lastEventArgs.Source); + } + + [WinFormsFact] + public void KioskModeManager_ProcessPowerBroadcast_Resume_CallsWakeup() + { + using SubKioskModeManager manager = new(); + int callCount = 0; + KioskModeWakeupEventArgs lastEventArgs = null; + manager.Wakeup += (sender, e) => + { + Assert.Same(manager, sender); + callCount++; + lastEventArgs = e; + }; + + Message message = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0012, 0); + manager.TestAccessor.Dynamic.ProcessPowerBroadcast(message.WParamInternal); + + Assert.Equal(1, callCount); + Assert.NotNull(lastEventArgs); + Assert.Equal(KioskModeWakeupSource.PowerResume, lastEventArgs.Source); + } + + [WinFormsTheory] + [InlineData(0x0001)] + [InlineData(0x0003)] + [InlineData(0x0005)] + [InlineData(0x0008)] + public void KioskModeManager_ProcessSessionChange_RecognizedReason_CallsWakeup(nint sessionReason) + { + using SubKioskModeManager manager = new(); + int callCount = 0; + KioskModeWakeupEventArgs lastEventArgs = null; + manager.Wakeup += (sender, e) => + { + Assert.Same(manager, sender); + callCount++; + lastEventArgs = e; + }; + + Message message = Message.Create(IntPtr.Zero, 0, sessionReason, 0); + manager.TestAccessor.Dynamic.ProcessSessionChange(message.WParamInternal); + + Assert.Equal(1, callCount); + Assert.NotNull(lastEventArgs); + Assert.Equal(KioskModeWakeupSource.Session, lastEventArgs.Source); + } + private class TestCommand : ICommand { + public bool CanExecuteResult { get; set; } = true; + + public int CanExecuteCount { get; private set; } + + public object LastCanExecuteParameter { get; private set; } + public int ExecuteCount { get; private set; } public object LastParameter { get; private set; } @@ -404,7 +662,12 @@ public event EventHandler CanExecuteChanged remove { } } - public bool CanExecute(object parameter) => true; + public bool CanExecute(object parameter) + { + CanExecuteCount++; + LastCanExecuteParameter = parameter; + return CanExecuteResult; + } public void Execute(object parameter) { @@ -417,7 +680,14 @@ private class SubKioskModeManager : KioskModeManager { public new bool DesignMode => base.DesignMode; + public new void OnContainerControlChanged(EventArgs e) + => base.OnContainerControlChanged(e); + + public new void OnFullScreenChanged(EventArgs e) + => base.OnFullScreenChanged(e); + public new void OnWakeup(KioskModeWakeupEventArgs e) => base.OnWakeup(e); } } +#endif From a5dfe622474038f52e647bae844f0f6b386a3fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20L=C3=B6ffelmann?= Date: Fri, 10 Jul 2026 01:35:51 -0700 Subject: [PATCH 05/12] Harden KioskModeManager lifecycle behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 20883488-cf1e-4ad3-a681-0724e9f16777 --- ...04_KioskManagerComponent.HighRiskReview.md | 38 +++++ .../KioskModeManager/KioskModeManager.cs | 84 +++++++++-- .../Windows/Forms/KioskModeManagerTests.cs | 136 +++++++++++++++++- 3 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md diff --git a/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md new file mode 100644 index 00000000000..b3cb075a1a1 --- /dev/null +++ b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md @@ -0,0 +1,38 @@ +# Net11Api_04 KioskModeManager High-Risk Review + +This document records review findings that require an API or lifecycle design decision before they can be patched safely. + +## FullScreen requested state and actual state differ + +`KioskModeManager.FullScreen` currently returns `true` when fullscreen has been requested but no target form is available. `FullScreenChanged`, however, is raised only when the resolved form actually enters or exits fullscreen. This creates two observable state models: + +- The property reports requested state through `_pendingFullScreen || _isFullScreen`. +- The event reports actual window state through changes to `_isFullScreen`. + +Consequently, setting `FullScreen = true` without a resolved form changes the property without raising the event. When a form later becomes available, the event is raised even though the property value remains `true`. + +Before changing this behavior, decide whether `FullScreen` represents requested state or actual window state. The decision must cover: + +- initialization and delayed parenting; +- missing, replaced, reparented, and disposed container controls; +- event ordering and two-way data binding; +- `ToggleFullScreen` behavior while a request is pending; +- disposal and failed fullscreen transitions; +- compatibility for applications already observing the property or event. + +Tests should define the complete transition matrix for requested, pending, entered, exited, reparented, and disposed states. + +## Session-notification registration is not retried + +`KioskModeFormObserver.RegisterSessionNotifications` records a failed `WTSRegisterSessionNotification` call as `false` and retries only after form handle recreation. During Windows startup, registration can fail with `RPC_S_INVALID_BINDING` before `Global\TermSrvReadyEvent` is signaled. A kiosk application started during that interval can therefore miss session notifications for the lifetime of its form handle. + +A safe fix needs a non-blocking retry design that specifies: + +- which Win32 errors are retryable and which are terminal; +- how waiting for `Global\TermSrvReadyEvent` is cancelled; +- how completion is marshalled to the form's UI thread; +- how stale callbacks are rejected after handle recreation; +- how registration and unregistration remain paired; +- how disposal races with an outstanding wait. + +Tests should cover service-not-ready startup, successful retry, handle recreation during the wait, and disposal before the wait completes. diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index 30bf82ff0b8..87befa212f9 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -72,7 +72,6 @@ public class KioskModeManager : Component, ISupportInitialize private const uint PowerRequestContextVersion = 0; private const POWER_REQUEST_CONTEXT_FLAGS PowerRequestContextSimpleString = (POWER_REQUEST_CONTEXT_FLAGS)0x00000001; private const uint NotifyForThisSession = 0; - private const nint PbtApmResumeSuspend = 0x0007; private const nint PbtApmResumeAutomatic = 0x0012; private const nint WtsConsoleConnect = 0x0001; private const nint WtsRemoteConnect = 0x0003; @@ -91,6 +90,7 @@ public class KioskModeManager : Component, ISupportInitialize private bool _pendingFullScreen; private bool _initializing; private bool _isCursorHidden; + private readonly List _parentChain = []; private KioskModeMessageFilter? _messageFilter; private KioskModeFormObserver? _formObserver; private Timer? _mousePointerAutoHideTimer; @@ -364,8 +364,18 @@ public bool SuppressPowerSaving return; } + bool previousValue = _suppressPowerSaving; _suppressPowerSaving = value; - UpdatePowerRequest(); + + try + { + UpdatePowerRequest(); + } + catch + { + _suppressPowerSaving = previousValue; + throw; + } } } @@ -715,6 +725,7 @@ private void AttachToForm() return; } + UpdateParentChangedSubscriptions(); EnsureMessageMonitoring(); ResolveTargetForm(); if (_pendingFullScreen && _targetForm is not null) @@ -725,6 +736,8 @@ private void AttachToForm() private void DetachFromForm() { + ClearParentChangedSubscriptions(); + if (_messageFilter is not null) { Application.RemoveMessageFilter(_messageFilter); @@ -737,6 +750,52 @@ private void DetachFromForm() _targetForm = null; } + private void OnContainerControlParentChanged(object? sender, EventArgs e) + { + UpdateParentChangedSubscriptions(); + + Form? newTarget = _containerControl as Form ?? _containerControl?.FindForm(); + if (ReferenceEquals(_targetForm, newTarget)) + { + return; + } + + bool restoreFullScreen = _pendingFullScreen || _isFullScreen; + + if (_isFullScreen) + { + ExitFullScreen(); + } + + ResolveTargetForm(); + + if (restoreFullScreen) + { + FullScreen = true; + } + } + + private void UpdateParentChangedSubscriptions() + { + ClearParentChangedSubscriptions(); + + for (Control? current = _containerControl; current is not null; current = current.Parent) + { + current.ParentChanged += OnContainerControlParentChanged; + _parentChain.Add(current); + } + } + + private void ClearParentChangedSubscriptions() + { + foreach (Control control in _parentChain) + { + control.ParentChanged -= OnContainerControlParentChanged; + } + + _parentChain.Clear(); + } + private Form? ResolveTargetForm() { // ContainerControl intentionally supports both Form and UserControl @@ -769,10 +828,7 @@ private void ApplyFullScreen(Form form) form.WindowState = FormWindowState.Normal; form.FormBorderStyle = FormBorderStyle.None; - if (_topMostInFullScreen) - { - form.TopMost = true; - } + form.TopMost = _topMostInFullScreen; if (_hideTaskbar) { @@ -815,8 +871,7 @@ private void UpdatePowerRequest() private void ProcessPowerBroadcast(WPARAM powerEvent) { - nint value = (nint)powerEvent.Value; - if (value is PbtApmResumeAutomatic or PbtApmResumeSuspend) + if ((nint)powerEvent.Value == PbtApmResumeAutomatic) { RaiseWakeup(KioskModeWakeupSource.PowerResume); } @@ -864,12 +919,17 @@ private void OnMousePointerAutoHideTimerTick(object? sender, EventArgs e) } } - private void ProcessKeyboardActivity(Keys keyData, Keys modifiers) + private void ProcessKeyboardActivity(Keys keyData, Keys modifiers, bool isRepeat) { // Any keyboard input wakes the kiosk experience, even when it does not // match one of the configured fullscreen control keys. RaiseWakeup(KioskModeWakeupSource.Keyboard); + if (isRepeat) + { + return; + } + Keys keyCode = keyData & Keys.KeyCode; if (keyCode == _toggleFullScreenKey && modifiers == Keys.None) @@ -1005,7 +1065,11 @@ private bool ProcessMessage(Message message) if (message.MsgInternal == PInvokeCore.WM_KEYDOWN || message.MsgInternal == PInvokeCore.WM_SYSKEYDOWN) { - ProcessKeyboardActivity((Keys)(nint)message.WParamInternal, Control.ModifierKeys & Keys.Modifiers); + bool isRepeat = ((nuint)(nint)message.LParamInternal & (1u << 30)) != 0; + ProcessKeyboardActivity( + (Keys)(nint)message.WParamInternal, + Control.ModifierKeys & Keys.Modifiers, + isRepeat); } else if (message.MsgInternal >= PInvokeCore.WM_MOUSEFIRST && message.MsgInternal <= PInvokeCore.WM_MOUSELAST) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index 6c3f1c2d652..d6a877d8bac 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -227,6 +227,90 @@ public void KioskModeManager_ToggleFullScreen_UserControlContainerParentedAfterS Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); } + [WinFormsFact] + public void KioskModeManager_FullScreen_SetBeforeUserControlIsParented_EntersWhenParented() + { + using Form form = new(); + using UserControl userControl = new(); + using KioskModeManager manager = new() + { + ContainerControl = userControl, + FullScreen = true + }; + + form.Controls.Add(userControl); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_UserControlReparented_TransfersToNewForm() + { + using Form firstForm = new() + { + FormBorderStyle = FormBorderStyle.FixedDialog + }; + using Form secondForm = new(); + using UserControl userControl = new(); + firstForm.Controls.Add(userControl); + using KioskModeManager manager = new() + { + ContainerControl = userControl, + FullScreen = true + }; + + secondForm.Controls.Add(userControl); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.FixedDialog, firstForm.FormBorderStyle); + Assert.Equal(FormBorderStyle.None, secondForm.FormBorderStyle); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_AncestorReparented_TransfersToNewForm() + { + using Form firstForm = new() + { + FormBorderStyle = FormBorderStyle.FixedDialog + }; + using Form secondForm = new(); + using Panel panel = new(); + using UserControl userControl = new(); + panel.Controls.Add(userControl); + firstForm.Controls.Add(panel); + using KioskModeManager manager = new() + { + ContainerControl = userControl, + FullScreen = true + }; + + secondForm.Controls.Add(panel); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.FixedDialog, firstForm.FormBorderStyle); + Assert.Equal(FormBorderStyle.None, secondForm.FormBorderStyle); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_SetBeforeAncestorIsParented_EntersWhenParented() + { + using Form form = new(); + using Panel panel = new(); + using UserControl userControl = new(); + panel.Controls.Add(userControl); + using KioskModeManager manager = new() + { + ContainerControl = userControl, + FullScreen = true + }; + + form.Controls.Add(panel); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + } + [WinFormsFact] public void KioskModeManager_ContainerControl_SetWhileFullScreen_RestoresPreviousForm() { @@ -297,6 +381,28 @@ public void KioskModeManager_FullScreen_SetTrueThenFalse_EntersAndExitsFullScree Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); } + [WinFormsFact] + public void KioskModeManager_FullScreen_TopMostDisabled_OverridesAndRestoresFormTopMost() + { + using Form form = new() + { + TopMost = true + }; + using KioskModeManager manager = new() + { + ContainerControl = form, + TopMostInFullScreen = false + }; + + manager.FullScreen = true; + + Assert.False(form.TopMost); + + manager.FullScreen = false; + + Assert.True(form.TopMost); + } + [WinFormsFact] public void KioskModeManager_FullScreen_SetWithHandler_CallsFullScreenChanged() { @@ -572,6 +678,28 @@ public void KioskModeManager_ProcessMessage_KeyDownThenKeyUp_TogglesOnce() Assert.True(manager.FullScreen); } + [WinFormsFact] + public void KioskModeManager_ProcessMessage_RepeatedKeyDown_DoesNotToggleAgain() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + Message keyDownMessage = Message.Create(form.Handle, (int)PInvokeCore.WM_KEYDOWN, (nint)Keys.F11, 0); + Message repeatedKeyDownMessage = Message.Create( + form.Handle, + (int)PInvokeCore.WM_KEYDOWN, + (nint)Keys.F11, + 1 << 30); + + manager.TestAccessor.Dynamic.ProcessMessage(keyDownMessage); + manager.TestAccessor.Dynamic.ProcessMessage(repeatedKeyDownMessage); + + Assert.True(manager.FullScreen); + } + [WinFormsFact] public void KioskModeManager_ProcessMessage_MouseMove_CallsWakeup() { @@ -599,7 +727,7 @@ public void KioskModeManager_ProcessMessage_MouseMove_CallsWakeup() } [WinFormsFact] - public void KioskModeManager_ProcessPowerBroadcast_Resume_CallsWakeup() + public void KioskModeManager_ProcessPowerBroadcast_InteractiveResume_CallsWakeupOnce() { using SubKioskModeManager manager = new(); int callCount = 0; @@ -611,8 +739,10 @@ public void KioskModeManager_ProcessPowerBroadcast_Resume_CallsWakeup() lastEventArgs = e; }; - Message message = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0012, 0); - manager.TestAccessor.Dynamic.ProcessPowerBroadcast(message.WParamInternal); + Message automaticResume = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0012, 0); + Message interactiveResume = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0007, 0); + manager.TestAccessor.Dynamic.ProcessPowerBroadcast(automaticResume.WParamInternal); + manager.TestAccessor.Dynamic.ProcessPowerBroadcast(interactiveResume.WParamInternal); Assert.Equal(1, callCount); Assert.NotNull(lastEventArgs); From 0e3b568bf2b83189e88ea236970e4111cfb7c9a8 Mon Sep 17 00:00:00 2001 From: Klaus Loeffelmann Date: Sun, 19 Jul 2026 09:31:29 -0700 Subject: [PATCH 06/12] Fix kiosk StatusStrip dropdown positioning Size borderless fullscreen forms directly to the selected screen bounds instead of maximizing them. This keeps StatusStrip items and working-area-constrained dropdowns on the same screen edge while preserving restored form state. Fixes dotnet/winforms#14765 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c352332b-4b81-4492-bcfc-9dec524c27c9 --- .../KioskModeManager/KioskModeManager.cs | 17 ++---- .../Windows/Forms/KioskModeManagerTests.cs | 56 ++++++++++++++++++- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index 87befa212f9..ad82738bf49 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -823,22 +823,17 @@ private void EnsureMessageMonitoring() private void ApplyFullScreen(Form form) { + Screen screen = Screen.FromRectangle(form.Bounds); + Rectangle fullScreenBounds = _hideTaskbar + ? screen.Bounds + : screen.WorkingArea; + // Apply fullscreen from a normal state so bounds and border changes do // not operate on Windows' maximized window rectangle. form.WindowState = FormWindowState.Normal; form.FormBorderStyle = FormBorderStyle.None; - form.TopMost = _topMostInFullScreen; - - if (_hideTaskbar) - { - Screen screen = Screen.FromControl(form); - form.Bounds = screen.Bounds; - } - else - { - form.WindowState = FormWindowState.Maximized; - } + form.Bounds = fullScreenBounds; } private void UpdateFormObserver() diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index d6a877d8bac..c54c4424ab0 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -176,13 +176,15 @@ public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() HideTaskbar = false, TopMostInFullScreen = true }; + Rectangle workingArea = Screen.FromRectangle(form.Bounds).WorkingArea; manager.ToggleFullScreen(); Assert.True(manager.FullScreen); Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); Assert.True(form.TopMost); - Assert.Equal(FormWindowState.Maximized, form.WindowState); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(workingArea, form.Bounds); manager.ToggleFullScreen(); @@ -193,6 +195,54 @@ public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() Assert.Equal(new Rectangle(10, 20, 300, 200), form.Bounds); } + [WinFormsFact] + public void KioskModeManager_FullScreen_HideTaskbar_UsesScreenBounds() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200) + }; + using KioskModeManager manager = new() + { + ContainerControl = form, + HideTaskbar = true + }; + Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; + + manager.FullScreen = true; + + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(screenBounds, form.Bounds); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_StatusStripDropDown_RemainsAttached() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200) + }; + using StatusStrip statusStrip = new(); + using ToolStripDropDownButton dropDownButton = new("Options"); + dropDownButton.DropDownItems.Add("First"); + statusStrip.Items.Add(dropDownButton); + form.Controls.Add(statusStrip); + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + form.Show(); + manager.FullScreen = true; + dropDownButton.ShowDropDown(); + + Rectangle itemScreenBounds = statusStrip.RectangleToScreen(dropDownButton.Bounds); + Assert.InRange( + Math.Abs(dropDownButton.DropDown.Bounds.Bottom - itemScreenBounds.Top), + 0, + 1); + } + [WinFormsFact] public void KioskModeManager_ToggleFullScreen_UserControlContainer_UsesParentForm() { @@ -619,6 +669,7 @@ public void KioskModeManager_BeginInit_FullScreenSet_DoesNotEnterUntilEndInit() using KioskModeManager manager = new(); ISupportInitialize supportInitialize = manager; + Rectangle workingArea = Screen.FromRectangle(form.Bounds).WorkingArea; supportInitialize.BeginInit(); manager.ContainerControl = form; @@ -631,7 +682,8 @@ public void KioskModeManager_BeginInit_FullScreenSet_DoesNotEnterUntilEndInit() Assert.True(manager.FullScreen); Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); - Assert.Equal(FormWindowState.Maximized, form.WindowState); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(workingArea, form.Bounds); } [WinFormsFact] From 7355debd03bd9eaed9d8812796cf49285e5496fe Mon Sep 17 00:00:00 2001 From: Klaus Loeffelmann Date: Sun, 19 Jul 2026 10:42:01 -0700 Subject: [PATCH 07/12] Preserve kiosk monitor selection when minimized Choose fullscreen bounds from RestoreBounds for minimized and maximized forms so a minimized-window sentinel cannot redirect kiosk mode to the wrong monitor. Fixes dotnet/winforms#14765 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c352332b-4b81-4492-bcfc-9dec524c27c9 --- .../KioskModeManager/KioskModeManager.cs | 5 +++- .../Windows/Forms/KioskModeManagerTests.cs | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index ad82738bf49..f7fe931a622 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -823,7 +823,10 @@ private void EnsureMessageMonitoring() private void ApplyFullScreen(Form form) { - Screen screen = Screen.FromRectangle(form.Bounds); + Rectangle screenReferenceBounds = form.WindowState == FormWindowState.Normal + ? form.Bounds + : form.RestoreBounds; + Screen screen = Screen.FromRectangle(screenReferenceBounds); Rectangle fullScreenBounds = _hideTaskbar ? screen.Bounds : screen.WorkingArea; diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index c54c4424ab0..05d3489818d 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -215,6 +215,29 @@ public void KioskModeManager_FullScreen_HideTaskbar_UsesScreenBounds() Assert.Equal(screenBounds, form.Bounds); } + [WinFormsFact] + public void KioskModeManager_FullScreen_MinimizedForm_UsesRestoreMonitor() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200), + StartPosition = FormStartPosition.Manual + }; + form.Show(); + form.WindowState = FormWindowState.Minimized; + Rectangle restoreBounds = form.RestoreBounds; + Rectangle workingArea = Screen.FromRectangle( + restoreBounds).WorkingArea; + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + manager.FullScreen = true; + + Assert.Equal(workingArea, form.Bounds); + } + [WinFormsFact] public void KioskModeManager_FullScreen_StatusStripDropDown_RemainsAttached() { From 5ee1647c89fb189a1709df29b98fdd2dba91081d Mon Sep 17 00:00:00 2001 From: Klaus Loeffelmann Date: Sun, 19 Jul 2026 10:52:44 -0700 Subject: [PATCH 08/12] Refresh kiosk bounds after display changes Reapply explicit fullscreen bounds when display geometry or the taskbar working area changes so the normal-state kiosk window tracks the selected screen like the previous maximized window did. Fixes dotnet/winforms#14765 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c352332b-4b81-4492-bcfc-9dec524c27c9 --- .../KioskModeManager/KioskModeManager.cs | 17 +++++++++++++ .../Windows/Forms/KioskModeManagerTests.cs | 25 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index f7fe931a622..0a7a98fede8 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -73,6 +73,7 @@ public class KioskModeManager : Component, ISupportInitialize private const POWER_REQUEST_CONTEXT_FLAGS PowerRequestContextSimpleString = (POWER_REQUEST_CONTEXT_FLAGS)0x00000001; private const uint NotifyForThisSession = 0; private const nint PbtApmResumeAutomatic = 0x0012; + private const nint SpiSetWorkArea = 0x002F; private const nint WtsConsoleConnect = 0x0001; private const nint WtsRemoteConnect = 0x0003; private const nint WtsSessionLogon = 0x0005; @@ -839,6 +840,15 @@ private void ApplyFullScreen(Form form) form.Bounds = fullScreenBounds; } + private void RefreshFullScreenBounds() + { + if (_isFullScreen + && _targetForm is { IsDisposed: false } form) + { + ApplyFullScreen(form); + } + } + private void UpdateFormObserver() { if (DesignMode) @@ -1201,6 +1211,13 @@ protected override void WndProc(ref Message m) } base.WndProc(ref m); + + if (m.MsgInternal == PInvokeCore.WM_DISPLAYCHANGE + || (m.MsgInternal == PInvokeCore.WM_SETTINGCHANGE + && (nint)m.WParamInternal == SpiSetWorkArea)) + { + _owner.RefreshFullScreenBounds(); + } } } } diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index 05d3489818d..f2d349e6bc3 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -238,6 +238,31 @@ public void KioskModeManager_FullScreen_MinimizedForm_UsesRestoreMonitor() Assert.Equal(workingArea, form.Bounds); } + [WinFormsFact] + public void KioskModeManager_FullScreen_RefreshBounds_ReappliesWorkingArea() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200) + }; + Rectangle workingArea = Screen.FromRectangle( + form.Bounds).WorkingArea; + using KioskModeManager manager = new() + { + ContainerControl = form, + FullScreen = true + }; + form.Bounds = new Rectangle( + workingArea.X + 10, + workingArea.Y + 10, + 300, + 200); + + manager.TestAccessor.Dynamic.RefreshFullScreenBounds(); + + Assert.Equal(workingArea, form.Bounds); + } + [WinFormsFact] public void KioskModeManager_FullScreen_StatusStripDropDown_RemainsAttached() { From 57e98b0e89764933cb3032f3cece568b7a8e20b8 Mon Sep 17 00:00:00 2001 From: Klaus Loffelmann Date: Tue, 28 Jul 2026 20:12:28 -0700 Subject: [PATCH 09/12] Remove wakeup surface from KioskModeManager API review feedback: the "Wakeup" elements are more general than kiosk mode and should not be part of KioskModeManager. They can reappear later with a broader scope. Removes the Wakeup event, OnWakeup, WakeUpCommand, KioskModeWakeupEventArgs, KioskModeWakeupEventHandler, and KioskModeWakeupSource, along with the power broadcast and terminal session notification plumbing that only fed them. Display change tracking, which keeps the fullscreen bounds current, is kept. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8999e9ff-9bcc-4755-b337-7fc5496d431c --- ...04_KioskManagerComponent.HighRiskReview.md | 15 -- .../src/NativeMethods.txt | 2 - .../PublicAPI.Unshipped.txt | 14 -- src/System.Windows.Forms/Resources/SR.resx | 6 - .../Resources/xlf/SR.cs.xlf | 10 - .../Resources/xlf/SR.de.xlf | 10 - .../Resources/xlf/SR.es.xlf | 10 - .../Resources/xlf/SR.fr.xlf | 10 - .../Resources/xlf/SR.it.xlf | 10 - .../Resources/xlf/SR.ja.xlf | 10 - .../Resources/xlf/SR.ko.xlf | 10 - .../Resources/xlf/SR.pl.xlf | 10 - .../Resources/xlf/SR.pt-BR.xlf | 10 - .../Resources/xlf/SR.ru.xlf | 10 - .../Resources/xlf/SR.tr.xlf | 10 - .../Resources/xlf/SR.zh-Hans.xlf | 10 - .../Resources/xlf/SR.zh-Hant.xlf | 10 - .../KioskModeManager/KioskModeManager.cs | 150 +------------- .../KioskModeWakeupEventArgs.cs | 36 ---- .../KioskModeWakeupEventHandler.cs | 14 -- .../KioskModeManager/KioskModeWakeupSource.cs | 35 ---- .../Windows/Forms/KioskModeManagerTests.cs | 190 +----------------- 22 files changed, 9 insertions(+), 583 deletions(-) delete mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs delete mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs delete mode 100644 src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs diff --git a/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md index b3cb075a1a1..95b4644865a 100644 --- a/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md +++ b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md @@ -21,18 +21,3 @@ Before changing this behavior, decide whether `FullScreen` represents requested - compatibility for applications already observing the property or event. Tests should define the complete transition matrix for requested, pending, entered, exited, reparented, and disposed states. - -## Session-notification registration is not retried - -`KioskModeFormObserver.RegisterSessionNotifications` records a failed `WTSRegisterSessionNotification` call as `false` and retries only after form handle recreation. During Windows startup, registration can fail with `RPC_S_INVALID_BINDING` before `Global\TermSrvReadyEvent` is signaled. A kiosk application started during that interval can therefore miss session notifications for the lifetime of its form handle. - -A safe fix needs a non-blocking retry design that specifies: - -- which Win32 errors are retryable and which are terminal; -- how waiting for `Global\TermSrvReadyEvent` is cancelled; -- how completion is marshalled to the form's UI thread; -- how stale callbacks are rejected after handle recreation; -- how registration and unregistration remain paired; -- how disposal races with an outstanding wait. - -Tests should cover service-not-ready startup, successful retry, handle recreation during the wait, and disposal before the wait completes. diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt index 1aefb4bdfd7..9828b18cad8 100644 --- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt +++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt @@ -700,8 +700,6 @@ WC_TABCONTROL WC_TREEVIEW WHEEL_DELTA WindowFromPoint -WTSRegisterSessionNotification -WTSUnRegisterSessionNotification WINDOWPOS WINEVENT_INCONTEXT WSF_VISIBLE diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 359d0780cfd..07946b1d340 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -125,7 +125,6 @@ System.Windows.Forms.KioskModeManager.MousePointerAutoHideDelay.get -> int System.Windows.Forms.KioskModeManager.MousePointerAutoHideDelay.set -> void virtual System.Windows.Forms.KioskModeManager.OnContainerControlChanged(System.EventArgs! e) -> void virtual System.Windows.Forms.KioskModeManager.OnFullScreenChanged(System.EventArgs! e) -> void -virtual System.Windows.Forms.KioskModeManager.OnWakeup(System.Windows.Forms.KioskModeWakeupEventArgs! e) -> void override System.Windows.Forms.KioskModeManager.Site.get -> System.ComponentModel.ISite? override System.Windows.Forms.KioskModeManager.Site.set -> void System.Windows.Forms.KioskModeManager.SuppressPowerSaving.get -> bool @@ -135,16 +134,3 @@ System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.get -> System.Windows. System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.set -> void System.Windows.Forms.KioskModeManager.TopMostInFullScreen.get -> bool System.Windows.Forms.KioskModeManager.TopMostInFullScreen.set -> void -System.Windows.Forms.KioskModeManager.Wakeup -> System.Windows.Forms.KioskModeWakeupEventHandler? -System.Windows.Forms.KioskModeManager.WakeUpCommand.get -> System.Windows.Input.ICommand? -System.Windows.Forms.KioskModeManager.WakeUpCommand.set -> void -System.Windows.Forms.KioskModeWakeupEventArgs -System.Windows.Forms.KioskModeWakeupEventArgs.KioskModeWakeupEventArgs(System.Windows.Forms.KioskModeWakeupSource source) -> void -System.Windows.Forms.KioskModeWakeupEventArgs.Source.get -> System.Windows.Forms.KioskModeWakeupSource -System.Windows.Forms.KioskModeWakeupEventHandler -virtual System.Windows.Forms.KioskModeWakeupEventHandler.Invoke(object? sender, System.Windows.Forms.KioskModeWakeupEventArgs! e) -> void -System.Windows.Forms.KioskModeWakeupSource -System.Windows.Forms.KioskModeWakeupSource.Keyboard = 0 -> System.Windows.Forms.KioskModeWakeupSource -System.Windows.Forms.KioskModeWakeupSource.Mouse = 1 -> System.Windows.Forms.KioskModeWakeupSource -System.Windows.Forms.KioskModeWakeupSource.PowerResume = 2 -> System.Windows.Forms.KioskModeWakeupSource -System.Windows.Forms.KioskModeWakeupSource.Session = 3 -> System.Windows.Forms.KioskModeWakeupSource diff --git a/src/System.Windows.Forms/Resources/SR.resx b/src/System.Windows.Forms/Resources/SR.resx index e9b9f061661..24a8d623e41 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -2724,12 +2724,6 @@ To replace this default dialog please handle the DataError event. Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - The command that is executed whenever the kiosk experience is woken. - Provides run-time information or descriptive text for a control. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf index 7632faf1b54..af9f9038066 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf @@ -6199,16 +6199,6 @@ Trasování zásobníku, kde došlo k neplatné operaci: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Umožňuje automatické zpracování textu, který je delší než šířka ovládacího prvku popisku. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf index 3a38493cfd0..1bf052b0643 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf @@ -6199,16 +6199,6 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Aktiviert die automatische Behandlung von Text, der über die Breite des Bezeichnungssteuerelements hinausgeht. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf index 68aabae5632..58b6f616587 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf @@ -6199,16 +6199,6 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Permite el control automático del texto que se extiende más allá del ancho del control de la etiqueta. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf index 74d3eb7e7fc..f46dccf6ea5 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf @@ -6199,16 +6199,6 @@ Cette opération non conforme s'est produite sur la trace de la pile : Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Active la gestion automatique du texte qui dépasse les limites de largeur du contrôle label. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf index 9f85ac4c131..a5744e4c816 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf @@ -6199,16 +6199,6 @@ Analisi dello stack dove si è verificata l'operazione non valida: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Consente la gestione automatica del testo che non è contenuto all'interno della larghezza del controllo etichetta. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf index b9b4ef99004..572c553c872 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf @@ -6199,16 +6199,6 @@ Stack trace where the illegal operation occurred was: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. ラベル コントロールの幅を越えるテキストの自動処理を有効にします。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf index faa7da7714a..f7f92a2e634 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf @@ -6199,16 +6199,6 @@ Stack trace where the illegal operation occurred was: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. 레이블 컨트롤의 너비보다 큰 텍스트를 자동으로 처리합니다. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf index 9a390fee7be..14a592b6e88 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf @@ -6199,16 +6199,6 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Włącza automatyczną obsługę tekstu, który mieści się w szerokości formantu etykiety. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf index feee155ef4e..9264dd6c196 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf @@ -6199,16 +6199,6 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Permite a manipulação automática de texto que ultrapassa a largura do controle de rótulo. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf index afc78fd9de2..1a50b92547d 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf @@ -6199,16 +6199,6 @@ Stack trace where the illegal operation occurred was: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Включает автоматическую обработку текста, выходящего за пределы ширины элемента управления с подписью. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf index a056935ac7d..9f60d98f33b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf @@ -6199,16 +6199,6 @@ Geçersiz işlemin gerçekleştiği yığın izi: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. Etiket denetimi genişliğini aşan metnin otomatik işlenmesini sağlar. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf index 6e3341c1c73..9ab9e403b50 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf @@ -6199,16 +6199,6 @@ Stack trace where the illegal operation occurred was: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. 启用对扩展到标签控件宽度以外的文本的自动处理。 diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf index 5750ea7d965..cce65ba2c9a 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf @@ -6199,16 +6199,6 @@ Stack trace where the illegal operation occurred was: Indicates whether the form is topmost while fullscreen. - - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - Occurs when keyboard, mouse, power resume, or session activity wakes the kiosk experience. - - - - The command that is executed whenever the kiosk experience is woken. - The command that is executed whenever the kiosk experience is woken. - - Enables the automatic handling of text that extends beyond the width of the label control. 啟用自動處理,以處理超出標籤控制項寬度的文件。 diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index 0a7a98fede8..83e546c553d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -5,7 +5,6 @@ using System.ComponentModel.Design; using System.Drawing; using System.Runtime.InteropServices; -using System.Windows.Input; using Windows.Win32.System.Power; using Windows.Win32.System.Threading; @@ -25,8 +24,7 @@ namespace System.Windows.Forms; /// /// The component can make the resolved form fullscreen, optionally cover the /// taskbar, keep the form topmost while fullscreen, suppress display and -/// system sleep, hide the mouse pointer after inactivity, and notify the -/// application when user or system activity wakes the kiosk experience. +/// system sleep, and hide the mouse pointer after a period of inactivity. /// /// /// @@ -48,14 +46,6 @@ namespace System.Windows.Forms; /// SuppressPowerSaving = true /// }; /// -/// _kioskModeManager.Wakeup += (sender, e) => -/// { -/// if (e.Source == KioskModeWakeupSource.Mouse) -/// { -/// // Refresh the kiosk surface after mouse activity. -/// } -/// }; -/// /// if (!_kioskModeManager.FullScreen) /// { /// _kioskModeManager.ToggleFullScreen(); @@ -71,18 +61,11 @@ public class KioskModeManager : Component, ISupportInitialize { private const uint PowerRequestContextVersion = 0; private const POWER_REQUEST_CONTEXT_FLAGS PowerRequestContextSimpleString = (POWER_REQUEST_CONTEXT_FLAGS)0x00000001; - private const uint NotifyForThisSession = 0; - private const nint PbtApmResumeAutomatic = 0x0012; private const nint SpiSetWorkArea = 0x002F; - private const nint WtsConsoleConnect = 0x0001; - private const nint WtsRemoteConnect = 0x0003; - private const nint WtsSessionLogon = 0x0005; - private const nint WtsSessionUnlock = 0x0008; private const string PowerRequestReason = "WinForms KioskModeManager: Preventing screen saver and sleep"; private static readonly object s_containerControlChangedEvent = new(); private static readonly object s_fullScreenChangedEvent = new(); - private static readonly object s_wakeupEvent = new(); private ContainerControl? _containerControl; private bool _containerControlExplicitlySet; @@ -107,7 +90,6 @@ public class KioskModeManager : Component, ISupportInitialize private bool _escapeExitsFullScreen = true; private bool _suppressPowerSaving; private int _mousePointerAutoHideDelay; - private ICommand? _wakeUpCommand; private HANDLE _powerRequestHandle; @@ -493,75 +475,6 @@ protected virtual void OnFullScreenChanged(EventArgs e) } } - /// - /// Occurs when keyboard, mouse, power resume, or session activity wakes - /// the kiosk experience. - /// - /// - /// - /// The event reports activity that the component can observe on the UI - /// thread or through Windows power/session notifications. It does not mean - /// that the component caused the computer to wake from sleep, and it does - /// not configure voice wake, network wake, or wake timers. - /// - /// - [SRCategory(nameof(SR.CatAction))] - [SRDescription(nameof(SR.KioskModeManagerWakeupDescr))] - public event KioskModeWakeupEventHandler? Wakeup - { - add => Events.AddHandler(s_wakeupEvent, value); - remove => Events.RemoveHandler(s_wakeupEvent, value); - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - [EditorBrowsable(EditorBrowsableState.Advanced)] - protected virtual void OnWakeup(KioskModeWakeupEventArgs e) - { - if (Events[s_wakeupEvent] is KioskModeWakeupEventHandler handler) - { - handler(this, e); - } - - if (_wakeUpCommand is not null) - { - // The wakeup source is passed as its string name so the bound command stays - // independent of the WinForms-specific enum type. - string parameter = e.Source.ToString(); - if (_wakeUpCommand.CanExecute(parameter)) - { - _wakeUpCommand.Execute(parameter); - } - } - } - - /// - /// Gets or sets the command that is executed whenever the - /// event is raised. - /// - /// - /// An to execute on every wakeup, or - /// to execute no command. The default is . - /// - /// - /// - /// The command is executed with the wakeup source name (the result of - /// .) as the command - /// parameter, keeping the command independent of the WinForms-specific enum type. - /// - /// - [SRCategory(nameof(SR.CatAction))] - [SRDescription(nameof(SR.KioskModeManagerWakeUpCommandDescr))] - [Bindable(true)] - [DefaultValue(null)] - public ICommand? WakeUpCommand - { - get => _wakeUpCommand; - set => _wakeUpCommand = value; - } - /// /// Places the resolved form in fullscreen mode. /// @@ -877,23 +790,6 @@ private void UpdatePowerRequest() } } - private void ProcessPowerBroadcast(WPARAM powerEvent) - { - if ((nint)powerEvent.Value == PbtApmResumeAutomatic) - { - RaiseWakeup(KioskModeWakeupSource.PowerResume); - } - } - - private void ProcessSessionChange(WPARAM sessionEvent) - { - nint value = (nint)sessionEvent.Value; - if (value is WtsConsoleConnect or WtsRemoteConnect or WtsSessionLogon or WtsSessionUnlock) - { - RaiseWakeup(KioskModeWakeupSource.Session); - } - } - private void RestartMousePointerAutoHideTimer() { // Use a WinForms timer so cursor changes happen on the UI thread that @@ -929,10 +825,6 @@ private void OnMousePointerAutoHideTimerTick(object? sender, EventArgs e) private void ProcessKeyboardActivity(Keys keyData, Keys modifiers, bool isRepeat) { - // Any keyboard input wakes the kiosk experience, even when it does not - // match one of the configured fullscreen control keys. - RaiseWakeup(KioskModeWakeupSource.Keyboard); - if (isRepeat) { return; @@ -955,7 +847,6 @@ private void ProcessMouseActivity() { ShowMousePointerIfHidden(); RestartMousePointerAutoHideTimer(); - RaiseWakeup(KioskModeWakeupSource.Mouse); } private void ShowMousePointerIfHidden() @@ -971,9 +862,6 @@ private void ShowMousePointerIfHidden() _isCursorHidden = false; } - private void RaiseWakeup(KioskModeWakeupSource source) - => OnWakeup(new KioskModeWakeupEventArgs(source)); - private unsafe void CreatePowerRequest() { // PowerClearRequest clears the active request, but only CloseHandle @@ -1105,13 +993,12 @@ public bool PreFilterMessage(ref Message m) } /// - /// Observes power and session messages that belong to the target form. + /// Observes display and work area changes that affect the target form. /// private sealed class KioskModeFormObserver : NativeWindow { private readonly KioskModeManager _owner; private Form? _form; - private bool _sessionNotificationsRegistered; public KioskModeFormObserver(KioskModeManager owner) { @@ -1151,7 +1038,6 @@ public void Detach() _form = null; } - UnregisterSessionNotifications(); if (!HWND.IsNull) { ReleaseHandle(); @@ -1166,50 +1052,18 @@ private void OnFormHandleCreated(object? sender, EventArgs e) } AssignHandle(_form.HWND); - RegisterSessionNotifications(); } private void OnFormHandleDestroyed(object? sender, EventArgs e) { - UnregisterSessionNotifications(); if (!HWND.IsNull) { ReleaseHandle(); } } - private void RegisterSessionNotifications() - { - if (_sessionNotificationsRegistered || HWND.IsNull) - { - return; - } - - _sessionNotificationsRegistered = PInvoke.WTSRegisterSessionNotification(HWND, NotifyForThisSession); - } - - private void UnregisterSessionNotifications() - { - if (!_sessionNotificationsRegistered) - { - return; - } - - PInvoke.WTSUnRegisterSessionNotification(HWND); - _sessionNotificationsRegistered = false; - } - protected override void WndProc(ref Message m) { - if (m.MsgInternal == PInvokeCore.WM_POWERBROADCAST) - { - _owner.ProcessPowerBroadcast(m.WParamInternal); - } - else if (m.MsgInternal == PInvokeCore.WM_WTSSESSION_CHANGE) - { - _owner.ProcessSessionChange(m.WParamInternal); - } - base.WndProc(ref m); if (m.MsgInternal == PInvokeCore.WM_DISPLAYCHANGE diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs deleted file mode 100644 index 1ad14d4fcc2..00000000000 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms; - -#if NET11_0_OR_GREATER -/// -/// Provides data for the event. -/// -/// -/// -/// The property identifies the kind of activity the -/// component observed. Applications can use this value to distinguish direct -/// user activity, such as mouse or keyboard input, from system activity such -/// as power resume or session unlock. -/// -/// -public class KioskModeWakeupEventArgs : EventArgs -{ - /// - /// Initializes a new instance of the - /// class. - /// - /// The source of the wakeup notification. - public KioskModeWakeupEventArgs(KioskModeWakeupSource source) - { - SourceGenerated.EnumValidator.Validate(source, nameof(source)); - Source = source; - } - - /// - /// Gets the source of the wakeup notification. - /// - public KioskModeWakeupSource Source { get; } -} -#endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs deleted file mode 100644 index b2784bb3b3c..00000000000 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupEventHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms; - -#if NET11_0_OR_GREATER -/// -/// Represents the method that will handle the -/// event. -/// -/// The source of the event. -/// A that contains the event data. -public delegate void KioskModeWakeupEventHandler(object? sender, KioskModeWakeupEventArgs e); -#endif diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs deleted file mode 100644 index 6c9a05784c4..00000000000 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeWakeupSource.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms; - -#if NET11_0_OR_GREATER -/// -/// Specifies the source of a -/// notification. -/// -public enum KioskModeWakeupSource -{ - /// - /// The wakeup notification was caused by keyboard activity. - /// - Keyboard = 0, - - /// - /// The wakeup notification was caused by mouse activity. - /// - Mouse = 1, - - /// - /// The wakeup notification was caused by the system resuming from a low - /// power state. - /// - PowerResume = 2, - - /// - /// The wakeup notification was caused by a Windows session activity, such - /// as logon or unlock. - /// - Session = 3, -} -#endif diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index f2d349e6bc3..7a89924733a 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -6,7 +6,6 @@ using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; -using System.Windows.Input; using Moq; namespace System.Windows.Forms.Tests; @@ -119,46 +118,6 @@ public void KioskModeManager_MousePointerAutoHideDelay_SetNegative_ThrowsArgumen Assert.Throws("value", () => manager.MousePointerAutoHideDelay = -1); } - [WinFormsTheory] - [EnumData] - public void KioskModeWakeupEventArgs_Ctor_Source(KioskModeWakeupSource source) - { - KioskModeWakeupEventArgs eventArgs = new(source); - - Assert.Equal(source, eventArgs.Source); - } - - [WinFormsTheory] - [InvalidEnumData] - public void KioskModeWakeupEventArgs_Ctor_InvalidSource_ThrowsInvalidEnumArgumentException(KioskModeWakeupSource source) - { - Assert.Throws("source", () => new KioskModeWakeupEventArgs(source)); - } - - [WinFormsTheory] - [EnumData] - public void KioskModeManager_OnWakeup_Invoke_CallsWakeup(KioskModeWakeupSource source) - { - using SubKioskModeManager manager = new(); - KioskModeWakeupEventArgs eventArgs = new(source); - int callCount = 0; - KioskModeWakeupEventHandler handler = (sender, e) => - { - Assert.Same(manager, sender); - Assert.Same(eventArgs, e); - Assert.Equal(source, e.Source); - callCount++; - }; - - manager.Wakeup += handler; - manager.OnWakeup(eventArgs); - Assert.Equal(1, callCount); - - manager.Wakeup -= handler; - manager.OnWakeup(eventArgs); - Assert.Equal(1, callCount); - } - [WinFormsFact] public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() { @@ -578,52 +537,6 @@ public void KioskModeManager_OnFullScreenChanged_Invoke_CallsFullScreenChanged(E Assert.Equal(1, callCount); } - [WinFormsTheory] - [EnumData] - public void KioskModeManager_WakeUpCommand_ExecutedOnWakeup_WithSourceName(KioskModeWakeupSource source) - { - using SubKioskModeManager manager = new(); - TestCommand command = new(); - manager.WakeUpCommand = command; - - Assert.Same(command, manager.WakeUpCommand); - - manager.OnWakeup(new KioskModeWakeupEventArgs(source)); - - Assert.Equal(1, command.CanExecuteCount); - Assert.Equal(source.ToString(), command.LastCanExecuteParameter); - Assert.Equal(1, command.ExecuteCount); - Assert.Equal(source.ToString(), command.LastParameter); - } - - [WinFormsFact] - public void KioskModeManager_WakeUpCommand_Null_DoesNotThrowOnWakeup() - { - using SubKioskModeManager manager = new(); - - Assert.Null(manager.WakeUpCommand); - - manager.OnWakeup(new KioskModeWakeupEventArgs(KioskModeWakeupSource.Keyboard)); - } - - [WinFormsFact] - public void KioskModeManager_WakeUpCommand_CanExecuteFalse_DoesNotExecute() - { - using SubKioskModeManager manager = new(); - TestCommand command = new() - { - CanExecuteResult = false - }; - - manager.WakeUpCommand = command; - manager.OnWakeup(new KioskModeWakeupEventArgs(KioskModeWakeupSource.Mouse)); - - Assert.Equal(1, command.CanExecuteCount); - Assert.Equal(KioskModeWakeupSource.Mouse.ToString(), command.LastCanExecuteParameter); - Assert.Equal(0, command.ExecuteCount); - Assert.Null(command.LastParameter); - } - [WinFormsFact] public void KioskModeManager_Site_Set_AssignsRootComponentAsContainerControl() { @@ -801,109 +714,23 @@ public void KioskModeManager_ProcessMessage_RepeatedKeyDown_DoesNotToggleAgain() } [WinFormsFact] - public void KioskModeManager_ProcessMessage_MouseMove_CallsWakeup() + public void KioskModeManager_ProcessMessage_MouseMove_ShowsHiddenMousePointer() { using Form form = new(); using KioskModeManager manager = new() { - ContainerControl = form + ContainerControl = form, + MousePointerAutoHideDelay = 1, + FullScreen = true }; - int callCount = 0; - KioskModeWakeupEventArgs lastEventArgs = null; - manager.Wakeup += (sender, e) => - { - Assert.Same(manager, sender); - callCount++; - lastEventArgs = e; - }; + manager.TestAccessor.Dynamic.OnMousePointerAutoHideTimerTick(null, EventArgs.Empty); + Assert.True(manager.TestAccessor.Dynamic._isCursorHidden); Message message = Message.Create(form.Handle, (int)PInvokeCore.WM_MOUSEMOVE, 0, 0); manager.TestAccessor.Dynamic.ProcessMessage(message); - Assert.Equal(1, callCount); - Assert.NotNull(lastEventArgs); - Assert.Equal(KioskModeWakeupSource.Mouse, lastEventArgs.Source); - } - - [WinFormsFact] - public void KioskModeManager_ProcessPowerBroadcast_InteractiveResume_CallsWakeupOnce() - { - using SubKioskModeManager manager = new(); - int callCount = 0; - KioskModeWakeupEventArgs lastEventArgs = null; - manager.Wakeup += (sender, e) => - { - Assert.Same(manager, sender); - callCount++; - lastEventArgs = e; - }; - - Message automaticResume = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0012, 0); - Message interactiveResume = Message.Create(IntPtr.Zero, (int)PInvokeCore.WM_POWERBROADCAST, (nint)0x0007, 0); - manager.TestAccessor.Dynamic.ProcessPowerBroadcast(automaticResume.WParamInternal); - manager.TestAccessor.Dynamic.ProcessPowerBroadcast(interactiveResume.WParamInternal); - - Assert.Equal(1, callCount); - Assert.NotNull(lastEventArgs); - Assert.Equal(KioskModeWakeupSource.PowerResume, lastEventArgs.Source); - } - - [WinFormsTheory] - [InlineData(0x0001)] - [InlineData(0x0003)] - [InlineData(0x0005)] - [InlineData(0x0008)] - public void KioskModeManager_ProcessSessionChange_RecognizedReason_CallsWakeup(nint sessionReason) - { - using SubKioskModeManager manager = new(); - int callCount = 0; - KioskModeWakeupEventArgs lastEventArgs = null; - manager.Wakeup += (sender, e) => - { - Assert.Same(manager, sender); - callCount++; - lastEventArgs = e; - }; - - Message message = Message.Create(IntPtr.Zero, 0, sessionReason, 0); - manager.TestAccessor.Dynamic.ProcessSessionChange(message.WParamInternal); - - Assert.Equal(1, callCount); - Assert.NotNull(lastEventArgs); - Assert.Equal(KioskModeWakeupSource.Session, lastEventArgs.Source); - } - - private class TestCommand : ICommand - { - public bool CanExecuteResult { get; set; } = true; - - public int CanExecuteCount { get; private set; } - - public object LastCanExecuteParameter { get; private set; } - - public int ExecuteCount { get; private set; } - - public object LastParameter { get; private set; } - - public event EventHandler CanExecuteChanged - { - add { } - remove { } - } - - public bool CanExecute(object parameter) - { - CanExecuteCount++; - LastCanExecuteParameter = parameter; - return CanExecuteResult; - } - - public void Execute(object parameter) - { - ExecuteCount++; - LastParameter = parameter; - } + Assert.False(manager.TestAccessor.Dynamic._isCursorHidden); } private class SubKioskModeManager : KioskModeManager @@ -915,9 +742,6 @@ private class SubKioskModeManager : KioskModeManager public new void OnFullScreenChanged(EventArgs e) => base.OnFullScreenChanged(e); - - public new void OnWakeup(KioskModeWakeupEventArgs e) - => base.OnWakeup(e); } } #endif From 9d6a3a433ef8fd149700955b78d46c8d73c3cd47 Mon Sep 17 00:00:00 2001 From: Klaus Loffelmann Date: Tue, 28 Jul 2026 20:22:36 -0700 Subject: [PATCH 10/12] Remove HideTaskbar and simplify the kiosk window model API review feedback: HideTaskbar was removed to simplify the window models. Kiosk fullscreen now always sizes the borderless form to the complete bounds of the screen that displays it, so there is a single window model and the kiosk surface always covers the taskbar. Because the fullscreen form now covers the taskbar, drop-downs owned by that form can no longer be constrained to the screen working area - doing so detaches them from the item that opened them (dotnet/winforms#14765). ToolStripDropDown and ToolStripDropDownItem now constrain to the complete screen bounds when the owning top-level window is borderless and covers the screen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8999e9ff-9bcc-4755-b337-7fc5496d431c --- .../PublicAPI.Unshipped.txt | 2 - src/System.Windows.Forms/Resources/SR.resx | 3 - .../Resources/xlf/SR.cs.xlf | 5 -- .../Resources/xlf/SR.de.xlf | 5 -- .../Resources/xlf/SR.es.xlf | 5 -- .../Resources/xlf/SR.fr.xlf | 5 -- .../Resources/xlf/SR.it.xlf | 5 -- .../Resources/xlf/SR.ja.xlf | 5 -- .../Resources/xlf/SR.ko.xlf | 5 -- .../Resources/xlf/SR.pl.xlf | 5 -- .../Resources/xlf/SR.pt-BR.xlf | 5 -- .../Resources/xlf/SR.ru.xlf | 5 -- .../Resources/xlf/SR.tr.xlf | 5 -- .../Resources/xlf/SR.zh-Hans.xlf | 5 -- .../Resources/xlf/SR.zh-Hant.xlf | 5 -- .../KioskModeManager/KioskModeManager.cs | 66 ++++--------------- .../Controls/ToolStrips/ToolStripDropDown.cs | 4 +- .../ToolStrips/ToolStripDropDownItem.cs | 4 +- .../Windows/Forms/Internal/WinFormsUtils.cs | 31 +++++++++ .../Windows/Forms/KioskModeManagerTests.cs | 33 +++++----- 20 files changed, 65 insertions(+), 143 deletions(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index 07946b1d340..baedd96e337 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -117,8 +117,6 @@ System.Windows.Forms.KioskModeManager.EscapeExitsFullScreen.set -> void System.Windows.Forms.KioskModeManager.FullScreen.get -> bool System.Windows.Forms.KioskModeManager.FullScreen.set -> void System.Windows.Forms.KioskModeManager.FullScreenChanged -> System.EventHandler? -System.Windows.Forms.KioskModeManager.HideTaskbar.get -> bool -System.Windows.Forms.KioskModeManager.HideTaskbar.set -> void System.Windows.Forms.KioskModeManager.KioskModeManager() -> void System.Windows.Forms.KioskModeManager.KioskModeManager(System.ComponentModel.IContainer! container) -> void System.Windows.Forms.KioskModeManager.MousePointerAutoHideDelay.get -> int diff --git a/src/System.Windows.Forms/Resources/SR.resx b/src/System.Windows.Forms/Resources/SR.resx index 24a8d623e41..8397a13ee10 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -2709,9 +2709,6 @@ To replace this default dialog please handle the DataError event. Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf index af9f9038066..6d905124e03 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf @@ -6174,11 +6174,6 @@ Trasování zásobníku, kde došlo k neplatné operaci: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf index 1bf052b0643..a71d457148d 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf @@ -6174,11 +6174,6 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf index 58b6f616587..3ba4aaac626 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf @@ -6174,11 +6174,6 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf index f46dccf6ea5..2bb376c3eb3 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf @@ -6174,11 +6174,6 @@ Cette opération non conforme s'est produite sur la trace de la pile : Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf index a5744e4c816..5fdd140e67a 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf @@ -6174,11 +6174,6 @@ Analisi dello stack dove si è verificata l'operazione non valida: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf index 572c553c872..5095dccfee6 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf @@ -6174,11 +6174,6 @@ Stack trace where the illegal operation occurred was: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf index f7f92a2e634..8379506e907 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf @@ -6174,11 +6174,6 @@ Stack trace where the illegal operation occurred was: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf index 14a592b6e88..f68a35bf803 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf @@ -6174,11 +6174,6 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf index 9264dd6c196..a4a50703bb8 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf @@ -6174,11 +6174,6 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf index 1a50b92547d..c7963c82519 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf @@ -6174,11 +6174,6 @@ Stack trace where the illegal operation occurred was: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf index 9f60d98f33b..8132d8893ce 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf @@ -6174,11 +6174,6 @@ Geçersiz işlemin gerçekleştiği yığın izi: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf index 9ab9e403b50..a896d0fcc79 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf @@ -6174,11 +6174,6 @@ Stack trace where the illegal operation occurred was: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf index cce65ba2c9a..c99559e050b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf @@ -6174,11 +6174,6 @@ Stack trace where the illegal operation occurred was: Occurs when the fullscreen state changes. - - Indicates whether fullscreen mode covers the Windows taskbar. - Indicates whether fullscreen mode covers the Windows taskbar. - - The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index 83e546c553d..8ac3df51da8 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -22,9 +22,9 @@ namespace System.Windows.Forms; /// when fullscreen behavior is needed. /// /// -/// The component can make the resolved form fullscreen, optionally cover the -/// taskbar, keep the form topmost while fullscreen, suppress display and -/// system sleep, and hide the mouse pointer after a period of inactivity. +/// The component can make the resolved form fullscreen, keep the form topmost +/// while fullscreen, suppress display and system sleep, and hide the mouse +/// pointer after a period of inactivity. /// /// /// @@ -40,7 +40,6 @@ namespace System.Windows.Forms; /// _kioskModeManager = new KioskModeManager /// { /// ContainerControl = this, -/// HideTaskbar = true, /// TopMostInFullScreen = true, /// MousePointerAutoHideDelay = 3000, /// SuppressPowerSaving = true @@ -61,7 +60,6 @@ public class KioskModeManager : Component, ISupportInitialize { private const uint PowerRequestContextVersion = 0; private const POWER_REQUEST_CONTEXT_FLAGS PowerRequestContextSimpleString = (POWER_REQUEST_CONTEXT_FLAGS)0x00000001; - private const nint SpiSetWorkArea = 0x002F; private const string PowerRequestReason = "WinForms KioskModeManager: Preventing screen saver and sleep"; private static readonly object s_containerControlChangedEvent = new(); @@ -84,7 +82,6 @@ public class KioskModeManager : Component, ISupportInitialize private Rectangle _savedBounds; private bool _savedTopMost; - private bool _hideTaskbar; private bool _topMostInFullScreen; private Keys _toggleFullScreenKey = Keys.F11; private bool _escapeExitsFullScreen = true; @@ -194,45 +191,6 @@ protected virtual void OnContainerControlChanged(EventArgs e) } } - /// - /// Gets or sets a value indicating whether fullscreen mode covers the - /// Windows taskbar. - /// - /// - /// to size the form to the complete screen bounds; - /// to use normal maximized form behavior. - /// The default is . - /// - /// - /// - /// When this property is , the form is maximized - /// and Windows keeps the taskbar available according to the user's shell - /// settings. When this property is , the form is - /// sized to the screen bounds so the kiosk surface covers the taskbar. - /// - /// - [SRCategory(nameof(SR.CatBehavior))] - [SRDescription(nameof(SR.KioskModeManagerHideTaskbarDescr))] - [DefaultValue(false)] - public bool HideTaskbar - { - get => _hideTaskbar; - set - { - if (_hideTaskbar == value) - { - return; - } - - _hideTaskbar = value; - - if (_isFullScreen && _targetForm is not null) - { - ApplyFullScreen(_targetForm); - } - } - } - /// /// Gets or sets a value indicating whether the form is topmost while /// fullscreen. @@ -424,6 +382,11 @@ public int MousePointerAutoHideDelay /// state. The property is bindable so it can participate in two-way data /// binding. /// + /// + /// In fullscreen mode the form is borderless and sized to the complete bounds + /// of the screen that currently displays it, so the kiosk surface also covers + /// the taskbar. + /// /// [SRCategory(nameof(SR.CatBehavior))] [SRDescription(nameof(SR.KioskModeManagerFullScreenDescr))] @@ -741,9 +704,10 @@ private void ApplyFullScreen(Form form) ? form.Bounds : form.RestoreBounds; Screen screen = Screen.FromRectangle(screenReferenceBounds); - Rectangle fullScreenBounds = _hideTaskbar - ? screen.Bounds - : screen.WorkingArea; + + // Kiosk fullscreen always covers the complete screen, including the + // taskbar, so there is only one window model to reason about. + Rectangle fullScreenBounds = screen.Bounds; // Apply fullscreen from a normal state so bounds and border changes do // not operate on Windows' maximized window rectangle. @@ -993,7 +957,7 @@ public bool PreFilterMessage(ref Message m) } /// - /// Observes display and work area changes that affect the target form. + /// Observes display changes that affect the target form. /// private sealed class KioskModeFormObserver : NativeWindow { @@ -1066,9 +1030,7 @@ protected override void WndProc(ref Message m) { base.WndProc(ref m); - if (m.MsgInternal == PInvokeCore.WM_DISPLAYCHANGE - || (m.MsgInternal == PInvokeCore.WM_SETTINGCHANGE - && (nint)m.WParamInternal == SpiSetWorkArea)) + if (m.MsgInternal == PInvokeCore.WM_DISPLAYCHANGE) { _owner.RefreshFullScreenBounds(); } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDown.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDown.cs index 44b2c6e0ecd..297970e2933 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDown.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDown.cs @@ -991,7 +991,7 @@ internal Rectangle GetDropDownBounds(Rectangle suggestedBounds) Rectangle suggestedScreenBounds = new(screenPoint, suggestedBounds.Size); dropDownBounds = WorkingAreaConstrained - ? WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds(suggestedScreenBounds) + ? WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds(suggestedScreenBounds, GetToplevelOwnerToolStrip()) : WindowsFormsUtils.ConstrainToScreenBounds(suggestedScreenBounds); } else @@ -1040,7 +1040,7 @@ internal Rectangle CalculateDropDownLocation(Point start, ToolStripDropDownDirec dropDownBounds.Location = new Point(start.X + offset.X, start.Y + offset.Y); if (WorkingAreaConstrained) { - dropDownBounds = WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds(dropDownBounds); + dropDownBounds = WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds(dropDownBounds, GetToplevelOwnerToolStrip()); } return dropDownBounds; diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDownItem.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDownItem.cs index 06c8f21ba53..0ed57f7f47e 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDownItem.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripDropDownItem.cs @@ -334,7 +334,9 @@ private Rectangle DropDownDirectionToDropDownBounds(ToolStripDropDownDirection d Point itemScreenLocation = TranslatePoint(Point.Empty, ToolStripPointType.ToolStripItemCoords, ToolStripPointType.ScreenCoords); dropDownBounds.Location = new Point(itemScreenLocation.X + offset.X, itemScreenLocation.Y + offset.Y); - dropDownBounds = WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds(dropDownBounds); + dropDownBounds = WindowsFormsUtils.ConstrainToScreenWorkingAreaBounds( + dropDownBounds, + Owner?.GetToplevelOwnerToolStrip()); return dropDownBounds; } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Internal/WinFormsUtils.cs b/src/System.Windows.Forms/System/Windows/Forms/Internal/WinFormsUtils.cs index 5496f69767f..20106cb4b6a 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Internal/WinFormsUtils.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Internal/WinFormsUtils.cs @@ -65,6 +65,37 @@ internal static Rectangle ConstrainToScreenWorkingAreaBounds(Rectangle bounds) return ConstrainToBounds(Screen.GetWorkingArea(bounds), bounds); } + /// + /// Constrains to the region that auxiliary windows owned by + /// may occupy. + /// + internal static Rectangle ConstrainToScreenWorkingAreaBounds(Rectangle bounds, Control? owner) + { + return ConstrainToBounds(GetAvailableScreenBounds(bounds, owner), bounds); + } + + /// + /// Gets the region on the screen that displays which auxiliary + /// windows, such as drop-downs, may occupy. + /// + /// + /// + /// This is the working area of that screen, unless belongs to a + /// borderless top-level window that already covers the complete screen. Such a window - a + /// kiosk-style fullscreen form, for example - also covers the taskbar, so constraining to + /// the working area would detach drop-downs from the items that own them. + /// + /// + internal static Rectangle GetAvailableScreenBounds(Rectangle bounds, Control? owner) + { + Screen screen = Screen.FromRectangle(bounds); + + return owner?.TopLevelControl is Form { FormBorderStyle: FormBorderStyle.None } form + && form.Bounds.Contains(screen.Bounds) + ? screen.Bounds + : screen.WorkingArea; + } + /// /// Given a rectangle, constrain it to fit onto the current screen. /// diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index 7a89924733a..bef6a210078 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -22,7 +22,6 @@ public void KioskModeManager_Ctor_Default() Assert.Null(manager.ContainerControl); Assert.False(manager.DesignMode); Assert.True(manager.EscapeExitsFullScreen); - Assert.False(manager.HideTaskbar); Assert.False(manager.FullScreen); Assert.Equal(0, manager.MousePointerAutoHideDelay); Assert.Null(manager.Site); @@ -132,10 +131,9 @@ public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() using KioskModeManager manager = new() { ContainerControl = form, - HideTaskbar = false, TopMostInFullScreen = true }; - Rectangle workingArea = Screen.FromRectangle(form.Bounds).WorkingArea; + Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; manager.ToggleFullScreen(); @@ -143,7 +141,7 @@ public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); Assert.True(form.TopMost); Assert.Equal(FormWindowState.Normal, form.WindowState); - Assert.Equal(workingArea, form.Bounds); + Assert.Equal(screenBounds, form.Bounds); manager.ToggleFullScreen(); @@ -155,7 +153,7 @@ public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() } [WinFormsFact] - public void KioskModeManager_FullScreen_HideTaskbar_UsesScreenBounds() + public void KioskModeManager_FullScreen_CoversCompleteScreenBounds() { using Form form = new() { @@ -163,8 +161,7 @@ public void KioskModeManager_FullScreen_HideTaskbar_UsesScreenBounds() }; using KioskModeManager manager = new() { - ContainerControl = form, - HideTaskbar = true + ContainerControl = form }; Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; @@ -185,8 +182,8 @@ public void KioskModeManager_FullScreen_MinimizedForm_UsesRestoreMonitor() form.Show(); form.WindowState = FormWindowState.Minimized; Rectangle restoreBounds = form.RestoreBounds; - Rectangle workingArea = Screen.FromRectangle( - restoreBounds).WorkingArea; + Rectangle screenBounds = Screen.FromRectangle( + restoreBounds).Bounds; using KioskModeManager manager = new() { ContainerControl = form @@ -194,32 +191,32 @@ public void KioskModeManager_FullScreen_MinimizedForm_UsesRestoreMonitor() manager.FullScreen = true; - Assert.Equal(workingArea, form.Bounds); + Assert.Equal(screenBounds, form.Bounds); } [WinFormsFact] - public void KioskModeManager_FullScreen_RefreshBounds_ReappliesWorkingArea() + public void KioskModeManager_FullScreen_RefreshBounds_ReappliesScreenBounds() { using Form form = new() { Bounds = new Rectangle(10, 20, 300, 200) }; - Rectangle workingArea = Screen.FromRectangle( - form.Bounds).WorkingArea; + Rectangle screenBounds = Screen.FromRectangle( + form.Bounds).Bounds; using KioskModeManager manager = new() { ContainerControl = form, FullScreen = true }; form.Bounds = new Rectangle( - workingArea.X + 10, - workingArea.Y + 10, + screenBounds.X + 10, + screenBounds.Y + 10, 300, 200); manager.TestAccessor.Dynamic.RefreshFullScreenBounds(); - Assert.Equal(workingArea, form.Bounds); + Assert.Equal(screenBounds, form.Bounds); } [WinFormsFact] @@ -630,7 +627,7 @@ public void KioskModeManager_BeginInit_FullScreenSet_DoesNotEnterUntilEndInit() using KioskModeManager manager = new(); ISupportInitialize supportInitialize = manager; - Rectangle workingArea = Screen.FromRectangle(form.Bounds).WorkingArea; + Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; supportInitialize.BeginInit(); manager.ContainerControl = form; @@ -644,7 +641,7 @@ public void KioskModeManager_BeginInit_FullScreenSet_DoesNotEnterUntilEndInit() Assert.True(manager.FullScreen); Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); Assert.Equal(FormWindowState.Normal, form.WindowState); - Assert.Equal(workingArea, form.Bounds); + Assert.Equal(screenBounds, form.Bounds); } [WinFormsFact] From 432d49f72cb749a62bfb3eb6904e72417902a141 Mon Sep 17 00:00:00 2001 From: Klaus Loffelmann Date: Tue, 28 Jul 2026 20:29:05 -0700 Subject: [PATCH 11/12] Rename KioskModeManager properties per API review - ToggleFullScreenKey => ToggleFullScreenKeys, for consistency with the plurality of the Keys type. The property now matches the complete key combination, including modifiers, and Keys.None disables key based toggling. - SuppressPowerSaving => AlwaysOn. Also documents that the fullscreen keys are focus driven: they are evaluated only for input dispatched to the managed form or one of its child controls, and no global keyboard hook or system wide hot key is installed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8999e9ff-9bcc-4755-b337-7fc5496d431c --- .../PublicAPI.Unshipped.txt | 8 +- src/System.Windows.Forms/Resources/SR.resx | 10 +-- .../Resources/xlf/SR.cs.xlf | 16 ++-- .../Resources/xlf/SR.de.xlf | 16 ++-- .../Resources/xlf/SR.es.xlf | 16 ++-- .../Resources/xlf/SR.fr.xlf | 16 ++-- .../Resources/xlf/SR.it.xlf | 16 ++-- .../Resources/xlf/SR.ja.xlf | 16 ++-- .../Resources/xlf/SR.ko.xlf | 16 ++-- .../Resources/xlf/SR.pl.xlf | 16 ++-- .../Resources/xlf/SR.pt-BR.xlf | 16 ++-- .../Resources/xlf/SR.ru.xlf | 16 ++-- .../Resources/xlf/SR.tr.xlf | 16 ++-- .../Resources/xlf/SR.zh-Hans.xlf | 16 ++-- .../Resources/xlf/SR.zh-Hant.xlf | 16 ++-- .../KioskModeManager/KioskModeManager.cs | 81 +++++++++++-------- .../Windows/Forms/KioskModeManagerTests.cs | 77 +++++++++++++++++- 17 files changed, 236 insertions(+), 148 deletions(-) diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt index baedd96e337..465036d1c62 100644 --- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt @@ -108,6 +108,8 @@ virtual System.Windows.Forms.Control.VisualStylesMode.set -> void override System.Windows.Forms.PrintPreviewControl.ForeColor.get -> System.Drawing.Color override System.Windows.Forms.PrintPreviewControl.ForeColor.set -> void System.Windows.Forms.KioskModeManager +System.Windows.Forms.KioskModeManager.AlwaysOn.get -> bool +System.Windows.Forms.KioskModeManager.AlwaysOn.set -> void System.Windows.Forms.KioskModeManager.ContainerControl.get -> System.Windows.Forms.ContainerControl? System.Windows.Forms.KioskModeManager.ContainerControl.set -> void System.Windows.Forms.KioskModeManager.ContainerControlChanged -> System.EventHandler? @@ -125,10 +127,8 @@ virtual System.Windows.Forms.KioskModeManager.OnContainerControlChanged(System.E virtual System.Windows.Forms.KioskModeManager.OnFullScreenChanged(System.EventArgs! e) -> void override System.Windows.Forms.KioskModeManager.Site.get -> System.ComponentModel.ISite? override System.Windows.Forms.KioskModeManager.Site.set -> void -System.Windows.Forms.KioskModeManager.SuppressPowerSaving.get -> bool -System.Windows.Forms.KioskModeManager.SuppressPowerSaving.set -> void System.Windows.Forms.KioskModeManager.ToggleFullScreen() -> void -System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.get -> System.Windows.Forms.Keys -System.Windows.Forms.KioskModeManager.ToggleFullScreenKey.set -> void +System.Windows.Forms.KioskModeManager.ToggleFullScreenKeys.get -> System.Windows.Forms.Keys +System.Windows.Forms.KioskModeManager.ToggleFullScreenKeys.set -> void System.Windows.Forms.KioskModeManager.TopMostInFullScreen.get -> bool System.Windows.Forms.KioskModeManager.TopMostInFullScreen.set -> void diff --git a/src/System.Windows.Forms/Resources/SR.resx b/src/System.Windows.Forms/Resources/SR.resx index 8397a13ee10..fe0a03d93fd 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -2694,6 +2694,9 @@ To replace this default dialog please handle the DataError event. Manages common fullscreen, input, cursor, and power behavior for kiosk-style WinForms applications. + + Indicates whether Windows should keep the display and system awake while this component is active. + The container whose containing form is controlled by the kiosk mode manager. @@ -2712,11 +2715,8 @@ To replace this default dialog please handle the DataError event. The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - - - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. Indicates whether the form is topmost while fullscreen. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf index 6d905124e03..6371646ca4e 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.cs.xlf @@ -6149,6 +6149,11 @@ Trasování zásobníku, kde došlo k neplatné operaci: Kombinace klíčů je neplatná. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Trasování zásobníku, kde došlo k neplatné operaci: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf index a71d457148d..59639e32839 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.de.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.de.xlf @@ -6149,6 +6149,11 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: Ungültige Schlüsselkombination. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stapelüberwachung, in der der unzulässige Vorgang auftrat: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf index 3ba4aaac626..5206fbf7ce5 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.es.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.es.xlf @@ -6149,6 +6149,11 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: La combinación de teclas no es válida. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ El seguimiento de la pila donde tuvo lugar la operación no válida fue: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf index 2bb376c3eb3..3db96186d82 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.fr.xlf @@ -6149,6 +6149,11 @@ Cette opération non conforme s'est produite sur la trace de la pile : La combinaison de touches n'est pas valide. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Cette opération non conforme s'est produite sur la trace de la pile : The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf index 5fdd140e67a..ccf859b7010 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.it.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.it.xlf @@ -6149,6 +6149,11 @@ Analisi dello stack dove si è verificata l'operazione non valida: Combinazione di tasti non valida. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Analisi dello stack dove si è verificata l'operazione non valida: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf index 5095dccfee6..ea1047d642b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ja.xlf @@ -6149,6 +6149,11 @@ Stack trace where the illegal operation occurred was: キーの組み合わせが有効ではありません。 + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stack trace where the illegal operation occurred was: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf index 8379506e907..bdfec7cec24 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ko.xlf @@ -6149,6 +6149,11 @@ Stack trace where the illegal operation occurred was: 키 조합이 잘못되었습니다. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stack trace where the illegal operation occurred was: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf index f68a35bf803..de8e90d7995 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pl.xlf @@ -6149,6 +6149,11 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: Kombinacja kluczy jest nieprawidłowa. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stos śledzenia, w którym wystąpiła zabroniona operacja: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf index a4a50703bb8..ada6d50447b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.pt-BR.xlf @@ -6149,6 +6149,11 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: A combinação de chave não é válida. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ O rastreamento de pilha em que a operação ilegal ocorreu foi: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf index c7963c82519..8ad13f09322 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.ru.xlf @@ -6149,6 +6149,11 @@ Stack trace where the illegal operation occurred was: Недопустимая комбинация ключа. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stack trace where the illegal operation occurred was: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf index 8132d8893ce..fb5ffa736c4 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.tr.xlf @@ -6149,6 +6149,11 @@ Geçersiz işlemin gerçekleştiği yığın izi: Anahtar birleşimi geçerli değil. + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Geçersiz işlemin gerçekleştiği yığın izi: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf index a896d0fcc79..b263d74579a 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hans.xlf @@ -6149,6 +6149,11 @@ Stack trace where the illegal operation occurred was: 组合键无效。 + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stack trace where the illegal operation occurred was: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf index c99559e050b..dfe89a7d89b 100644 --- a/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf +++ b/src/System.Windows.Forms/Resources/xlf/SR.zh-Hant.xlf @@ -6149,6 +6149,11 @@ Stack trace where the illegal operation occurred was: 組合鍵無效。 + + Indicates whether Windows should keep the display and system awake while this component is active. + Indicates whether Windows should keep the display and system awake while this component is active. + + Occurs when the ContainerControl property value changes. Occurs when the ContainerControl property value changes. @@ -6179,14 +6184,9 @@ Stack trace where the illegal operation occurred was: The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. - - Indicates whether Windows should keep the display and system awake while this component is active. - Indicates whether Windows should keep the display and system awake while this component is active. - - - - The key that toggles between fullscreen and restored mode. - The key that toggles between fullscreen and restored mode. + + The key combination that toggles between fullscreen and restored mode. + The key combination that toggles between fullscreen and restored mode. diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index 8ac3df51da8..d28d652f226 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -42,7 +42,7 @@ namespace System.Windows.Forms; /// ContainerControl = this, /// TopMostInFullScreen = true, /// MousePointerAutoHideDelay = 3000, -/// SuppressPowerSaving = true +/// AlwaysOn = true /// }; /// /// if (!_kioskModeManager.FullScreen) @@ -53,7 +53,7 @@ namespace System.Windows.Forms; /// } /// /// -[DefaultProperty(nameof(ToggleFullScreenKey))] +[DefaultProperty(nameof(ToggleFullScreenKeys))] [ToolboxItemFilter("System.Windows.Forms")] [SRDescription(nameof(SR.DescriptionKioskModeManager))] public class KioskModeManager : Component, ISupportInitialize @@ -83,9 +83,9 @@ public class KioskModeManager : Component, ISupportInitialize private bool _savedTopMost; private bool _topMostInFullScreen; - private Keys _toggleFullScreenKey = Keys.F11; + private Keys _toggleFullScreenKeys = Keys.F11; private bool _escapeExitsFullScreen = true; - private bool _suppressPowerSaving; + private bool _alwaysOn; private int _mousePointerAutoHideDelay; private HANDLE _powerRequestHandle; @@ -223,26 +223,35 @@ public bool TopMostInFullScreen } /// - /// Gets or sets the key that toggles fullscreen mode. + /// Gets or sets the key combination that toggles fullscreen mode. /// /// - /// A value that is handled without modifiers. The - /// default is . + /// A value that combines a key code with the modifiers that + /// must be pressed with it, or to disable key-based + /// toggling. The default is . /// + /// + /// + /// The key combination is evaluated only while the managed form or one of its + /// child controls has input focus. The component observes the application's own + /// message queue and never installs a global keyboard hook or registers a + /// system-wide hot key, so other applications keep receiving these keys. + /// + /// [SRCategory(nameof(SR.CatBehavior))] - [SRDescription(nameof(SR.KioskModeManagerToggleFullScreenKeyDescr))] + [SRDescription(nameof(SR.KioskModeManagerToggleFullScreenKeysDescr))] [DefaultValue(Keys.F11)] - public Keys ToggleFullScreenKey + public Keys ToggleFullScreenKeys { - get => _toggleFullScreenKey; + get => _toggleFullScreenKeys; set { - if (_toggleFullScreenKey == value) + if (_toggleFullScreenKeys == value) { return; } - _toggleFullScreenKey = value; + _toggleFullScreenKeys = value; } } @@ -255,6 +264,12 @@ public Keys ToggleFullScreenKey /// without modifiers; otherwise, . The default is /// . /// + /// + /// + /// Like , Escape is evaluated only while the + /// managed form or one of its child controls has input focus. + /// + /// [SRCategory(nameof(SR.CatBehavior))] [SRDescription(nameof(SR.KioskModeManagerEscapeExitsFullScreenDescr))] [DefaultValue(true)] @@ -293,20 +308,20 @@ public bool EscapeExitsFullScreen /// Windows could not create or activate the power request. /// [SRCategory(nameof(SR.CatBehavior))] - [SRDescription(nameof(SR.KioskModeManagerSuppressPowerSavingDescr))] + [SRDescription(nameof(SR.KioskModeManagerAlwaysOnDescr))] [DefaultValue(false)] - public bool SuppressPowerSaving + public bool AlwaysOn { - get => _suppressPowerSaving; + get => _alwaysOn; set { - if (_suppressPowerSaving == value) + if (_alwaysOn == value) { return; } - bool previousValue = _suppressPowerSaving; - _suppressPowerSaving = value; + bool previousValue = _alwaysOn; + _alwaysOn = value; try { @@ -314,7 +329,7 @@ public bool SuppressPowerSaving } catch { - _suppressPowerSaving = previousValue; + _alwaysOn = previousValue; throw; } } @@ -689,8 +704,10 @@ private void ClearParentChangedSubscriptions() private void EnsureMessageMonitoring() { - // IMessageFilter observes input for child controls without changing - // Form.KeyPreview or installing global keyboard/mouse hooks. + // IMessageFilter observes the input this application already receives, so keys + // stay focus driven: messages are only acted on when they target the managed + // form or one of its child controls. This changes neither Form.KeyPreview nor + // installs a global keyboard or mouse hook. if (_messageFilter is null) { _messageFilter = new KioskModeMessageFilter(this); @@ -744,7 +761,7 @@ private void UpdatePowerRequest() return; } - if (_suppressPowerSaving) + if (_alwaysOn) { CreatePowerRequest(); } @@ -787,21 +804,19 @@ private void OnMousePointerAutoHideTimerTick(object? sender, EventArgs e) } } - private void ProcessKeyboardActivity(Keys keyData, Keys modifiers, bool isRepeat) + private void ProcessKeyboardActivity(Keys keyData, bool isRepeat) { + // Key repeats must not toggle fullscreen mode again while the key is held down. if (isRepeat) { return; } - Keys keyCode = keyData & Keys.KeyCode; - - if (keyCode == _toggleFullScreenKey && modifiers == Keys.None) + if (_toggleFullScreenKeys != Keys.None && keyData == _toggleFullScreenKeys) { ToggleFullScreen(); } - else if (keyCode == Keys.Escape && modifiers == Keys.None - && _escapeExitsFullScreen && _isFullScreen) + else if (keyData == Keys.Escape && _escapeExitsFullScreen && _isFullScreen) { ExitFullScreen(); } @@ -926,10 +941,9 @@ private bool ProcessMessage(Message message) || message.MsgInternal == PInvokeCore.WM_SYSKEYDOWN) { bool isRepeat = ((nuint)(nint)message.LParamInternal & (1u << 30)) != 0; - ProcessKeyboardActivity( - (Keys)(nint)message.WParamInternal, - Control.ModifierKeys & Keys.Modifiers, - isRepeat); + Keys keyData = ((Keys)(nint)message.WParamInternal & Keys.KeyCode) + | (Control.ModifierKeys & Keys.Modifiers); + ProcessKeyboardActivity(keyData, isRepeat); } else if (message.MsgInternal >= PInvokeCore.WM_MOUSEFIRST && message.MsgInternal <= PInvokeCore.WM_MOUSELAST) @@ -941,7 +955,8 @@ private bool ProcessMessage(Message message) } /// - /// Observes keyboard and mouse messages that belong to the target form. + /// Observes keyboard and mouse messages that are dispatched to the target form + /// or to one of its child controls. /// private sealed class KioskModeMessageFilter : IMessageFilter { diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index bef6a210078..2172b886920 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -18,6 +18,7 @@ public void KioskModeManager_Ctor_Default() { using SubKioskModeManager manager = new(); + Assert.False(manager.AlwaysOn); Assert.Null(manager.Container); Assert.Null(manager.ContainerControl); Assert.False(manager.DesignMode); @@ -25,8 +26,7 @@ public void KioskModeManager_Ctor_Default() Assert.False(manager.FullScreen); Assert.Equal(0, manager.MousePointerAutoHideDelay); Assert.Null(manager.Site); - Assert.False(manager.SuppressPowerSaving); - Assert.Equal(Keys.F11, manager.ToggleFullScreenKey); + Assert.Equal(Keys.F11, manager.ToggleFullScreenKeys); Assert.False(manager.TopMostInFullScreen); } @@ -117,6 +117,79 @@ public void KioskModeManager_MousePointerAutoHideDelay_SetNegative_ThrowsArgumen Assert.Throws("value", () => manager.MousePointerAutoHideDelay = -1); } + [WinFormsTheory] + [InlineData(Keys.F11)] + [InlineData(Keys.None)] + [InlineData(Keys.Alt | Keys.Enter)] + [InlineData(Keys.Control | Keys.Shift | Keys.F)] + public void KioskModeManager_ToggleFullScreenKeys_Set_GetReturnsExpected(Keys value) + { + using KioskModeManager manager = new() + { + ToggleFullScreenKeys = value + }; + + Assert.Equal(value, manager.ToggleFullScreenKeys); + + manager.ToggleFullScreenKeys = value; + Assert.Equal(value, manager.ToggleFullScreenKeys); + } + + [WinFormsTheory] + [BoolData] + public void KioskModeManager_AlwaysOn_Set_GetReturnsExpected(bool value) + { + using KioskModeManager manager = new() + { + AlwaysOn = value + }; + + Assert.Equal(value, manager.AlwaysOn); + + manager.AlwaysOn = value; + Assert.Equal(value, manager.AlwaysOn); + + manager.AlwaysOn = !value; + Assert.Equal(!value, manager.AlwaysOn); + } + + [WinFormsTheory] + [InlineData(Keys.Alt | Keys.Enter)] + [InlineData(Keys.Control | Keys.Shift | Keys.F)] + public void KioskModeManager_ProcessKeyboardActivity_MatchingKeyCombination_TogglesFullScreen(Keys keys) + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + ToggleFullScreenKeys = keys + }; + + manager.TestAccessor.Dynamic.ProcessKeyboardActivity(keys, false); + Assert.True(manager.FullScreen); + + manager.TestAccessor.Dynamic.ProcessKeyboardActivity(keys & Keys.KeyCode, false); + Assert.True(manager.FullScreen); + + manager.TestAccessor.Dynamic.ProcessKeyboardActivity(keys, false); + Assert.False(manager.FullScreen); + } + + [WinFormsFact] + public void KioskModeManager_ProcessKeyboardActivity_KeysNone_DoesNotToggleFullScreen() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + ToggleFullScreenKeys = Keys.None + }; + + manager.TestAccessor.Dynamic.ProcessKeyboardActivity(Keys.None, false); + + Assert.False(manager.FullScreen); + } + [WinFormsFact] public void KioskModeManager_ToggleFullScreen_Form_RestoresExpected() { From 6db1f47188212ab49599423eaea04f82e1eb8839 Mon Sep 17 00:00:00 2001 From: Klaus Loeffelmann Date: Fri, 14 Aug 2026 10:50:59 -0700 Subject: [PATCH 12/12] Refine auto-hide logic and add pointer timer tests - Clarified MousePointerAutoHideDelay XML docs regarding message queue observation and lack of hooks. - Updated OnMousePointerAutoHideTimerTick to only hide cursor in fullscreen, with delay > 0, and when not already hidden. - Added unit tests for timer interval, delay changes, zero-delay behavior, and pointer visibility logic. --- .../KioskModeManager/KioskModeManager.cs | 7 +- .../Windows/Forms/KioskModeManagerTests.cs | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs index d28d652f226..5ce39877f0b 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -347,7 +347,9 @@ public bool AlwaysOn /// /// The timer is active only while the component is in fullscreen mode. /// When the user moves the mouse after the component hid the pointer, the - /// pointer is shown immediately and the timer starts again. + /// pointer is shown immediately and the timer starts again. Mouse activity + /// is observed from the application's message queue; the component does not + /// install a mouse or keyboard hook to restore the pointer. /// /// /// @@ -797,7 +799,8 @@ private void OnMousePointerAutoHideTimerTick(object? sender, EventArgs e) { StopMousePointerAutoHideTimer(); - if (!_isCursorHidden) + // A timer message can already be queued when the timer is stopped. + if (_isFullScreen && _mousePointerAutoHideDelay > 0 && !_isCursorHidden) { Cursor.Hide(); _isCursorHidden = true; diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs index 2172b886920..449f987d071 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -117,6 +117,78 @@ public void KioskModeManager_MousePointerAutoHideDelay_SetNegative_ThrowsArgumen Assert.Throws("value", () => manager.MousePointerAutoHideDelay = -1); } + [WinFormsFact] + public void KioskModeManager_FullScreen_WithMousePointerAutoHideDelay_StartsTimer() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + MousePointerAutoHideDelay = 3000, + FullScreen = true + }; + + Timer timer = manager.TestAccessor.Dynamic._mousePointerAutoHideTimer; + + Assert.True(timer.Enabled); + Assert.Equal(3000, timer.Interval); + } + + [WinFormsFact] + public void KioskModeManager_MousePointerAutoHideDelay_SetWhileFullScreen_UpdatesTimer() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + MousePointerAutoHideDelay = 1000, + FullScreen = true + }; + + manager.MousePointerAutoHideDelay = 2500; + Timer timer = manager.TestAccessor.Dynamic._mousePointerAutoHideTimer; + + Assert.True(timer.Enabled); + Assert.Equal(2500, timer.Interval); + } + + [WinFormsFact] + public void KioskModeManager_MousePointerAutoHideDelay_SetZero_ShowsPointerAndStopsTimer() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + MousePointerAutoHideDelay = 1000, + FullScreen = true + }; + manager.TestAccessor.Dynamic.OnMousePointerAutoHideTimerTick(null, EventArgs.Empty); + + manager.MousePointerAutoHideDelay = 0; + Timer timer = manager.TestAccessor.Dynamic._mousePointerAutoHideTimer; + + Assert.False(manager.TestAccessor.Dynamic._isCursorHidden); + Assert.False(timer.Enabled); + } + + [WinFormsTheory] + [BoolData] + public void KioskModeManager_MousePointerAutoHideTimerTick_WhenInactive_DoesNotHidePointer( + bool fullScreen) + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + MousePointerAutoHideDelay = fullScreen ? 0 : 1000, + FullScreen = fullScreen + }; + + manager.TestAccessor.Dynamic.OnMousePointerAutoHideTimerTick(null, EventArgs.Empty); + + Assert.False(manager.TestAccessor.Dynamic._isCursorHidden); + } + [WinFormsTheory] [InlineData(Keys.F11)] [InlineData(Keys.None)] @@ -799,8 +871,11 @@ public void KioskModeManager_ProcessMessage_MouseMove_ShowsHiddenMousePointer() Message message = Message.Create(form.Handle, (int)PInvokeCore.WM_MOUSEMOVE, 0, 0); manager.TestAccessor.Dynamic.ProcessMessage(message); + Timer timer = manager.TestAccessor.Dynamic._mousePointerAutoHideTimer; Assert.False(manager.TestAccessor.Dynamic._isCursorHidden); + Assert.True(timer.Enabled); + Assert.Equal(1, timer.Interval); } private class SubKioskModeManager : KioskModeManager