Files
DP44/enriched-qwen3-coder-next/DataPRO/Modules/DatabaseImporter/DatabaseImport/App.md

107 lines
6.7 KiB
Markdown
Raw Normal View History

2026-04-17 14:55:32 -04:00
---
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.