Laden...

Schlechte Performance des ItemsControls trotz Virtualisierung

Erstellt von nicky vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.794 Views
N
nicky Themenstarter:in
232 Beiträge seit 2011
vor 6 Jahren
Schlechte Performance des ItemsControls trotz Virtualisierung

Hallo,

die Performance meines ItemsControls ist schlecht. Ich habe ein recht komplexes Template und versuche das bereits zu vereinfachen. Mein erster Eindruck ist jedoch das hier nicht viel zu holen ist, jedes Item besteht aus einigen Steuerelementen und Bindings.

Ich habe die Virtualisierung des ItemsControl bereits ausprobiert.

Mit Virtualisierung: Schnelles initiales Laden, sehr hakliges scrollen
Ohne Virtualisierung: Langsames initiales Laden, flüssiges scrollen

Beides ist auf Grund der Nachteile nicht wirklich brauchbar.

Ich suche einen Kompromiss. Zu Beginn möchte ich durch Virtualisierung ein schnelles Laden erreichen. Es werden also nur die Items in den Arbeitsspeicher geladen die auch tatsächlich angezeigt werden. Danach sollen die restlichen Items im Hintergrund in den Arbeitsspeicher geladen werden, sodass das Scrollen flüssig geschehen kann.

Hat jemand eine Idee oder kennt ein Control was das so oder so ähnlich umsetzt?

nicky

849 Beiträge seit 2006
vor 6 Jahren

Nur eine Frage,

klingt jetzt bestimmt komisch, aber hast Du schon mal ohne Debugger einen Release Build probiert?
Da hab ich schon Leute dran verzweifeln sehen.

T
461 Beiträge seit 2013
vor 6 Jahren

Hallo,

weiß zwar nicht in welchem Ausmaß dein Control liegt und was du schon alles ausgebessert hast aber es gäbe unter Umständen die Möglichkeit, bei statischen Anzeigen diese durch selbst zeichnen, im Gegensatz zu (Steuer)Controls, zu verwenden.

Dadurch fallen einige, in dem Fall unnötige Elemente(Initiierungen) weg (pro Ctrl), die dann so oder so nie gebraucht werden, weniger Speicher verbraten aber auch schneller aufgebaut werden können.

Grüße

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

N
nicky Themenstarter:in
232 Beiträge seit 2011
vor 6 Jahren

Hallo unconnected, danke die Release Version macht jedoch ähnliche Probleme.

Hallo ThomasE., was genau meinst du mit selber zeichnen, hast du da ein Beispiel?

T
461 Beiträge seit 2013
vor 6 Jahren

Im Grunde ist es schwer zu beurteilen, da du doch noch wenig Informationen geliefert hast.

Was ich meine ist das, daß man auf fertige Controls verzichtet (wo es möglich ist) und stattdessen selbst zeichnet. Kann allerdings um einiges mehr Aufwand sein (je nach Anforderung), doch kann es sich durchaus positiv auf die Leistung auswirken.

Hier den ersten Link den ich gefunden habe in 2Sekunden: https://docs.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/shapes-and-basic-drawing-in-wpf-overview

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

N
nicky Themenstarter:in
232 Beiträge seit 2011
vor 6 Jahren

Hi, ich zeichne Button jetzt selbst, finde aber die Performance immer noch nicht optimal. Es hakelt noch immer. Anbei mal der Code, wohl des Buttons als auch des ItemsControls.

Sind hier noch Schwachpunkte hinsichtlich der Performance zu erkennen?

public class BetButtonPerformance : FrameworkElement
    {
        private const int WIDTH = 62;
        private const int HEIGHT = 62;

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            if (!IsLocked)
            {
                IsChecked = !IsChecked;

                if (Command != null)
                    Command.Execute(CommandParameter);
                base.OnMouseDown(e);

                InvalidateVisual();
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(WIDTH, HEIGHT);
        }

        protected override void OnRender(DrawingContext dc)
        {
            DrawBackground(dc);
            if (!IsLocked)
                DrawText(dc);
            else
                DrawLock(dc);
        }

        private void DrawBackground(DrawingContext dc)
        {
            var margin = 1;
            var backgroundGradient = new GradientStopCollection
                                    {
                                        new GradientStop(Color.FromArgb(255, 0xf7, 0xf6, 0xfb), 0.0),
                                        new GradientStop(Color.FromArgb(255, 0xe6, 0xe5, 0xe3), 0.5),
                                        new GradientStop(Color.FromArgb(255, 0xd3, 0xd3, 0xd3), 1.0),
                                    };
            Brush background = new LinearGradientBrush(backgroundGradient);

            if (IsChecked)
            {
                var converter = new System.Windows.Media.BrushConverter();
                background = (Brush)converter.ConvertFromString("#FF00A75D");
            }

            dc.DrawRectangle(
                background,
                null,
                new Rect(new Point(0 + margin, 0 + margin), new Size(ActualWidth - margin * 2, ActualHeight - margin * 2)));
        }

        private void DrawText(DrawingContext dc)
        {
            if (formattedText != null)
            {
                if (IsChecked)
                    formattedText.SetForegroundBrush(Brushes.White);
                else
                    formattedText.SetForegroundBrush(Brushes.Black);

                var centerPoint = new Point(WIDTH / 2, HEIGHT / 2);
                Point textLocation = new Point(centerPoint.X - formattedText.WidthIncludingTrailingWhitespace / 2, centerPoint.Y - formattedText.Height / 2);
                dc.DrawText(formattedText, textLocation);
            }
        }

        private void DrawLock(DrawingContext dc)
        {
            var margin = 20;
            var destination = new Rect(new Point(0 + margin, 0 + margin), new Size(ActualWidth - margin * 2, ActualHeight - margin * 2));

            var imageSource = new BitmapImage(new Uri(@"pack://application:,,,/Media/IconLock.png"));
            dc.DrawImage(imageSource, destination);
        }

        private FormattedText formattedText;

        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
             DependencyProperty.Register(
                 "Value",
                 typeof(double),
                 typeof(BetButtonPerformance),
                 new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure,
                    (o, e) => ((BetButtonPerformance)o).ValuePropertyChanged((double)e.NewValue)));

        private void ValuePropertyChanged(double value)
        {
            var typeface = new Typeface(
                new FontFamily("Segoe UI"),
                FontStyles.Normal, FontWeights.Normal, FontStretches.Normal
            );

            formattedText = new FormattedText(
                String.Format("{0:N2}", value),
                CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                typeface,
                16,
                Brushes.Black
            );
        }

        public bool IsChecked
        {
            get { return (bool)GetValue(IsCheckedProperty); }
            set { SetValue(IsCheckedProperty, value); }
        }

        public static readonly DependencyProperty IsCheckedProperty =
            DependencyProperty.Register("IsChecked", typeof(bool), typeof(BetButtonPerformance), new PropertyMetadata(false, IsCheckedCallback));

        private static void IsCheckedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is BetButtonPerformance)
            {
                (d as BetButtonPerformance).InvalidateVisual();
            }
        }

        public bool IsLocked
        {
            get { return (bool)GetValue(IsLockedProperty); }
            set { SetValue(IsLockedProperty, value); }
        }

        public static readonly DependencyProperty IsLockedProperty =
            DependencyProperty.Register("IsLocked", typeof(bool), typeof(BetButtonPerformance), new PropertyMetadata(false));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public static DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(BetButtonPerformance));

        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        public static DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(BetButtonPerformance));
    }

<ItemsControl ItemsSource="{Binding Events}"
							Margin="0 50 0 0"
							VirtualizingStackPanel.IsVirtualizing="True" 
							VirtualizingStackPanel.VirtualizationMode="Standard">
	<ItemsControl.ItemsPanel>
			<ItemsPanelTemplate>
					<VirtualizingStackPanel/>
			</ItemsPanelTemplate>
	</ItemsControl.ItemsPanel>
	<ItemsControl.ItemTemplate>
			<DataTemplate>
					<StackPanel>
							<Grid>
									<Grid.ColumnDefinitions>
											<ColumnDefinition Width="60"/>
											<ColumnDefinition Width="70"/>
											<ColumnDefinition Width="*"/>
											<ColumnDefinition Width="*"/>
											<ColumnDefinition Width="60"/>
									</Grid.ColumnDefinitions>
									<StackPanel Grid.Column="3" Orientation="Horizontal">
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimaryFirst}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimarySecond}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimaryThird}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimaryFourth}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimaryFifth}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
											<controls:BetButtonPerformance DataContext="{Binding BetsPrimarySixth}"
																										 Value="{Binding BetValue, Mode=OneWay}" 
																										 IsChecked="{Binding IsChecked, Mode=TwoWay}"
																										 IsLocked="{Binding IsLocked, Mode=OneWay}"
																										 Command="{Binding DataContext.ToggleBetCommand, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
																										 CommandParameter="{Binding ., Mode=OneWay}"/>
									</StackPanel>
							</Grid>
							<Rectangle Margin="0 0 0 2" Height="0.5" Fill="Gray"/>
					</StackPanel>
			</DataTemplate>
	</ItemsControl.ItemTemplate>
	<ItemsControl.Template>
			<ControlTemplate>
					<Border BorderThickness="{TemplateBinding Border.BorderThickness}"
									Padding="{TemplateBinding Control.Padding}"
									BorderBrush="{TemplateBinding Border.BorderBrush}"
									Background="{TemplateBinding Panel.Background}"
									SnapsToDevicePixels="True">
							<ScrollViewer IsDeferredScrollingEnabled="False" 
														CanContentScroll="True" 
														Padding="{TemplateBinding Padding}">
									<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
							</ScrollViewer>
					</Border>
			</ControlTemplate>
	</ItemsControl.Template>
</ItemsControl>
4.931 Beiträge seit 2008
vor 6 Jahren

Du könntest die in den Zeichenmethoden (z.B. DrawBackground) konstanten Objekte (z.B. "Brush background") als (statische) Membervariablen deklarieren und im (statischen) Konstruktor setzen, so daß nicht jedesmal diese Objekte neu erzeugt werden müssen (und dann der GC danach wieder aufräumen muß).