diff --git a/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md new file mode 100644 index 00000000000..95b4644865a --- /dev/null +++ b/docs/Net11Api_04_KioskManagerComponent.HighRiskReview.md @@ -0,0 +1,23 @@ +# 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. diff --git a/src/BuildAssist/BuildAssist.msbuildproj b/src/BuildAssist/BuildAssist.msbuildproj index 2e188f76ff6..400c78f44e6 100644 --- a/src/BuildAssist/BuildAssist.msbuildproj +++ b/src/BuildAssist/BuildAssist.msbuildproj @@ -37,7 +37,7 @@ void virtual System.Windows.Forms.Control.VisualStylesMode.get -> System.Windows.Forms.VisualStylesMode virtual System.Windows.Forms.Control.VisualStylesMode.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? +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.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 +override System.Windows.Forms.KioskModeManager.Site.get -> System.ComponentModel.ISite? +override System.Windows.Forms.KioskModeManager.Site.set -> void +System.Windows.Forms.KioskModeManager.ToggleFullScreen() -> 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 d3a89671107..62f785c0944 100644 --- a/src/System.Windows.Forms/Resources/SR.resx +++ b/src/System.Windows.Forms/Resources/SR.resx @@ -2691,6 +2691,36 @@ 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. + + + 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. + + + 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. + + + The delay, in milliseconds, before the mouse pointer is hidden while fullscreen. A value of 0 disables automatic pointer hiding. + + + The key combination that toggles between fullscreen and restored mode. + + + Indicates whether the form is topmost while fullscreen. + Provides run-time information or descriptive text for a control. @@ -7075,4 +7105,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 3516b3584de..d296de7a3a0 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 1b1a7896269..530ac718fe5 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 650b5dc8f1c..988fe2976c3 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 762393d64b1..08335c39e44 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 3c34571e6df..798017b6158 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 05f5f03a962..75b2c82c1b0 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 06b9e480dcb..048e1451032 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 5f0edef73e8..5e707c725ca 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 94b1dd779a0..ed1b1f0d8d7 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 31f2fc77cf5..72f0299cebc 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 2388df399e0..32be056ef1f 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 3f81edc8ec1..c87262791cd 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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 72b32f99c14..bdb8cfaf821 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,51 @@ 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. + + + + 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. + + + + 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. + + + + The key combination 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. + Indicates whether the form is topmost while fullscreen. + + 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/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/Components/KioskModeManager/KioskModeManager.cs b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs new file mode 100644 index 00000000000..d28d652f226 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Components/KioskModeManager/KioskModeManager.cs @@ -0,0 +1,1055 @@ +// 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 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, keep the form topmost +/// while fullscreen, suppress display and system sleep, and hide the mouse +/// pointer after a period of inactivity. +/// +/// +/// +/// +/// public partial class MainForm : Form +/// { +/// private readonly KioskModeManager _kioskModeManager; +/// +/// public MainForm() +/// { +/// InitializeComponent(); +/// +/// _kioskModeManager = new KioskModeManager +/// { +/// ContainerControl = this, +/// TopMostInFullScreen = true, +/// MousePointerAutoHideDelay = 3000, +/// AlwaysOn = true +/// }; +/// +/// if (!_kioskModeManager.FullScreen) +/// { +/// _kioskModeManager.ToggleFullScreen(); +/// } +/// } +/// } +/// +/// +[DefaultProperty(nameof(ToggleFullScreenKeys))] +[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 string PowerRequestReason = "WinForms KioskModeManager: Preventing screen saver and sleep"; + + private static readonly object s_containerControlChangedEvent = new(); + private static readonly object s_fullScreenChangedEvent = new(); + + private ContainerControl? _containerControl; + private bool _containerControlExplicitlySet; + private Form? _targetForm; + private bool _isFullScreen; + private bool _pendingFullScreen; + private bool _initializing; + private bool _isCursorHidden; + private readonly List _parentChain = []; + private KioskModeMessageFilter? _messageFilter; + private KioskModeFormObserver? _formObserver; + private Timer? _mousePointerAutoHideTimer; + + private FormBorderStyle _savedBorderStyle; + private FormWindowState _savedWindowState; + private Rectangle _savedBounds; + private bool _savedTopMost; + + private bool _topMostInFullScreen; + private Keys _toggleFullScreenKeys = Keys.F11; + private bool _escapeExitsFullScreen = true; + private bool _alwaysOn; + private int _mousePointerAutoHideDelay; + + 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 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 combination that toggles fullscreen mode. + /// + /// + /// 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.KioskModeManagerToggleFullScreenKeysDescr))] + [DefaultValue(Keys.F11)] + public Keys ToggleFullScreenKeys + { + get => _toggleFullScreenKeys; + set + { + if (_toggleFullScreenKeys == value) + { + return; + } + + _toggleFullScreenKeys = 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 + /// . + /// + /// + /// + /// 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)] + 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.KioskModeManagerAlwaysOnDescr))] + [DefaultValue(false)] + public bool AlwaysOn + { + get => _alwaysOn; + set + { + if (_alwaysOn == value) + { + return; + } + + bool previousValue = _alwaysOn; + _alwaysOn = value; + + try + { + UpdatePowerRequest(); + } + catch + { + _alwaysOn = previousValue; + throw; + } + } + } + + /// + /// 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. + /// + /// + /// 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))] + [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); + } + } + + /// + /// 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; + } + + UpdateParentChangedSubscriptions(); + EnsureMessageMonitoring(); + ResolveTargetForm(); + if (_pendingFullScreen && _targetForm is not null) + { + EnterFullScreen(); + } + } + + private void DetachFromForm() + { + ClearParentChangedSubscriptions(); + + if (_messageFilter is not null) + { + Application.RemoveMessageFilter(_messageFilter); + _messageFilter = null; + } + + _formObserver?.Detach(); + StopMousePointerAutoHideTimer(); + ShowMousePointerIfHidden(); + _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 + // 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 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); + Application.AddMessageFilter(_messageFilter); + } + } + + private void ApplyFullScreen(Form form) + { + Rectangle screenReferenceBounds = form.WindowState == FormWindowState.Normal + ? form.Bounds + : form.RestoreBounds; + Screen screen = Screen.FromRectangle(screenReferenceBounds); + + // 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. + form.WindowState = FormWindowState.Normal; + form.FormBorderStyle = FormBorderStyle.None; + form.TopMost = _topMostInFullScreen; + form.Bounds = fullScreenBounds; + } + + private void RefreshFullScreenBounds() + { + if (_isFullScreen + && _targetForm is { IsDisposed: false } form) + { + ApplyFullScreen(form); + } + } + + private void UpdateFormObserver() + { + if (DesignMode) + { + return; + } + + _formObserver ??= new KioskModeFormObserver(this); + _formObserver.Attach(_targetForm); + } + + private void UpdatePowerRequest() + { + if (_initializing || DesignMode) + { + return; + } + + if (_alwaysOn) + { + CreatePowerRequest(); + } + else + { + ReleasePowerRequest(); + } + } + + 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, bool isRepeat) + { + // Key repeats must not toggle fullscreen mode again while the key is held down. + if (isRepeat) + { + return; + } + + if (_toggleFullScreenKeys != Keys.None && keyData == _toggleFullScreenKeys) + { + ToggleFullScreen(); + } + else if (keyData == Keys.Escape && _escapeExitsFullScreen && _isFullScreen) + { + ExitFullScreen(); + } + } + + private void ProcessMouseActivity() + { + ShowMousePointerIfHidden(); + RestartMousePointerAutoHideTimer(); + } + + 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 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) + { + bool isRepeat = ((nuint)(nint)message.LParamInternal & (1u << 30)) != 0; + 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) + { + ProcessMouseActivity(); + } + + return false; + } + + /// + /// Observes keyboard and mouse messages that are dispatched to the target form + /// or to one of its child controls. + /// + 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 display changes that affect the target form. + /// + private sealed class KioskModeFormObserver : NativeWindow + { + private readonly KioskModeManager _owner; + private Form? _form; + + 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; + } + + if (!HWND.IsNull) + { + ReleaseHandle(); + } + } + + private void OnFormHandleCreated(object? sender, EventArgs e) + { + if (_form is null || _form.IsDisposed || !_form.IsHandleCreated) + { + return; + } + + AssignHandle(_form.HWND); + } + + private void OnFormHandleDestroyed(object? sender, EventArgs e) + { + if (!HWND.IsNull) + { + ReleaseHandle(); + } + } + + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + + if (m.MsgInternal == PInvokeCore.WM_DISPLAYCHANGE) + { + _owner.RefreshFullScreenBounds(); + } + } + } +} +#endif 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 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 new file mode 100644 index 00000000000..2172b886920 --- /dev/null +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/KioskModeManagerTests.cs @@ -0,0 +1,817 @@ +// 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 Moq; + +namespace System.Windows.Forms.Tests; + +#if NET11_0_OR_GREATER +public class KioskModeManagerTests +{ + [WinFormsFact] + 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); + Assert.True(manager.EscapeExitsFullScreen); + Assert.False(manager.FullScreen); + Assert.Equal(0, manager.MousePointerAutoHideDelay); + Assert.Null(manager.Site); + Assert.Equal(Keys.F11, manager.ToggleFullScreenKeys); + 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] + [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() + { + 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, + TopMostInFullScreen = true + }; + Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; + + manager.ToggleFullScreen(); + + Assert.True(manager.FullScreen); + Assert.Equal(FormBorderStyle.None, form.FormBorderStyle); + Assert.True(form.TopMost); + Assert.Equal(FormWindowState.Normal, form.WindowState); + Assert.Equal(screenBounds, form.Bounds); + + 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_FullScreen_CoversCompleteScreenBounds() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200) + }; + using KioskModeManager manager = new() + { + ContainerControl = form + }; + 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_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 screenBounds = Screen.FromRectangle( + restoreBounds).Bounds; + using KioskModeManager manager = new() + { + ContainerControl = form + }; + + manager.FullScreen = true; + + Assert.Equal(screenBounds, form.Bounds); + } + + [WinFormsFact] + public void KioskModeManager_FullScreen_RefreshBounds_ReappliesScreenBounds() + { + using Form form = new() + { + Bounds = new Rectangle(10, 20, 300, 200) + }; + Rectangle screenBounds = Screen.FromRectangle( + form.Bounds).Bounds; + using KioskModeManager manager = new() + { + ContainerControl = form, + FullScreen = true + }; + form.Bounds = new Rectangle( + screenBounds.X + 10, + screenBounds.Y + 10, + 300, + 200); + + manager.TestAccessor.Dynamic.RefreshFullScreenBounds(); + + 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() + { + 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_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() + { + 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_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() + { + 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] + [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); + } + + [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); + } + + [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; + Rectangle screenBounds = Screen.FromRectangle(form.Bounds).Bounds; + + 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.Normal, form.WindowState); + Assert.Equal(screenBounds, form.Bounds); + } + + [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_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_ShowsHiddenMousePointer() + { + using Form form = new(); + using KioskModeManager manager = new() + { + ContainerControl = form, + MousePointerAutoHideDelay = 1, + FullScreen = true + }; + + 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.False(manager.TestAccessor.Dynamic._isCursorHidden); + } + + 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); + } +} +#endif