【问题标题】:Show BusyIndicator while Initializing UserControls (WPF + Mvvm + DataTemplate Application)在初始化用户控件时显示 BusyIndi​​cator(WPF + Mvvm + DataTemplate 应用程序)
【发布时间】:2016-06-11 14:59:23
【问题描述】:

现状

我正在使用以下方法为匹配的 ViewModel 解析视图。 (简体)

<Window.Resources>
    <ResourceDictionary>
        <DataTemplate DataType="{x:Type local:DemoVm2}">
            <local:DemoViewTwo />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:DemoVm}">
            <local:DemoView />
        </DataTemplate>
    </ResourceDictionary>
</Window.Resources>

<DockPanel LastChildFill="True">
    <Button Content="Switch To VmOne" Click="ButtonBase_OnClick"></Button>
    <Button Content="Switch To VmTwo" Click="ButtonBase_OnClick2"></Button>

    <ContentPresenter Content="{Binding CurrentContent}" />
</DockPanel>

在 ContentPresenter 中切换 ViewModel 后,WPF 会自动解析视图。

当使用可能需要 2-4 秒进行初始化的复杂视图时,我想显示一个 BusyIndi​​cator。由于视觉效果而非数据量,它们最多需要 2-4 秒。

问题

我不知道 View 何时完成初始化/加载过程,因为我只能访问当前的 ViewModel。

我的方法

我的想法是在 InitializeComponent() 完成或处理其 LoadedEvent 后,为每个 UserControl 附加一个行为,该行为可能会为其附加的 ViewModel (IsBusy=false) 设置一个布尔值。此属性可以绑定到其他地方的 BusyIndi​​cator。

我对这个解决方案并不满意,因为我需要将此行为附加到每个单独的用户控件/视图。

对于此类问题,有人有其他解决方案吗?我想我不是唯一一个想要对用户隐藏 GUI 加载过程的人?!

我最近遇到了这个线程 http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx 。但由于这是从 2007 年开始,可能有一些更好/更方便的方法来实现我的目标?

【问题讨论】:

  • 如果我错了,有人会纠正我,但我认为你的处境很糟糕。繁忙指示器必须在 UI 线程上运行,但控件初始化也在 UI 线程上运行。我认为您的忙碌指示器不会在初始化视图所需的 2 到 4 秒内更新。
  • 我不知道 DevExpress 的人是如何处理这种情况的,但我看到他们的 WPF LoadingDecorator 做了类似的事情。只要它们正在加载,它就会装饰每个 ChildControl。实现这样的装饰器也可以,但我想我必须将它插入到每个数据模板中。
  • 替代:stackoverflow.com/questions/3601125/… 我也遇到了视觉树加载缓慢的问题,但在切换到缓存的 ContentPresenter 后,我很高兴。

标签: c# wpf mvvm datatemplate busyindicator


【解决方案1】:

对于这个问题没有简单而通用的解决方案。 在每种具体情况下,您都应该为非阻塞可视化树初始化编写自定义逻辑。

这里是一个示例,如何使用初始化指示器实现 ListView 的非阻塞初始化。

包含 ListView 和初始化指示器的用户控件:

XAML:

<UserControl x:Class="WpfApplication1.AsyncListUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Margin="5" Grid.Row="1">
        <ListView x:Name="listView"/>
        <Label x:Name="itemsLoadingIndicator" Visibility="Collapsed" Background="Red" HorizontalAlignment="Center" VerticalAlignment="Center">Loading...</Label>
    </Grid>
</UserControl>

CS:

public partial class AsyncListUserControl : UserControl
{
    public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable), typeof(AsyncListUserControl), new PropertyMetadata(null, OnItemsChanged));

    private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        AsyncListUserControl control = d as AsyncListUserControl;
        control.InitializeItemsAsync(e.NewValue as IEnumerable);
    }

    private CancellationTokenSource _itemsLoadiog = new CancellationTokenSource();
    private readonly object _itemsLoadingLock = new object();

    public IEnumerable Items
    {
        get
        {
            return (IEnumerable)this.GetValue(ItemsProperty);
        }
        set
        {
            this.SetValue(ItemsProperty, value);
        }
    }

    public AsyncListUserControl()
    {
        InitializeComponent();
    }

    private void InitializeItemsAsync(IEnumerable items)
    {
        lock(_itemsLoadingLock)
        {
            if (_itemsLoadiog!=null)
            {
                _itemsLoadiog.Cancel();
            }

            _itemsLoadiog = new CancellationTokenSource();
        }

        listView.IsEnabled = false;
        itemsLoadingIndicator.Visibility = Visibility.Visible;
        this.listView.Items.Clear();

        ItemsLoadingState state = new ItemsLoadingState(_itemsLoadiog.Token, this.Dispatcher, items);

        Task.Factory.StartNew(() =>
        {
            int pendingItems = 0;
            ManualResetEvent pendingItemsCompleted = new ManualResetEvent(false);

            foreach(object item in state.Items)
            {
                if (state.CancellationToken.IsCancellationRequested)
                {
                    pendingItemsCompleted.Set();
                    return;
                }

                Interlocked.Increment(ref pendingItems);
                pendingItemsCompleted.Reset();

                state.Dispatcher.BeginInvoke(
                    DispatcherPriority.Background,
                    (Action<object>)((i) =>
                    {
                        if (state.CancellationToken.IsCancellationRequested)
                        {
                            pendingItemsCompleted.Set();
                            return;
                        }

                        this.listView.Items.Add(i);
                        if (Interlocked.Decrement(ref pendingItems) == 0)
                        {
                            pendingItemsCompleted.Set();
                        }
                    }), item);
            }

            pendingItemsCompleted.WaitOne();
            state.Dispatcher.Invoke(() =>
            {
                if (state.CancellationToken.IsCancellationRequested)
                {
                    pendingItemsCompleted.Set();
                    return;
                }

                itemsLoadingIndicator.Visibility = Visibility.Collapsed;
                listView.IsEnabled = true;
            });
        });
    }

    private class ItemsLoadingState
    {
        public CancellationToken CancellationToken { get; private set; }
        public Dispatcher Dispatcher { get; private set; }
        public IEnumerable Items { get; private set; }

        public ItemsLoadingState(CancellationToken cancellationToken, Dispatcher dispatcher, IEnumerable items)
        {
            CancellationToken = cancellationToken;
            Dispatcher = dispatcher;
            Items = items;
        }
    }
}

使用示例:

<Window x:Class="WpfApplication1.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:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Load Items" Command="{Binding LoadItemsCommand}" />
        <local:AsyncListUserControl Grid.Row="1" Items="{Binding Items}"/>
    </Grid>
</Window>

视图模型:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;

namespace WpfApplication1
{
    public class MainWindowViewModel:INotifyPropertyChanged
    {
        private readonly ICommand _loadItemsCommand;
        private IEnumerable<string> _items;

        public event PropertyChangedEventHandler PropertyChanged;

        public MainWindowViewModel()
        {
            _loadItemsCommand = new DelegateCommand(LoadItemsExecute);
        }

        public IEnumerable<string> Items
        {
            get { return _items; }
            set { _items = value; OnPropertyChanged(nameof(Items)); }
        }

        public ICommand LoadItemsCommand
        {
            get { return _loadItemsCommand; }
        }

        private void LoadItemsExecute(object p)
        {
            Items = GenerateItems();
        }

        private IEnumerable<string> GenerateItems()
        {
            for(int i=0; i<10000; ++i)
            {
                yield return "Item " + i;
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            var h = PropertyChanged;
            if (h!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public class DelegateCommand : ICommand
        {
            private readonly Predicate<object> _canExecute;
            private readonly Action<object> _execute;
            public event EventHandler CanExecuteChanged;

            public DelegateCommand(Action<object> execute) : this(execute, null) { }

            public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
            {
                _execute = execute;
                _canExecute = canExecute;
            }

            public bool CanExecute(object parameter)
            {
                if (_canExecute == null)
                {
                    return true;
                }

                return _canExecute(parameter);
            }

            public void Execute(object parameter)
            {
                _execute(parameter);
            }

            public void RaiseCanExecuteChanged()
            {
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }
        }
    }
}

这种方法的主要特点:

  1. 需要大量 UI 的数据的自定义依赖属性 初始化。

  2. DependencyPropertyChanged 回调启动管理工作线程 界面初始化。

  3. 工作线程调度低执行优先级的小动作 进入让 UI 负责的 UI 线程。

  4. 额外的逻辑来保持一致的状态,以防万一 初始化再次执行,而先前的初始化不是 尚未完成。

【讨论】:

  • 您在答案中投入了大量工作。可悲的是它不能解决我的问题。我的问题解决了长加载 UserControl 的指示器装饰,它只调用 InitializeComponents() 而不加载数据项。所以只需隐藏渲染过程。
  • 我想知道静态可视化树应该有多大才能有这么长的加载时间。您是否尝试在不绑定 DataContext 的情况下运行视图并测量时间?
【解决方案2】:

另一种方法是从隐藏 UserControl 并将 IsBusy 设为 true 开始。在 Application.Dispatcher 上的单独线程中开始加载。胎面的最后语句是 IsBusy=false; UserControl.Visibility = Visibility.Visible;

【讨论】:

  • WPF 可视化树不能从不同的线程初始化。你会得到 InvalidOperationException
猜你喜欢
  • 1970-01-01
  • 2018-07-03
  • 2012-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多