Silverlight 之模态对话框的通用模拟

Silverlight 给我们带来了如同 Winform 一样便捷的开发方式,然而在桌面应用程序中一个非常重要的元素——模态对话框,却不具备。
ScottGu 的 Silverlight 教程中提供了一种简单的方法,可以模拟出类似模态对话框的显示。
其原理跟我们在 HTML 中实现模拟的模态对话框的方法类似,无非是动态创建一个半透明的层盖住整个页面背景,让背后的元素无法操作,然后在其上显示对话框内容即可。不过,在这篇教程中创建的子窗体必须在调用者页面的 XAML 中手工声明,假如我们需要根据情况调出 n 个不同的对话框呢?这样做显然不够灵活。本文的目标是基于这种场景提供一个简单的封装,以便更方便的实现主/从调用的场景。

例子的运行效果如下:

modaldialog1

点击第一个按钮后,显示如下:

modaldialog2

 

下面看看如何来实现这个效果。


首先,我们来定义一个模态对话框的基类,它是一个用户控件(UserControl):

using System;
using System.Windows.Controls;

namespace ModalPopupDemo
{
   
public class ModalDialog: UserControl
    {
       
public event EventHandler Closed;

       
public void Close()
        {
           
if (Closed != null)
            {
                Closed(
this, EventArgs.Empty);
            }
        }
    }

   
public interface IModalDialogOpener
    {
       
void ShowModalDialog(ModalDialog dialog);

       
void CloseModalDialog();
    }
}


因为我们想在模态框关闭时,从父窗口中得到通知,所以给它定义了一个 Closed 事件。并且提供了该事件的触发函数 Close().

同时,我们还为模态框的父窗体(opener) 定义了一个接口 IModalDialogOpener.
其中顾名思义定义了打开和关闭模态框的两个方法。

好了,现在我们创建一个模态对话框试试。假设是一个登录窗体,就叫它 Login 吧——在项目中添加一个用户控件 Login.

在 Login.xaml 文件里添加了一些 Markup 后,我们修改后端代码文件 Login.xaml.cs 如下:

using System.Windows;

namespace ModalPopupDemo
{
   
public partial class Login : ModalDialog
    {
       
public Login()
        {
            InitializeComponent();
        }

       
private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}


这里注意基类改成了 ModalDialog,就像在 ASP.NET 页面基类里所做的那样。并且我们在按钮的点击事件处理函数里调用了刚才定义的 Close 方法来关闭窗体。

这时 Login.xaml 是这样的:

<local:ModalDialog x:Class="ModalPopupDemo.Login"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local
="clr-namespace:ModalPopupDemo">
   
<Grid x:Name="LayoutRoot">
       
<Border CornerRadius="20" Background="#eeeeee" Width="300" Height="150">
           
<StackPanel Orientation="Vertical" Margin="10">
               
<StackPanel Orientation="Horizontal" Margin="10">
                   
<TextBlock Width="100">UserName:</TextBlock>
                   
<TextBox x:Name="txtUserName" Width="150" HorizontalAlignment="Right" />
               
</StackPanel>
               
<StackPanel Orientation="Horizontal" Margin="10">
                   
<TextBlock Width="100">Password:</TextBlock>
                   
<TextBox x:Name="txtPassword" Width="150" />
               
</StackPanel>
               
<StackPanel Margin="10">
                   
<Button x:Name="btnLogin" Content="Login" Width="50"
                        HorizontalAlignment
="Right" Click="btnLogin_Click" />
               
</StackPanel>
           
</StackPanel>
       
</Border>
   
</Grid>
</local:ModalDialog>


需要注意的是这个 XAML 文档的根元素从默认的 UserControl 改成了 local:ModalDialog. 而这正是后台 partial class 的基类,Silverlight 中规定这两者必须一致的。这里 local 是一个名称空间,可以看到稍后的 xmlns:local="clr-namespace:ModalPopupDemo" 这段代码将它声明为了当前程序集的 ModalPopupDemo 名称空间。

实现了子窗体,我们来看看如何实现父窗体。

我们的父窗体 Page.xaml 是这样的:

<UserControl x:Class="ModalPopupDemo.Page"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
   
<Grid x:Name="LayoutRoot" Background="White">
       
<StackPanel Orientation="Vertical">
           
<TextBlock FontSize="30" HorizontalAlignment="Center">This is the main page.</TextBlock>
           
<Button x:Name="btnOpen1" Content="Open Login form" Click="btnOpen_Click" Width="150" />
           
<Button x:Name="btnOpen2" Content="Open another form" Click="btnOpen2_Click" Width="150" Margin="0,10" />
       
</StackPanel>
       
<!-- 占位元素,用于在其中加载模态窗口 -->
       
<Border x:Name="placeHolder" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                Visibility
="Collapsed"
                Background
="#ff8a8a8a" Opacity="0.765" />
   
</Grid>
</UserControl>


这里照搬了 ScottGu 文章中的那个技巧,不过我改成了用一个 Border 元素来占位,同时将它的大小设置为全屏、半透明、不可见状态。这样它就既可以作为子窗体的容器,又可以在弹出模态对话框时遮住背景,一举两得。

后端代码 Page.xaml.cs:

using System.Windows;
using System.Windows.Controls;

namespace ModalPopupDemo
{
   
public partial class Page : UserControl, IModalDialogOpener
    {
       
public Page()
        {
            InitializeComponent();
        }

       
private void btnOpen_Click(object sender, RoutedEventArgs e)
        {
            var login
= new Login();
            login.Closed
+= (sender2, args2) => CloseModalDialog();
            ShowModalDialog(login);
        }

       
private void btnOpen2_Click(object sender, RoutedEventArgs e)
        {
            var form2
= new Form2();
            form2.Closed
+= (sender2, args2) => CloseModalDialog();
            ShowModalDialog(form2);
        }

       
#region IModalDialogOpener implementations
       
public void ShowModalDialog(ModalDialog dialog)
        {
            placeHolder.Child
= dialog;
            placeHolder.Visibility
= Visibility.Visible;
        }

       
public void CloseModalDialog()
        {
            placeHolder.Child
= null;
            placeHolder.Visibility
= Visibility.Collapsed;
        }
       
#endregion
    }
}


这里 ShowModalDialog 和 CloseModalDialog 的实现相当简单,分别就是附加/移除相关的子窗体控件实例,并且显示/隐藏占位控件。

页面中除了 Login 窗体外,还调用了另一个简单的用户控件 Form2,调用方式是非常类似的。Form2 的代码如下:
Form2.xaml:

<local:ModalDialog x:Class="ModalPopupDemo.Form2"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local
="clr-namespace:ModalPopupDemo">
   
<Grid>
       
<Button x:Name="btnClose" Width="300" Height="200" Content="Close Me!"
                Click
="btnClose_Click" FontSize="40" />
   
</Grid>
</local:ModalDialog>


Form2.xaml.cs:

using System.Windows;

namespace ModalPopupDemo
{
   
public partial class Form2 : ModalDialog
    {
       
public Form2()
        {
            InitializeComponent();
        }

       
private void btnClose_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}

弹出 Form2 的运行效果如下:


modaldialog3

 

至此,我们就实现了一个还算灵活的模态对话框弹出机制。这个好处在于,当我们要创建新的模态对话框时,在它的 Xaml 中只需关注其自身的显示逻辑即可,而不用关心如何显示到父窗体中,如何设置屏蔽层等细节问题,做到了一定程度的解耦。

 

注:本文提供了一个简单的实现方式,如果采用创建自定义控件等方法,也许能做到更好的封装,这个留待后续研究和探讨。

posted @ 2008-07-05 01:29 木野狐(Neil Chen) 阅读(1674) 评论(13)  编辑 收藏 所属分类: Silverlight

  回复  引用    
#1楼 2008-07-05 03:06 | kes.king [未注册用户]
兄弟我顶你,虽然简单。
但图文并茂,比前边那个老温实在多了,看他的文章实在太恶了,我受不了。
  回复  引用  查看    
#2楼 2008-07-05 06:11 | Angel Lucifer      
尽管对 SL 兴趣缺缺,还是要顶下狐兄的帖子嘀。

好文章就应该这样图文并茂,:-)

想想自己的拙文,惭愧...
  回复  引用  查看    
#3楼 2008-07-05 07:44 | 真见      
写得真好。但是我想鄙视你的昵称,又是“木”的又是“野”的,我看这别扭,分明就是日本的昵称嘛,呵呵。
  回复  引用    
#4楼 2008-07-05 08:31 | allenpan [未注册用户]
2008-07-05 07:44 by 真见
写得真好。但是我想鄙视你的昵称,又是“木”的又是“野”的,我看这别扭,分明就是日本的昵称嘛,呵呵。
----------------------------------
木野狐是指围棋,无论围棋还是这个术语,都是正宗中国的.
把中华文化推给别人, 可不太好哇.
  回复  引用  查看    
#5楼 [楼主]2008-07-05 09:57 | 木野狐(Neil Chen)      
@kes.king
@Angel Lucifer
@真见
@allenpan

谢谢各位仁兄关注,@allenpan 说的很对,这个昵称和日本没关系的,我不喜欢日本,呵呵。我是棋迷。


  回复  引用  查看    
#6楼 2008-07-05 22:49 | dada7357      
刚用google查了大哥的大名,木野狐指的是围棋:
  孙中山先生上海故居的文章,文中说道中山先生的居室里除了书籍地图之外,还放着一副围棋,这是他工作读书之暇唯一的娱乐。我们想象这位革命伟人在规划国家大事之余,灯下与一二知交丁丁敲棋,执子凝思,真是一幅感人极深的图画。
  围棋是比象棋复杂得多的智力游戏。象棋三十二子愈下愈少,围棋三百六十一格却是愈下愈多,到中盘时头绪纷繁。牵一发而动全身,四面八方,几百只棋子每一只都有关联,复杂之极,也真是有趣之极。在我所认识的人中,凡是学会围棋而下了一两年之后,几乎没有一个不是废寝忘食地喜爱。古人称它为“木野狐”,因为棋盘木制,它就像是一只狐狸精那么缠人。我在《碧血剑》那部武侠小说中写木桑道人沉迷着棋,千方百计地找寻弈友,在生活中确是有这种人的。


  回复  引用  查看    
#7楼 2008-07-05 22:51 | dada7357      
原来大哥做过scottgu的Silverlight 文章的翻译,可真是辛苦啦!
  回复  引用  查看    
#8楼 [楼主]2008-07-05 23:32 | 木野狐(Neil Chen)      
@dada7357

谢谢您的关注,更增加了我继续发文的动力!

翻译文章的同时也是一种学习,不会觉得辛苦的。
  回复  引用  查看    
#9楼 2008-07-07 10:44 | 小庄      
恩!这个好!好像不能拖动?
  回复  引用  查看    
#10楼 [楼主]2008-07-07 10:50 | 木野狐(Neil Chen)      
@小庄

拖动需要标题栏,而我还没想到好办法对子窗体的 UI 做封装。。。

  回复  引用    
#11楼 2008-07-12 07:22 | 曝光机 [未注册用户]
无模态对话框一个问题。*
http://www.szghjx.net
  回复  引用    
#12楼 2008-08-05 10:12 | htw [未注册用户]
我想问一下
login.Closed += (sender2, args2) => CloseModalDialog();
=>是什么意思?
  回复  引用  查看    
#13楼 [楼主]2008-08-05 10:42 | 木野狐(Neil Chen)      
@htw



你好:



(sener, args) => {



}



这是一个 lambda 表达式的语法,代表一个匿名函数。

因为 { } 里面只有一句话,所以括号就省略了。



这里就是给 login.Closed 注册事件处理函数。

和传统的



login.Closed += new EventHandler(aaa);

..

}



public void aaa (object sender, EventArgs args)

{



}

一样的。





标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-07-05 02:08 编辑过


相关链接: