6

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

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

4

4 回答 4

2

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

我将解释四种特定于 WPF 的方法来执行此操作:

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

  • 您可以将 MergedDictionary 添加到窗口的 ResourceDictionary 中,这会导致所有文本框变为文本块、所有按钮变为禁用等,除非使用自定义附加属性显式覆盖。因此,您无需在所有 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 添加鼠标事件处理程序并调用VisualTreeHelper.HitTestContentPresenter 来确定在原始内容中单击了哪个对象。从这一点开始,您可以选择忽略鼠标事件,将其转发到原始内容进行处理,或者在滴管控件或对象选择功能的情况下,只需提取所需的对象/信息。

MergedDictionary 方法的详细信息

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

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

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

此方法提供了一种方便的方法,通过简单地添加另一个附加属性“IgnoreModalMode”来选择性地启用/禁用 UI 的某些部分。您可以在不希望应用 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 的强大功能吗?

于 2010-05-14T17:46:39.643 回答
0

您正在寻找的是类似于Multiple Document Interface。默认情况下,这在 WPF 中不可用,但有一些努力支持这一点,包括免费的和商业的。

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

于 2010-05-14T14:51:34.740 回答
0

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

于 2010-05-14T14:56:02.897 回答
0

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

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

于 2010-05-17T11:49:23.743 回答