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
To make WPF layout faster, you need to enable virtualization. In your code:
ScrollViewer
which wraps all your controls.Replace top-level ItemsControl
with ListBox
:
<ListBox Name="items" HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ... >
Replace StackPanel
in the ListBox
's ItemsPanel
with VirtualizingStackPanel
:
<VirtualizingStackPanel Orientation="Vertical" ScrollUnit="Pixel"
VirtualizationMode="Recycling"/>
This will enable virtualization for top-level items. On my computer, this allows to display 100,000 items within 1 second.
N.B.:
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.
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
<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;
}
}
}