如何在Binding中加载控件而不阻止UI?

时间:2014-04-16 12:50:26

标签: c# wpf multithreading binding

我遇到这种情况:

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

    我在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");}}
    // ...
    

并且我想在加载第一个完全开始加载第二个...之后逐个加载所有人列表而不阻止主窗口我希望我的窗口看起来像:

UI Example

我厌倦了这样做,但我失败了。谢谢。

3 个答案:

答案 0 :(得分:2)

存在使用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>

此代码会多次更新视图(在本例中为按钮)。我认为这可以解决你的初步问题。

<强> ---- ---- EDIT

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

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

希望这将成为一个更明确的例子。

答案 1 :(得分:1)

我会这样做:

  • IsLoading标志添加到项目
  • 该项目的DataTemplate应检查该标志,并为 IsLoading=true它应该显示一些重大进展,否则 真实数据
  • 使用IsLoading = TRUE将空项对象添加到ObservableCollection, 然后开始在另一个线程上检索项目数据
  • 检索项目数据后,在UI线程中设置IsLoading = FALSE
  • 该项目应实施&#39; INotifyPropertyChanged&#39;一切都可见 属性应该发送PropertyChanged事件

答案 2 :(得分:0)

您有两种方法可以实现您想要的方案:

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

  • 使用 TPL ,您可以立即开始使用Task.Factory.StartNew()并将操作设为 你的数据加载。其中一个是构造函数,您可以传递TaskScheduler.FromCurrentSynchronizationContext(),以便它可以将其编组到UI Dispatcher

  • Task Parallel Library

  • 的示例
  • BackgroundWorker

  • 的示例