如何等待触发器在 WPF 中采取行动

how to wait for trigger taking action in WPF

我有一个程序,其中有一个加载图像,当一个持久的动作发生时。我想使用一个 ContentControl,其中包含一个 Button 作为动作,或者显示一个加载 Image。我已经将 Trigger 设置为 IsLoading 属性,因此它可以交换 Content.

查看:

<Window x:Class="UIHangsTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:UIHangsTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:VM />
    </Window.DataContext>
    <Grid>
        <ContentControl>
            <ContentControl.Style>
                <Style TargetType="ContentControl" >
                    <Setter Property="Content">
                        <Setter.Value>
                            <Button Content="shiny disappearing button" Command="{Binding DoCommand}" IsDefault="True" />
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsLoading}" Value="True" >
                            <Setter Property="Content">
                                <Setter.Value>
                                    <Image />
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </Grid>
</Window>

如果您需要在 UI(调度程序)线程上 运行 长 运行ning 任务,请使用此选项。
视图模型:

namespace UIHangsTest
{
    using System;
    using DevExpress.Mvvm;
    using System.Threading;
    using System.Windows;
    using System.Windows.Threading;

    public class VM: ViewModelBase
    {
        public VM()
        {
            DoCommand = new DelegateCommand(Do, () => true);
            IsLoading = false;
        }

        private void Do()
        {
            // I have set some content control's trigger to show a waiting symbol if IsLoading is true.
            IsLoading = true;
            var handle = new ManualResetEventSlim(false);
            // shows 1.                
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            // I'd like to wait for the UI to complete the swapping of ContentControl's Content
            Application.Current.Dispatcher.Invoke(new Action(() => {
                handle.Set();
                // shows 1
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                }), DispatcherPriority.Background);

            handle.Wait();
            // for 1 second, the empty image should be visible, but it's the half clicked Button.
            Thread.Sleep(1000);
            IsLoading = false;
        }

        public DelegateCommand DoCommand { get; private set; }

        // not working:
        // public bool IsLoading { get; set; }

        // working:
        public bool IsLoading
        {
            get
            {
                return _isLoading;
            }
            set
            {
                _isLoading = value;
                RaisePropertyChanged("IsLoading");
            }
        }
    }
}

我不知道 Do 命令将在 UI 线程上 运行。而且,如果我更改代码,那么后台线程上的持久操作 运行s,它仍然不会将 Content 更改为空 Image,如果您不提高 属性变了(ˇ_ˇ'!l).

使用这个,如果你可以 运行 一些后台线程上的长 运行ning 任务:

private void Do()
{
    IsLoading = true;
    // shows 1
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Task.Factory.StartNew(() =>
    {
        // shows 5
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        //this operation is performed on a background thread...
        Thread.Sleep(1000);
    }).ContinueWith(task =>
    {
        IsLoading = false;
    }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

编辑:

第一种方法有效。因此,如果您仅将 IsLoading setter 更改为 RaisePropertyChanged("IsLoading"),它将正常工作。无需将长 运行ning 任务放在后台线程中(我一开始就想避免)。在我的具体情况下,Thread.Sleep(1000) 是一个很长的 运行ning 过程,由于可能的对话 window 创建以通知用户有关异常情况。

您应该在后台线程上执行长 运行 操作,即本例中的 Thread.Sleep

private void Do()
{
    IsLoading = true;
    Task.Factory.StartNew(() =>
    {
        //this operation is performed on a background thread...
        Thread.Sleep(1000);
    }).ContinueWith(task =>
    {
        IsLoading = false;
    }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

UI 线程无法同时休眠和更新您的视图。

您还需要在 IsLoading 的 setter 中引发 PropertyChanged 事件 属性:

private bool _isLoading;
public bool IsLoading
{
    get { return _isLoading; }
    set { _isLoading = value; RaisePropertyChanged(); }
}