问题
I am trying to implement the busy indicator from the WPF Extended Toolkit in my application's "shell." The goal is to implement the indicator in one place and then be able to set the IsBusy property from anywhere so that it can be initialized. Here is my shell:
<Window x:Class="Foundation.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Library.Controls.Views;assembly=Library"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
WindowStyle="None"
AllowsTransparency="False"
Name="ShellView"
FontFamily="Yu Gothic Light"
Background="{StaticResource AiWhiteBrush}">
<!--Region Outer Most Grid-->
<xctk:BusyIndicator IsBusy="{Binding IsBusy}">
<Grid x:Name="OuterGrid">
<!-- CONTENT HERE -->
</Grid>
</xctk:BusyIndicator>
<!--End Region-->
Then, my Shell's ViewModel looks like this:
using CashDrawer.Views;
using Library.BaseClasses;
using Library.StaticClasses;
using Microsoft.Practices.Prism.Commands;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WpfPageTransitions;
namespace Foundation
{
public class ShellViewModel : ViewModelBase
{
#region constructor(s)
public ShellViewModel()
{
StateManager.IsBusyChange += new StateManager.IsBusyHandler(IsBusyEventAction);
}
#endregion constructor(s)
#region properties
private bool _IsBusy;
public bool IsBusy
{
get
{
return _IsBusy;
}
set
{
if (_IsBusy != value)
{
_IsBusy = value;
OnPropertyChanged("IsBusy");
}
}
}
#endregion properties
#region actions, functions, and methods
private void IsBusyEventAction(object sender, EventArgs e)
{
if (StateManager.IsBusy)
{
this.IsBusy = true;
}
else
{
this.IsBusy = false;
}
}
#endregion actions, functions, and methods
}
}
Last, I have created a static StateManager class:
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using WpfPageTransitions;
namespace Library.StaticClasses
{
public static class StateManager
{
private static bool _IsBusy;
public static bool IsBusy
{
get
{
return _IsBusy;
}
set
{
if (_IsBusy != value)
{
_IsBusy = value;
IsBusyChange(null, null);
}
}
}
public delegate void IsBusyHandler(object sender, EventArgs e);
public static event IsBusyHandler IsBusyChange;
}
}
The idea is that when the StateManager's IsBusy property is changed, it will fire an event that will change the IsBusy property in the ShellViewModel accordingly. The logic is working fine. However, the busy indicator isn't working as expected. Here is a code snippet from another view model that switches the IsBusy property:
private void SaveCommand_Action()
{
StateManager.IsBusy = true;
this.Validate();
if (!HasValidationErrors)
{
if (this.CustomerControlVM.SaveCustomer() != 0)
{
VehicleControlVM.VehicleModel.CustomerID = this.CustomerControlVM.CustomerModel.CustomerID;
this.VehicleControlVM.SaveVehicle();
ComplaintsView complaintsControl = new ComplaintsView();
(complaintsControl.DataContext as ComplaintsViewModel).CurrentVehicle = this.VehicleControlVM.VehicleModel;
(complaintsControl.DataContext as ComplaintsViewModel).CurrentCustomer = this.CustomerControlVM.CustomerModel;
StateManager.LoadView(complaintsControl, PageTransitionType.SlideLeft);
}
}
StateManager.IsBusy = false;
}
I am seeing some lag in the code, but I never see the busy indicator appear. I can remove StateManager.IsBusy = false;
and the busy indicator will appear (and show indefinitely of course.) I have tried creating a longer delay between the IsBusy state changes and the indicator still doesn't appear. I have read multiple posts and articles trying to understand what may be going wrong but I am not seeing anything helpful. I am aware that the IsBusy indicator is happening on the UI thread, but I am changing the IsBusy states in the ViewModel which should not be on the UI thread. Any thoughts or suggestions?
回答1:
Regarding my last comment.
You could change the statemanager to take in an action instead, so you pass in the method, and the state manager can setup the invoke etc
public static class StateManager
{
public static void Process(Action action) {
IsBusy = true;
Application.Current.Dispatcher.Invoke(action, System.Windows.Threading.DispatcherPriority.Background);
IsBusy = false;
}
private static bool _IsBusy;
public static bool IsBusy {
get {
return _IsBusy;
}
set {
if (_IsBusy != value) {
_IsBusy = value;
IsBusyChange(null, null);
}
}
}
public delegate void IsBusyHandler(object sender, EventArgs e);
public static event IsBusyHandler IsBusyChange;
}
Then you could do:
private void SaveCommand_Action()
{
StateManager.Process(() =>
{
this.Validate();
if (!HasValidationErrors)
{
if (this.CustomerControlVM.SaveCustomer() != 0)
{
VehicleControlVM.VehicleModel.CustomerID = this.CustomerControlVM.CustomerModel.CustomerID;
this.VehicleControlVM.SaveVehicle();
ComplaintsView complaintsControl = new ComplaintsView();
(complaintsControl.DataContext as ComplaintsViewModel).CurrentVehicle = this.VehicleControlVM.VehicleModel;
(complaintsControl.DataContext as ComplaintsViewModel).CurrentCustomer = this.CustomerControlVM.CustomerModel;
StateManager.LoadView(complaintsControl, PageTransitionType.SlideLeft);
}
}
StateManager.IsBusy = false;
});
}
回答2:
Thanks to sa_ddam213, I have the issue under wraps. The problem was the priority. This code is what took care of it:
private void SaveCommand_Action()
{
StateManager.IsBusy = true;
Application.Current.Dispatcher.Invoke(() => Save(), System.Windows.Threading.DispatcherPriority.Background);
}
private void Save()
{
this.Validate();
if (!HasValidationErrors)
{
if (this.CustomerControlVM.SaveCustomer() != 0)
{
VehicleControlVM.VehicleModel.CustomerID = this.CustomerControlVM.CustomerModel.CustomerID;
this.VehicleControlVM.SaveVehicle();
ComplaintsView complaintsControl = new ComplaintsView();
(complaintsControl.DataContext as ComplaintsViewModel).CurrentVehicle = this.VehicleControlVM.VehicleModel;
(complaintsControl.DataContext as ComplaintsViewModel).CurrentCustomer = this.CustomerControlVM.CustomerModel;
StateManager.LoadView(complaintsControl, PageTransitionType.SlideLeft);
StateManager.IsBusy = false;
}
}
}
I have a little more work to do so I don't need to do this with each IsBusy state change, but with what I've learned, I can figure it out. Thanks so much sa_ddam213.
来源:https://stackoverflow.com/questions/25194615/how-to-implement-busy-indicator-in-application-shell