【问题标题】:How i can make loading controls in Binding without blocking UI?如何在不阻塞 UI 的情况下在 Binding 中加载控件?
【发布时间】:2014-04-17 02:14:08
【问题描述】:

我有这种情况:

我想在 MainWindow 的 ViewModel 中加载一个集合,我有一个大数据,所以当我进入人员集合的幻灯片时,它应该等待完成加载,然后我可以看到完整列表,我有以下内容:

  • 在我拥有的 xaml 代码中:

    <ListView ..... 
    ItemsSource{Binding PeopleList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
    ... />
    
  • 在视图模式下我有:

    // ...
    private ObservableCollection<Person> _People;
    public ObservableCollection<Person> People
    { get{return _People;} set{ _People = value; RaisePropertyChange("People");}}
    // ...
    

我想在加载第一个完全开始加载第二个之后一个接一个地加载所有人员列表......等等而不阻塞主窗口我希望我的窗口看起来像:

我厌倦了自己做,但我失败了。之前谢谢。

【问题讨论】:

  • 在第一段中你完成加载然后在第二段中你要一个一个地加载。你要解决什么问题?
  • @Blam 在第一个中我的意思是在逻辑中加载,在第二个中我的意思是在 UI 上加载。
  • 一旦你有了数据,你为什么要一次绘制一个人的 UI。那应该非常非常快。使用虚拟化仅绘制视图中的项目。你想解决什么问题?
  • @Blam 我厌倦了可视化,但它也失败了。因为我有一个大数据,在我访问人员列表的每个加载时间,并且项目模板也很大。
  • 虚拟化不是可视化。一次绘制一个控件并不是解决渲染缓慢的正确方法。

标签: c# wpf multithreading binding


【解决方案1】:

存在使用 SynchronizationContext 从另一个线程修改视图的方法。

请看这个例子:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var sync = SynchronizationContext.Current;
            BackgroundWorker w = new BackgroundWorker();
            w.DoWork+=(_, __)=>
                {
                    //sync.Post(p => { button.Content = "Working"; }, null);
                    int j = 0;
                    for (int i = 0; i < 10; i++)
                    {
                        j++;
                        sync.Post(p => { button.Content = j.ToString(); }, null);
                        Thread.Sleep(1000);
                    }
                    sync.Post(p => { button.Background = Brushes.Aqua; button.Content = "Some Content"; }, null);
                };
            w.RunWorkerAsync();
        }

这是视图:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="button" Content="Some Content" Click="Button_Click"/>
    </Grid>
</Window>

此代码多次更新视图(在本例中为按钮)。我认为这解决了您最初的问题。

----编辑----

这是使用这个想法的更好方法:我建议在基本视图模型中创建这样的方法:

    public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
    {
        if (IsBusy)
            return;
        var currentSyncContext = SynchronizationContext.Current;
        ActiveThread = new Thread((_) =>
        {                
            currentSyncContext.Send(t =>
            {
                IsBusy = true;
                BusyText = string.IsNullOrEmpty(text) ? "Wait please..." : text;
                if (beforeVisualAction != null)
                    beforeVisualAction();
            }, null);
            action();
            currentSyncContext.Send(t =>
            {
                IsBusy = false;
                BusyText = "";
                if (afterVisualAction != null)
                    afterVisualAction();
            }, null);
        });
        ActiveThread.Start();
    }

通过这种方式,任何子视图模型都可以使用它来执行大量数据处理,并且 UI 不会冻结。 IsBusyBusyText 是视图模型变量,它们绑定到视图等待消息和等待元素的可见性。 这是在子视图模型的命令中使用的示例:

  private RelayCommand _SomeCommand;
  public RelayCommand SomeCommand
    {
        get { return _SomeCommand ?? (_SomeCommand = new RelayCommand(ExecuteSomeCommand, CanExecuteSomeCommand)); }
    }

    private void ExecuteSomeCommand()
    {
        Action t = ()=> 
        {
            //some action
        };

        LockAndDoInBackground(t, "Generating Information...");
    }

    private bool CanExecuteSomeCommand()
    {
        return SelectedItem != null;
    }

希望这将成为一个更清楚的例子。

【讨论】:

  • 您的解决方案不符合 WPF 中任何已建立的良好实践和模式。您应该使用 DataBinding 和适当的 ViewModel,而不是像这样将代码耦合到 UI。
  • HighCore:这只是一个简单的示例,其想法是发布更改视图的操作(在同步竞赛中)(例如:更改绑定到视图项的属性值) .
  • It is just a simple example - 您的简单示例不符合 WPF 中任何已建立的良好实践和模式。您应该使用 DataBinding 和适当的 ViewModel,而不是像这样将代码耦合到 UI。
  • 这个idea id使用的方式是程序员的责任。
  • -或者-您可以删除这个鼓励各种可怕的不良做法的答案,并发布一个符合 WPF 心态而不是过时的 winforms 方法的正确答案。
【解决方案2】:

我会怎么做:

  • 为项目添加IsLoading标志
  • 项目的DataTemplate 应检查该标志,并检查 IsLoading=true 它应该显示一些字幕进度,否则 真实数据
  • 使用IsLoading = TRUE将空项目对象添加到ObservableCollection, 然后开始在另一个线程上检索项目数据
  • 检索项目数据后,在 UI 线程中设置IsLoading = FALSE
  • 该项目应实现“INotifyPropertyChanged”并且所有可见 属性应该发送 PropertyChanged 事件

【讨论】:

【解决方案3】:

您有两种选择来实现您想要的场景:

  • 使用 BackgroundWorker 并实施它。 ProgressChanged 如果您想在进度中使用它,将是合适的。 DoWork 将是您数据的重载。 CompletedEvent 是您完成该任务的结果。

  • 使用 TPL,您可以通过 Task.Factory.StartNew() 立即开始使用它,并将操作指定为 您的数据加载。还有一个是构造函数,你可以传入TaskScheduler.FromCurrentSynchronizationContext(),这样它就可以将它编组到UI Dispatcher

  • Task Parallel Library 的示例

  • BackgroundWorker 的示例

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-15
    • 1970-01-01
    相关资源
    最近更新 更多