WPF自定义布局/虚拟化自定义、布局、WPF

2023-09-03 11:37:01 作者:昔年°

这正是我试图实现与WPF。一个TextBlock的标题和下面的 WrapPanel中。问题的按钮是,这需要滚动等。 我已经使用这个ItemsControl的和各组结合来实现。 我有,有一个StackPanel中作为paneltemplate一个ItemsControl其ItemTemplate中是一个文本块和一个WrapPanel中。

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 this using ItemsControl and binding for each group. I have an ItemsControl that has a stackpanel as paneltemplate and its itemtemplate is a textblock and a wrappanel .

它的工作原理,但它是在实例化时慢了Intel GMA +原子机器速度慢时,项目很多。看来,使心不是可视化树的问题,而是创造。 所以,我在这里只选择是创建一个自定义面板与虚拟化我猜?

It works but it is slow at instantiation at slow intel gma + atom machines when items are many . It seems that rendering isnt the problem but creation of the Visual Tree. So my only bet here is to create a custom panel with virtualization i guess?

下面是我做了什么。 http://pastebin.com/u8C7ddP0 上面的解决方案是在一些机器慢

Here is what i have done. http://pastebin.com/u8C7ddP0 Above solution is slow at some machines.

我要寻找一个解决方案,将采取最高100毫秒,在慢的机器制造。 谢谢

I am looking for a solution that it would take max 100ms at slow machines to create. Thank you

更新

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

推荐答案

要进行WPF的布局速度更快,需要启用虚拟化。在您的code:

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

删除的ScrollViewer 它包装所有控件。

替换顶级的ItemsControl 列表框: Remove ScrollViewer which wraps all your controls.

Replace top-level ItemsControl with ListBox:

<ListBox Name="items" HorizontalContentAlignment="Stretch"
         ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... >

替换的StackPanel 列表框 ItemsPanel VirtualizingStackPanel

Replace StackPanel in the ListBox's ItemsPanel with VirtualizingStackPanel:

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

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

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

N.B:

虽然你认为瓶颈是WPF的布局,你可能是错误的,因为你没有异形您的应用程序。因此,尽管这个回答你的问题,它可能无法真正解决与窗口工作慢的问题。分析器可以分析不仅是你的code,但框架code了。他们分析通话,内存等,而不是你的源代码。他们是一个很好的工具,以提高你的表现,唯一正确的方法来找到性能问题的来源。 WPF and Silverlight 学习笔记 八 WPF布局管理之Grid

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.

有关所有的爱情是神圣的,请阅读 http://sscce.org !你不会有足够的信誉来给您解决所有code问题,如果你不试着让你的例子短,自足和编译。只要运行你的榜样,我要创建我自己的看法,车型,摆脱了所有无关的code,简化了绑定,更不用提各种这是无处描述自己的转换器,控制器和绑定。

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.

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

这是需要设置地方:PixelBasedScrollingBehavior.IsEnabled =真无论在列表框 VirtualizingStackPanel ,否则滚动将工作在项目模式。在code编译在.NET 4.0中。如果.NET 4.5安装,它将使用新的属性。

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.

工作的例子:

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