现在的位置: 首页 > 综合 > 正文

WPF:更好得理解对话框和ShowDialog方法

2012年05月07日 ⁄ 综合 ⁄ 共 3627字 ⁄ 字号 评论关闭

 

 

 

返回目录

1. 关于标准对话框

首先,一个标准的对话框应该严格具备至少如下特点:

  1. 只要背后父窗体显示,它一定会显示,并且覆盖在父窗体之上。
  2. 对话框的窗口标题不会显示在任务栏中的,任务栏仅会显示主窗体的名称。
  3. 对于模式对话框(Model Dialog),只有对话框关闭后,背后父窗体才会获得焦点。无模式对话框(Modeless Dialog)没有这样的限制。

 

那么,举个例子,比如记事本中的字体选择对话框就是一个模式对话框,它的行为举止完全符合上述特点。

image

 

 

除了上述3点最不可缺少的。其次就是一些细节问题,比如对话框该不该显示图标?

一般情况下多数对话框都是没有图标的,不过额外为对话框添加图标也可以的。

 

还有就是对话框的大小问题,对话框该不该允许用户调节大小?

我个人认为如果没有什么特殊要求的话,每个对话框都应该允许用户调节大小的。毕竟固定大小的对话框会在大分辨率环境下觉得太小,或者小分辨率环境下用户觉得太大。当然有些对话框在显示前会根据显示器环境参数来自动调节大小,不过如果可能的话,还是允许用户调整大小比较好也比较方便。

 

 

好了,通过上面了解了对话框的基本要求和特点,下面就开始讲对于WPF框架,怎样创建一个更好的对话框。

首先我们需要把Window的ShowInTaskbar属性设置成False,这样对话框不会在任务栏中显示。

 

其次是对话框的颜色问题,标准对话框的背景颜色应该和Windows窗体控件颜色保持一致,在WPF中,窗体的默认颜色是白色的,在Windows 7下多数亮度比较大的Aero主题下可能看得不明显,把主题调节成经典Windows主题,再看对话框背景就很明显了,比如,记事本中的对话框:

image

 

WPF中的SystemColors类型提供系统预定义颜色的封装,可以动过一个简单的DynamicResource标记扩展就可以显示成系统控件颜色。其中的ControlColor就是我们想要的颜色。由于WPF中背景颜色类型是Brush同时用DynamicResource绑定一个资源键,所以最后用SystemColors的ControlBrushKey属性。(这里用DynamicResource可以随时根据系统颜色变化而改变当前程序的颜色)。

 

代码如下:

Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"

 

 

接着关于对话框的大小,对于固定大小的对话框,设置Window类型的ResizeMode属性为NoResize,对话框的大小就固定了,如下:

image

 

对于可以调节大小的对话框,你可以直接使用默认样式,不过这样的话最大化和最小化按钮还在,这种对话框看起来比较不舒服也很少见,所以你可以把WindowStyle设置成ToolWindow就是工具窗口,这样就只有关闭按钮显示了(不过,工具窗口的关闭按钮要略小):

image

 

而对于大多数标准可调节大小的Windows对话框,比如打开文件对话框,它的右上角只显示标准的关闭按钮(比工具窗口的关闭按钮要大),同时右下角有一个调节大小指示区域来提示用户虽然我看起来像固定大小的对话框但是我是可以调节大小的:

image

 

对于右下角的缩放提示区域,可以设置Window类型的ResizeMode为CanResizeWithGrip。

 

上述要求,在WPF中可以实现的功能有如下代码:

ShowInTaskbar="False"

ResizeMode="CanResizeWithGrip"

Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"

 

不过不幸的是,当前的WPF不能直接隐藏最小化按钮和最大化按钮,同时也不支持窗体图标的隐藏。解决方案可以参考:WPF:窗体不显示或禁用最大化、最小化、关闭按钮、图标以及对话框显示

 

那么当上述功能都实现后,一个最标准的可调节大小的Window对话框就的结果应该是这样的(注意背景不是白色的):

image

 

在经典主题下是这样的:

image

 

上面都是讲标准Windows对话框。不过,我个人认为,既然对话框应该允许用户更改大小,那么为什么要把最大化按钮去掉呢?有了最大化按钮可以使对话框立即铺满屏幕而不需要用户再用鼠标慢慢放大。当然最小化按钮是不该有的,因为对话框不显示在任务栏里。

 

所以理想对话框应该是这样的(可能看起来有些不优美,但是最实用):

image

 

(文章后面下载部分会给出一个实例工程可以快速创建出上述各种对话框)

 

 

返回目录

2. 关于ShowDialog方法

OK,创建好对话框后,就是怎么显示的问题了。这里还是有一些问题的需要警惕的。

 

在Windows Forms框架中,显示对话框窗体的方法:Form.ShowDialog有一个可以指定窗体所有者Owner的重载:

public DialogResult ShowDialog(IWin32Window owner);

Form类型也继承自IWin32Window接口。所以通过这个方法我们可以很快得显示一个对话框并且设置它的Owner。

 

 

而WPF中的窗体:Window类型并没有提供这样的重载,它的ShowDialog方法只有这样一个原型:

public bool? ShowDialog();

 

于是开发者调用ShowDialog时往往会直接忘记设置对话框的Owner属性,这样的后果是什么呢?我们慢慢来演示。

 

比如我们已经创建好了一个对话框,然后在主窗体里需要按照模式对话框显示,但是没有设置Owner直接调用ShowDialog:

new MyDialog().ShowDialog();

 

结果:

image

 

OK,看起来不错,对话框显示在主窗体上面,同时对话框不关闭主窗体无法获得焦点。

 

不过当你在任务栏中点击主窗体的图标后,你会发现,这个对话框会显示在主窗体之后了:

image

而且此时主窗体仍无法获得焦点,只有关闭后面的对话框才可以。

 

那么问题是当你把主窗体最大化后,再点任务栏中的主窗体图标,对话框没了,现在的问题就是由于主窗体是最大化状态,你根本没把点到也不可能关闭背后的对话框!而主窗体无法获得焦点没法点,程序就这样明明没死但是因无法操作而完蛋了。(如果WindowStyle不是ToolWindow的对话框,此时可以通过切换其他窗口或再次点击程序任务栏图标对话框即可再次出现在主窗体之上,如果没法出现,程序只能通过任务管理器强制结束。)

 

因此不管上面的问题你怎么看,相信谁也不想因为这种小问题而惹恼用户,那么乖乖地设置Owner吧,像这样:

var dlg = new MyDialog();

dlg.Owner = this;

dlg.ShowDialog();

 

这样不仅不会由上面的问题,当模式对话框没有关闭但用户尝试点击主窗体时,对话框还会不停得闪动来提示用户先完成对话框的工作。

 

当然,对于上面讲的WPF的ShowDialog没有提供快速设置Owner属性的重载,我们可以写一个扩展方法。一个可以设置Owner,另一个会自动把当前应用程序的主窗体(通过Application.Current.MainWindow)作为对话框的Owner并显示对话框。

static class WindowExtension

{

    public static void ShowDialog(this Window win, Window owner)

    {

        win.Owner = owner;

        win.ShowDialog();

    }

 

    public static void ShowDialogEx(this Window win)

    {

        win.ShowDialog(Application.Current.MainWindow);

    }

}

 

这样的话,使用起来就更方便了:

new MyDialog().ShowDialogEx();

//或者

new MyDialog().ShowDialog(this);

 

 

 

返回目录

3. 演示工程下载

上面介绍了很多内容,但是内容比较分散,读者实现起来可能比较麻烦。

因为许多操作WPF没有内置实现(但是Windows Forms有实现...),比如最大化按钮和最小化按钮的禁用,以及窗体图标的隐藏。所以如果读者想亲自实现的话,可能需要和Win32 API的GetWindowLongPtr和SetWindowLongPtr打交道,或许可以参考下这篇文章:.NET(C#) 平台调用:不依赖平台的GetWindowLongPtr和SetWindowLongPtr API

 

我自己也写了个简单的Win32窗体API封装工程,实现了上述所有功能,演示工程源代码可以参考:

WPF:窗体不显示或禁用最大化、最小化、关闭按钮、图标以及对话框显示

 

 

 

 

 

:D

抱歉!评论已关闭.