ListBox的ItemsSource集合更改后如何在更新Box时显示等待光标?

时间:2019-11-26 00:17:05

标签: c# wpf

我有一个由视图模型属性(ListBox)馈给的Photos,该属性是ObservableCollection对象,其中包含图像文件的路径。 ListBox显示的图像可能很多:

查看:

<ListBox ItemsSource="{Binding Path=Photos}"
         SelectionChanged="PhotoBox_SelectionChanged">
    ...
</ListBox>

后面的代码(可以改进以异步运行...):

void RefreshPhotoCollection (string path) {
    Photos.Clear ();
    var info = new DirectoryInfo (path);
    try {
        foreach (var fileInfo in info.EnumerateFiles ()) {
            if (FileFilters.Contains (fileInfo.Extension)) {
                Photos.Add (new Photo (fileInfo.FullName));
            }
        }
    }
    catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) {
        ...
    }
}

在运行RefreshPhotoCollection时,我已经设法通过使用this method来显示等待光标,该事件涉及一个IDisposable:

using (this.BusyStackLifeTracker.GetNewLifeTracker())
    {
        // long job
    }

但是,当向视图通知收集更改时,此方法结束时将光标重置为指针。然后ListBox会自己渲染,但在此过程中不会显示等待光标。我有个问题。

如何使ListBox显示等待光标,直到更新完成?

2 个答案:

答案 0 :(得分:2)

首先在视图模型中创建一个“忙”标志:

private bool _Busy = false;
public bool Busy
{
    get { return this._Busy; }
    set
    {
        if (this._Busy != value)
        {
            this._Busy = value;
            RaisePropertyChanged(() => this.Busy);
        }
    }
}

然后将所有工作移至一个Task,该任务会设置该忙碌标志并在之后将其重置(请注意,每当将照片添加到集合本身时,您将必须调用GUI线程的分派器):

private void DoSomeWork()
{
    Task.Run(WorkerProc);
}

private async Task WorkerProc()
{
    this.Busy = true;
    for (int i = 0; i < 100; i++)
    {
        // simulate loading a photo in 500ms
        var photo = i.ToString();
        await Task.Delay(TimeSpan.FromMilliseconds(500));

        // add it to the main collection
        Application.Current.Dispatcher.Invoke(() => this.Photos.Add(photo));
    }
    this.Busy = false;
}

然后在XAML中为MainWindow提供一种样式,该样式可以在设置此标志时设置光标:

<Window.Style>
    <Style TargetType="{x:Type Window}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Busy}" Value="True">
                <Setter Property="Cursor" Value="Wait" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Style>

更新:将工作放在Task中意味着您不会饿死GUI线程,因此ListBox会随着您的更新而更新,而不是等到完成所有照片的加载。如果是ListBox更新本身花费的时间太长,那么这就是您应该尝试解决的问题,例如查看是否有任何转换器运行缓慢,或者是否需要以最佳格式将照片数据结构提供给视图。我想您会发现,仅启用虚拟化可能会大大改善前端响应能力:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <VirtualizingStackPanel />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

不幸的是,虚拟化是一个很容易破解的东西,因此您需要通过在运行应用程序时检查Live Visual Tree来确保其正常工作,以确保仅针对实际存在的项创建ListBoxItems。在屏幕上可见。

答案 1 :(得分:2)

此模式可以执行以下操作:

  1. 将光标更改逻辑放置在标志变量中以供其他用途。
  2. 为其他线程 上的gui线程提供安全的方式来更改GUI项。
  3. 将照片添加到单独的线程中,以允许UI响应。
  4. 为了安全起见,添加到图片集中的真实照片是在Gui线程上完成的。
  5. 等待光标不会 跳转 /添加照片时更改状态。
  6. 检查最短等待处理时间,以给出正在做某事的外观。该等待时间可以根据需要进行更改。如果未达到最小等待时间,则不会执行额外时间。

VM上的通知。这允许任何非GUI线程执行安全的GUI操作。

public static void SafeOperationToGuiThread(Action operation)
{
    System.Windows.Application.Current?.Dispatcher?.Invoke(operation);
}

状态标志 然后提供一个正在进行的标记,如果需要可以重新使用该视图,该标记还可以设置光标:

private bool _IsOperationOnGoing;

public bool IsOperationOnGoing
{
    get { return _IsOperationOnGoing; }
    set {
        if (_IsOperationOnGoing != value)
        {
            _IsOperationOnGoing = value;

            SafeOperationToGuiThread(() => Mouse.OverrideCursor = (_IsOperationOnGoing) ? Cursors.Wait : null  );
            OnPropertyChanged(nameof(IsOperationOnGoing));
        }
    }
}

在单独的线程中添加照片,然后在您的照片添加中,执行以下操作:GUI线程关闭光标,然后由单独的任务/线程加载照片。同时监视时间,以显示可行的一致等待时间光标:

private Task RunningTask { get; set; }

void RefreshPhotoCollection (string path) 
{
   IsOperationOnGoing = true;
   RunningTask = Task.Run(() =>
     {
         try { 
              TimeIt( () =>
                          {
                          ... // Enumerate photos like before, but add the safe invoke:

                          SafeOperationToGuiThread(() => Photos.Add (new Photo (fileInfo.FullName););
                          },
                         4   // Must run for 4 seconds to show the user.
                    );
              }
         catch() {}
         finally 
             {
               SafeOperationToGuiThread(() => IsOperationOnGoing = false);
             }
          });
     }

}

等待时间检查

  

我如何设法维持等待光标,直到视图ListBox更新完成,以便用户真正知道UI何时再次响应?

这里是等待操作方法,如果未达到最小操作时间,它将填充等待时间:

public static void TimeIt(Action work, int secondsToDisplay = 2)
{
    var sw = Stopwatch.StartNew();

    work.Invoke();

    sw.Stop();

    if (sw.Elapsed.Seconds < secondsToDisplay)
        Thread.Sleep((secondsToDisplay - sw.Elapsed.Seconds) * 1000);    
}

如果需要,可以对其进行修改以使用异步调用,而只需进行很少的更改。


替代个人资料中的@mins标记行:

 Light a man a fire and he is warm for a day. 
 Set a man a fire and he is warm for the rest of his life.