Silverlight中的异步UI交互

在开发Silverlight应用程序时,经常会遇到需要用户输入的场景。例如,一个表单需要用户填写,包含几个字段和一个保存按钮。用户点击保存按钮时,会验证输入,如果验证失败,则阻止保存操作。这种验证机制虽然有效,但它的反馈只有“通过”或“不通过”,没有中间状态。

设想一个场景,需要用户的确认。虽然可以通过在验证系统中使用一个标志来实现,如果用户没有改变值并且第二次保存,那么它就是正确的。但这不是一个很好的解决方案,它可能会让用户感到困惑。

对话框的需求

因此,希望有其他方式来请求用户输入。通常的做法是向用户展示一个对话框,用户可以通过点击按钮来选择是否继续。已经使用它们很多年了,它们直观易用。

然而,在Silverlight中,对话框存在问题。当然,可以使用框架中包含的对话框。但是,这会使用浏览器来展示对话框,这看起来并不美观。更重要的是,它的功能非常有限。幸运的是,构建一个提供想要功能的自定义用户控件并不是什么大问题。甚至可以通过在UI上放置一个Canvas,然后在上面放置一个Popup(或者使用ChildWindow),来使其成为模态的。

代码分割问题

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

  • 验证输入
  • 如果所有字段都正确,开始保存数据
  • 一旦保存完成,刷新当前数据

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

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

从这些步骤中,可以看到,从只有一个保存完成的事件处理程序,到添加一个对话框的回调。所以,不是有两个保存代码的方法,而是有三个方法。并且随着添加的每个对话框,又增加了一个回调,从而又增加了一个包含保存操作代码的方法。可以看到这很快就会变得难以控制。

同步化处理

那么,这将解决问题,然而……Silverlight只有一个UI线程的调度器。这意味着,一旦阻塞了UI线程,应用程序就会变得无响应(基本上,它会挂起,直到解除阻塞)。在显示对话框时这样做并不实用,因为用户将无法点击按钮(这完全违背了目的,对吧?)。

实际上,这意味着没有直接的方式来提供同步的UI交互。它将是事件驱动的,因此是异步的。然而,有一种方法可以解决这个问题。不能阻塞UI线程,但可以阻塞任何其他线程,所以如果将所有涉及保存的代码移动到后台线程,可以消除对话框的回调。以下是调用这样一个对话框的示例代码:

private void saveButton_Click(object sender, RoutedEventArgs e) { // 确保在UI线程上创建对话框 _dialog = new DialogWindow(); // 将保存逻辑传递给另一个线程 ThreadPool.QueueUserWorkItem(SaveData, this); } private void SaveData(object stateInfo) { if (DialogWindow.ShowDialog(_dialog, stateInfo)) { // 实际保存数据 } }

基本上,将与保存操作相关的任何代码(除了验证)在后台线程上运行。还传递了一个对调用UI控件的引用。原因很快就会变得清晰。

在UI线程上运行代码

为了使这工作,显然需要能够在UI线程上运行一些代码,然后阻塞线程以等待结果。以下是ShowDialog方法的代码:

public static bool ShowDialog(DialogWindow window, object stateInfo) { bool result = false; DependencyObject dependencyObject = stateInfo as DependencyObject; // 如果得到了一个依赖对象,并且可以访问它的调度器, // 那么就处于UI线程,不能阻塞 if (dependencyObject == null || dependencyObject.Dispatcher.CheckAccess()) { window.Show(); return false; } Action action = new Action(window.Show); // 在UI线程上运行Show方法 dependencyObject.Dispatcher.BeginInvoke(action); // 阻塞此线程,直到有结果 while (!window.DialogResult.HasValue) { Thread.Sleep(50); } result = window.DialogResult.Value; return result; }

首先检查否真的在UI线程上。如果在,不想阻塞。请注意,CheckAccess方法不会在IntelliSense中显示,但它可以毫无问题地编译。

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

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485