如何在视图模型重新加载绑定的数据时显示“正在加载...”覆盖层

8
我想做一件听起来非常简单的事,但我发现很难实现。
假设有一些内容需要进行缓慢加载操作,例如从本地SQL检索可观察列表并需要几秒钟。在此期间,我希望通过“正在加载…”文本或其他“请稍候”类型的内容叠加在内容呈现器(例如Groupbox)上。
我很快得出结论,在操作之前和之后简单地切换绑定到UI的布尔标志是不行的。UI直到整个操作完成才会刷新。也许是因为这个操作占用了CPU,我不知道。
我现在正在研究Adorner,但在搜索与“繁忙指示符”叠加相关的信息时只能找到很少的信息。互联网上只有一些解决方案,大约是5年前的,我无法使它们中的任何一个起作用。
问题是:
尽管听起来很简单,但如何在视图模型正在更新绑定数据的情况下暂时在屏幕上显示某些内容?

2
那个轻易就会投票关闭的版主——也许这个问题太琐碎了,你可以用一两句话非常清楚地描述解决方案吗? - hyankov
布尔标志是一个通知属性吗?它是一个带有支持字段并引发属性更改通知的属性吗? - Eric
1
@hyankov同意,5年过去了,我发现这个话题和被接受的答案非常有帮助。 - undefined
2个回答

9
我很快得出结论,仅在操作之前和之后切换绑定到UI的布尔标志是行不通的。UI直到整个操作完成后才会被刷新。也许是因为操作需要大量CPU资源,我不确定。
是的,只要您确实在后台线程上执行长时间运行的操作,它应该可以工作。
请参考以下简单示例。
视图:
<Window x:Class="WpfApplication2.Window1"
        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:WpfApplication2"
        mc:Ignorable="d"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="Window1" Height="300" Width="300">
    <Window.DataContext>
        <local:Window1ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <TextBlock>Content...</TextBlock>
        <Grid Background="Yellow" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Loading...</TextBlock>
        </Grid>
    </Grid>
</Window>

视图模型:

public class Window1ViewModel : INotifyPropertyChanged
{
    public Window1ViewModel()
    {
        IsLoading = true;
        //call the long running method on a background thread...
        Task.Run(() => LongRunningMethod())
            .ContinueWith(task =>
            {
                //and set the IsLoading property back to false back on the UI thread once the task has finished
                IsLoading = false;
            }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    }

    public void LongRunningMethod()
    {
        System.Threading.Thread.Sleep(5000);
    }

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

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

2
这是一个示例,介绍如何设置一个视图,在ViewModel/Model正在处理长时间任务时显示“加载”界面。 窗口
<Window x:Class="Loading.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:Loading"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:VisibilityConverter x:Key="visibilityConverter" />
</Window.Resources>
<Window.DataContext>
    <local:ViewModel x:Name="viewModel" />
</Window.DataContext>
<Grid>
    <Button Content="Perform" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30" Command="{Binding HandleRequestCommand}" />
    <Border Visibility="{Binding Path=IsLoading,Converter={StaticResource visibilityConverter}}" Background="#AAAAAAAA" Margin="5">
        <TextBlock Text="Loading..." VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Border>
</Grid>

VisibilityConverter.cs(简单的帮助转换器)

class VisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (bool)value ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool isLoading;

    public ViewModel()
    {
        HandleRequestCommand = new Command(HandleRequest);
    }

    public bool IsLoading
    {
        get
        {
            return isLoading;
        }
        set
        {
            if (value != isLoading)
            {
                isLoading = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLoading)));
            }
        }
    }

    public ICommand HandleRequestCommand
    {
        get;
    }

    public void HandleRequest()
    {
        IsLoading = true;

        Task.Factory.StartNew(LongRunningOperation);
    }

    private void LongRunningOperation()
    {
        // *** INSERT LONG RUNNING OPERATION ***

        Dispatcher.CurrentDispatcher.Invoke(() => IsLoading = false);
    }
}

只需使用ProgressRing,将isActive属性绑定到您的视图模型工作属性。确保工作属性具有更改通知,并引发RaisePropertyChanged事件。 例如:IsActive = "{Binding Working}" - SimperT
基本上,解决方案围绕将数据加载到单独的“任务”中,并通过“Dispatcher.CurrentDispatcher”更新UI,是吗? - hyankov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接