【问题标题】:WPF custom layout / virtualizationWPF 自定义布局/虚拟化
【发布时间】:2014-02-25 19:16:04
【问题描述】:

这是我试图用 WPF 实现的。 wrappanel 中作为标题和下方按钮的文本块。问题是这需要滚动等。 我已经使用 ItemsControl 和每个组的绑定来实现这一点。 我有一个 ItemsControl,它有一个 stackpanel 作为 paneltemplate,它的 itemtemplate 是一个文本块和一个 wrappanel。

它可以工作,但是当项目很多时,在慢速 intel gma + atom 机器上实例化很慢。似乎渲染不是问题,而是视觉树的创建。 所以我唯一的选择是创建一个带有虚拟化的自定义面板?

这是我所做的。 http://pastebin.com/u8C7ddP0
上述解决方案在某些机器上运行缓慢。

我正在寻找一种解决方案,在慢速机器上创建最多需要 100 毫秒。 谢谢

更新

 public class PreferenceCheckedConvertor : IMultiValueConverter
    {


    public object Convert(object[] values, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
    {

        var preference = values[0] as OrderItemPreference;
        var items = values[1] as ObservableCollection<OrderItemPreference>;

        var found = items.FirstOrDefault(item => item.Preference.Id == preference.Preference.Id);
        if (found == null)
        {
            return false;
        }
        return true;

    }
    public object[] ConvertBack(object value, Type[] targetTypes,
           object parameter, System.Globalization.CultureInfo culture)
    {
        try
        {
            return null;
        }
        catch (Exception e)
        {
            return null;
        }
    }


}

ff

public class PreferenceConvertor : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType,
                object parameter, System.Globalization.CultureInfo culture)
        {
            var preferences=values[0] as IEnumerable<Preference>;
            var items=values[1] as ObservableCollection<OrderItemPreference>;

            var newList = new List<OrderItemPreference>(preferences.Count());



            foreach (var preference in preferences)
            {
                var curItem = items.FirstOrDefault(item => item.Preference.Id == preference.Id);

                if (curItem == null)
                {
                    newList.Add(new OrderItemPreference()
                    {
                        Preference = preference
                    });
                }
                else
                {
                    newList.Add(curItem);
                }

            }

            return newList;







        }
        public object[] ConvertBack(object value, Type[] targetTypes,
               object parameter, System.Globalization.CultureInfo culture)
        {
            try
            {
                return null;
            }
            catch (Exception e)
            {
                return null;
            }
        }}

【问题讨论】:

  • 您的代码中有很多来自命名空间的附加属性,这些属性从未被提及。这看起来特别可疑:cal:Message.Attach="[Event Checked]=[Action AddPreference($dataContext,false)]; [Event Unchecked]=[Action RemovePreference($datacontext,false)]"。您从未提及您绑定到的对象的性质和确切数量。总而言之,代码太多了。见sscce.org 为什么你认为虚拟化是瓶颈?您是否分析过您的应用程序?
  • 这是 caliburn micro 的活动。实际上,它没有绑定到列表的代码。与您看到的属性。项目并不总是很多。
  • 在询问如何提高性能之前,您需要分析您的代码。见c# profiler
  • @Athari 我删除了 cal:Message.Attach 并看到了改进。好的,您是在告诉我要分析没有任何 c# 代码的东西吗?它可以分析 xaml 速度吗?

标签: c# .net wpf virtualization


【解决方案1】:

要使 WPF 布局更快,您需要启用虚拟化。在您的代码中:

  1. 删除包裹所有控件的ScrollViewer
  2. ListBox替换顶级ItemsControl

    <ListBox Name="items" HorizontalContentAlignment="Stretch"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... >
    
  3. ListBoxItemsPanel 中的StackPanel 替换为VirtualizingStackPanel

    <VirtualizingStackPanel Orientation="Vertical" ScrollUnit="Pixel"
                            VirtualizationMode="Recycling"/>
    

这将为顶级项目启用虚拟化。在我的电脑上,这允许在 1 秒内显示 100,000 个项目。

注意:

  1. 虽然您认为瓶颈是 WPF 布局,但您可能错了,因为您没有分析您的应用程序。因此,虽然这回答了您的问题,但它实际上可能无法解决窗口工作缓慢的问题。 Profiler 不仅可以分析您的代码,还可以分析框架代码。他们分析呼叫、内存等,而不是您的来源。它们是提高绩效的绝佳工具,也是找出绩效问题根源的唯一真正方法。

  2. 为了所有神圣的爱,请阅读http://sscce.org!如果您不尝试使您的示例简短、独立且可编译,那么您将没有足够的声誉来解决您所有的代码问题。为了运行您的示例,我必须创建自己的视图模型,摆脱所有不相关的代码,简化绑定,更不用说您自己的各种转换器、控件和绑定,这些都没有描述。

    李>

已更新以支持 .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;
    }
}

local:PixelBasedScrollingBehavior.IsEnabled="True"必须同时设置在ListBoxVirtualizingStackPanel上,否则滚动会在项目模式下工作。代码在 .NET 4.0 中编译。如果安装了 .NET 4.5,它将使用新属性。

工作示例:

MainWindow.xaml

<Window x:Class="So17371439ItemsLayoutBounty.MainWindow" x:Name="root"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:So17371439ItemsLayoutBounty"
        Title="MainWindow">

    <Window.Resources>
        <Style x:Key="OrderRadioButton" TargetType="{x:Type RadioButton}"></Style>
        <Style x:Key="OrderCheckboxButton" TargetType="{x:Type ToggleButton}"></Style>
        <Style x:Key="OrderProductButton" TargetType="{x:Type Button}"></Style>
    </Window.Resources>

    <ListBox Name="items" ItemsSource="{Binding PreferenceGroups, ElementName=root}" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" local:PixelBasedScrollingBehavior.IsEnabled="True">
        <ItemsControl.Resources>
            <ItemsPanelTemplate x:Key="wrapPanel">
                <WrapPanel/>
            </ItemsPanelTemplate>

            <DataTemplate x:Key="SoloSelection" DataType="local:PreferenceGroup">
                <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <RadioButton Width="146" Height="58" Margin="0,0,4,4" GroupName="{Binding GroupId}" Style="{StaticResource OrderRadioButton}">
                                <TextBlock Margin="4,0,3,0" VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Name}"/>
                            </RadioButton>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>

            <DataTemplate x:Key="MultiSelection" DataType="local:PreferenceGroup">
                <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <ToggleButton Width="146" Height="58" Margin="0,0,4,4" Style="{StaticResource OrderCheckboxButton}">
                                <TextBlock Margin="4,0,3,0" VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Name}"/>
                            </ToggleButton>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>

            <DataTemplate x:Key="MultiQuantitySelection" DataType="local:PreferenceGroup">
                <ItemsControl ItemsSource="{Binding Preferences}" ItemsPanel="{StaticResource wrapPanel}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid Width="146" Height="58" Margin="0,0,4,4">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Button Name="quantity" Background="White" Width="45" Style="{StaticResource OrderProductButton}">
                                    <TextBlock Text="{Binding Quantity}"/>
                                </Button>
                                <Button Margin="-1,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Style="{StaticResource OrderProductButton}">
                                    <TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" TextTrimming="CharacterEllipsis" Text="{Binding Name}"/>
                                </Button>
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>

        </ItemsControl.Resources>

        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock FontSize="25" FontWeight="Light" Margin="0,8,0,5" Text="{Binding Name}"/>
                    <ContentControl Content="{Binding}" Name="items"/>
                </StackPanel>

                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding SelectionMode}" Value="1">
                        <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource SoloSelection}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding SelectionMode}" Value="2">
                        <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource MultiSelection}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding SelectionMode}" Value="3">
                        <Setter TargetName="items" Property="ContentTemplate" Value="{StaticResource MultiQuantitySelection}"/>
                    </DataTrigger>
                </DataTemplate.Triggers>

            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel x:Name="panel" Orientation="Vertical" VirtualizationMode="Recycling" local:PixelBasedScrollingBehavior.IsEnabled="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

    </ListBox>

</Window>

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<PreferenceGroup> PreferenceGroups { get; private set; }

        public MainWindow ()
        {
            var rnd = new Random();
            PreferenceGroups = new ObservableCollection<PreferenceGroup>();
            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<Preference> Preferences { get; private set; }

        public PreferenceGroup ()
        {
            Preferences = new ObservableCollection<Preference>();
        }
    }

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

【讨论】:

  • 好的,但这是有影响的,因为有时按钮不适合屏幕(就像一个有很多偏好的偏好组)。所以如果没有虚拟化 wrappanel 它会跳转。在移除那个附加属性并做了一些 tweeks 并修复了一个正在触发的计时器之后,我得到了更好的性能。但是,在某些平台上存在滞后。我对 ATOM 和 GMA GPU 有疑问。但即使使用 Pentium 4 和旧的 Matrox g450 卡也没有延迟..
  • “不切实际”到底是什么意思?如果您的示例和我的示例之间的行为有任何差异,请描述它。请记住,我无法运行您的示例(请参阅第二个 N.B.)。什么“那个”附加属性?什么计时器?哦,拜托,你真的希望我读懂你的想法吗?如果您不提供任何信息,我将无法帮助您。你甚至没有说你需要展示多少个项目。
  • 你的例子和我的很接近。我的意思是 90% 除了造型。要运行我的示例,这将非常困难,因为我必须包含许多文件。但是我将使用转换器进行更新。我所说的附加财产是指您在对我的问题发表第一条评论时告诉我的。项目数量各不相同。我的意思是有些用户从 5 到 100。另外因为我没有提到我没有使用 WPF 4.5,因为我不能支持 WIN XP 所以没有像素滚动:(。试过但没有像这里提到的那样工作@987654322 @
  • 我没有在您的代码中看到与像素滚动相关的附加属性。如果不起作用,请提供代码。
  • 您使用了 ScrollUnit="Pixel" 我使用了这种行为 stackoverflow.com/a/9875475/294022 并将 ScrollUnit="Pixel" 替换为此答案中的内容。但是没有用
猜你喜欢
  • 1970-01-01
  • 2013-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多