Files
DP44/enriched-qwen3-coder-next/DataPRO/Modules/DatabaseImporter/DatabaseImport/App.md
2026-04-17 14:55:32 -04:00

6.7 KiB

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
DataPRO/Modules/DatabaseImporter/DatabaseImport/App/WaitCursor.cs
DataPRO/Modules/DatabaseImporter/DatabaseImport/App/App.cs
2026-04-16T04:29:09.171697+00:00 Qwen/Qwen3-Coder-Next-FP8 1 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:
    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.