0

By clicking on the button, a large number (about 200 pieces) of UserControl is created (the collection bind to ItemsControl on the MVVM principle), as a result of which the application freezes for 5 seconds. Is it possible to make this process painless? At the moment in the creation loop I use Task.Delay (100); The application hangs slightly, but it is possible to continue working.

Using:

<ItemsControl ItemsSource="{Binding Path=CurrentSession.Messages}" Background="Transparent" BorderThickness="0">
  <ItemsControl.ItemTemplate>
     <DataTemplate>
       <local:ChatMessageControl/>
     </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

XAML UserControl: (Other collections used in this control are disabled. They are only in XAML and are always empty for the duration of the test)

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>
        <Grid Grid.Column="0">
            <Border Height="45" Width="45" CornerRadius="50,50,50,50" Visibility="{Binding NeedIcon}" VerticalAlignment="Top" Margin="0 10">
                <Border.Background>
                <ImageBrush ImageSource="{Binding Icon, FallbackValue={StaticResource DefaultImage},
                                TargetNullValue={StaticResource DefaultImage}}"/>
            </Border.Background>
        </Border>
         </Grid>
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Vertical">
                <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0 2 0 0" Visibility="{Binding NeedIcon}">
                    <TextBlock Text="{Binding Name,FallbackValue=Name}" Foreground="White" FontSize="16" Margin="10 10 10 0"/>
                    <TextBlock Text="{Binding Time, FallbackValue=0:00}" Foreground="#bdbebd" HorizontalAlignment="Center" Margin="0 10" />
                </StackPanel>
                <TextBlock Grid.Row="0" Text="{Binding Message,FallbackValue=Text}" HorizontalAlignment="Left" FontSize="14" Visibility="{Binding HasMessage, Converter={StaticResource converter}}" TextWrapping="Wrap" Margin="10 0 0 0"/>
                <StackPanel Grid.Row="1" Visibility="{Binding HasImage, Converter={StaticResource converter}}" HorizontalAlignment="Left" Margin="0 10">
                    <ItemsControl ItemsSource="{Binding Images}" Background="Transparent" BorderThickness="0" Margin="10 0 0 0">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <UniformGrid Columns="3" MaxWidth="600"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <local:ImageUserControl/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
                <StackPanel Grid.Row="1" Visibility="{Binding HasOtherDocs, Converter={StaticResource BoolToVis}}">
                    <local:OtherDocsUserControl DataContext="{Binding OtherDocs}"/>
                </StackPanel>
                <StackPanel Grid.Row="2" Margin="0 5" Visibility="{Binding HasAudio, Converter={StaticResource BoolToVis}}" HorizontalAlignment="Left">
                    <ItemsControl ItemsSource="{Binding Audios}" Background="Transparent" BorderThickness="0" Margin="10 0 0 0">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel Orientation="Horizontal"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackPanel>
                                    <local:AudioMessageUserControl/>
                                </StackPanel>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </StackPanel>
            </StackPanel>
        </Grid>

Loop.Event subscription

private async void AddMessages(List<DialogMessageModel> msg)
        {
            App.Current.Dispatcher.Invoke((Action)async delegate
            {
                msg.Reverse();
                for (int i = 0; i < msg.Count; i++)
                {
                    await Task.Delay(50);
                        CurrentSession.Messages.Add(msg[i]);
                }
            });
        }
Ricarnev
  • 27
  • 6
  • Instead of executing your for loop as a whole on the Dispatcher thread, schedule `CurrentSession.Messages.Add...` on the Dispatcher thread alone. Keep your for loop and the msg.Reverse thing away from the UI thread. It won't make it faster, but it should prevent the UI thread from blocking while the for loop is running... –  Feb 20 '19 at 16:52
  • Also, you might not need to use Dispatcher.Invoke at all. Just add your items to the CurrentSession.Messages collection (i suppose it is an ObservableCollection) in a background thread (see this answer: https://stackoverflow.com/a/14602121/2819245) –  Feb 20 '19 at 16:56
  • Another option is to create a new messages collection with the messages completely in the background thread, and once this collection is created and filled, simply assign it to the CurrentSession.Messages property (don't forget property change notification for this property) –  Feb 20 '19 at 16:59
  • If the number of added UserControls makes your UI sluggish even after adding them to the CurrentSession.Messages collection has been finished, you might want to look into UI virtualization (https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/optimizing-performance-controls). There are some additional things to be aware of when trying to use UI visualization with a simple ItemsControl, though (https://stackoverflow.com/questions/2783845/virtualizing-an-itemscontrol) –  Feb 20 '19 at 17:04
  • @elgonzo create a new messages collection with the messages completely in the background thread? You mean I should create ObservableCollection in method then assign it to ObservableCollection which bind? – Ricarnev Feb 20 '19 at 17:21
  • 3
    Try virtualization. Start with a ListBox, which virtualizes by default. If you really need only an ItemsControl, take a look at this: https://stackoverflow.com/questions/2783845/virtualizing-an-itemscontrol – Clemens Feb 20 '19 at 17:32
  • @Ricarnev, technically, it's an option. Whether doing this gels well with your code or whether doing so is not fitting well into to your code is up to you to see, judge and decide. –  Feb 20 '19 at 17:44
  • @elgonzo so what way is the smoothest for GUI? In your opinion – Ricarnev Feb 20 '19 at 20:31
  • They are all kind of smooth to different degrees. The small differences in "smoothness" will likely not matter, thus i recommend you favour an approach that you can easily and correctly(!!!) implement. –  Feb 20 '19 at 20:37
  • @elgonzo I apologize for the obsessive questions, I'm just starting to learn with #. Which of the ways is the easiest to implement? – Ricarnev Feb 20 '19 at 20:40
  • Creating and filling a new collection in the background thread, and after having it completely filled, assign it to `CurrentSession.Messages` is the easiest to implement... (just my opinion, of course). But i would suggest you simply try for yourself. If one approach is turning out to be too difficult for you to implement (or you feel it leads you into a dead end), nothing stops you from switching and trying another approach... –  Feb 20 '19 at 20:49
  • @elgonzo Kind of: var ObservableCollection--->Fill it by items what we got--->Assign this Observable to our binding Observable? – Ricarnev Feb 20 '19 at 20:53
  • Yep... all done in a background thread... –  Feb 20 '19 at 20:56
  • @elgonzo and last question... Should I change ItemsControl to ListBox? Or doesn't matter? – Ricarnev Feb 20 '19 at 21:03
  • Right now, keep ItemsControl (don't try to change too much in your code at once. You would only make it easier for you to get lost in your own code if you are "dancing at too many parties" at once). Only start worrying about ItemsControl if the ItemsControl itself is becoming a problem/obstacle/unfit for what you want to achieve... –  Feb 20 '19 at 21:05
  • @elgonzo thank you very much! Can you make answer? I will choise as best way. You can just copy your text – Ricarnev Feb 20 '19 at 21:12
  • Use virtualization which renders items dynamically based on user interaction. By default ItemsControl doesn't virtualize items (https://stackoverflow.com/questions/2783845). I think the easiest would be to use a ListView control which virtualizes by default. You can also follow the advise in above SO question – Mohammad Feb 21 '19 at 09:46

1 Answers1

-2

You can try to implement a kind of Application.DoEvents() - its deprecated in WPF - I would suggest you to use BackgroundWorker or Dispatcher

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
Soufiane Tahiri
  • 171
  • 1
  • 15