【问题标题】:A window that behaves both modally and non-modally模态和非模态行为的窗口
【发布时间】:2010-05-14 14:33:52
【问题描述】:

我想创建一个 WPF 窗口,该窗口的行为类似于模式对话框,同时便于在同一应用程序的某些其他窗口上进行选定操作。这种行为的一个例子可以在 Adob​​e Photoshop 中看到,它提供了几个对话框,允许用户使用吸管工具从图像中进行选择,同时几乎禁用所有其他应用程序功能。

我猜测前进的方向是创建一个非模态的、始终在顶部的对话,并以编程方式禁用那些不适用于对话的应用程序功能。有没有一种简单的方法可以在 WPF 中实现这一点?或者也许我可以采用一种设计模式。

【问题讨论】:

  • 我认为您走在正确的道路上。您可以在打开/关闭窗口时在代码隐藏中禁用/启用控件,也可以在 XAML 中使用 DataTriggers,根据新窗口是否打开为各种控件设置“IsEnabled”属性。

标签: wpf modal-dialog non-modal


【解决方案1】:

是的,您描述了以编程方式启用/禁用功能的传统方法,但 WPF 还开辟了一些在 WinForms 和旧技术中真正不可能实现的新可能性。

我将解释四种特定于 WPF 的方法:

  • 您可以使用带有 VisualBrush 的 Rectangle 秘密地自动将窗口的内容替换为其内容的 图片,从而有效地禁用它。对用户来说,它看起来好像窗口没有变化,但实际内容将在图片下方,因此您可以使用它进行命中测试,甚至将选定的事件转发给它。

  • 您可以将 MergedDictionary 添加到窗口的 ResourceDictionary 中,这会导致所有 TextBox 变为 TextBlock、所有 Button 变为禁用等,除非使用自定义附加属性显式覆盖。因此,您无需在所有 UI 中循环选择性地启用/禁用,只需从 MergedDictionaries 集合中添加或删除一个对象。

  • 您可以使用 InputManager 以编程方式生成和处理禁用窗口的特定部分中的真实鼠标事件,禁止任何未对“已批准”内容进行点击测试的鼠标事件。

    李>
  • 使用数据绑定和样式来启用/禁用单个控件,而不是遍历它们

用窗口图片替换窗口的细节

对于此解决方案,迭代您的应用程序窗口并将每个内容替换为包含原始内容和矩形的网格,如下所示:

<Window ...>
  <Grid>
    <ContentPresenter x:Name="OriginalContent" />
    <Rectangle>
      <Rectangle.Fill>
        <VisualBrush Visual="{Binding ElementName=OriginalContent}" />
      </Rectangle.Fill>
    </Rectangle>
  </Grid>
</Window>

这可以通过编程或使用 Window 上的模板来完成,但我更喜欢创建自定义控件并使用其模板创建上述结构。如果这样做了,您可以将您的窗口编码为:

<Window ...>
  <my:SelectiveDisabler>
    <Grid x:Name="LayoutRoot"> ... </Grid>  <!-- Original content -->
  </my:SelectiveDisabler>
</Window>

通过向 Rectangle 添加鼠标事件处理程序并在 ContentPresenter 上调用 VisualTreeHelper.HitTest 来确定在原始内容中单击了哪个对象。从这一点您可以选择忽略鼠标事件,将其转发到原始内容进行处理,或者在滴管控件或对象选择功能的情况下,只需提取所需的对象/信息。

MergedDictionary 方法的详细信息

显然,您可以使用合并到窗口资源中的 ResourceDictionary 来重新设置整个 UI 的样式。

一种天真的方法是在合并的 ResourceDictionary 中简单地创建隐式样式,以使所有 TextBox 显示为 TextBlock,所有 Button 显示为 Borders 等。这不能很好地工作,因为任何具有自己的样式或 ControlTemplate 的 TextBox显式设置可能会错过更新。此外,您可能无法获得所需的所有对象,并且无法轻松地从按钮中删除 Commands 或 Click 事件,因为它们是明确指定的,并且样式不会覆盖它。

解决此问题的更好方法是让合并的 ResourceDictionary 中的样式设置附加属性,然后使用 PropertyChangedCallback 中的代码隐藏来更新您真正想要更改的属性。您附加的“ModalMode”属性,如果设置为 true,将在对象的私有 DependencyProperty 中保存许多属性(模板、命令、单击、IsEnabled 等)的所有本地值和绑定,然后用标准值覆盖这些.例如,按钮的 Command 属性将暂时设置为 null。当附加的“ModalMode”属性为 false 时,所有原始本地值和绑定都会从临时存储中复制回来,并清除临时存储。

此方法提供了一种方便的方法来选择性地启用/禁用部分 UI,只需添加另一个附加属性“IgnoreModalMode”。您可以在不希望应用 ModalMode 更改的任何 UIElement 上手动将其设置为 True。你的 ModalMode PropertyChangedCallback 然后检查这个,如果是真的,它什么也不做。

InputManager 方法的详细信息

如果你捕捉到鼠标,无论它移动到哪里,你都可以获得鼠标坐标。使用 CompositionTarget.TransformToDevice() 将这些转换为屏幕坐标,然后在每个候选窗口上使用 CompositionTarget.TransformFromDevice()。如果鼠标坐标在范围内,则对禁用的窗口进行命中测试(即使窗口被禁用,仍然可以这样做),如果您喜欢用户单击的对象,请使用 InputManager.ProcesInput 来触发鼠标事件在另一个窗口中处理,就像它没有被禁用一样。

有关使用数据绑定的详细信息

您可以使用样式将按钮、菜单项等的 IsEnabled 属性绑定到静态值,如下所示:

<Setter Property="IsEnabled" Value="{Binding NonModal, Source={x:Static local:ModalModeTracker.Instance}}" />

现在默认情况下,当您的 NonModal 属性变为 false 时,所有具有这些样式的项目都会自动禁用。但是,任何单独的控件都可以使用IsEnabled="true" 覆盖,即使在您的模态模式下也可以保持启用状态。更复杂的绑定可以使用 MultiBinding 和 EDF ExpressionBinding 来设置您想要的任何规则。


这些方法都不需要遍历您的可视界面、启用和禁用功能。您实际选择哪一个取决于您在模态模式下实际想要提供什么功能,以及您的 UI 的其余部分是如何设计的。

无论如何,WPF 比在 WinForms 时代更容易做到这一点。你不只是喜欢 WPF 的强大功能吗?

【讨论】:

    【解决方案2】:

    您要查找的内容类似于Multiple Document Interface。默认情况下,这在 WPF 中不可用,但有一些努力支持这一点,freecommercial

    您可以自行确定应用程序的当前状态并启用/禁用 UI 元素以响应此情况。

    【讨论】:

    • 碰巧我正在构建一个 MDI,但 MDI 的内在性质不支持我描述的行为。我同意你的第二段,但有一个潜在的头痛:如果我想禁用一些 ICommand,那么我需要一种以编程方式执行此操作的方法,并且稍微考虑一下,这似乎需要做很多工作。
    【解决方案3】:

    我认为以编程方式禁用某些应用功能的始终处于顶部的窗口是实现此目的的方法。在此表单打开时保留可以启用的功能的“白名单”可能更容易,然后禁用不在列表中的所有内容(而不是尝试维护所有内容的“黑名单”无法启用)。

    【讨论】:

      【解决方案4】:

      我认为解决此问题的最佳方法是使用前面提到的 InputManager 方法。此设计模式允许您将命令连接到工具栏按钮/菜单项等,并且每个都将调用您为命令指定的 CanExecute 处理程序。在此处理程序中,如果您的始终处于顶部的非模态窗口已打开,您可以将命令设置为不启用。

      http://msdn.microsoft.com/en-us/library/ms752308.aspx

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多