如果长时间运行的操作在 UI 线程上,则显示忙碌指示器

本文关键字:显示 指示器 线程 运行 长时间 操作 UI 如果 | 更新日期: 2023-09-27 18:33:13

我有我的xaml:

        <Window x:Class="Views.ShellView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
                xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                Height="768" Width="1024" WindowStartupLocation="CenterScreen"
                Title="{Binding Path=DisplayName}">
            <xctk:BusyIndicator x:Name="BusyIndicator"  IsBusy="{Binding IsBusy, UpdateSourceTrigger=PropertyChanged}" >
                <TreeView Style="{StaticResource TableSchemaTreeViewStyle}" ItemContainerStyle="{StaticResource SchemaTreeViewStyle}" Margin="0,15,0,0" 
                                  x:Name="TreeViewSchema" 
                                  TreeViewItem.Expanded="TreeViewSchema_OnExpanded" 
                                  TreeViewItem.Selected="TreeViewSchema_OnSelected" 
                                  Grid.Row="2" 
                                  ItemsSource="{Binding CurrentProject.Tables, Converter={StaticResource TreeViewSortingConverter}, ConverterParameter=FullTableName}">
                </TreeView>
            </xctk:BusyIndicator>
        </Window>

假设我在代码隐藏中有长时间运行的任务,该任务在 UI 线程上执行(树视图的长时间过滤,它可以有 1000 多个表,每个表中有 100 多个列(。

假设我迭代并为每个项目设置tableTreeViewItem.Visibility = Visibility.Collapsed;

我想要的:在此操作之前将其设置为 true,以显示 BusyIndicator:

BusyIndicator.IsBusy = true; .

问题:UI 线程和绑定上的操作都无法按预期工作。我尝试了几件事:

BusyIndicator.IsBusy = true;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
    tableTreeViewItem.Visibility = Visibility.Collapsed;
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler).ContinueWith(task => Dispatcher.Invoke(() =>
{
    BusyIndicator.IsBusy = false;
}));

并使用调度程序:

BusyIndicator.IsBusy = true;
//long-running UI task
tableTreeViewItem.Visibility = Visibility.Collapsed;
BusyIndicator.IsBusy = false;

但是它不起作用,有什么想法如何解决吗?

PSS

做了一些更新,我决定获取所有数据并存储哪个树视图项目应该是可见的或隐藏的。

所以我有存储过滤器方法的表格、可见性和可见列的类

 class TreeViewItemVisibilityTableContainer
{
    private TreeViewItem _treeViewItem;
    private TableModel _table;
    private Visibility _visibility;
    private List<ColumnModel> _visibleColumns;
    public TableModel Table
    {
        get { return _table; }
        set { _table = value; }
    }
    public TreeViewItem TreeViewItem
    {
        get { return _treeViewItem; }
        set { _treeViewItem = value; }
    }
    public Visibility Visibility
    {
        get { return _visibility; }
        set { _visibility = value; }
    }
    public List<ColumnModel> VisibleColumns
    {
        get { return _visibleColumns; }
        set { _visibleColumns = value; }
    }
}

现在我可以直接在 UI 线程上过滤所有这些员工:

 System.Action filterTreeViewItemsVisibility = () => Dispatcher.Invoke(() =>
            {
                foreach (var item in itemsToFilter)
                {
                    item.TreeViewItem.Visibility = item.Visibility;
                    var capturedItemForClosure = item;
                    if (item.Visibility == Visibility.Visible)
                    {
                        if (item.VisibleColumns.Any())
                        {
                            item.TreeViewItem.Items.Filter = item.TreeViewItem.Items.Filter =
                                treeViewItem =>
                                    capturedItemForClosure.VisibleColumns.Any(
                                        columnModel => columnModel.Equals(treeViewItem));
                        }
                        else
                        {
                            item.TreeViewItem.Visibility = Visibility.Collapsed;
                        }
                    }
                }
            });
            IoC.Get<IBusyIndicatorHelper>().PerformLongrunningAction(filterTreeViewItemsVisibility, IoC.Get<IShellViewModel>());

但它仍然非常慢

如果长时间运行的操作在 UI 线程上,则显示忙碌指示器

这是我对繁忙指标的解决方案。

显示解决方案的组件很少

  • 忙指示器用户控件
  • 可中止后台辅助角色
  • 窗口作为应用程序窗口

忙指示器用户控件

BusyIndicator.xaml - 非常简单

<UserControl x:Class="BusyIndicatorExample.BusyInidicator"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Visibility="Collapsed">
<Grid Background="#BFFFFFFF" >
        <TextBlock Text="Loading data..." HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF2C2C2C" FontSize="16" FontWeight="Bold" />
</Grid>
</UserControl>

BusyIndicator.xaml.cs

using System;
using System.Windows.Controls;
namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for BusyInidcator.xaml
/// </summary>
public partial class BusyInidicator : UserControl
{
    public BusyInidicator()
    {
        InitializeComponent();
    }

指标显示方法

    public void Start()
    {
        this.Dispatcher.Invoke(new Action(delegate()
        {
            this.Visibility = System.Windows.Visibility.Visible;
        }), System.Windows.Threading.DispatcherPriority.Normal);
    }

一种隐藏指示器的方法

    public void Stop()
    {
        this.Dispatcher.Invoke(new Action(delegate()
        {
            this.Visibility = System.Windows.Visibility.Collapsed;
        }), System.Windows.Threading.DispatcherPriority.Normal);
    }
}
}

用于模拟非 UI 任务的可中止后台辅助角色

using System;
using System.ComponentModel;
using System.Threading;
namespace BusyIndicatorExample
{
/// <summary>
/// Abortable background worker
/// </summary>
public class AbortableBackgroundWorker : BackgroundWorker
{
    //Internal Thread
    private Thread workerThread;
    protected override void OnDoWork(DoWorkEventArgs e)
    {
        try
        {
            base.OnDoWork(e);
        }
        catch (ThreadAbortException)
        {
            e.Cancel = true;        //We must set Cancel property to true! 
            Thread.ResetAbort();    //Prevents ThreadAbortException propagation 
        }
    }
    public void Abort()
    {
        if (workerThread != null)
        {
            workerThread.Abort();
            workerThread = null;
        }
    }
} 
}

最后,模拟过程的主窗口

MainWindow.xaml

<Window x:Class="BusyIndicatorExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BusyIndicatorExample"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Button Content="Start Data Loading" HorizontalAlignment="Left" Margin="63,42,0,0" VerticalAlignment="Top" Width="125" Height="28" Click="Button_Click"/>
   <TextBox HorizontalAlignment="Left" Height="23" Margin="63,87,0,0" TextWrapping="Wrap" Text="{Binding DataString}" VerticalAlignment="Top" Width="412"/>
    <local:BusyInidicator x:Name="busyIndicator" HorizontalAlignment="Left" Height="100" Margin="177,140,0,0" VerticalAlignment="Top" Width="300"/>
</Grid>
</Window>

MainWindow.xaml.cs - 这是应用程序代码

using System.ComponentModel;
using System.Windows;
namespace BusyIndicatorExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
    private AbortableBackgroundWorker _worker;

绑定到文本框的构造函数和公共属性

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }
    private string _dataString = "No Data";
    public string DataString
    {
        get { return _dataString; }
        set {
            if (_dataString != value)
            {
                _dataString = value;
                if (PropertyChanged != null)
                    PropertyChanged.Invoke(this, new PropertyChangedEventArgs("DataString"));
            }
        }
    }

按钮单击事件 - 初始化后台工作线程并启动它

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if(_worker == null)
        {
            _worker = new AbortableBackgroundWorker();
            _worker.WorkerReportsProgress = true;
            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += _worker_DoWork;
            _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
        }
        if (!_worker.IsBusy)
            _worker.RunWorkerAsync();
    }

后台工作线程事件处理程序

运行工作线程已完成更新数据字符串和隐藏指示器。

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        DataString = "Data has been loaded";
        busyIndicator.Stop();
    }

DoWork 显示指示器并进入睡眠状态线程 5 秒。

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        DataString = "No Data";
        busyIndicator.Start();
            System.Threading.Thread.Sleep(5000);
    }
    public event PropertyChangedEventHandler PropertyChanged;
}
}

希望这有帮助。根据需要修改代码以适应你的方案完整的示例项目代码可在此处下载