爲了讓WPF佈局速度快,就需要啓用虛擬化。在您的代碼中:
- 刪除
ScrollViewer
包裝所有控件。
更換頂級ItemsControl
與ListBox
:
<ListBox Name="items" HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... >
在更換StackPanel
的ListBox
的ItemsPanel
與VirtualizingStackPanel
:
<VirtualizingStackPanel Orientation="Vertical" ScrollUnit="Pixel"
VirtualizationMode="Recycling"/>
這將啓用頂級項目的虛擬化。在我的電腦上,這允許在1秒內顯示100,000個項目。
N.B:
當你認爲瓶頸是WPF佈局,你可能是錯誤的,因爲你還沒有成型的應用程序。所以,雖然這回答你的問題,但實際上可能無法解決窗口工作緩慢的問題。 Profiler不僅可以分析您的代碼,還可以分析框架代碼。他們分析電話,記憶等,而不是你的來源。它們是改善性能的絕佳工具,也是找到性能問題根源的唯一正確途徑。
對於所有神聖的愛,請閱讀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"
上都ListBox
和VirtualizingStackPanel
這是必要的,否則會滾動項模式下工作。代碼在.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;
}
}
}
有很多在你的代碼附加屬性的命名空間,從它們從未提及。這特別看起來很可疑:'cal:Message.Attach =「[Event Checked] = [Action AddPreference($ dataContext,false)]; [未事件] = [Action RemovePreference($ datacontext,false)]」'。你永遠不會提到你綁定的對象的性質和確切數量。總而言之,代碼太多了。請參閱http://sscce.org/ 您爲什麼認爲虛擬化是瓶頸?你有沒有分析你的應用程序? – Athari
這是caliburn micro的事件。實際上,它沒有綁定到列表的代碼。與你看到的屬性。項目並不總是很多。 – GorillaApe
在詢問如何提高性能之前,您需要分析您的代碼。請參閱[c#profiler](https://www.google.com/search?q=c%23+profiler)。 – Athari