如何在视图模型重新加载绑定数据时显示 'Loading...' 覆盖
How to show 'Loading...' overlay while the View Model reloads the bound data
我想做一件听起来很简单,做起来却很难的事情。
让我们假设我有一些内容绑定到缓慢加载操作。例如,从本地 SQL 检索并需要几秒钟的可观察列表。当发生这种情况时,我想用 "Loading ..." 文本或任何其他 'please wait' 类型的内容覆盖内容呈现器(例如 Groupbox
)。
我很快得出结论,简单地在操作前后切换绑定到 UI 的布尔标志是行不通的。在整个操作完成之前,UI 不会刷新。可能是因为手术 CPU 密集,我不知道。
我现在正在研究 Adorner
s,但是我在 'busy indicator' 覆盖的上下文中搜索它时得到的信息很少。大约 5 年前,Internet 上只有一些解决方案,但我无法使用它们中的任何一个。
问题:
听起来很简单 - 如何在 View Model 正在更新绑定数据的同时临时在屏幕上显示一些内容?
这里是一个示例,说明如何在 ViewModel\Model 处理一些较长的任务时设置具有 "Loading" 显示的视图。
Window
<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);
}
}
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
是的,如果您实际上是在后台线程上执行 long-running 操作,它应该可以工作。
请参考下面的简单例子
查看:
<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));
}
}
我想做一件听起来很简单,做起来却很难的事情。
让我们假设我有一些内容绑定到缓慢加载操作。例如,从本地 SQL 检索并需要几秒钟的可观察列表。当发生这种情况时,我想用 "Loading ..." 文本或任何其他 'please wait' 类型的内容覆盖内容呈现器(例如 Groupbox
)。
我很快得出结论,简单地在操作前后切换绑定到 UI 的布尔标志是行不通的。在整个操作完成之前,UI 不会刷新。可能是因为手术 CPU 密集,我不知道。
我现在正在研究 Adorner
s,但是我在 'busy indicator' 覆盖的上下文中搜索它时得到的信息很少。大约 5 年前,Internet 上只有一些解决方案,但我无法使用它们中的任何一个。
问题:
听起来很简单 - 如何在 View Model 正在更新绑定数据的同时临时在屏幕上显示一些内容?
这里是一个示例,说明如何在 ViewModel\Model 处理一些较长的任务时设置具有 "Loading" 显示的视图。
Window
<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);
}
}
I quickly came to the conclusion that simply switching a boolean flag bound to the UI, before and after the operation doesn't work. The UI does not get refreshed until the entire operation completes. Maybe because the operation is CPU-intensive, I don't know.
是的,如果您实际上是在后台线程上执行 long-running 操作,它应该可以工作。
请参考下面的简单例子
查看:
<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));
}
}