Ok. Delete all your code and start all over.
This is my take on a "Dynamic Grid" of Labels
with X number of rows and Y number of columns based off a 2D string array:
<Window x:Class="MiscSamples.LabelsGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LabelsGrid" Height="300" Width="300">
<DockPanel>
<Button DockPanel.Dock="Top" Content="Fill" Click="Fill"/>
<ItemsControl ItemsSource="{Binding Items}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="true"
ScrollViewer.PanningMode="Both">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
</Window>
Code Behind:
public partial class LabelsGrid : Window
{
private LabelsGridViewModel ViewModel { get; set; }
public LabelsGrid()
{
InitializeComponent();
DataContext = ViewModel = new LabelsGridViewModel();
}
private void Fill(object sender, RoutedEventArgs e)
{
var array = new string[1600,20];
for (int i = 0; i < 1600; i++)
{
for (int j = 0; j < 20; j++)
{
array[i, j] = "Item" + i + "-" + j;
}
}
ViewModel.PopulateGrid(array);
}
}
ViewModel:
public class LabelsGridViewModel: PropertyChangedBase
{
public ObservableCollection<LabelGridItem> Items { get; set; }
public LabelsGridViewModel()
{
Items = new ObservableCollection<LabelGridItem>();
}
public void PopulateGrid(string[,] values)
{
Items.Clear();
var cols = values.GetUpperBound(1) + 1;
int rows = values.GetUpperBound(0) + 1;
for (int i = 0; i < rows; i++)
{
var item = new LabelGridItem();
for (int j = 0; j < cols; j++)
{
item.Items.Add(values[i, j]);
}
Items.Add(item);
}
}
}
Data Item:
public class LabelGridItem: PropertyChangedBase
{
public ObservableCollection<string> Items { get; set; }
public LabelGridItem()
{
Items = new ObservableCollection<string>();
}
}
PropertyChangedBase class (MVVM Helper)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Result:
Performance is AWESOME. Notice I'm using 20 columns instead of the 10 you suggested. The Filling of the grid is IMMEDIATE when you click the button. I'm sure performance is much better than crappy dinosaur winforms due to Built-in UI Virtualization.
The UI is defined in XAML, as opposed to creating UI elements in procedural code, which is a bad practice.
The UI and data are kept separate, thus increasing maintainability and scalability and cleanliness.
Copy and paste my code in a
File -> New -> WPF Application
and see the results for yourself.Also, keep in mind that if you're only going to display text, you'd better use a
TextBlock
instead of aLabel
, which is a much lightweight Text element.WPF rocks, even if at edge cases it might present performance degradation, it's still 12837091723 better than anything currently in existence.
Edit:
I went ahead and added 0 zeros to the row count (160000). Performance is still acceptable. It took less than 1 second to populate the Grid.
Notice that the "Columns" are NOT being virtualized in my example. This can lead to performance issues if there's a big number of them, but that's not what you described.
Edit2:
Based on your comments and clarifications, I made a new example, this time based in a System.Data.DataTable
. No ObservableCollections, no async stuff (there was nothing async in my previous example anyways). And just 10 columns. Horizontal Scrollbar was there due to the fact that the window was too small (Width="300"
) and was not enough to show the data. WPF is resolution independent, unlike dinosaur frameworks, and it shows scrollbars when needed, but also stretches the content to the available space (you can see this by resizing the window, etc).
I also put the array initializing code in the Window's constructor (to deal with the lack of INotifyPropertyChanged
) so it's going to take a little bit more to load and show it, and I noticed this sample using System.Data.DataTable
is slightly slower than the previous one.
However, I must warn you that Binding to Non-INotifyPropertyChanged
objects may cause a Memory Leak.
Still, you will NOT be able to use a simple Grid
control, because it does not do UI Virtualization. If you want a Virtualizing Grid, you will have to implement it yourself.
You will also NOT be able to use a winforms approach to this. It's simply irrelevant and useless in WPF.
<ItemsControl ItemsSource="{Binding Rows}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="true"
ScrollViewer.PanningMode="Both">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding ItemArray}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizationMode="Recycling" IsVirtualizing="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Code Behind:
public partial class LabelsGrid : Window
{
public LabelsGrid()
{
var array = new string[160000, 10];
for (int i = 0; i < 160000; i++)
{
for (int j = 0; j < 10; j++)
{
array[i, j] = "Item" + i + "-" + j;
}
}
DataContext = Array2DataTable(array);
InitializeComponent();
}
internal static DataTable Array2DataTable(string[,] arrString)
{
//... Your same exact code here
}
}
Bottom line is to do something in WPF you have to do it the WPF way. It's not just a UI framework, it's more of an Application Framework by itself.
Edit3:
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding}"/>
DataContext = Array2DataTable(array).DefaultView;
Works perfectly fine for me. Loading time is not noticeable with 160000 rows. What .Net framework version are you using?