如何等待触发器在 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(); }
}
我有一个程序,其中有一个加载图像,当一个持久的动作发生时。我想使用一个 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(); }
}