Implement dialog windows for the win32 platform (#176309)

## What's new?
- Implemented `DialogWindowControllerWin32` for win32 dialogs :rocket:
- Refactored and updated the Win32 embedder to support dialogs
- Updated the `multiple_windows` example to demonstrate both modal and
modeless dialogs
- Added integration tests for the dialog windows
- Added tests for dialogs in the embedder

## How to test?
1. Run the `multiple_windows` example application with a local engine
built from this pull request
2. Click the `Modeless Dialog` creation button on the main window
3. Open a regular window and click the `Modal Dialog` creation button
4. Note the behavior of modal and modeless dialogs

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
diff --git a/dev/integration_tests/windowing_test/lib/main.dart b/dev/integration_tests/windowing_test/lib/main.dart
index 5f9416d..c40a456 100644
--- a/dev/integration_tests/windowing_test/lib/main.dart
+++ b/dev/integration_tests/windowing_test/lib/main.dart
@@ -23,6 +23,9 @@
 }
 
 late final RegularWindowController controller;
+final ValueNotifier<DialogWindowController?> dialogController = ValueNotifier(
+  null,
+);
 
 void main() {
   final Completer<void> windowCreated = Completer();
@@ -61,7 +64,9 @@
         controller.removeListener(notificationHandler);
       }
 
-      if (jsonMap['type'] == 'get_size') {
+      if (jsonMap['type'] == 'ping') {
+        return jsonEncode({'type': 'pong'});
+      } else if (jsonMap['type'] == 'get_size') {
         return jsonEncode({
           'width': controller.contentSize.width,
           'height': controller.contentSize.height,
@@ -129,6 +134,23 @@
         }, () => controller.isActivated);
       } else if (jsonMap['type'] == 'get_activated') {
         return jsonEncode({'isActivated': controller.isActivated});
+      } else if (jsonMap['type'] == 'open_dialog') {
+        if (dialogController.value != null) {
+          return jsonEncode({'result': false});
+        }
+        dialogController.value = DialogWindowController(
+          preferredSize: const Size(200, 200),
+          parent: controller,
+          delegate: MyDialogWindowControllerDelegate(
+            onDestroyed: () {
+              dialogController.value = null;
+            },
+          ),
+        );
+        return jsonEncode({'result': true});
+      } else if (jsonMap['type'] == 'close_dialog') {
+        dialogController.value?.destroy();
+        return jsonEncode({'result': true});
       } else {
         throw ArgumentError('Unknown message type: ${jsonMap['type']}');
       }
@@ -169,38 +191,82 @@
   State<MyHomePage> createState() => _MyHomePageState();
 }
 
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
+class MyDialogWindowControllerDelegate extends DialogWindowControllerDelegate {
+  MyDialogWindowControllerDelegate({required this.onDestroyed});
 
-  void _incrementCounter() {
-    setState(() {
-      _counter++;
-    });
+  final VoidCallback onDestroyed;
+
+  @override
+  void onWindowDestroyed() {
+    onDestroyed();
+    super.onWindowDestroyed();
   }
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  @override
+  Widget build(BuildContext context) {
+    return ValueListenableBuilder(
+      valueListenable: dialogController,
+      builder:
+          (
+            BuildContext context,
+            DialogWindowController? dialogController,
+            Widget? child,
+          ) {
+            return ViewAnchor(
+              view: dialogController != null
+                  ? DialogWindow(
+                      controller: dialogController,
+                      child: MyDialogPage(controller: dialogController),
+                    )
+                  : null,
+              child: Scaffold(
+                appBar: AppBar(
+                  backgroundColor: Theme.of(context).colorScheme.inversePrimary,
+                  title: Text(widget.title),
+                ),
+                body: Center(
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    children: <Widget>[const Text('This is the main window.')],
+                  ),
+                ),
+              ),
+            );
+          },
+    );
+  }
+}
+
+class MyDialogPage extends StatelessWidget {
+  const MyDialogPage({super.key, required this.controller});
+
+  final DialogWindowController controller;
 
   @override
   Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
-        title: Text(widget.title),
-      ),
-      body: Center(
-        child: Column(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            const Text('You have pushed the button this many times:'),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headlineMedium,
-            ),
-          ],
+    return MaterialApp(
+      home: Scaffold(
+        appBar: AppBar(
+          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
+          title: Text('Dialog'),
         ),
-      ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: const Icon(Icons.add),
+        body: Center(
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              const Text('This is a dialog window.'),
+              ElevatedButton(
+                key: const ValueKey<String>('close_dialog'),
+                onPressed: () {
+                  controller.destroy();
+                },
+                child: Text('Close Dialog'),
+              ),
+            ],
+          ),
+        ),
       ),
     );
   }
diff --git a/dev/integration_tests/windowing_test/test_driver/main_test.dart b/dev/integration_tests/windowing_test/test_driver/main_test.dart
index d6572d6..0a7ab93 100644
--- a/dev/integration_tests/windowing_test/test_driver/main_test.dart
+++ b/dev/integration_tests/windowing_test/test_driver/main_test.dart
@@ -13,6 +13,7 @@
 
     setUpAll(() async {
       driver = await FlutterDriver.connect();
+      await driver.requestData(jsonEncode({'type': 'ping'}));
     });
 
     tearDownAll(() async {
@@ -168,5 +169,16 @@
       timeout: Timeout.none,
       onPlatform: {'linux': Skip('isMinimized is not supported on Wayland')},
     );
+
+    test(
+      'Can open dialog',
+      () async {
+        await driver.requestData(jsonEncode({'type': 'open_dialog'}));
+        await driver.waitFor(find.byValueKey('close_dialog'));
+        await driver.requestData(jsonEncode({'type': 'close_dialog'}));
+      },
+      timeout: Timeout.none,
+      onPlatform: {'linux': Skip('Dialogs are not yet supported on Wayland')},
+    );
   });
 }
diff --git a/engine/src/flutter/shell/platform/common/windowing.h b/engine/src/flutter/shell/platform/common/windowing.h
index 5c37df3..beb2ca4 100644
--- a/engine/src/flutter/shell/platform/common/windowing.h
+++ b/engine/src/flutter/shell/platform/common/windowing.h
@@ -8,11 +8,12 @@
 namespace flutter {
 
 // Types of windows.
-// The value must match value from WindowType in the Dart code
-// in packages/flutter/lib/src/widgets/window.dart
 enum class WindowArchetype {
   // Regular top-level window.
   kRegular,
+
+  // Dialog window.
+  kDialog,
 };
 
 }  // namespace flutter
diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn
index 62730cc..7616ccb 100644
--- a/engine/src/flutter/shell/platform/windows/BUILD.gn
+++ b/engine/src/flutter/shell/platform/windows/BUILD.gn
@@ -93,6 +93,10 @@
     "flutter_windows_view_controller.h",
     "host_window.cc",
     "host_window.h",
+    "host_window_dialog.cc",
+    "host_window_dialog.h",
+    "host_window_regular.cc",
+    "host_window_regular.h",
     "keyboard_handler_base.h",
     "keyboard_key_channel_handler.cc",
     "keyboard_key_channel_handler.h",
diff --git a/engine/src/flutter/shell/platform/windows/host_window.cc b/engine/src/flutter/shell/platform/windows/host_window.cc
index 3854b17..501d936 100644
--- a/engine/src/flutter/shell/platform/windows/host_window.cc
+++ b/engine/src/flutter/shell/platform/windows/host_window.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "flutter/shell/platform/windows/host_window.h"
+#include "flutter/shell/platform/windows/host_window_dialog.h"
+#include "flutter/shell/platform/windows/host_window_regular.h"
 
 #include <dwmapi.h>
 
@@ -90,61 +92,6 @@
   return oss.str();
 }
 
-// Calculates the required window size, in physical coordinates, to
-// accommodate the given |client_size|, in logical coordinates, constrained by
-// optional |smallest| and |biggest|, for a window with the specified
-// |window_style| and |extended_window_style|. If |owner_hwnd| is not null, the
-// DPI of the display with the largest area of intersection with |owner_hwnd| is
-// used for the calculation; otherwise, the primary display's DPI is used. The
-// resulting size includes window borders, non-client areas, and drop shadows.
-// On error, returns std::nullopt and logs an error message.
-std::optional<flutter::Size> GetWindowSizeForClientSize(
-    flutter::WindowsProcTable const& win32,
-    flutter::Size const& client_size,
-    std::optional<flutter::Size> smallest,
-    std::optional<flutter::Size> biggest,
-    DWORD window_style,
-    DWORD extended_window_style,
-    HWND owner_hwnd) {
-  UINT const dpi = flutter::GetDpiForHWND(owner_hwnd);
-  double const scale_factor =
-      static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
-  RECT rect = {
-      .right = static_cast<LONG>(client_size.width() * scale_factor),
-      .bottom = static_cast<LONG>(client_size.height() * scale_factor)};
-
-  if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE,
-                                      extended_window_style, dpi)) {
-    FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: "
-                   << GetLastErrorAsString();
-    return std::nullopt;
-  }
-
-  double width = static_cast<double>(rect.right - rect.left);
-  double height = static_cast<double>(rect.bottom - rect.top);
-
-  // Apply size constraints
-  double const non_client_width = width - (client_size.width() * scale_factor);
-  double const non_client_height =
-      height - (client_size.height() * scale_factor);
-  if (smallest) {
-    flutter::Size min_physical_size = ClampToVirtualScreen(
-        flutter::Size(smallest->width() * scale_factor + non_client_width,
-                      smallest->height() * scale_factor + non_client_height));
-    width = std::max(width, min_physical_size.width());
-    height = std::max(height, min_physical_size.height());
-  }
-  if (biggest) {
-    flutter::Size max_physical_size = ClampToVirtualScreen(
-        flutter::Size(biggest->width() * scale_factor + non_client_width,
-                      biggest->height() * scale_factor + non_client_height));
-    width = std::min(width, max_physical_size.width());
-    height = std::min(height, max_physical_size.height());
-  }
-
-  return flutter::Size{width, height};
-}
-
 // Checks whether the window class of name |class_name| is registered for the
 // current application.
 bool IsClassRegistered(LPCWSTR class_name) {
@@ -260,27 +207,37 @@
     const WindowSizeRequest& preferred_size,
     const WindowConstraints& preferred_constraints,
     LPCWSTR title) {
-  DWORD window_style = WS_OVERLAPPEDWINDOW;
-  DWORD extended_window_style = 0;
-  auto const box_constraints = FromWindowConstraints(preferred_constraints);
+  return std::unique_ptr<HostWindow>(new HostWindowRegular(
+      window_manager, engine, preferred_size,
+      FromWindowConstraints(preferred_constraints), title));
+}
 
-  // TODO(knopp): What about windows sized to content?
-  FML_CHECK(preferred_size.has_preferred_view_size);
+std::unique_ptr<HostWindow> HostWindow::CreateDialogWindow(
+    WindowManager* window_manager,
+    FlutterWindowsEngine* engine,
+    const WindowSizeRequest& preferred_size,
+    const WindowConstraints& preferred_constraints,
+    LPCWSTR title,
+    HWND parent) {
+  return std::unique_ptr<HostWindow>(
+      new HostWindowDialog(window_manager, engine, preferred_size,
+                           FromWindowConstraints(preferred_constraints), title,
+                           parent ? parent : std::optional<HWND>()));
+}
 
-  // Calculate the screen space window rectangle for the new window.
-  // Default positioning values (CW_USEDEFAULT) are used
-  // if the window has no owner.
-  Rect const initial_window_rect = [&]() -> Rect {
-    std::optional<Size> const window_size = GetWindowSizeForClientSize(
-        *engine->windows_proc_table(),
-        Size(preferred_size.preferred_view_width,
-             preferred_size.preferred_view_height),
-        box_constraints.smallest(), box_constraints.biggest(), window_style,
-        extended_window_style, nullptr);
-    return {{CW_USEDEFAULT, CW_USEDEFAULT},
-            window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}};
-  }();
-
+HostWindow::HostWindow(WindowManager* window_manager,
+                       FlutterWindowsEngine* engine,
+                       WindowArchetype archetype,
+                       DWORD window_style,
+                       DWORD extended_window_style,
+                       const BoxConstraints& box_constraints,
+                       Rect const initial_window_rect,
+                       LPCWSTR title,
+                       std::optional<HWND> const& owner_window)
+    : window_manager_(window_manager),
+      engine_(engine),
+      archetype_(archetype),
+      box_constraints_(box_constraints) {
   // Set up the view.
   auto view_window = std::make_unique<FlutterWindow>(
       initial_window_rect.width(), initial_window_rect.height(),
@@ -288,12 +245,9 @@
 
   std::unique_ptr<FlutterWindowsView> view =
       engine->CreateView(std::move(view_window));
-  if (view == nullptr) {
-    FML_LOG(ERROR) << "Failed to create view";
-    return nullptr;
-  }
+  FML_CHECK(view != nullptr);
 
-  std::unique_ptr<FlutterWindowsViewController> view_controller =
+  view_controller_ =
       std::make_unique<FlutterWindowsViewController>(nullptr, std::move(view));
   FML_CHECK(engine->running());
   // The Windows embedder listens to accessibility updates using the
@@ -317,69 +271,46 @@
     window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
     window_class.lpszClassName = kWindowClassName;
 
-    if (!RegisterClassEx(&window_class)) {
-      FML_LOG(ERROR) << "Cannot register window class "
-                     << WCharBufferToString(kWindowClassName) << ": "
-                     << GetLastErrorAsString();
-      return nullptr;
-    }
+    FML_CHECK(RegisterClassEx(&window_class));
   }
 
   // Create the native window.
-  HWND hwnd = CreateWindowEx(
+  window_handle_ = CreateWindowEx(
       extended_window_style, kWindowClassName, title, window_style,
       initial_window_rect.left(), initial_window_rect.top(),
-      initial_window_rect.width(), initial_window_rect.height(), nullptr,
-      nullptr, GetModuleHandle(nullptr), engine->windows_proc_table().get());
-  if (!hwnd) {
-    FML_LOG(ERROR) << "Cannot create window: " << GetLastErrorAsString();
-    return nullptr;
-  }
+      initial_window_rect.width(), initial_window_rect.height(),
+      owner_window ? *owner_window : nullptr, nullptr, GetModuleHandle(nullptr),
+      engine->windows_proc_table().get());
+  FML_CHECK(window_handle_ != nullptr);
 
   // Adjust the window position so its origin aligns with the top-left corner
   // of the window frame, not the window rectangle (which includes the
   // drop-shadow). This adjustment must be done post-creation since the frame
   // rectangle is only available after the window has been created.
   RECT frame_rect;
-  DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame_rect,
-                        sizeof(frame_rect));
+  DwmGetWindowAttribute(window_handle_, DWMWA_EXTENDED_FRAME_BOUNDS,
+                        &frame_rect, sizeof(frame_rect));
   RECT window_rect;
-  GetWindowRect(hwnd, &window_rect);
+  GetWindowRect(window_handle_, &window_rect);
   LONG const left_dropshadow_width = frame_rect.left - window_rect.left;
   LONG const top_dropshadow_height = window_rect.top - frame_rect.top;
-  SetWindowPos(hwnd, nullptr, window_rect.left - left_dropshadow_width,
+  SetWindowPos(window_handle_, nullptr,
+               window_rect.left - left_dropshadow_width,
                window_rect.top - top_dropshadow_height, 0, 0,
                SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
 
-  UpdateTheme(hwnd);
+  UpdateTheme(window_handle_);
 
-  SetChildContent(view_controller->view()->GetWindowHandle(), hwnd);
+  SetChildContent(view_controller_->view()->GetWindowHandle(), window_handle_);
 
   // TODO(loicsharma): Hide the window until the first frame is rendered.
   // Single window apps use the engine's next frame callback to show the
   // window. This doesn't work for multi window apps as the engine cannot have
   // multiple next frame callbacks. If multiple windows are created, only the
   // last one will be shown.
-  ShowWindow(hwnd, SW_SHOWNORMAL);
-  return std::unique_ptr<HostWindow>(
-      new HostWindow(window_manager, engine, WindowArchetype::kRegular,
-                     std::move(view_controller), box_constraints, hwnd));
-}
-
-HostWindow::HostWindow(
-    WindowManager* window_manager,
-    FlutterWindowsEngine* engine,
-    WindowArchetype archetype,
-    std::unique_ptr<FlutterWindowsViewController> view_controller,
-    const BoxConstraints& box_constraints,
-    HWND hwnd)
-    : window_manager_(window_manager),
-      engine_(engine),
-      archetype_(archetype),
-      view_controller_(std::move(view_controller)),
-      window_handle_(hwnd),
-      box_constraints_(box_constraints) {
-  SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
+  ShowWindow(window_handle_, SW_SHOWNORMAL);
+  SetWindowLongPtr(window_handle_, GWLP_USERDATA,
+                   reinterpret_cast<LONG_PTR>(this));
 }
 
 HostWindow::~HostWindow() {
@@ -394,6 +325,17 @@
 }
 
 HostWindow* HostWindow::GetThisFromHandle(HWND hwnd) {
+  wchar_t class_name[256];
+  if (!GetClassName(hwnd, class_name, sizeof(class_name) / sizeof(wchar_t))) {
+    FML_LOG(ERROR) << "Failed to get class name for window handle " << hwnd
+                   << ": " << GetLastErrorAsString();
+    return nullptr;
+  }
+  // Ignore window handles that do not match the expected class name.
+  if (wcscmp(class_name, kWindowClassName) != 0) {
+    return nullptr;
+  }
+
   return reinterpret_cast<HostWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
 }
 
@@ -401,7 +343,7 @@
   return window_handle_;
 }
 
-void HostWindow::FocusViewOf(HostWindow* window) {
+void HostWindow::FocusRootViewOf(HostWindow* window) {
   auto child_content = window->view_controller_->view()->GetWindowHandle();
   if (window != nullptr && child_content != nullptr) {
     SetFocus(child_content);
@@ -436,6 +378,10 @@
   }
 
   switch (message) {
+    case WM_DESTROY:
+      is_being_destroyed_ = true;
+      break;
+
     case WM_DPICHANGED: {
       auto* const new_scaled_window_rect = reinterpret_cast<RECT*>(lparam);
       LONG const width =
@@ -494,13 +440,9 @@
     }
 
     case WM_ACTIVATE:
-      FocusViewOf(this);
+      FocusRootViewOf(this);
       return 0;
 
-    case WM_MOUSEACTIVATE:
-      FocusViewOf(this);
-      return MA_ACTIVATE;
-
     case WM_DWMCOLORIZATIONCOLORCHANGED:
       UpdateTheme(hwnd);
       return 0;
@@ -761,4 +703,140 @@
       .height = rect.bottom / dpr,
   };
 }
+
+std::optional<Size> HostWindow::GetWindowSizeForClientSize(
+    WindowsProcTable const& win32,
+    Size const& client_size,
+    std::optional<Size> smallest,
+    std::optional<Size> biggest,
+    DWORD window_style,
+    DWORD extended_window_style,
+    std::optional<HWND> const& owner_hwnd) {
+  UINT const dpi = GetDpiForHWND(owner_hwnd ? *owner_hwnd : nullptr);
+  double const scale_factor =
+      static_cast<double>(dpi) / USER_DEFAULT_SCREEN_DPI;
+  RECT rect = {
+      .right = static_cast<LONG>(client_size.width() * scale_factor),
+      .bottom = static_cast<LONG>(client_size.height() * scale_factor)};
+
+  if (!win32.AdjustWindowRectExForDpi(&rect, window_style, FALSE,
+                                      extended_window_style, dpi)) {
+    FML_LOG(ERROR) << "Failed to run AdjustWindowRectExForDpi: "
+                   << GetLastErrorAsString();
+    return std::nullopt;
+  }
+
+  double width = static_cast<double>(rect.right - rect.left);
+  double height = static_cast<double>(rect.bottom - rect.top);
+
+  // Apply size constraints.
+  double const non_client_width = width - (client_size.width() * scale_factor);
+  double const non_client_height =
+      height - (client_size.height() * scale_factor);
+  if (smallest) {
+    flutter::Size min_physical_size = ClampToVirtualScreen(
+        flutter::Size(smallest->width() * scale_factor + non_client_width,
+                      smallest->height() * scale_factor + non_client_height));
+    width = std::max(width, min_physical_size.width());
+    height = std::max(height, min_physical_size.height());
+  }
+  if (biggest) {
+    flutter::Size max_physical_size = ClampToVirtualScreen(
+        flutter::Size(biggest->width() * scale_factor + non_client_width,
+                      biggest->height() * scale_factor + non_client_height));
+    width = std::min(width, max_physical_size.width());
+    height = std::min(height, max_physical_size.height());
+  }
+
+  return flutter::Size{width, height};
+}
+
+void HostWindow::EnableRecursively(bool enable) {
+  EnableWindow(window_handle_, enable);
+
+  for (HostWindow* const owned : GetOwnedWindows()) {
+    owned->EnableRecursively(enable);
+  }
+}
+
+HostWindow* HostWindow::FindFirstEnabledDescendant() const {
+  if (IsWindowEnabled(window_handle_)) {
+    return const_cast<HostWindow*>(this);
+  }
+
+  for (HostWindow* const owned : GetOwnedWindows()) {
+    if (HostWindow* const result = owned->FindFirstEnabledDescendant()) {
+      return result;
+    }
+  }
+
+  return nullptr;
+}
+
+std::vector<HostWindow*> HostWindow::GetOwnedWindows() const {
+  std::vector<HostWindow*> owned_windows;
+  struct EnumData {
+    HWND owner_window_handle;
+    std::vector<HostWindow*>* owned_windows;
+  } data{window_handle_, &owned_windows};
+
+  EnumWindows(
+      [](HWND hwnd, LPARAM lparam) -> BOOL {
+        auto* const data = reinterpret_cast<EnumData*>(lparam);
+        if (GetWindow(hwnd, GW_OWNER) == data->owner_window_handle) {
+          HostWindow* const window = GetThisFromHandle(hwnd);
+          if (window && !window->is_being_destroyed_) {
+            data->owned_windows->push_back(window);
+          }
+        }
+        return TRUE;
+      },
+      reinterpret_cast<LPARAM>(&data));
+
+  return owned_windows;
+}
+
+HostWindow* HostWindow::GetOwnerWindow() const {
+  if (HWND const owner_window_handle = GetWindow(GetWindowHandle(), GW_OWNER)) {
+    return GetThisFromHandle(owner_window_handle);
+  }
+  return nullptr;
+};
+
+void HostWindow::DisableRecursively() {
+  // Disable the window itself.
+  EnableWindow(window_handle_, false);
+
+  for (HostWindow* const owned : GetOwnedWindows()) {
+    owned->DisableRecursively();
+  }
+}
+
+void HostWindow::UpdateModalStateLayer() {
+  auto children = GetOwnedWindows();
+  if (children.empty()) {
+    // Leaf window in the active path, enable it.
+    EnableWindow(window_handle_, true);
+  } else {
+    // Non-leaf window in the active path, disable it and process children.
+    EnableWindow(window_handle_, false);
+
+    // On same level of window hierarchy the most recently created window
+    // will remain enabled.
+    auto latest_child = *std::max_element(
+        children.begin(), children.end(), [](HostWindow* a, HostWindow* b) {
+          return a->view_controller_->view()->view_id() <
+                 b->view_controller_->view()->view_id();
+        });
+
+    for (HostWindow* const child : children) {
+      if (child == latest_child) {
+        child->UpdateModalStateLayer();
+      } else {
+        child->DisableRecursively();
+      }
+    }
+  }
+}
+
 }  // namespace flutter
diff --git a/engine/src/flutter/shell/platform/windows/host_window.h b/engine/src/flutter/shell/platform/windows/host_window.h
index 77e28aa..88639ae3 100644
--- a/engine/src/flutter/shell/platform/windows/host_window.h
+++ b/engine/src/flutter/shell/platform/windows/host_window.h
@@ -19,6 +19,7 @@
 namespace flutter {
 
 class WindowManager;
+class WindowsProcTable;
 class FlutterWindowsView;
 class FlutterWindowsViewController;
 
@@ -27,19 +28,42 @@
  public:
   virtual ~HostWindow();
 
-  // Creates a native Win32 window with a child view confined to its client
-  // area. |controller| is a pointer to the controller that manages the
+  // Creates a regular Win32 window with a child view confined to its client
+  // area. |window_manager| is a pointer to the window manager that manages the
   // |HostWindow|. |engine| is a pointer to the engine that manages
-  // the controller. On success, a valid window handle can be retrieved
+  // the window manager. |preferred_size| is the preferred size of the window.
+  // |preferred_constraints| are the constraints set on the window's size.
+  // |title| is the title of the window.
+  //
+  // On success, a valid window handle can be retrieved
   // via |HostWindow::GetWindowHandle|. |nullptr| will be returned
   // on failure.
   static std::unique_ptr<HostWindow> CreateRegularWindow(
-      WindowManager* controller,
+      WindowManager* window_manager,
       FlutterWindowsEngine* engine,
       const WindowSizeRequest& preferred_size,
       const WindowConstraints& preferred_constraints,
       LPCWSTR title);
 
+  // Creates a dialog Win32 window with a child view confined to its client
+  // area. |window_manager| is a pointer to the window manager that manages the
+  // |HostWindow|. |engine| is a pointer to the engine that manages
+  // the window manager. |preferred_size| is the preferred size of the window.
+  // |preferred_constraints| are the constraints set on the window's size.
+  // |title| is the title of the window. |parent| is the parent of this dialog,
+  // which can be `nullptr`.
+  //
+  // On success, a valid window handle can be retrieved
+  // via |HostWindow::GetWindowHandle|. `nullptr` will be returned
+  // on failure.
+  static std::unique_ptr<HostWindow> CreateDialogWindow(
+      WindowManager* window_manager,
+      FlutterWindowsEngine* engine,
+      const WindowSizeRequest& preferred_size,
+      const WindowConstraints& preferred_constraints,
+      LPCWSTR title,
+      HWND parent);
+
   // Returns the instance pointer for |hwnd| or nullptr if invalid.
   static HostWindow* GetThisFromHandle(HWND hwnd);
 
@@ -60,17 +84,25 @@
   // Set the fullscreen state. |display_id| indicates the display where
   // the window should be shown fullscreen; std::nullopt indicates
   // that no display was specified, so the current display may be used.
-  void SetFullscreen(bool fullscreen,
-                     std::optional<FlutterEngineDisplayId> display_id);
+  virtual void SetFullscreen(bool fullscreen,
+                             std::optional<FlutterEngineDisplayId> display_id);
 
   // Returns |true| if this window is fullscreen, otherwise |false|.
-  bool GetFullscreen() const;
+  virtual bool GetFullscreen() const;
 
   // Given a window identifier, returns the window content size of the
   // window.
   static ActualWindowSize GetWindowContentSize(HWND hwnd);
 
- private:
+  // Returns the owner window, or nullptr if none.
+  HostWindow* GetOwnerWindow() const;
+
+  // This method is called when a dialog is created or destroyed.
+  // It walks the path of child windows to make sure that the right
+  // windows are enabled or disabled.
+  void UpdateModalStateLayer();
+
+ protected:
   friend WindowManager;
 
   // Information saved before going into fullscreen mode, used to restore the
@@ -85,15 +117,63 @@
     MONITORINFO monitor_info;
   };
 
-  HostWindow(WindowManager* controller,
+  // Construct a host window.
+  //
+  // See:
+  // - https://learn.microsoft.com/windows/win32/winmsg/window-styles
+  // - https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles
+  HostWindow(WindowManager* window_manager,
              FlutterWindowsEngine* engine,
              WindowArchetype archetype,
-             std::unique_ptr<FlutterWindowsViewController> view_controller,
-             const BoxConstraints& constraints,
-             HWND hwnd);
+             DWORD window_style,
+             DWORD extended_window_style,
+             const BoxConstraints& box_constraints,
+             Rect const initial_window_rect,
+             LPCWSTR title,
+             std::optional<HWND> const& owner_window);
+
+  // Calculates the required window size, in physical coordinates, to
+  // accommodate the given |client_size|, in logical coordinates, constrained by
+  // optional |smallest| and |biggest|, for a window with the specified
+  // |window_style| and |extended_window_style|. If |owner_hwnd| is not null,
+  // the DPI of the display with the largest area of intersection with
+  // |owner_hwnd| is used for the calculation; otherwise, the primary display's
+  // DPI is used. The resulting size includes window borders, non-client areas,
+  // and drop shadows. On error, returns std::nullopt and logs an error message.
+  static std::optional<Size> GetWindowSizeForClientSize(
+      WindowsProcTable const& win32,
+      Size const& client_size,
+      std::optional<Size> smallest,
+      std::optional<Size> biggest,
+      DWORD window_style,
+      DWORD extended_window_style,
+      std::optional<HWND> const& owner_hwnd);
+
+  // Processes and routes salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT HandleMessage(HWND hwnd,
+                                UINT message,
+                                WPARAM wparam,
+                                LPARAM lparam);
 
   // Sets the focus to the child view window of |window|.
-  static void FocusViewOf(HostWindow* window);
+  static void FocusRootViewOf(HostWindow* window);
+
+  // Enables or disables mouse and keyboard input to this window and all its
+  // descendants.
+  void EnableRecursively(bool enable);
+
+  // Returns the first enabled descendant window. If the current window itself
+  // is enabled, returns the current window. If no window is enabled, returns
+  // `nullptr`.
+  HostWindow* FindFirstEnabledDescendant() const;
+
+  // Returns windows owned by this window.
+  std::vector<HostWindow*> GetOwnedWindows() const;
+
+  // Disables mouse and keyboard input to the window and all its descendants.
+  void DisableRecursively();
 
   // OS callback called by message pump. Handles the WM_NCCREATE message which
   // is passed when the non-client area is being created and enables automatic
@@ -101,11 +181,6 @@
   // responds to changes in DPI. Delegates other messages to the controller.
   static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
 
-  // Processes and routes salient window messages for mouse handling,
-  // size change and DPI. Delegates handling of these to member overloads that
-  // inheriting classes can handle.
-  LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
-
   // Controller for this window.
   WindowManager* const window_manager_ = nullptr;
 
@@ -121,11 +196,14 @@
   WindowArchetype archetype_ = WindowArchetype::kRegular;
 
   // Backing handle for this window.
-  HWND window_handle_ = nullptr;
+  HWND window_handle_;
 
   // The constraints on the window's client area.
   BoxConstraints box_constraints_;
 
+  // True while handling WM_DESTROY; used to detect in-progress destruction.
+  bool is_being_destroyed_ = false;
+
   // Whether or not the window is currently in a fullscreen state.
   bool is_fullscreen_ = false;
 
diff --git a/engine/src/flutter/shell/platform/windows/host_window_dialog.cc b/engine/src/flutter/shell/platform/windows/host_window_dialog.cc
new file mode 100644
index 0000000..de6518d
--- /dev/null
+++ b/engine/src/flutter/shell/platform/windows/host_window_dialog.cc
@@ -0,0 +1,142 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/windows/host_window_dialog.h"
+
+#include "flutter/shell/platform/windows/flutter_windows_engine.h"
+
+namespace {
+DWORD GetWindowStyleForDialog(std::optional<HWND> const& owner_window) {
+  DWORD window_style = WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME;
+  if (!owner_window) {
+    // If the dialog has no owner, add a minimize box and a system menu.
+    window_style |= WS_MINIMIZEBOX | WS_SYSMENU;
+  }
+
+  return window_style;
+}
+
+DWORD GetExtendedWindowStyleForDialog(std::optional<HWND> const& owner_window) {
+  DWORD extended_window_style = WS_EX_DLGMODALFRAME;
+  if (owner_window) {
+    // If the owner window has WS_EX_TOOLWINDOW style, apply the same
+    // style to the dialog.
+    if (GetWindowLongPtr(*owner_window, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) {
+      extended_window_style |= WS_EX_TOOLWINDOW;
+    }
+  }
+  return extended_window_style;
+}
+}  // namespace
+
+namespace flutter {
+
+HostWindowDialog::HostWindowDialog(WindowManager* window_manager,
+                                   FlutterWindowsEngine* engine,
+                                   const WindowSizeRequest& preferred_size,
+                                   const BoxConstraints& constraints,
+                                   LPCWSTR title,
+                                   std::optional<HWND> const& owner_window)
+    : HostWindow(
+          window_manager,
+          engine,
+          WindowArchetype::kDialog,
+          GetWindowStyleForDialog(owner_window),
+          GetExtendedWindowStyleForDialog(owner_window),
+          constraints,
+          GetInitialRect(engine, preferred_size, constraints, owner_window),
+          title,
+          owner_window) {
+  auto hwnd = window_handle_;
+  if (owner_window == nullptr) {
+    if (HMENU hMenu = GetSystemMenu(hwnd, FALSE)) {
+      EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
+    }
+  }
+
+  if (owner_window != nullptr) {
+    UpdateModalState();
+  }
+}
+
+Rect HostWindowDialog::GetInitialRect(FlutterWindowsEngine* engine,
+                                      const WindowSizeRequest& preferred_size,
+                                      const BoxConstraints& constraints,
+                                      std::optional<HWND> const& owner_window) {
+  auto const window_style = GetWindowStyleForDialog(owner_window);
+  auto const extended_window_style =
+      GetExtendedWindowStyleForDialog(owner_window);
+  std::optional<Size> const window_size =
+      HostWindow::GetWindowSizeForClientSize(
+          *engine->windows_proc_table(),
+          Size(preferred_size.preferred_view_width,
+               preferred_size.preferred_view_height),
+          constraints.smallest(), constraints.biggest(), window_style,
+          extended_window_style, owner_window);
+  Point window_origin = {CW_USEDEFAULT, CW_USEDEFAULT};
+  if (owner_window && window_size.has_value()) {
+    // Center dialog in the owner's frame.
+    RECT frame;
+    DwmGetWindowAttribute(*owner_window, DWMWA_EXTENDED_FRAME_BOUNDS, &frame,
+                          sizeof(frame));
+    window_origin = {(frame.left + frame.right - window_size->width()) * 0.5,
+                     (frame.top + frame.bottom - window_size->height()) * 0.5};
+  }
+
+  return {window_origin,
+          window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}};
+}
+
+LRESULT HostWindowDialog::HandleMessage(HWND hwnd,
+                                        UINT message,
+                                        WPARAM wparam,
+                                        LPARAM lparam) {
+  switch (message) {
+    case WM_DESTROY:
+      is_being_destroyed_ = true;
+      if (HostWindow* const owner_window = GetOwnerWindow()) {
+        UpdateModalState();
+        FocusRootViewOf(owner_window);
+      }
+      break;
+
+    case WM_ACTIVATE:
+      if (LOWORD(wparam) != WA_INACTIVE) {
+        // Prevent disabled window from being activated using the task
+        // switcher.
+        if (!IsWindowEnabled(hwnd)) {
+          // Redirect focus and activation to the first enabled descendant.
+          if (HostWindow* enabled_descendant = FindFirstEnabledDescendant()) {
+            SetActiveWindow(enabled_descendant->GetWindowHandle());
+            FocusRootViewOf(this);
+          }
+          return 0;
+        }
+        FocusRootViewOf(this);
+      }
+      return 0;
+  }
+
+  return HostWindow::HandleMessage(hwnd, message, wparam, lparam);
+}
+
+void HostWindowDialog::UpdateModalState() {
+  // Find the root window of the window hierarchy and process
+  // modal state update for the entire branch.
+  HostWindow* root = this;
+  while (HostWindow* const owner = root->GetOwnerWindow()) {
+    root = owner;
+  }
+  root->UpdateModalStateLayer();
+}
+
+void HostWindowDialog::SetFullscreen(
+    bool fullscreen,
+    std::optional<FlutterEngineDisplayId> display_id) {}
+
+bool HostWindowDialog::GetFullscreen() const {
+  return false;
+}
+
+}  // namespace flutter
diff --git a/engine/src/flutter/shell/platform/windows/host_window_dialog.h b/engine/src/flutter/shell/platform/windows/host_window_dialog.h
new file mode 100644
index 0000000..0be455b
--- /dev/null
+++ b/engine/src/flutter/shell/platform/windows/host_window_dialog.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_DIALOG_H_
+#define FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_DIALOG_H_
+
+#include "host_window.h"
+
+namespace flutter {
+class HostWindowDialog : public HostWindow {
+ public:
+  // Creates a dialog window.
+  //
+  // If |owner_window| is not null, the dialog will be modal to the owner.
+  // This also affects the dialog window's styling.
+  HostWindowDialog(WindowManager* window_manager,
+                   FlutterWindowsEngine* engine,
+                   const WindowSizeRequest& preferred_size,
+                   const BoxConstraints& constraints,
+                   LPCWSTR title,
+                   std::optional<HWND> const& owner_window);
+
+  void SetFullscreen(bool fullscreen,
+                     std::optional<FlutterEngineDisplayId> display_id) override;
+  bool GetFullscreen() const override;
+
+ protected:
+  LRESULT HandleMessage(HWND hwnd,
+                        UINT message,
+                        WPARAM wparam,
+                        LPARAM lparam) override;
+
+ private:
+  // Enforces modal behavior. This favors enabling most recently created
+  // modal window higest up in the window hierarchy.
+  void UpdateModalState();
+
+  static Rect GetInitialRect(FlutterWindowsEngine* engine,
+                             const WindowSizeRequest& preferred_size,
+                             const BoxConstraints& constraints,
+                             std::optional<HWND> const& owner_window);
+};
+}  // namespace flutter
+
+#endif  // FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_DIALOG_H_
diff --git a/engine/src/flutter/shell/platform/windows/host_window_regular.cc b/engine/src/flutter/shell/platform/windows/host_window_regular.cc
new file mode 100644
index 0000000..6f5458a
--- /dev/null
+++ b/engine/src/flutter/shell/platform/windows/host_window_regular.cc
@@ -0,0 +1,44 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/windows/host_window_regular.h"
+
+#include "flutter/shell/platform/windows/flutter_windows_engine.h"
+
+namespace flutter {
+HostWindowRegular::HostWindowRegular(WindowManager* window_manager,
+                                     FlutterWindowsEngine* engine,
+                                     const WindowSizeRequest& preferred_size,
+                                     const BoxConstraints& constraints,
+                                     LPCWSTR title)
+
+    : HostWindow(window_manager,
+                 engine,
+                 WindowArchetype::kRegular,
+                 WS_OVERLAPPEDWINDOW,
+                 0,
+                 constraints,
+                 GetInitialRect(engine, preferred_size, constraints),
+                 title,
+                 std::nullopt) {
+  // TODO(knopp): Investigate sizing the window to its content with the help of
+  // https://github.com/flutter/flutter/pull/173610.
+  FML_CHECK(preferred_size.has_preferred_view_size);
+}
+
+Rect HostWindowRegular::GetInitialRect(FlutterWindowsEngine* engine,
+                                       const WindowSizeRequest& preferred_size,
+                                       const BoxConstraints& constraints) {
+  std::optional<Size> const window_size =
+      HostWindow::GetWindowSizeForClientSize(
+          *engine->windows_proc_table(),
+          Size(preferred_size.preferred_view_width,
+               preferred_size.preferred_view_height),
+          constraints.smallest(), constraints.biggest(), WS_OVERLAPPEDWINDOW, 0,
+          nullptr);
+  return {{CW_USEDEFAULT, CW_USEDEFAULT},
+          window_size ? *window_size : Size{CW_USEDEFAULT, CW_USEDEFAULT}};
+}
+
+}  // namespace flutter
diff --git a/engine/src/flutter/shell/platform/windows/host_window_regular.h b/engine/src/flutter/shell/platform/windows/host_window_regular.h
new file mode 100644
index 0000000..4118283
--- /dev/null
+++ b/engine/src/flutter/shell/platform/windows/host_window_regular.h
@@ -0,0 +1,27 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_REGULAR_H_
+#define FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_REGULAR_H_
+
+#include "host_window.h"
+
+namespace flutter {
+class HostWindowRegular : public HostWindow {
+ public:
+  // Creates a regular window.
+  HostWindowRegular(WindowManager* window_manager,
+                    FlutterWindowsEngine* engine,
+                    const WindowSizeRequest& preferred_size,
+                    const BoxConstraints& constraints,
+                    LPCWSTR title);
+
+ private:
+  static Rect GetInitialRect(FlutterWindowsEngine* engine,
+                             const WindowSizeRequest& preferred_size,
+                             const BoxConstraints& constraints);
+};
+}  // namespace flutter
+
+#endif  // FLUTTER_SHELL_PLATFORM_WINDOWS_HOST_WINDOW_REGULAR_H_
diff --git a/engine/src/flutter/shell/platform/windows/window_manager.cc b/engine/src/flutter/shell/platform/windows/window_manager.cc
index 012e889..93fdccd 100644
--- a/engine/src/flutter/shell/platform/windows/window_manager.cc
+++ b/engine/src/flutter/shell/platform/windows/window_manager.cc
@@ -32,7 +32,7 @@
 }
 
 FlutterViewId WindowManager::CreateRegularWindow(
-    const WindowCreationRequest* request) {
+    const RegularWindowCreationRequest* request) {
   auto window = HostWindow::CreateRegularWindow(
       this, engine_, request->preferred_size, request->preferred_constraints,
       request->title);
@@ -45,6 +45,20 @@
   return view_id;
 }
 
+FlutterViewId WindowManager::CreateDialogWindow(
+    const DialogWindowCreationRequest* request) {
+  auto window = HostWindow::CreateDialogWindow(
+      this, engine_, request->preferred_size, request->preferred_constraints,
+      request->title, request->parent_or_null);
+  if (!window || !window->GetWindowHandle()) {
+    FML_LOG(ERROR) << "Failed to create host window";
+    return -1;
+  }
+  FlutterViewId const view_id = window->view_controller_->view()->view_id();
+  active_windows_[window->GetWindowHandle()] = std::move(window);
+  return view_id;
+}
+
 void WindowManager::OnEngineShutdown() {
   // Don't send any more messages to isolate.
   on_message_ = nullptr;
@@ -116,12 +130,21 @@
 
 FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow(
     int64_t engine_id,
-    const flutter::WindowCreationRequest* request) {
+    const flutter::RegularWindowCreationRequest* request) {
   flutter::FlutterWindowsEngine* engine =
       flutter::FlutterWindowsEngine::GetEngineForId(engine_id);
   return engine->window_manager()->CreateRegularWindow(request);
 }
 
+FLUTTER_EXPORT
+FlutterViewId InternalFlutterWindows_WindowManager_CreateDialogWindow(
+    int64_t engine_id,
+    const flutter::DialogWindowCreationRequest* request) {
+  flutter::FlutterWindowsEngine* engine =
+      flutter::FlutterWindowsEngine::GetEngineForId(engine_id);
+  return engine->window_manager()->CreateDialogWindow(request);
+}
+
 HWND InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(
     int64_t engine_id,
     FlutterViewId view_id) {
diff --git a/engine/src/flutter/shell/platform/windows/window_manager.h b/engine/src/flutter/shell/platform/windows/window_manager.h
index 9cad1d5..c37ba56 100644
--- a/engine/src/flutter/shell/platform/windows/window_manager.h
+++ b/engine/src/flutter/shell/platform/windows/window_manager.h
@@ -39,12 +39,19 @@
 };
 
 // Sent by the framework to request a new window be created.
-struct WindowCreationRequest {
+struct RegularWindowCreationRequest {
   WindowSizeRequest preferred_size;
   WindowConstraints preferred_constraints;
   LPCWSTR title;
 };
 
+struct DialogWindowCreationRequest {
+  WindowSizeRequest preferred_size;
+  WindowConstraints preferred_constraints;
+  LPCWSTR title;
+  HWND parent_or_null;
+};
+
 struct WindowsMessage {
   FlutterViewId view_id;
   HWND hwnd;
@@ -83,7 +90,10 @@
 
   bool HasTopLevelWindows() const;
 
-  FlutterViewId CreateRegularWindow(const WindowCreationRequest* request);
+  FlutterViewId CreateRegularWindow(
+      const RegularWindowCreationRequest* request);
+
+  FlutterViewId CreateDialogWindow(const DialogWindowCreationRequest* request);
 
   // Message handler called by |HostWindow::WndProc| to process window
   // messages before delegating them to the host window. This allows the
@@ -128,7 +138,12 @@
 FLUTTER_EXPORT
 FlutterViewId InternalFlutterWindows_WindowManager_CreateRegularWindow(
     int64_t engine_id,
-    const flutter::WindowCreationRequest* request);
+    const flutter::RegularWindowCreationRequest* request);
+
+FLUTTER_EXPORT
+FlutterViewId InternalFlutterWindows_WindowManager_CreateDialogWindow(
+    int64_t engine_id,
+    const flutter::DialogWindowCreationRequest* request);
 
 // Retrives the HWND associated with this |engine_id| and |view_id|. Returns
 // NULL if the HWND cannot be found
diff --git a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc
index cc0e0c9..72ecf3b 100644
--- a/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc
+++ b/engine/src/flutter/shell/platform/windows/window_manager_unittests.cc
@@ -44,12 +44,14 @@
 
   int64_t engine_id() { return reinterpret_cast<int64_t>(engine_.get()); }
   flutter::Isolate& isolate() { return *isolate_; }
-  WindowCreationRequest* creation_request() { return &creation_request_; }
+  RegularWindowCreationRequest* regular_creation_request() {
+    return &regular_creation_request_;
+  }
 
  private:
   std::unique_ptr<FlutterWindowsEngine> engine_;
   std::optional<flutter::Isolate> isolate_;
-  WindowCreationRequest creation_request_{
+  RegularWindowCreationRequest regular_creation_request_{
       .preferred_size =
           {
               .has_preferred_view_size = true,
@@ -73,7 +75,7 @@
   InternalFlutterWindows_WindowManager_Initialize(engine_id(), &init_request);
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   DestroyWindow(InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(
       engine_id(), view_id));
 
@@ -87,8 +89,8 @@
       InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id());
   EXPECT_FALSE(has_top_level_windows);
 
-  InternalFlutterWindows_WindowManager_CreateRegularWindow(engine_id(),
-                                                           creation_request());
+  InternalFlutterWindows_WindowManager_CreateRegularWindow(
+      engine_id(), regular_creation_request());
   has_top_level_windows =
       InternalFlutterWindows_WindowManager_HasTopLevelWindows(engine_id());
   EXPECT_TRUE(has_top_level_windows);
@@ -99,7 +101,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   EXPECT_EQ(view_id, 0);
 }
 
@@ -108,7 +110,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -120,7 +122,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -129,9 +131,9 @@
       InternalFlutterWindows_WindowManager_GetWindowContentSize(window_handle);
 
   EXPECT_EQ(size.width,
-            creation_request()->preferred_size.preferred_view_width);
+            regular_creation_request()->preferred_size.preferred_view_width);
   EXPECT_EQ(size.height,
-            creation_request()->preferred_size.preferred_view_height);
+            regular_creation_request()->preferred_size.preferred_view_height);
 }
 
 TEST_F(WindowManagerTest, SetWindowSize) {
@@ -139,7 +141,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -164,7 +166,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -187,7 +189,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -210,7 +212,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -233,7 +235,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -257,7 +259,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -288,7 +290,7 @@
 
   const int64_t view_id =
       InternalFlutterWindows_WindowManager_CreateRegularWindow(
-          engine_id(), creation_request());
+          engine_id(), regular_creation_request());
   const HWND window_handle =
       InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
                                                                    view_id);
@@ -313,5 +315,79 @@
   EXPECT_EQ(actual_size.height, 500);
 }
 
+TEST_F(WindowManagerTest, CreateModelessDialogWindow) {
+  IsolateScope isolate_scope(isolate());
+  DialogWindowCreationRequest creation_request{
+      .preferred_size = {.has_preferred_view_size = true,
+                         .preferred_view_width = 800,
+                         .preferred_view_height = 600},
+      .preferred_constraints = {.has_view_constraints = false},
+      .title = L"Hello World",
+      .parent_or_null = nullptr};
+  const int64_t view_id =
+      InternalFlutterWindows_WindowManager_CreateDialogWindow(
+          engine_id(), &creation_request);
+  EXPECT_EQ(view_id, 0);
+}
+
+TEST_F(WindowManagerTest, CreateModalDialogWindow) {
+  IsolateScope isolate_scope(isolate());
+
+  const int64_t parent_view_id =
+      InternalFlutterWindows_WindowManager_CreateRegularWindow(
+          engine_id(), regular_creation_request());
+  const HWND parent_window_handle =
+      InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(
+          engine_id(), parent_view_id);
+
+  DialogWindowCreationRequest creation_request{
+      .preferred_size =
+          {
+              .has_preferred_view_size = true,
+              .preferred_view_width = 800,
+              .preferred_view_height = 600,
+          },
+      .preferred_constraints = {.has_view_constraints = false},
+      .title = L"Hello World",
+      .parent_or_null = parent_window_handle};
+
+  const int64_t view_id =
+      InternalFlutterWindows_WindowManager_CreateDialogWindow(
+          engine_id(), &creation_request);
+  EXPECT_EQ(view_id, 1);
+
+  const HWND window_handle =
+      InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
+                                                                   view_id);
+  HostWindow* host_window = HostWindow::GetThisFromHandle(window_handle);
+  EXPECT_EQ(host_window->GetOwnerWindow()->GetWindowHandle(),
+            parent_window_handle);
+}
+
+TEST_F(WindowManagerTest, DialogCanNeverBeFullscreen) {
+  IsolateScope isolate_scope(isolate());
+
+  DialogWindowCreationRequest creation_request{
+      .preferred_size =
+          {
+              .has_preferred_view_size = true,
+              .preferred_view_width = 800,
+              .preferred_view_height = 600,
+          },
+  };
+
+  const int64_t view_id =
+      InternalFlutterWindows_WindowManager_CreateDialogWindow(
+          engine_id(), &creation_request);
+  const HWND window_handle =
+      InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle(engine_id(),
+                                                                   view_id);
+
+  FullscreenRequest request{.fullscreen = true, .has_display_id = false};
+  InternalFlutterWindows_WindowManager_SetFullscreen(window_handle, &request);
+  EXPECT_FALSE(
+      InternalFlutterWindows_WindowManager_GetFullscreen(window_handle));
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/examples/multiple_windows/lib/app/dialog_window_content.dart b/examples/multiple_windows/lib/app/dialog_window_content.dart
new file mode 100644
index 0000000..ae60166
--- /dev/null
+++ b/examples/multiple_windows/lib/app/dialog_window_content.dart
@@ -0,0 +1,83 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: invalid_use_of_internal_member
+// ignore_for_file: implementation_imports
+
+import 'package:flutter/material.dart';
+import 'models.dart';
+import 'window_content.dart';
+import 'package:flutter/src/widgets/_window.dart';
+
+class DialogWindowContent extends StatelessWidget {
+  const DialogWindowContent({super.key, required this.window});
+
+  final DialogWindowController window;
+
+  @override
+  Widget build(BuildContext context) {
+    final WindowManager windowManager = WindowManagerAccessor.of(context);
+
+    final child = FocusScope(
+      autofocus: true,
+      child: Scaffold(
+        appBar: AppBar(title: Text('Dialog')),
+        body: SingleChildScrollView(
+          child: Center(
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: [
+                ListenableBuilder(
+                  listenable: window,
+                  builder: (BuildContext context, Widget? _) {
+                    final dpr = MediaQuery.of(context).devicePixelRatio;
+                    final windowSize = WindowScope.contentSizeOf(context);
+                    return Text(
+                      'View ID: ${window.rootView.viewId}\n'
+                      'Parent View ID: ${window.parent?.rootView.viewId ?? "None"}\n'
+                      'Size: ${(windowSize.width).toStringAsFixed(1)}\u00D7${(windowSize.height).toStringAsFixed(1)}\n'
+                      'Device Pixel Ratio: $dpr',
+                      textAlign: TextAlign.center,
+                    );
+                  },
+                ),
+                const SizedBox(height: 16.0),
+                ElevatedButton(
+                  onPressed: () {
+                    window.destroy();
+                  },
+                  child: const Text('Close'),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    return ViewAnchor(
+      view: ListenableBuilder(
+        listenable: windowManager,
+        builder: (BuildContext context, Widget? child) {
+          final List<Widget> childViews = <Widget>[];
+          for (final KeyedWindow window in windowManager.windows) {
+            if (window.parent == window.controller) {
+              childViews.add(
+                WindowContent(
+                  controller: window.controller,
+                  windowKey: window.key,
+                  onDestroyed: () => windowManager.remove(window.key),
+                  onError: () => windowManager.remove(window.key),
+                ),
+              );
+            }
+          }
+
+          return ViewCollection(views: childViews);
+        },
+      ),
+      child: child,
+    );
+  }
+}
diff --git a/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart b/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart
new file mode 100644
index 0000000..52e1c25
--- /dev/null
+++ b/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart
@@ -0,0 +1,160 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: invalid_use_of_internal_member
+// ignore_for_file: implementation_imports
+
+import 'package:flutter/material.dart';
+import 'package:flutter/src/widgets/_window.dart';
+
+void showDialogWindowEditDialog({
+  required BuildContext context,
+  required DialogWindowController controller,
+}) {
+  showDialog(
+    context: context,
+    builder: (context) => _DialogWindowEditDialog(
+      controller: controller,
+      onClose: () => Navigator.pop(context),
+    ),
+  );
+}
+
+class _DialogWindowEditDialog extends StatefulWidget {
+  const _DialogWindowEditDialog({
+    required this.controller,
+    required this.onClose,
+  });
+
+  final DialogWindowController controller;
+  final VoidCallback onClose;
+
+  @override
+  State<StatefulWidget> createState() => _DialogWindowEditDialogState();
+}
+
+class _DialogWindowEditDialogState extends State<_DialogWindowEditDialog> {
+  late Size initialSize;
+  late String initialTitle;
+  late bool initialMinimized;
+
+  late final TextEditingController widthController;
+  late final TextEditingController heightController;
+  late final TextEditingController titleController;
+
+  bool? nextIsMinimized;
+
+  void _init() {
+    widget.controller.addListener(_onNotification);
+    initialSize = widget.controller.contentSize;
+    initialTitle = widget.controller.title;
+    initialMinimized = widget.controller.isMinimized;
+
+    widthController = TextEditingController(text: initialSize.width.toString());
+    heightController = TextEditingController(
+      text: initialSize.height.toString(),
+    );
+    titleController = TextEditingController(text: initialTitle);
+    nextIsMinimized = null;
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _init();
+  }
+
+  @override
+  void didUpdateWidget(covariant _DialogWindowEditDialog oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.controller != widget.controller) {
+      oldWidget.controller.removeListener(_onNotification);
+      _init();
+    }
+  }
+
+  void _onNotification() {
+    // We listen on the state of the controller. If a value that the user
+    // can edit changes from what it was initially set to, we invalidate
+    // their current change and store the new "initial" value.
+    if (widget.controller.contentSize != initialSize) {
+      initialSize = widget.controller.contentSize;
+      widthController.text = widget.controller.contentSize.width.toString();
+      heightController.text = widget.controller.contentSize.height.toString();
+    }
+    if (widget.controller.isMinimized != initialMinimized) {
+      setState(() {
+        initialMinimized = widget.controller.isMinimized;
+        nextIsMinimized = null;
+      });
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.controller.removeListener(_onNotification);
+    widthController.dispose();
+    heightController.dispose();
+    titleController.dispose();
+    super.dispose();
+  }
+
+  void _onSave() {
+    double? width = double.tryParse(widthController.text);
+    double? height = double.tryParse(heightController.text);
+    String? title = titleController.text.isEmpty ? null : titleController.text;
+    if (width != null &&
+        height != null &&
+        (width != initialSize.width || height != initialSize.height)) {
+      widget.controller.setSize(Size(width, height));
+    }
+    if (title != null && title != initialTitle) {
+      widget.controller.setTitle(title);
+    }
+    if (nextIsMinimized != null && nextIsMinimized != initialMinimized) {
+      widget.controller.setMinimized(nextIsMinimized!);
+    }
+
+    widget.onClose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return AlertDialog(
+      title: Text('Edit Window Properties'),
+      content: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          TextField(
+            controller: widthController,
+            keyboardType: TextInputType.number,
+            decoration: InputDecoration(labelText: 'Width'),
+          ),
+          TextField(
+            controller: heightController,
+            keyboardType: TextInputType.number,
+            decoration: InputDecoration(labelText: 'Height'),
+          ),
+          TextField(
+            controller: titleController,
+            decoration: InputDecoration(labelText: 'Title'),
+          ),
+          CheckboxListTile(
+            title: const Text('Minimized'),
+            value: nextIsMinimized ?? initialMinimized,
+            onChanged: (bool? value) {
+              if (value != null) {
+                setState(() => nextIsMinimized = value);
+              }
+            },
+          ),
+        ],
+      ),
+      actions: [
+        TextButton(onPressed: widget.onClose, child: Text('Cancel')),
+        TextButton(onPressed: _onSave, child: Text('Save')),
+      ],
+    );
+  }
+}
diff --git a/examples/multiple_windows/lib/app/main_window.dart b/examples/multiple_windows/lib/app/main_window.dart
index 9d42ab2..cbcd19f 100644
--- a/examples/multiple_windows/lib/app/main_window.dart
+++ b/examples/multiple_windows/lib/app/main_window.dart
@@ -12,6 +12,7 @@
 import 'window_settings_dialog.dart';
 import 'models.dart';
 import 'regular_window_edit_dialog.dart';
+import 'dialog_window_edit_dialog.dart';
 
 class MainWindow extends StatelessWidget {
   const MainWindow({super.key});
@@ -96,7 +97,10 @@
         context: context,
         controller: regular,
       ),
-      DialogWindowController() => throw UnimplementedError(),
+      final DialogWindowController dialog => showDialogWindowEditDialog(
+        context: context,
+        controller: dialog,
+      ),
     };
   }
 
@@ -173,6 +177,25 @@
                   child: const Text('Regular'),
                 ),
                 const SizedBox(height: 8),
+                OutlinedButton(
+                  onPressed: () async {
+                    final UniqueKey key = UniqueKey();
+                    windowManager.add(
+                      KeyedWindow(
+                        key: key,
+                        controller: DialogWindowController(
+                          delegate: CallbackDialogWindowControllerDelegate(
+                            onDestroyed: () => windowManager.remove(key),
+                          ),
+                          title: 'Modeless Dialog',
+                          preferredSize: windowSettings.dialogSize,
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('Modeless Dialog'),
+                ),
+                const SizedBox(height: 8),
                 Container(
                   alignment: Alignment.bottomRight,
                   child: TextButton(
diff --git a/examples/multiple_windows/lib/app/models.dart b/examples/multiple_windows/lib/app/models.dart
index 2465c21..5800338 100644
--- a/examples/multiple_windows/lib/app/models.dart
+++ b/examples/multiple_windows/lib/app/models.dart
@@ -63,10 +63,16 @@
 
 /// Settings that control the behavior of newly created windows.
 class WindowSettings {
-  WindowSettings({this.regularSize = const Size(400, 300)});
+  WindowSettings({
+    this.regularSize = const Size(400, 300),
+    this.dialogSize = const Size(200, 200),
+  });
 
   /// The initial size for newly created regular windows.
   Size regularSize;
+
+  /// The initial size of the dialog window.
+  Size dialogSize;
 }
 
 /// Provides access to the [WindowSettings] from the widget tree.
diff --git a/examples/multiple_windows/lib/app/regular_window_content.dart b/examples/multiple_windows/lib/app/regular_window_content.dart
index 150c1cc..0324087 100644
--- a/examples/multiple_windows/lib/app/regular_window_content.dart
+++ b/examples/multiple_windows/lib/app/regular_window_content.dart
@@ -69,6 +69,26 @@
                   child: const Text('Create Regular Window'),
                 ),
                 const SizedBox(height: 20),
+                ElevatedButton(
+                  onPressed: () {
+                    final UniqueKey key = UniqueKey();
+                    windowManager.add(
+                      KeyedWindow(
+                        key: key,
+                        controller: DialogWindowController(
+                          preferredSize: windowSettings.dialogSize,
+                          delegate: CallbackDialogWindowControllerDelegate(
+                            onDestroyed: () => windowManager.remove(key),
+                          ),
+                          parent: window,
+                          title: 'Dialog',
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('Create Modal Dialog'),
+                ),
+                const SizedBox(height: 20),
                 Text(
                   'View #${window.rootView.viewId}\n'
                   'Size: ${(windowSize.width).toStringAsFixed(1)}\u00D7${(windowSize.height).toStringAsFixed(1)}\n'
@@ -120,3 +140,16 @@
 
   final VoidCallback onDestroyed;
 }
+
+class CallbackDialogWindowControllerDelegate
+    with DialogWindowControllerDelegate {
+  CallbackDialogWindowControllerDelegate({required this.onDestroyed});
+
+  @override
+  void onWindowDestroyed() {
+    onDestroyed();
+    super.onWindowDestroyed();
+  }
+
+  final VoidCallback onDestroyed;
+}
diff --git a/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart b/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart
index 7a5a16f..63e0a9e 100644
--- a/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart
+++ b/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart
@@ -47,7 +47,7 @@
 
   bool? nextIsFullscreen;
   bool? nextIsMaximized;
-  bool? nextIsMinized;
+  bool? nextIsMinimized;
 
   void _init() {
     widget.controller.addListener(_onNotification);
@@ -64,7 +64,7 @@
     titleController = TextEditingController(text: initialTitle);
     nextIsFullscreen = null;
     nextIsMaximized = null;
-    nextIsMinized = null;
+    nextIsMinimized = null;
   }
 
   @override
@@ -106,7 +106,7 @@
     if (widget.controller.isMinimized != initialMinimized) {
       setState(() {
         initialMinimized = widget.controller.isMinimized;
-        nextIsMinized = null;
+        nextIsMinimized = null;
       });
     }
   }
@@ -114,6 +114,9 @@
   @override
   void dispose() {
     widget.controller.removeListener(_onNotification);
+    widthController.dispose();
+    heightController.dispose();
+    titleController.dispose();
     super.dispose();
   }
 
@@ -135,8 +138,8 @@
     if (nextIsMaximized != null && nextIsMaximized != initialMaximized) {
       widget.controller.setMaximized(nextIsMaximized!);
     }
-    if (nextIsMinized != null && nextIsMinized != initialMinimized) {
-      widget.controller.setMinimized(nextIsMinized!);
+    if (nextIsMinimized != null && nextIsMinimized != initialMinimized) {
+      widget.controller.setMinimized(nextIsMinimized!);
     }
 
     widget.onClose();
@@ -183,10 +186,10 @@
           ),
           CheckboxListTile(
             title: const Text('Minimized'),
-            value: nextIsMinized ?? initialMinimized,
+            value: nextIsMinimized ?? initialMinimized,
             onChanged: (bool? value) {
               if (value != null) {
-                setState(() => nextIsMinized = value);
+                setState(() => nextIsMinimized = value);
               }
             },
           ),
diff --git a/examples/multiple_windows/lib/app/window_content.dart b/examples/multiple_windows/lib/app/window_content.dart
index 880b552..cc30e03 100644
--- a/examples/multiple_windows/lib/app/window_content.dart
+++ b/examples/multiple_windows/lib/app/window_content.dart
@@ -6,6 +6,7 @@
 // ignore_for_file: implementation_imports
 
 import 'package:flutter/material.dart';
+import 'dialog_window_content.dart';
 import 'regular_window_content.dart';
 import 'package:flutter/src/widgets/_window.dart';
 
@@ -33,7 +34,11 @@
         controller: regular,
         child: MaterialApp(home: RegularWindowContent(window: regular)),
       ),
-      DialogWindowController() => throw UnimplementedError(),
+      final DialogWindowController dialog => DialogWindow(
+        key: windowKey,
+        controller: dialog,
+        child: MaterialApp(home: DialogWindowContent(window: dialog)),
+      ),
     };
   }
 }
diff --git a/examples/multiple_windows/lib/app/window_settings_dialog.dart b/examples/multiple_windows/lib/app/window_settings_dialog.dart
index 4e31dfc..f6774d8 100644
--- a/examples/multiple_windows/lib/app/window_settings_dialog.dart
+++ b/examples/multiple_windows/lib/app/window_settings_dialog.dart
@@ -35,6 +35,8 @@
   final TextEditingController _regularWidthController = TextEditingController();
   final TextEditingController _regularHeightController =
       TextEditingController();
+  final TextEditingController _dialogWidthController = TextEditingController();
+  final TextEditingController _dialogHeightController = TextEditingController();
 
   @override
   void initState() {
@@ -42,6 +44,8 @@
     _regularWidthController.text = widget.settings.regularSize.width.toString();
     _regularHeightController.text = widget.settings.regularSize.height
         .toString();
+    _dialogWidthController.text = widget.settings.dialogSize.width.toString();
+    _dialogHeightController.text = widget.settings.dialogSize.height.toString();
   }
 
   @override
@@ -73,6 +77,28 @@
             ],
           ),
         ),
+        ListTile(
+          title: const Text('Dialog'),
+          subtitle: Row(
+            children: [
+              Expanded(
+                child: TextFormField(
+                  controller: _dialogWidthController,
+                  decoration: const InputDecoration(labelText: 'Initial width'),
+                ),
+              ),
+              const SizedBox(width: 20),
+              Expanded(
+                child: TextFormField(
+                  controller: _dialogHeightController,
+                  decoration: const InputDecoration(
+                    labelText: 'Initial height',
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
         Padding(
           padding: const EdgeInsets.symmetric(horizontal: 16),
           child: TextButton(
@@ -83,6 +109,12 @@
                 double.tryParse(_regularHeightController.text) ??
                     widget.settings.regularSize.height,
               );
+              widget.settings.dialogSize = Size(
+                double.tryParse(_dialogWidthController.text) ??
+                    widget.settings.dialogSize.width,
+                double.tryParse(_dialogHeightController.text) ??
+                    widget.settings.dialogSize.height,
+              );
               widget.onClose();
             },
             child: const Text('Apply'),
diff --git a/packages/flutter/lib/src/widgets/_window_win32.dart b/packages/flutter/lib/src/widgets/_window_win32.dart
index 46647a6..505b1c2 100644
--- a/packages/flutter/lib/src/widgets/_window_win32.dart
+++ b/packages/flutter/lib/src/widgets/_window_win32.dart
@@ -30,6 +30,7 @@
 @internal
 typedef HWND = ffi.Pointer<ffi.Void>;
 
+const int _WM_DESTROY = 0x0002;
 const int _WM_SIZE = 0x0005;
 const int _WM_ACTIVATE = 0x0006;
 const int _WM_CLOSE = 0x0010;
@@ -157,7 +158,14 @@
     BaseWindowController? parent,
     String? title,
   }) {
-    throw UnimplementedError('Dialog windows are not yet implemented on Windows.');
+    return DialogWindowControllerWin32(
+      owner: this,
+      delegate: delegate,
+      preferredSize: preferredSize,
+      preferredConstraints: preferredConstraints,
+      title: title,
+      parent: parent,
+    );
   }
 
   /// Register a new [WindowsMessageHandler].
@@ -270,13 +278,17 @@
 
     _handler = _RegularWindowMesageHandler(controller: this);
     owner._addMessageHandler(_handler);
-    final int viewId = _Win32PlatformInterface.createWindow(
+    final int viewId = _Win32PlatformInterface.createRegularWindow(
       _owner.allocator,
       PlatformDispatcher.instance.engineId!,
       preferredSize,
       preferredConstraints,
       title,
     );
+    if (viewId < 0) {
+      throw Exception('Windows failed to create a regular window with a valid view id.');
+    }
+
     final FlutterView flutterView = PlatformDispatcher.instance.views.firstWhere(
       (FlutterView view) => view.viewId == viewId,
     );
@@ -418,8 +430,6 @@
     }
     _Win32PlatformInterface.destroyWindow(getWindowHandle());
     _destroyed = true;
-    _owner._removeMessageHandler(_handler);
-    _delegate.onWindowDestroyed();
   }
 
   int? _handleWindowsMessage(
@@ -436,6 +446,225 @@
     if (message == _WM_CLOSE) {
       _delegate.onWindowCloseRequested(this);
       return 0;
+    } else if (message == _WM_DESTROY) {
+      _destroyed = true;
+      _owner._removeMessageHandler(_handler);
+      _delegate.onWindowDestroyed();
+      return 0;
+    } else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
+      notifyListeners();
+    }
+    return null;
+  }
+}
+
+class _DialogWindowMesageHandler implements _WindowsMessageHandler {
+  _DialogWindowMesageHandler({required this.controller});
+
+  final DialogWindowControllerWin32 controller;
+
+  @override
+  int? handleWindowsMessage(
+    FlutterView view,
+    HWND windowHandle,
+    int message,
+    int wParam,
+    int lParam,
+  ) {
+    return controller._handleWindowsMessage(view, windowHandle, message, wParam, lParam);
+  }
+}
+
+/// Implementation of [DialogWindowController] for the Windows platform.
+///
+/// {@macro flutter.widgets.windowing.experimental}
+///
+/// See also:
+///
+///  * [DialogWindowController], the base class for dialog windows.
+class DialogWindowControllerWin32 extends DialogWindowController {
+  /// Creates a new dialog window controller for Win32.
+  ///
+  /// When this constructor completes the native window has been created and
+  /// has a view associated with it.
+  ///
+  /// {@macro flutter.widgets.windowing.experimental}
+  ///
+  /// See also:
+  ///
+  ///  * [DialogWindowController], the base class for dialog windows.
+  @internal
+  DialogWindowControllerWin32({
+    required WindowingOwnerWin32 owner,
+    required DialogWindowControllerDelegate delegate,
+    Size? preferredSize,
+    BoxConstraints? preferredConstraints,
+    String? title,
+    BaseWindowController? parent,
+  }) : _owner = owner,
+       _delegate = delegate,
+       _parent = parent,
+       super.empty() {
+    if (!isWindowingEnabled) {
+      throw UnsupportedError(_kWindowingDisabledErrorMessage);
+    }
+
+    _handler = _DialogWindowMesageHandler(controller: this);
+    owner._addMessageHandler(_handler);
+    final int viewId = _Win32PlatformInterface.createDialogWindow(
+      _owner.allocator,
+      PlatformDispatcher.instance.engineId!,
+      preferredSize,
+      preferredConstraints,
+      title,
+      parent != null
+          ? _Win32PlatformInterface.getWindowHandle(
+              PlatformDispatcher.instance.engineId!,
+              parent.rootView.viewId,
+            )
+          : null,
+    );
+    if (viewId < 0) {
+      throw Exception('Windows failed to create a dialog window with a valid view id.');
+    }
+
+    final FlutterView flutterView = PlatformDispatcher.instance.views.firstWhere(
+      (FlutterView view) => view.viewId == viewId,
+    );
+    rootView = flutterView;
+  }
+
+  final WindowingOwnerWin32 _owner;
+  final DialogWindowControllerDelegate _delegate;
+  final BaseWindowController? _parent;
+  late final _DialogWindowMesageHandler _handler;
+  bool _destroyed = false;
+
+  @override
+  @internal
+  Size get contentSize {
+    _ensureNotDestroyed();
+    final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
+    final Size result = Size(size.width, size.height);
+    return result;
+  }
+
+  @override
+  @internal
+  String get title {
+    _ensureNotDestroyed();
+    return _Win32PlatformInterface.getWindowTitle(_owner.allocator, getWindowHandle());
+  }
+
+  @override
+  @internal
+  bool get isActivated {
+    _ensureNotDestroyed();
+    return _Win32PlatformInterface.getForegroundWindow() == getWindowHandle();
+  }
+
+  @override
+  @internal
+  bool get isMinimized {
+    _ensureNotDestroyed();
+    return _Win32PlatformInterface.isIconic(getWindowHandle()) != 0;
+  }
+
+  @override
+  @internal
+  void setSize(Size? size) {
+    _ensureNotDestroyed();
+    _Win32PlatformInterface.setWindowContentSize(_owner.allocator, getWindowHandle(), size);
+    // Note that we do not notify the listener when setting the size,
+    // as that will happen when the WM_SIZE message is received in
+    // _handleWindowsMessage.
+  }
+
+  @override
+  @internal
+  void setConstraints(BoxConstraints constraints) {
+    _ensureNotDestroyed();
+    _Win32PlatformInterface.setWindowConstraints(_owner.allocator, getWindowHandle(), constraints);
+    notifyListeners();
+  }
+
+  @override
+  @internal
+  void setTitle(String title) {
+    _ensureNotDestroyed();
+    _Win32PlatformInterface.setWindowTitle(_owner.allocator, getWindowHandle(), title);
+    notifyListeners();
+  }
+
+  @override
+  @internal
+  void activate() {
+    _ensureNotDestroyed();
+    _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
+  }
+
+  @override
+  @internal
+  void setMinimized(bool minimized) {
+    if (parent != null) {
+      return;
+    }
+
+    _ensureNotDestroyed();
+    if (minimized) {
+      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MINIMIZE);
+    } else {
+      _Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
+    }
+  }
+
+  @override
+  @internal
+  BaseWindowController? get parent => _parent;
+
+  /// Returns HWND pointer to the top level window.
+  @internal
+  HWND getWindowHandle() {
+    _ensureNotDestroyed();
+    return _Win32PlatformInterface.getWindowHandle(
+      PlatformDispatcher.instance.engineId!,
+      rootView.viewId,
+    );
+  }
+
+  void _ensureNotDestroyed() {
+    if (_destroyed) {
+      throw StateError('Window has been destroyed.');
+    }
+  }
+
+  @override
+  void destroy() {
+    if (_destroyed) {
+      return;
+    }
+    _Win32PlatformInterface.destroyWindow(getWindowHandle());
+  }
+
+  int? _handleWindowsMessage(
+    FlutterView view,
+    HWND windowHandle,
+    int message,
+    int wParam,
+    int lParam,
+  ) {
+    if (view.viewId != rootView.viewId) {
+      return null;
+    }
+
+    if (message == _WM_CLOSE) {
+      _delegate.onWindowCloseRequested(this);
+      return 0;
+    } else if (message == _WM_DESTROY) {
+      _destroyed = true;
+      _owner._removeMessageHandler(_handler);
+      _delegate.onWindowDestroyed();
+      return 0;
     } else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
       notifyListeners();
     }
@@ -474,28 +703,61 @@
     ffi.Pointer<_WindowingInitRequest> request,
   );
 
-  static int createWindow(
+  static int createRegularWindow(
     ffi.Allocator allocator,
     int engineId,
     Size? preferredSize,
     BoxConstraints? preferredConstraints,
     String? title,
   ) {
-    final ffi.Pointer<_WindowCreationRequest> request = allocator<_WindowCreationRequest>();
+    final ffi.Pointer<_RegularWindowCreationRequest> request =
+        allocator<_RegularWindowCreationRequest>();
     try {
       request.ref.preferredSize.from(preferredSize);
       request.ref.preferredConstraints.from(preferredConstraints);
       request.ref.title = (title ?? 'Regular window').toNativeUtf16(allocator: allocator);
-      return _createWindow(engineId, request);
+      return _createRegularWindow(engineId, request);
     } finally {
       allocator.free(request);
     }
   }
 
-  @ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_WindowCreationRequest>)>(
+  @ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_RegularWindowCreationRequest>)>(
     symbol: 'InternalFlutterWindows_WindowManager_CreateRegularWindow',
   )
-  external static int _createWindow(int engineId, ffi.Pointer<_WindowCreationRequest> request);
+  external static int _createRegularWindow(
+    int engineId,
+    ffi.Pointer<_RegularWindowCreationRequest> request,
+  );
+
+  static int createDialogWindow(
+    ffi.Allocator allocator,
+    int engineId,
+    Size? preferredSize,
+    BoxConstraints? preferredConstraints,
+    String? title,
+    HWND? parent,
+  ) {
+    final ffi.Pointer<_DialogWindowCreationRequest> request =
+        allocator<_DialogWindowCreationRequest>();
+    try {
+      request.ref.preferredSize.from(preferredSize);
+      request.ref.preferredConstraints.from(preferredConstraints);
+      request.ref.title = (title ?? 'Dialog window').toNativeUtf16(allocator: allocator);
+      request.ref.parentOrNull = parent ?? ffi.Pointer<ffi.Void>.fromAddress(0);
+      return _createDialogWindow(engineId, request);
+    } finally {
+      allocator.free(request);
+    }
+  }
+
+  @ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_DialogWindowCreationRequest>)>(
+    symbol: 'InternalFlutterWindows_WindowManager_CreateDialogWindow',
+  )
+  external static int _createDialogWindow(
+    int engineId,
+    ffi.Pointer<_DialogWindowCreationRequest> request,
+  );
 
   @ffi.Native<HWND Function(ffi.Int64, ffi.Int64)>(
     symbol: 'InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle',
@@ -629,13 +891,21 @@
   external static HWND getForegroundWindow();
 }
 
-/// Payload for the creation method used by [_Win32PlatformInterface.createWindow].
-final class _WindowCreationRequest extends ffi.Struct {
+/// Payload for the creation method used by [_Win32PlatformInterface.createRegularWindow].
+final class _RegularWindowCreationRequest extends ffi.Struct {
   external _WindowSizeRequest preferredSize;
   external _WindowConstraintsRequest preferredConstraints;
   external ffi.Pointer<_Utf16> title;
 }
 
+/// Payload for the creation method used by [_Win32PlatformInterface.createDialogWindow].
+final class _DialogWindowCreationRequest extends ffi.Struct {
+  external _WindowSizeRequest preferredSize;
+  external _WindowConstraintsRequest preferredConstraints;
+  external ffi.Pointer<_Utf16> title;
+  external HWND parentOrNull;
+}
+
 /// Payload for the initialization request for the windowing subsystem used
 /// by the constructor for [WindowingOwnerWin32].
 final class _WindowingInitRequest extends ffi.Struct {
@@ -643,7 +913,7 @@
   onMessage;
 }
 
-/// Payload for the size of a window used by [_WindowCreationRequest] and
+/// Payload for the size of a window used by [_RegularWindowCreationRequest] and
 /// [_Win32PlatformInterface.setWindowContentSize].
 final class _WindowSizeRequest extends ffi.Struct {
   @ffi.Bool()
@@ -662,7 +932,7 @@
   }
 }
 
-/// Payload for the constraints of a window used by [_WindowCreationRequest] and
+/// Payload for the constraints of a window used by [_RegularWindowCreationRequest] and
 /// [_Win32PlatformInterface.setWindowConstraints].
 final class _WindowConstraintsRequest extends ffi.Struct {
   @ffi.Bool()