我是 WPF 和 C# 新手,我遇到了这个问题,我有一个加载微调器和一个在框架内加载新页面的按钮。
我想要的是将旋转器渲染在框架顶部,并在另一个线程上加载页面(该页面包含一个数据网格,其中有大量数据使UI冻结),这样旋转器可以旋转而不会冻结,然后当页面完成渲染数据网格上的所有项目时消失。
到目前为止我有这个:
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
LoadingSpinner.Visibility = Visibility.Visible;
var ClickedButton = e.Source as UserControls.MenuButton;
if (ClickedButton.Command != NavigationCommands.BrowseBack && ClickedButton.NavUri != null)
{
Page page = (Page)Application.LoadComponent(new Uri(ClickedButton.NavUri.ToString(), UriKind.Relative));
page.DataContext = DataContext;
pgContenedorConfiguracion.NavigationService.Navigate(page);
}
LoadingSpinner.Visibility = Visibility.Collapsed;
}
这段代码有两个问题:
LoadingSpinner.Visibility = Visibility.Collapsed;
,旋转器就会开始冻结,然后,当页面完成渲染时,它开始旋转。然后我想出了这段代码:
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
LoadingSpinner.Visibility = Visibility.Visible;
var ClickedButton = e.Source as UserControls.MenuButton;
if (ClickedButton.Command != NavigationCommands.BrowseBack && ClickedButton.NavUri != null)
{
Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
{
Page page = (Page)Application.LoadComponent(new Uri(ClickedButton.NavUri.ToString(), UriKind.Relative));
page.DataContext = DataContext;
pgContenedorConfiguracion.NavigationService.Navigate(page);
}));
}
Dispatcher.Invoke(DispatcherPriority.Loaded, new Action(() =>
{
LoadingSpinner.Visibility = Visibility.Collapsed;
}));
}
它有点有效,现在旋转器出现在开头,不会立即消失,但它冻结了,并且在页面渲染后以某种方式消失了......有人可以向我解释这里发生了什么吗?我知道调度员会优先考虑,但仅此而已。
有什么最好的方法来做我想做的事吗?
您无法在不同线程上加载
Page
,因为 Page
是 DispatcherObject
,因此具有线程关联性。应尽可能避免使用对象进行导航,因为这会影响性能。 WPF 导航控件保存历史记录以启用向后和向前导航。如果您使用对象进行导航,控件会将完整的对象保留在内存中。但如果您通过 URI 导航,内存占用量会显着降低(由于实例化,加载可能需要更长的时间)。
如果您想在导航主机中重用
Page
实例以缩短加载时间(如有必要),那么您还应该将引用存储在导航器类中,而不是每次都创建新实例。Page
实例的引用。
要禁用繁忙的微调器,您必须等待初始化完成。否则,你就会过早地隐藏它。我建议为此目的收听一个活动。
此外,
Page
提供了链接到父导航主机的NavigationService
实例。您不需要在页面中引用任何导航主机。这只会导致糟糕的设计决策。
private readonly myPage = new MyPage();
private void ShowSpinner()
=> LoadingSpinner.Visibility = Visibility.Visible;
private void HideSpinner()
=> LoadingSpinner.Visibility = Visibility.Collapsed;
private void StackPanel_Click(object sender, RoutedEventArgs e)
{
ShowSpinner();
var clickedButton = e.Source as UserControls.MenuButton;
if (clickedButton.Command != NavigationCommands.BrowseBack
&& clickedButton.NavUri != null)
{
// Get notified when the navigation to the page being completed
// (Page is created but not loaded at this point).
// We then can observe the page and wait for it to complete the initialization.
this.NavigationService.Navigated += OnPageNavigated;
// Don't create new instance on each call
//Page page = (Page)Application.LoadComponent(new Uri(ClickedButton.NavUri.ToString(), UriKind.Relative));
// DataContext is inherited. The following line is redundant.
//page.DataContext = DataContext;
// Instead, reuse the page instance
this.NavigationService.Navigate(this.myPage);
// However, if you need a fresh instance, then navigate by URI
this.NavigationService.Navigate(new Uri(ClickedButton.NavUri.ToString(), UriKind.Relative));
}
}
private void OnPageNavigated(object sender, NavigationEventArgs e)
{
this.NavigationService.Navigated -= OnPageNavigated;
var myPage = (MyPage)e.Content;
if (myPage.IsInitializing)
{
myPage.InitializationCompleted += OnPageInitializationCompleted;
}
else
{
HideSpinner();
}
}
private void OnPageInitializationCompleted(object sender, NavigationEventArgs e)
{
var myPage = (MyPage)sender;
myPage.InitializationCompleted += OnPageInitializationCompleted;
HideSpinner();
}
视图中异步或并发初始化的一个好模式是让控件实现一个
InitializeAsync
方法,然后从 Loaded
事件中调用该方法:
partial class MyPage : Page
{
public event EventHandler InitializationCompleted;
public bool IsInitializing { get; private set; }
public MyPage()
{
InitializeComponent();
this. Loaded += OnLoaded;
}
private async void OnLoaded(object sender, RoutedEventArgs e)
=> await InitializeAsnyc();
private async Task InitializeAsnyc()
{
this.IsInitializing = true;
// Preferably call async APIs
await GetDataAsymc();
// You can offload long running and CPU intensive work
// to a background thread.
// If not awaited, the context immediately continues while the Task.Run operation
// executes in the background
await Task.Run(DoSomethingInTheBackground);
// TODO::Finalize initialization
this.IsInitializing = false;
OnInitializationCompleted();
}
// The CPU intensive and long running operation
private void DoSomethingInTheBackground()
{
}
protected virtual void OnInitializationCompleted(object sender, RoutedEventArgs e)
=> this.InitializationCompleted?.Invoke(this, EventArgs.Empty);
}