--- source_files: - DataPRO/Modules/DatabaseImporter/DatabaseImport/App/WaitCursor.cs - DataPRO/Modules/DatabaseImporter/DatabaseImport/App/App.cs generated_at: "2026-04-16T04:29:09.171697+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "85d98116aaeec020" --- # App ## Documentation: `WaitCursor` and `App` Module (DatabaseImporter) --- ### 1. Purpose This module provides UI-level cursor and application state management during long-running operations in a WPF-based database import workflow. Specifically, the `WaitCursor` class offers a lightweight, `IDisposable` wrapper to temporarily override the mouse cursor to a wait state and restore it upon disposal. The `App` class (a partial class for `Application`) extends this functionality at the application level by coordinating global busy/idle states: it disables the main window and manages a singleton `WaitCursor` instance across thread boundaries using the WPF `Dispatcher`, ensuring safe, re-entrant, and exception-safe busy/idle transitions—typically used before/after data-intensive operations (e.g., database refreshes or imports). --- ### 2. Public Interface #### `WaitCursor` class (`DataImport.WaitCursor`) - **`public WaitCursor()`** Constructor. Saves the current `Mouse.OverrideCursor`, sets `Mouse.OverrideCursor = Cursors.Wait`, and prepares for later restoration via `Dispose()`. - **`public void Dispose()`** Restores the previously saved cursor (`_previousCursor`) to `Mouse.OverrideCursor`. Must be called exactly once per instance (per `using` or manual disposal pattern). No-op if called multiple times (but cursor may be incorrectly restored if disposed more than once). #### `App` class (`DatabaseImport.App`, partial) - **`public ISO13499FileDb IsoDb { get; }`** Lazy-initialized singleton accessor for the `ISO13499FileDb` database instance. On first access, instantiates `_isoDb`, calls `_isoDb.RefreshAllData()`, and returns it. Thread-safety is *not* guaranteed (no locking around initialization or `RefreshAllData()`). - **`public void SetAppBusy()`** Sets the application into a *busy* state: - Marshals to the UI thread via `Dispatcher.BeginInvoke` if called from a non-UI thread. - Disposes any existing `_wc` (if not null). - Creates a new `WaitCursor` instance and assigns it to `_wc`. - Sets `MainWindow.IsEnabled = false`. - Uses `lock (WaitCursorLock)` to serialize concurrent calls. - *No-op* if `MainWindow` is null. - **`public void SetAppAvailable()`** Sets the application into an *available* (idle) state: - Marshals to the UI thread via `Dispatcher.BeginInvoke` if called from a non-UI thread. - Disposes and nullifies `_wc` (if not null). - Sets `MainWindow.IsEnabled = true`. - Uses `lock (WaitCursorLock)` to serialize concurrent calls. - *No-op* if `MainWindow` is null. --- ### 3. Invariants - **Cursor restoration guarantee**: After a `WaitCursor` instance is disposed, `Mouse.OverrideCursor` is restored to the value it held at the time of construction. - **Application busy/idle state consistency**: - `MainWindow.IsEnabled` is `false` *only* when `_wc` is non-null (i.e., during a `SetAppBusy()` call with no intervening `SetAppAvailable()`). - `_wc` is `null` *only* when the application is not busy (i.e., after `SetAppAvailable()` or before any `SetAppBusy()` call). - **Thread-safety of state transitions**: - `SetAppBusy()` and `SetAppAvailable()` are thread-safe *with respect to each other* via `lock (WaitCursorLock)`. - Both methods *always* marshal to the UI thread via `Dispatcher.BeginInvoke` if invoked off-thread, ensuring UI mutations occur on the correct thread. - **Singleton `IsoDb` initialization**: `_isoDb` is initialized at most once, and only upon first access to `IsoDb`. `RefreshAllData()` is called exactly once per instance creation. --- ### 4. Dependencies #### This module depends on: - `System.Windows.Input.Cursors` (for `Cursors.Wait`) - `System.Windows.Input.Mouse` (for `Mouse.OverrideCursor`) - `System.Windows.Threading.Dispatcher` (for `Dispatcher.CheckAccess`, `BeginInvoke`) - `System.Windows.Application` (base class `App : Application`) - `System.Windows.Window` (via `MainWindow`) - `DatabaseImport.ISO13499FileDb` (referenced in `App.IsoDb` property; defined elsewhere in the codebase) #### This module is depended on by: - Any code that needs to wrap long-running operations in a wait cursor (e.g., `using (new WaitCursor()) { ... }`). - Code that triggers database refreshes or imports (uses `App.IsoDb`). - UI or service layers that call `App.SetAppBusy()` before and `App.SetAppAvailable()` in `finally` blocks (as recommended in comments). --- ### 5. Gotchas - **`IsoDb` initialization is not thread-safe**: Multiple concurrent first-time accesses to `IsoDb` may result in multiple `ISO13499FileDb` instances being created and `RefreshAllData()` being called multiple times. No locking or double-check pattern is used. - **`WaitCursor` is not re-entrant**: If `WaitCursor` is constructed multiple times without disposal (e.g., nested `using` blocks), only the *outermost* cursor state is preserved in `_previousCursor`. Inner disposals will overwrite the cursor prematurely. *Example*: ```csharp using (new WaitCursor()) // Saves cursor A, sets Wait { using (new WaitCursor()) // Saves cursor Wait, sets Wait again { // ... } // Restores cursor Wait → incorrect! } // Restores cursor A ``` - **`SetAppBusy()` / `SetAppAvailable()` may silently no-op**: If `MainWindow` is `null` at the time of call (e.g., during early startup or after window close), the methods exit early with no side effects—no exception is thrown. - **`WaitCursorLock` is static and shared across all `App` instances**: Since `App` is a singleton in WPF, this is expected—but if multiple `App` instances were ever created (e.g., in tests), locking would be incorrectly shared. - **No exception handling in `SetAppBusy()`/`SetAppAvailable()`**: If `MainWindow.IsEnabled = false/true` or `WaitCursor.Dispose()` throws, the busy/idle state may be left inconsistent (e.g., `_wc` disposed but `MainWindow.IsEnabled` not updated). - **`_wc` is not thread-local**: Though marshaled to the UI thread, `_wc` is a *single instance field*—concurrent calls via `BeginInvoke` are serialized by `WaitCursorLock`, but the timing of disposal/creation may cause transient cursor flicker if not used carefully (e.g., overlapping busy periods). - **`WaitCursor` does not prevent user input beyond disabling `MainWindow`**: `Mouse.OverrideCursor` alone does not block input; the disabling of `MainWindow` is what prevents interaction. If `MainWindow` is hidden or replaced, the busy state may persist incorrectly. None identified beyond the above.