模态和非模态行为的窗口 [英] A window that behaves both modally and non-modally

查看:79
本文介绍了模态和非模态行为的窗口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to create a WPF window that behaves as a modal dialogue box while at the same time facilitating selected operations on certain other windows of the same application. An example of this behaviour can be seen in Adobe Photoshop, which offers several dialogues that allow the user to use an eyedropper tool to make selections from an image while disabling virtually all other application features.

我猜测前进的道路是创建非模态的,始终处于最上层的对话框,并以编程方式禁用那些不适用于该对话框的应用程序功能.在WPF中有实现此目标的简便方法吗?也许有一种我可以采用的设计模式.

I'm guessing that the way forward is to create a non-modal, always-on-top dialogue and programmatically disable those application features that are not applicable to the dialogue. Is there an easy way to achieve this in WPF? Or perhaps there's a design pattern I could adopt.

推荐答案

是的,您描述的是传统方法,您可以在其中以编程方式启用/禁用功能,但是WPF还打开了WinForms和WinForms中实际上无法实现的几个新功能.较老的技术.

Yes, there is the traditional approach you describe where you programmatically enable/disable features, but WPF also opens up several new possiblities that were really not possible in WinForms and older technologies.

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

I will explain four WPF-specific ways to do this:

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

  • You can secretly and automatically replace a window's contents with a picture of its contents using a Rectangle with a VisualBrush, thereby effectively disabling it. To the user it will look as if the window is unchanged, but the actual contents will be there underneath the picture, so you can use it for hit-testing and even forward selected events to it.

您可以将MergedDictionary添加到窗口的ResourceDictionary中,使所有TextBox变为TextBlocks,所有Button变为禁用,等等,除非使用自定义附加属性明确覆盖.因此,您无需在所有UI中有选择地启用/禁用循环,只需在MergedDictionaries集合中添加或删除对象即可.

You can add a MergedDictionary to your window's ResourceDictionary that causes all TextBoxes to become TextBlocks, all Buttons to become disabled, etc, except as explicitly overridden using custom attached properties. So instead of looping through all your UI selectively enabling/disabling, you simply add or remove an object from the MergedDictionaries collection.

您可以使用InputManager在禁用窗口的特定部分中以编程方式生成和处理真实的鼠标事件,从而禁止任何未经批准"测试的鼠标事件.

You can use the InputManager to programmatically generate and process real mouse events in particular parts of a disabled window, disallowing any mouse events that don't hit-test to something "approved."

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

Use data binding and styles to enable/disable individual controls rather than iterating through them

用窗口图片替换窗口的详细信息

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

For this solution, iterate your app windows and replace each content with a Grid containing the original Content and a Rectangle, like this:

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

这可以通过编程或通过在Window上使用模板来完成,但是我的首选是创建一个自定义控件,并使用其模板创建上述结构.如果完成此操作,则可以将Windows编写为以下简单代码:

This can be done programmatically or by using a template on the Window, but my preference is to create a custom control and create the above structure using its template. If this is done, you can code your windows as simply this:

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

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

By adding mouse event handlers to the Rectangle and calling VisualTreeHelper.HitTest on the ContentPresenter to determine what object was clicked in the original content. From this point you can choose to ignore the mouse event, forward it to the original content for processing, or in the case of an eyedropper control or an object selection feature, simply extract the desired objects/information.

关于MergedDictionary方法的详细信息

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

Obviously you can restyle your whole UI using a ResourceDictionary merged into your window's resources.

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

A naiive way to do this is to simply create implicit styles in the merged ResourceDictionary to make all TextBoxes appear as TextBlocks, all Buttons appear as Borders, etc. This does not work very well because any TextBox with its own style or ControlTemplate explicitly set may miss the updates. In addition, you may not get all objects as desired, and there is no way to easily remove the Commands or Click events from buttons because they are explicitly specified and the style won't override that.

一种更好的解决方法是让合并的ResourceDictionary中的样式设置一个附加属性,然后在PropertyChangedCallback中使用代码隐藏来更新您真正想要更改的属性.您附加的"ModalMode"属性(如果设置为true)将在对象的私有DependencyProperty中保存所有局部值和许多属性(模板,命令,Click,IsEnabled等)的绑定,然后用标准值覆盖它们.例如,按钮的Command属性将被临时设置为null.当附加的"ModalMode"属性为false时,将从临时存储中复制回所有原始本地值和绑定,并清除临时存储.

A better way to work this is to have the styles in the merged ResourceDictionary set an attached property, then use code-behind in the PropertyChangedCallback to update the properties you really want to change. Your attached "ModalMode" property, if set to true, would save all the local values and bindings for a number of properties (Template, Command, Click, IsEnabled, etc) in a private DependencyProperty on the object, then overwrite these with standard values. For example a button's Command property would be set to null temporarily. When the attached "ModalMode" property goes false, all the original local values and bindings are copied back from the temporary storage and the temporary storage is cleared.

此方法提供了一种简便的方法,只需添加另一个附加属性"IgnoreModalMode",即可有选择地启用/禁用UI的某些部分.您可以在不希望对ModalMode进行更改的任何UIElement上手动将其设置为True.然后,您的ModalMode PropertyChangedCallback会对此进行检查,如果为true,则不执行任何操作.

This method provides a convenient way to selectively enable/disable portions of your UI by simply adding another attached property "IgnoreModalMode". You can manually set this to True on any UIElements that you don't want the ModalMode changes to apply to. Your ModalMode PropertyChangedCallback then checks this and if is true, it does nothing.

有关InputManager方法的详细信息

如果捕获了鼠标,则无论鼠标移动到哪里,都可以获取鼠标坐标.使用CompositionTarget.TransformToDevice()将其转换为屏幕坐标,然后在每个候选窗口上使用CompositionTarget.TransformFromDevice().如果鼠标坐标在范围内,请对禁用的窗口进行命中测试(即使禁用了窗口也可以执行此操作),并且如果您喜欢用户单击的对象,请使用InputManager.ProcesInput来使鼠标事件发生完全在另一个窗口中进行处理,就像未禁用该窗口一样.

If you capture the mouse, you can get mouse coordinates no matter where it is moved. Translate these to screen coordinates using CompositionTarget.TransformToDevice(), then use CompositionTarget.TransformFromDevice() on each candidate window. If the mouse coordinates are in bounds, hit-test the disabled window (this can still be done even when a window is disabled), and if you like the object the user clicked on, use InputManager.ProcesInput to cause the mouse event to be processed in the other window exactly as if it was not disabled.

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

您可以使用样式将Buttons,MenuItems等的IsEnabled属性绑定到这样的静态值:

You can use a styles to bind the IsEnabled property of Buttons, MenuItems, etc to a static value like this:

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

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

Now by default all items with these styles will automatically disable when your NonModal property goes false. However any individual control can override with IsEnabled="true" to stay enabled even in your modal mode. More complex bindings can be done with MultiBinding and EDF ExpressionBinding to set whatever rules you want.

这些方法都不要求在视觉界面上进行迭代,启用和禁用功能.您真正选择的是哪一个与您要在模式模式下实际提供的功能以及UI的其余部分的设计方式有关.

None of these approaches require iterating through your visual interface, enabling and disabling functionality. Which of these you actually select is a matter of what functionality you actually want to provide during modal mode, and how the rest of your UI is designed.

在任何情况下,WPF都比WinForms时代要容易得多.您不只是喜欢WPF的功能吗?

In any case, WPF makes this much easier than it was in WinForms days. Don't you just love WPF's power?

这篇关于模态和非模态行为的窗口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆