如何从 WPF Extended Toolkit 正确实现 Busy Indicator

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25221866/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:18:51  来源:igfitidea点击:

How to properly implement Busy Indicator from WPF Extended Toolkit

c#wpfmvvmwpftoolkit

提问by flyNflip

Ok, I have been struggling with this issue for quite some time and have read article after article trying to get an idea on the issue. I am trying to implement the Busy Indicator and am only partially successful. I have a Shell view that I have wrapped with the BusyIndicator like so:

好的,我已经在这个问题上苦苦挣扎了很长一段时间,并阅读了一篇又一篇文章,试图对这个问题有所了解。我正在尝试实施 Busy Indicator 并且只是部分成功。我有一个 Shell 视图,我用 BusyIndi​​cator 包裹,如下所示:

<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:l="clr-namespace:Library.StaticClasses"
    xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
    Name="ShellView"
    Style="{StaticResource BusyWindowStyle}"
    SourceInitialized="Window_SourceInitialized"
    DataContext="{StaticResource ShellVM}">

    <xctk:BusyIndicator x:Name="ShellBusyIndicator" IsBusy="{Binding IsBusy}"  DisplayAfter="0">
        <Grid>
            <!--Content Here -->
        </Grid>
    </xctk:BusyIndicator>

Following the MVVM pattern, I have a ShellViewModel that looks like so:

遵循 MVVM 模式,我有一个如下所示的 ShellViewModel:

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");
            }
        }
    }

    private string _IsBusyMessage;
    public string IsBusyMessage
    {
        get
        {
            return _IsBusyMessage;
        }
        set
        {
            if (_IsBusyMessage != value)
            {
                _IsBusyMessage = value;
                OnPropertyChanged("IsBusyMessage");
            }
        }
    }

    #endregion properties

    #region actions, functions, and methods

    private void IsBusyEventAction(object sender, EventArgs e)
    {
        if (StateManager.IsBusy)
        {
            this.IsBusy = true;
            this.IsBusyMessage = StateManager.IsBusyMessage;
        }
        else
        {
            this.IsBusy = false;
        }
    }

    #endregion actions, functions, and methods
}

Next, I am using a static class called StateManager that I can call from anywhere that will trigger the busy indicator:

接下来,我使用了一个名为 StateManager 的静态类,我可以从任何地方调用它来触发忙碌指示器:

public static class StateManager
{
    public static String IsBusyMessage { get; set; }

    public static void IsBusyProcess(Action action)
    {
        IsBusyProcess(action, "Processing...");
    }

    public static void IsBusyProcess(Action action, String isBusyMessage)
    {
        IsBusyMessage = isBusyMessage;
        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;
}

Finally, I am triggering the busy indicator in code like so:

最后,我在代码中触发忙碌指示器,如下所示:

StateManager.IsBusyProcess(() =>
        {
            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;
        });

To summarize, the StateManager can be called from anywhere in the application. When the StateManager.IsBusy property is changed, an event, which is subscribed to in the ShellViewModel, is fired. When this happens, the IsBusy property in the ShellViewModel is set to true supposedly causing the IsBusy indicator to show. The logic is working fine in the sense that the IsBusy in the ShellViewModel is being set and the busy indicator evens shows as expected. What isn't working right is that the busy indicator does not animate. It simply stays stagnate. I feel like this is a threading issue but either my understanding of the UI thread is tilted or I am not seeing something that is hopefully obvious to you. I realize the IsBusy logic cannot be on the same thread as the UI, but I would think that since my logic is happening in the view model, it shouldn't be an issue. Am I wrong? Any ideas or thoughts? One other thing that might be helpful: I was having trouble even getting the indicator to show and created this post which was resolved: How to implement busy indicator in application shell

总而言之,可以从应用程序的任何地方调用 StateManager。当 StateManager.IsBusy 属性更改时,会触发在 ShellViewModel 中订阅的事件。发生这种情况时,ShellViewModel 中的 IsBusy 属性被设置为 true,这可能会导致 IsBusy 指示器显示。从某种意义上说,正在设置 ShellViewModel 中的 IsBusy 并且忙碌指示器甚至按预期显示,该逻辑运行良好。工作不正常的是忙碌指示器没有动画。它只是停滞不前。我觉得这是一个线程问题,但我对 UI 线程的理解有偏差,或者我没有看到对你来说很明显的东西。我意识到 IsBusy 逻辑不能与 UI 位于同一线程上,但我认为由于我的逻辑发生在视图模型中,这应该不是问题。我错了吗?有什么想法或想法吗?另一件事可能会有帮助:我什至无法显示指标并创建了这篇已解决的帖子:如何在应用程序shell中实现忙碌指示器

回答by flyNflip

Well, I finally have the busy indicator implemented. It's been quite the arduous task, but I've learned a bit along the way. At least I have something to show for it. While I didn't figure out why my code wasn't working as I have it posted, I did find that I was trying to execute UI related code in the action that would certainly be a problem. I'm not 100% sure because I did a bit of a re-write and I'm not froggy enough to revert my code to play with it. But, the question is answered and here is how I implemented the busy indicator:

好吧,我终于实现了忙碌指标。这是一项相当艰巨的任务,但我在此过程中学到了一些东西。至少我有一些东西可以展示。虽然我没有弄清楚为什么我的代码在发布时不起作用,但我确实发现我试图在肯定会出现问题的操作中执行 UI 相关代码。我不是 100% 确定,因为我重新编写了一些代码,而且我还不够笨拙,无法恢复我的代码来使用它。但是,问题得到了解答,以下是我如何实现忙碌指示器:

First, nothing changed in my view except the property set and binding for IsBusy can be removed. I ended up moving my event subscription from the view model and into the code behind of the view. My view's code behind looks like this:

首先,在我看来,除了可以删除 IsBusy 的属性集和绑定之外,没有任何变化。我最终将我的事件订阅从视图模型移到了视图背后的代码中。我的视图背后的代码如下所示:

public partial class Shell : Window
{
    #region Constructor(s)

    public Shell()
    {
        InitializeComponent();
        StateManager.IsBusyChange += new StateManager.IsBusyHandler(InitiateIsBusy);
    }

    #endregion Constructor(s)

    #region Event Actions

    public void InitiateIsBusy(object sender, BusyActionEventArgs e)
    {
        BackgroundWorker worker = new BackgroundWorker();

        worker.DoWork += (o, ea) =>
        {
            e.IsBusyAction.Invoke();
            //Dispatcher.Invoke((Action)(() => e.IsBusyAction()));
        };
        worker.RunWorkerCompleted += (o, ea) =>
        {
            this.ShellBusyIndicator.IsBusy = false;
        };

        this.ShellBusyIndicator.BusyContent = e.BusyMessage;
        this.ShellBusyIndicator.IsBusy = true;

        worker.RunWorkerAsync();
    }

    #endregion Event Actions
}

First, note that I created the BusyActionEventArgs class that extends EventArgs. It is placed into my Library project which is a dependency on every project in my solution:

首先,请注意我创建了扩展 EventArgs 的 BusyActionEventArgs 类。它被放置在我的库项目中,该项目依赖于我的解决方案中的每个项目:

public class BusyActionEventArgs : EventArgs
{
    public Action IsBusyAction { get; set; }
    public string BusyMessage { get; set; }
}

Last, my StateManager now looks like this:

最后,我的 StateManager 现在看起来像这样:

public static class StateManager
{
    public static void IsBusyProcess(Action action)
    {
        IsBusyProcess(action, "Processing...");
    }

    public static void IsBusyProcess(Action action, String isBusyMessage)
    {
        BusyActionEventArgs args = new BusyActionEventArgs()
        {
            IsBusyAction = action,
            BusyMessage = isBusyMessage
        };

        IsBusyChange(null, args);
    }

    public delegate void IsBusyHandler(object sender, BusyActionEventArgs e);

    public static event IsBusyHandler IsBusyChange;
}

Note that I created an overload for the IsBusyProcess method so I can send in a custom Busy Message if I'd like.

请注意,我为 IsBusyProcess 方法创建了一个重载,因此我可以根据需要发送自定义的 Busy 消息。

Now if I modify the code from my question's code snippet where I am actually using the Busy Indicator, it looks like this:

现在,如果我修改问题的代码片段中的代码,我实际使用的是忙碌指示器,它看起来像这样:

StateManager.IsBusyProcess(() =>
        {
            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);

Note the code I removed from the action's scope. This code is UI code and will cause an error if it is inside the scope.

请注意我从操作范围中删除的代码。此代码是 UI 代码,如果它在范围内,则会导致错误。

I hope this helps someone else who might want to implement something similar or is having troubles. Thanks stackoverflow for just existing on this one. Sometimes talking (or typing) it out makes the difference.

我希望这可以帮助其他可能想要实现类似功能或遇到问题的人。感谢 stackoverflow 只存在于这个。有时说(或打字)它会有所不同。