WPF custom layout / virtualization

后端 未结 1 524
囚心锁ツ
囚心锁ツ 2021-02-06 13:10

This what i am trying to achieve with WPF. A textblock as title and below buttons in a wrappanel .The problem is that this needs scrolling etc. I have achieved thi

1条回答
  •  盖世英雄少女心
    2021-02-06 13:56

    To make WPF layout faster, you need to enable virtualization. In your code:

    1. Remove ScrollViewer which wraps all your controls.
    2. Replace top-level ItemsControl with ListBox:

      
      
    3. Replace StackPanel in the ListBox's ItemsPanel with VirtualizingStackPanel:

      
      

    This will enable virtualization for top-level items. On my computer, this allows to display 100,000 items within 1 second.

    N.B.:

    1. While you think that the bottleneck is WPF layout, you may be wrong, as you haven't profiled your application. So while this answers your question, it may not actually solve the problem with the window working slow. Profilers can analyze not only your code, but framework code too. They analyze calls, memory etc., not your sources. They are a great tool to improve your performance and the only true way to find the source of performance issues.

    2. For the love of all that is holy, please, read http://sscce.org! You won't have enough reputation to give to solve all your code issues if you don't try to make your examples short, self-contained and compilable. Just to run your example, I had to create my own view-models, get rid of all irrelevant code, simplify bindings, not to mention all kinds of your own converters, controls and bindings which are nowhere described.

    UPDATED to support .NET 4.0

    public static class PixelBasedScrollingBehavior
    {
        public static bool GetIsEnabled (DependencyObject obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }
    
        public static void SetIsEnabled (DependencyObject obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }
    
        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior),
                new UIPropertyMetadata(false, IsEnabledChanged));
    
        private static void IsEnabledChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var isEnabled = (bool)e.NewValue;
    
            if (d is VirtualizingPanel) {
                if (TrySetScrollUnit(d, isEnabled))
                    return;
                if (!TrySetIsPixelBased(d, isEnabled))
                    throw new InvalidOperationException("Failed to set IsPixelBased or ScrollUnit property.");
            }
            if (d is ItemsControl) {
                TrySetScrollUnit(d, isEnabled);
            }
        }
    
        private static bool TrySetScrollUnit (DependencyObject ctl, bool isEnabled)
        {
            // .NET 4.5: ctl.SetValue(VirtualizingPanel.ScrollUnitProperty, isEnabled ? ScrollUnit.Pixel : ScrollUnit.Item);
    
            var propScrollUnit = typeof(VirtualizingPanel).GetField("ScrollUnitProperty", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
            if (propScrollUnit == null)
                return false;
            var dpScrollUnit = (DependencyProperty)propScrollUnit.GetValue(null);
    
            var assemblyPresentationFramework = typeof(Window).Assembly;
            var typeScrollUnit = assemblyPresentationFramework.GetType("System.Windows.Controls.ScrollUnit");
            if (typeScrollUnit == null)
                return false;
            var valueScrollUnit = Enum.Parse(typeScrollUnit, isEnabled ? "Pixel" : "Item");
    
            ctl.SetValue(dpScrollUnit, valueScrollUnit);
            return true;
        }
    
        private static bool TrySetIsPixelBased (DependencyObject ctl, bool isEnabled)
        {
            // .NET 4.0: ctl.IsPixelBased = isEnabled;
    
            var propIsPixelBased = ctl.GetType().GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance);
            if (propIsPixelBased == null)
                return false;
    
            propIsPixelBased.SetValue(ctl, isEnabled, null);
            return true;
        }
    }
    

    It is necessary to set local:PixelBasedScrollingBehavior.IsEnabled="True" both on ListBox and VirtualizingStackPanel, otherwise scrolling will work in item mode. The code compiles in .NET 4.0. If .NET 4.5 is installed, it will use new properties.

    Working example:

    MainWindow.xaml

    
    
        
            
            
            
        
    
        
            
                
                    
                
    
                
                    
                        
                            
                                
                                    
                                
                            
                        
                    
                
    
                
                    
                        
                            
                                
                                    
                                
                            
                        
                    
                
    
                
                    
                        
                            
                                
                                    
                                        
                                        
                                    
                                    
                                    
                                
                            
                        
                    
                
    
            
    
            
                
                    
                        
                        
                    
    
                    
                        
                            
                        
                        
                            
                        
                        
                            
                        
                    
    
                
            
            
                
                    
                
            
    
        
    
    
    

    MainWindow.xaml.cs

    using System;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace So17371439ItemsLayoutBounty
    {
        public partial class MainWindow
        {
            public ObservableCollection PreferenceGroups { get; private set; }
    
            public MainWindow ()
            {
                var rnd = new Random();
                PreferenceGroups = new ObservableCollection();
                for (int i = 0; i < 100000; i++) {
                    var group = new PreferenceGroup { Name = string.Format("Group {0}", i), SelectionMode = rnd.Next(1, 4) };
                    int nprefs = rnd.Next(5, 40);
                    for (int j = 0; j < nprefs; j++)
                        group.Preferences.Add(new Preference { Name = string.Format("Pref {0}", j), Quantity = rnd.Next(100) });
                    PreferenceGroups.Add(group);
                }
                InitializeComponent();
            }
        }
    
        public class PreferenceGroup
        {
            public string Name { get; set; }
            public int SelectionMode { get; set; }
            public ObservableCollection Preferences { get; private set; }
    
            public PreferenceGroup ()
            {
                Preferences = new ObservableCollection();
            }
        }
    
        public class Preference
        {
            public string Name { get; set; }
            public string GroupId { get; set; }
            public int Quantity { get; set; }
        }
    
        public static class PixelBasedScrollingBehavior
        {
            public static bool GetIsEnabled (DependencyObject obj)
            {
                return (bool)obj.GetValue(IsEnabledProperty);
            }
    
            public static void SetIsEnabled (DependencyObject obj, bool value)
            {
                obj.SetValue(IsEnabledProperty, value);
            }
    
            public static readonly DependencyProperty IsEnabledProperty =
                DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior),
                    new UIPropertyMetadata(false, IsEnabledChanged));
    
            private static void IsEnabledChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var isEnabled = (bool)e.NewValue;
    
                if (d is VirtualizingPanel) {
                    if (TrySetScrollUnit(d, isEnabled))
                        return;
                    if (!TrySetIsPixelBased(d, isEnabled))
                        throw new InvalidOperationException("Failed to set IsPixelBased or ScrollUnit property.");
                }
                if (d is ItemsControl) {
                    TrySetScrollUnit(d, isEnabled);
                }
            }
    
            private static bool TrySetScrollUnit (DependencyObject ctl, bool isEnabled)
            {
                // .NET 4.5: ctl.SetValue(VirtualizingPanel.ScrollUnitProperty, isEnabled ? ScrollUnit.Pixel : ScrollUnit.Item);
    
                var propScrollUnit = typeof(VirtualizingPanel).GetField("ScrollUnitProperty", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                if (propScrollUnit == null)
                    return false;
                var dpScrollUnit = (DependencyProperty)propScrollUnit.GetValue(null);
    
                var assemblyPresentationFramework = typeof(Window).Assembly;
                var typeScrollUnit = assemblyPresentationFramework.GetType("System.Windows.Controls.ScrollUnit");
                if (typeScrollUnit == null)
                    return false;
                var valueScrollUnit = Enum.Parse(typeScrollUnit, isEnabled ? "Pixel" : "Item");
    
                ctl.SetValue(dpScrollUnit, valueScrollUnit);
                return true;
            }
    
            private static bool TrySetIsPixelBased (DependencyObject ctl, bool isEnabled)
            {
                // .NET 4.0: ctl.IsPixelBased = isEnabled;
    
                var propIsPixelBased = ctl.GetType().GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance);
                if (propIsPixelBased == null)
                    return false;
    
                propIsPixelBased.SetValue(ctl, isEnabled, null);
                return true;
            }
        }
    }
    

    0 讨论(0)
提交回复
热议问题