65.9K
CodeProject 正在变化。 阅读更多。
Home

构建 Silverlight 企业应用程序时的冒险 - 第 38 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2010年11月16日

CPOL

5分钟阅读

viewsIcon

9824

我们在构建 Silverlight 业务应用程序时遇到的大多数问题 - 它涉及到 UI 范式与异步通信模型的结合

在本文中,我想探讨在构建 Silverlight 业务线 (Line Of Business) 应用程序时,我们大多数人都会遇到的一个问题。它涉及到 UI 范式与异步通信模型的结合。

问题

我们都曾遇到过这种情况。你有一个需要用户填写的表单,其中包含几个字段和一个保存按钮。当用户点击保存按钮时,你会验证他们的输入,如果验证失败,你就会阻止保存操作。

一切都很顺利。用户可以根据需要更正输入并重试。然而,验证有一个缺点。它仅限于truefalse,中间没有其他选项。

让我们考虑一个需要用户确认的场景。你可能会通过使用一个标志来 hack 这个验证系统,如果用户第二次保存时没有更改值,那么它肯定是正确的。然而,我认为这不是一个很好的解决方案,并且会让用户感到困惑。

我需要一个对话框!

所以我们需要一种其他方式来向用户请求输入。通常的做法是向用户展示一个对话框,用户可以通过按一个按钮来选择是否继续。我们与它们打了多年的交道,它们使用起来很直观。

然而,在 Silverlight 中,它们会带来问题。当然,你可以使用框架中包含的对话框。然而,这会使用浏览器来显示对话框,而且外观不佳。更重要的是,它的功能非常有限。幸运的是,构建一个提供我们所需功能的自定义用户控件并不难。我们甚至可以通过在整个 UI 上放置一个 Canvas,然后将自定义用户控件放在 Popup (或者使用 childwindow) 中来实现模态效果。

然后问题就开始了。如果你要构建一个用户控件来显示对话框,就像我们所做的那样,你很可能通过回调来通知应用程序用户点击了对话框中的按钮,并且操作已完成。虽然这是一个经典的异步模型,但与之协作可能会变得困难。

拆分代码

让我们回到之前的场景。我们有一个包含一些字段的表单。有些字段需要验证,有些字段可能需要弹出一个对话框。在我们引入对话框之前,保存数据的步骤如下:

  1. 验证输入
  2. 如果所有字段都正确,则开始保存数据
  3. 保存完成后,刷新当前数据

现在,如果我们引入一个允许用户选择停止或继续的单个对话框,步骤将如下:

  1. 验证输入
  2. 如果所有字段都正确,则检查是否需要对话框
  3. 如果需要对话框,则显示对话框
  4. 一旦用户做出选择并希望继续,则开始保存数据。如果用户不想继续,则停止。
  5. 保存完成后,刷新当前数据

看看这些步骤,我们从一个用于保存完成的事件处理程序,变成了为对话框添加一个回调。所以,我们不再有两个包含保存代码的方法,而是有了三个方法。每增加一个对话框,我们就会增加一个回调,从而增加一个包含我们保存操作代码的方法。你可以看到这种情况很快就会变得难以管理。

所以让它变成同步的……

嗯,这可以解决我们的问题,但是…… Silverlight 只有一个 UI 线程的调度器。这意味着一旦你阻塞了 UI 线程,你的应用程序就会变得无响应(基本上,它会挂起直到你解除阻塞)。虽然显示一个对话框不是一件非常实际的事情,因为用户将无法点击按钮(这会破坏整个目的,对吧?)。

实际上,这意味着没有直接的方法可以提供同步的 UI 交互。它将是基于事件的,因此是异步的。不过,有一个解决办法。你不能阻塞 UI 线程,但你可以阻塞任何其他线程,所以如果我们把所有涉及保存的代码都移到一个后台线程,我们就可以消除对话框的回调。下面是一些示例代码,展示了调用这样的对话框会是什么样子:

   1: private void saveButton_Click(object sender, RoutedEventArgs e)
   2: {
   3:     // Make sure to create your dialog while still running on the UI thread
   4:     _dialog = new DialogWindow();
   5:     
   6:     // Pass off the save logic to another thread
   7:     ThreadPool.QueueUserWorkItem(SaveData, this);
   8: }
   9:  
  10: private void SaveData(object stateInfo)
  11: {
  12:     if (DialogWindow.ShowDialog(_dialog, stateInfo))
  13:     {
  14:         // Actually save the data
  15:     }
  16: }

所以基本上,你将保存操作中涉及的任何内容(除了验证,可能)放在一个后台线程上运行。我还传递了对调用 UI 控件的引用。原因很快就会清楚。

在后台线程上运行时,你可以调用我的 DialogWindow.ShowDialog 方法,它会阻塞你的线程。这意味着你可以立即使用它的结果。

在 UI 线程上运行代码

为了让这一切起作用,我们显然需要能够运行一些代码在 UI 线程上,然后阻塞我们的线程等待结果。下面是 ShowDialog 方法的代码:

   1: public static bool ShowDialog(DialogWindow window, object stateInfo)
   2: {
   3:     bool result = false;
   4:     DependencyObject dependencyObject = stateInfo as DependencyObject;
   5:  
   6:     // If we got passed a dependency object and we have access 
   7:     // to its dispatcher, then we are on the UI thread and we can't block
   8:     if (dependencyObject == null  dependencyObject.Dispatcher.CheckAccess())
   9:     {
  10:         window.Show();
  11:         return false;
  12:     }
  13:  
  14:     Action action = new Action(window.Show);
  15:     // Run the Show method on the UI thread
  16:     dependencyObject.Dispatcher.BeginInvoke(action);
  17:     // Block this thread until we have a result
  18:     while (!window.DialogResult.HasValue)
  19:     {
  20:         Thread.Sleep(50);
  21:     }
  22:     result = window.DialogResult.Value;
  23:  
  24:     return result;
  25: }

我们首先要做的是检查我们是否真的在 UI 线程上。如果我们是在 UI 线程上,我们就不想阻塞。请注意,CheckAccess 方法不知何故没有显示在 IntelliSense 中。尽管如此,它编译起来没有问题。

如果我们在 UI 线程上,我们就可以使用传递给我们的依赖对象的调度器,在 UI 线程上实际运行 Show 方法。然后,我们只需等待结果并将其传回。

结论

为了实现同步对话框,我们只是引入了一个线程来运行我们的代码,该线程在 UI 线程上运行一些代码,并等待其完成。这应该使我们更容易地在代码中组合更复杂的流程。

你可以在 此处 下载代码。

© . All rights reserved.