1

i have this situation:

i want to load a collection in my ViewModel in the MainWindow i have a big data so when i enter to the slide of people collection it should wait to finish loading then i can see the full list, i have the follwing:

  • in the xaml code i have:

    <ListView ..... 
    ItemsSource{Binding PeopleList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
    ... />
    
  • and in the viewmode i have:

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

and i want to load all the people list one by one after load the first one completely start loading the second ... etc without blocking the mainwindow i want my window looks like:

UI Example

i tired to do it my self but i fail. thanks before.

Henka Programmer
  • 433
  • 6
  • 18
  • In paragraph one you finish loading then in paragraph two you want to load one by one. What problem are you tying to solve? – paparazzo Apr 16 '14 at 13:17
  • @Blam in the first one i meant loading in the logic, and in the second i meant the loading on the UI. – Henka Programmer Apr 16 '14 at 13:52
  • Once you have the data then why do you want to paint the UI a person at a time. That should be very very fast. With virtualization is only paint what is on the items in view. What problem are you trying to solve? – paparazzo Apr 16 '14 at 14:44
  • @Blam i tired the visualization but also it is failed. cause i have a big data, in every loading time when i visit the persons list, and the items template is very big also. – Henka Programmer Apr 16 '14 at 15:50
  • Post your full code and XAML, and a `clear`, specific explanation of your current problem. – Federico Berasategui Apr 16 '14 at 15:51
  • 1
    Virtualization not visualization. Paint one control at a time is not the correct way to address a slow rendering. – paparazzo Apr 16 '14 at 16:03

3 Answers3

2

There exist ways for to modify view from another thread, using the SynchronizationContext.

Please see this example:

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

And this is the view:

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

This code, updates several times the view (a button in this case). I think this solve your initial question.

----EDIT----

This is a better way of using this idea: I propose to create a method like this in a base view model:

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

In this way any child view model can use this for excecuting big amount of data processing, and the UI does not get freeze. IsBusy and BusyText are the view models variables wich are bound to the View Wait Message and the Visibility of a Wait Element. This is an example of use in a child view model's command:

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

Hope this will become a more clear example.

Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65
  • Your solution does not conform to any of the established good practices and patterns in WPF. You should be using DataBinding and a proper ViewModel instead of having your code coupled to the UI like this. – Federico Berasategui Apr 16 '14 at 15:49
  • HighCore: It is just a simple example, the idea is to post the actions (in the sync contest) that change the view (for instace: change the properties values that are bound to the view items). – Raúl Otaño Apr 16 '14 at 17:28
  • 1
    `It is just a simple example` - your simple example does not conform to any of the established good practices and patterns in WPF. You should be using DataBinding and a proper ViewModel instead of having your code coupled to the UI like this. – Federico Berasategui Apr 16 '14 at 17:29
  • The way this idea id used is programmer responsabillity. – Raúl Otaño Apr 16 '14 at 17:30
  • -Or- you can delete this answer which encourages all sorts of horribly bad practices and post a proper answer which conforms to the WPF mentality rather than an archaic winforms approach. – Federico Berasategui Apr 16 '14 at 17:31
  • +1 for a properly designed approach and a comprehensive answer. – Federico Berasategui Apr 16 '14 at 18:06
1

The way I would do it:

  • add IsLoading flag to item
  • the DataTemplate for the item should check for that flag, and for IsLoading=true it should show some marquee progress, otherwise the real data
  • add empty item object to ObservableCollection with IsLoading = TRUE, then start retrieving item data on another thread
  • after retrieving item data, in the UI thread set the IsLoading = FALSE
  • the item should implement 'INotifyPropertyChanged' and all visible properties should send PropertyChanged event
amnezjak
  • 2,011
  • 1
  • 14
  • 18
  • thanks for ur answer please could u see this [problem after do some thing like what u said](http://stackoverflow.com/questions/23228046/why-my-listview-re-render-all-items-at-every-itemscollection-changing) – Henka Programmer Apr 22 '14 at 19:18
0

You have two options to achieve your desired scenario:

  • Use BackgroundWorker and implement it. The ProgressChanged will be appropriate if you want to use it with a progress. The DoWork will be your heavy loading of your data. The CompletedEvent is your finish up on that task.

  • Use TPL, you can start using it right away with Task.Factory.StartNew() and give the action as your data load. And one one it's constructor you can pass in TaskScheduler.FromCurrentSynchronizationContext() so it can marshall it to the UI Dispatcher.

  • Sample for Task Parallel Library

  • Sample for BackgroundWorker

123 456 789 0
  • 10,565
  • 4
  • 43
  • 72