如何在WPF中等待触发器执行操作

时间:2017-06-02 15:02:04

标签: c# wpf data-binding dispatcher

我有一个程序,当有一个加载图像时,会发生持久的动作。我想使用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(调度程序)线程上运行长时间运行的任务,请使用此方法 视图模型:

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线程上运行。但是,如果我更改代码,那么长时间操作在后台线程上运行,它仍然不会将Content更改为空Image,如果你没有提高属性已经改变(_'!l )。

如果您可以在某个后台线程上运行长时间运行的任务,请使用此选项:

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"),那么它将正常工作。无需将长时间运行的任务放在后台线程中(我想首先避免)。在我的特定情况下,Thread.Sleep(1000)是一个长时间运行的进程,它还需要在UI(调度程序)线程上运行,因为可能创建Dialog窗口以通知用户异常。

1 个答案:

答案 0 :(得分:3)

您应该在后台线程上执行长时间运行的操作,即本例中的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线程不能同时睡眠和更新您的视图。

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

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