以反应方式在 window 上显示确认关闭
Showing a Confirmation on window close in a reactive way
我有一个用于显示确认对话框的 Observable,签名大致如下:
IObservable<DialogResult> ShowDialog(string title, string message);
这向用户显示对话框,带有是/否按钮组合。
在我的主 window 中,我是这样访问结束事件的:
this.Events().Closing.[Do something here]
我希望能够:
- 关闭 observable 触发时显示确认对话框
- 如果用户单击 "no" 按钮,则将
CancelEventArgs.Cancel
属性 设置为真。
我试过直接订阅:
this.Events().Closing.Subscribe(e =>
{
var res = Dialogs.ShowDialog("Close?", "Really Close?").Wait();
e.Cancel = res == DialogResult.Ok;
});
但是由于 Wait()
调用而挂起,我还尝试了异步变体:
this.Events().Closing.Subscribe(async e =>
{
var res = await Dialogs.ShowDialog("Close?", "Really Close?");
e.Cancel = res == DialogResult.Ok;
});
这不好,因为它马上 returns。
我真正想做的是:
this.Events().Closing.ThenDo(_ => Dialogs.ShowDialog("Close?", "Really Close?"))
.Subscribe((cancelEventArgs, dialogResult) =>
{
cancelEventArgs.Cancel == dialogResult == DialogResult.Ok;
});
但这并不存在,我知道这里的关键在于我如何组合这两个可观察量,但我不知道该怎么做,以及文档对实际例子有点了解。
合并两个可观察流的最佳方式是 CombineLatest。
当其中一个更新为每个流中的最后一个值时,这将触发。
您需要阻止 Closing 事件处理程序,因此异步(或 Rx 操作)在这里对您没有太大帮助。
但您还需要以 UI 事件仍在处理的方式阻止它,因此 UI 不会冻结。
最常见的解决方案是使用 Window.ShowDialog 而不是 Show,此代码有效:
this.Events().Closing.Subscribe(e =>
{
var ret = new Window().ShowDialog();
e.Cancel = true;
});
但是在你的 ShowDialog Rx 方法中使用它会阻止它的订阅调用,这不太可能是你想要的(对于其他情况,在这种情况下,它 是你需要的) .
或者,您可以 运行 内部调度程序循环,如下所示:
this.Events().Closing.Subscribe(e =>
{
var dialog = Dialogs.ShowDialog("Close?", "Really Close?");
var dispatcherFrame = new DispatcherFrame();
dialog.Take(1).Subscribe(res => {
e.Cancel = res == DialogResult.Ok;
dispatcherFrame.Continue = false;
});
// this will process UI events till the above fires
Dispatcher.PushFrame(dispatcherFrame);
});
仅当 windows 都使用同一个 Dispatcher 时才有效。
编辑:
或者,您可以通过始终取消关闭并稍后自己关闭表单来避免阻塞,这可能更符合 Rx 方式,即:
var forceClose = default(bool);
this.Events().Closing
.Where(_ => !forceClose)
.Do(e => e.Cancel = true)
.SelectMany(_ => Dialogs.ShowDialog("Close?", "Really Close?"))
.Where(res => res == DialogResult.Ok)
.Do(_ => forceClose = true)
.Subscribe(_ => this.Close());
我有一个用于显示确认对话框的 Observable,签名大致如下:
IObservable<DialogResult> ShowDialog(string title, string message);
这向用户显示对话框,带有是/否按钮组合。
在我的主 window 中,我是这样访问结束事件的:
this.Events().Closing.[Do something here]
我希望能够:
- 关闭 observable 触发时显示确认对话框
- 如果用户单击 "no" 按钮,则将
CancelEventArgs.Cancel
属性 设置为真。
我试过直接订阅:
this.Events().Closing.Subscribe(e =>
{
var res = Dialogs.ShowDialog("Close?", "Really Close?").Wait();
e.Cancel = res == DialogResult.Ok;
});
但是由于 Wait()
调用而挂起,我还尝试了异步变体:
this.Events().Closing.Subscribe(async e =>
{
var res = await Dialogs.ShowDialog("Close?", "Really Close?");
e.Cancel = res == DialogResult.Ok;
});
这不好,因为它马上 returns。
我真正想做的是:
this.Events().Closing.ThenDo(_ => Dialogs.ShowDialog("Close?", "Really Close?"))
.Subscribe((cancelEventArgs, dialogResult) =>
{
cancelEventArgs.Cancel == dialogResult == DialogResult.Ok;
});
但这并不存在,我知道这里的关键在于我如何组合这两个可观察量,但我不知道该怎么做,以及文档对实际例子有点了解。
合并两个可观察流的最佳方式是 CombineLatest。 当其中一个更新为每个流中的最后一个值时,这将触发。
您需要阻止 Closing 事件处理程序,因此异步(或 Rx 操作)在这里对您没有太大帮助。
但您还需要以 UI 事件仍在处理的方式阻止它,因此 UI 不会冻结。
最常见的解决方案是使用 Window.ShowDialog 而不是 Show,此代码有效:
this.Events().Closing.Subscribe(e =>
{
var ret = new Window().ShowDialog();
e.Cancel = true;
});
但是在你的 ShowDialog Rx 方法中使用它会阻止它的订阅调用,这不太可能是你想要的(对于其他情况,在这种情况下,它 是你需要的) .
或者,您可以 运行 内部调度程序循环,如下所示:
this.Events().Closing.Subscribe(e =>
{
var dialog = Dialogs.ShowDialog("Close?", "Really Close?");
var dispatcherFrame = new DispatcherFrame();
dialog.Take(1).Subscribe(res => {
e.Cancel = res == DialogResult.Ok;
dispatcherFrame.Continue = false;
});
// this will process UI events till the above fires
Dispatcher.PushFrame(dispatcherFrame);
});
仅当 windows 都使用同一个 Dispatcher 时才有效。
编辑:
或者,您可以通过始终取消关闭并稍后自己关闭表单来避免阻塞,这可能更符合 Rx 方式,即:
var forceClose = default(bool);
this.Events().Closing
.Where(_ => !forceClose)
.Do(e => e.Cancel = true)
.SelectMany(_ => Dialogs.ShowDialog("Close?", "Really Close?"))
.Where(res => res == DialogResult.Ok)
.Do(_ => forceClose = true)
.Subscribe(_ => this.Close());