Skip to content

Alright — this is one of the most important topics in real WPF systems.

If you understand ItemsControl + Virtualization, you understand:

  • why some apps feel instant
  • why others freeze with 10k items
  • how WPF actually builds UI from data

Let’s go step by step, from intuition → internals → production.


1. Big Picture

WPF is built around this idea:

“UI is just a projection of data.”

When you bind a collection:

csharp
ObservableCollection<InspectionResult>

WPF does not render data directly.

Instead, it:

  1. Creates a UI container per item
  2. Applies a template
  3. Adds it to the visual tree

👉 So:

Data → Container → Template → Visual Tree

⚠️ Problem:

If you have 10,000 items, naive rendering means:

  • 10,000 UI elements
  • 10,000 layouts
  • 10,000 bindings

→ 💥 UI freezes, memory explodes


👉 Solution: Virtualization

Only render what the user can see.


2. Beginner Mental Model

ItemsControl

Think of it like:

“Repeat this UI for every item in a collection”

xml
ItemsControl = foreach (item in ItemsSource) → render UI

Virtualization

Think of it like:

“Only create UI for visible items”

If you see 20 rows:

  • Only 20 UI elements exist
  • Not 10,000

👉 Simple analogy:

Without virtualizationWith virtualization
Render entire bookRender only visible page

3. Basic Example

ViewModel

csharp
public class InspectionViewModel
{
    public ObservableCollection<InspectionResult> Results { get; } = new();

    public InspectionViewModel()
    {
        for (int i = 0; i < 1000; i++)
        {
            Results.Add(new InspectionResult
            {
                Id = i,
                Status = i % 2 == 0 ? "OK" : "NG"
            });
        }
    }
}

ItemsControl

xml
<ItemsControl ItemsSource="{Binding Results}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Id}" Margin="5"/>
                <TextBlock Text="{Binding Status}" Margin="5"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

👉 Result:

  • 1000 items → 1000 UI elements
  • No virtualization
  • OK for small lists, bad for large ones

4. How It Really Works in WPF

Now the real engine.


4.1 ItemContainer

Each ItemsControl creates a container per item.

Examples:

ControlContainer
ItemsControlContentPresenter
ListBoxListBoxItem
ListViewListViewItem

👉 So actual structure:

ListBox
 ├── ListBoxItem
 │    └── (DataTemplate UI)
 ├── ListBoxItem
 │    └── (DataTemplate UI)

4.2 ItemContainerGenerator

This is the hidden engine.

Converts data items → UI containers

Conceptually:

csharp
foreach (var item in ItemsSource)
{
    var container = CreateContainer(item);
    ApplyTemplate(container);
    AddToVisualTree(container);
}

👉 Important:

  • Happens lazily when virtualization is enabled
  • Happens eagerly when disabled

4.3 DataTemplate

This defines:

“What UI does each item look like?”

xml
<DataTemplate>
    <TextBlock Text="{Binding Name}" />
</DataTemplate>

👉 Flow:

Data → Container → DataTemplate → Visual elements

5. Virtualization Deep Dive

Now the real power.


5.1 What Virtualization Actually Does

Without virtualization:

10000 items → 10000 UI elements

With virtualization:

Viewport shows 20 items → 20 UI elements

As you scroll:

  • old items destroyed or reused
  • new items created or reused

5.2 VirtualizingStackPanel

This is the key.

Default panel used by ListBox/ListView for virtualization

xml
<ListBox>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

👉 Responsibilities:

  • Measure only visible items
  • Request containers only for visible range
  • Manage scrolling offset

5.3 Container Recycling

Two modes:

Standard mode

  • Destroy old containers
  • Create new ones

Recycling mode (better)

xml
VirtualizingStackPanel.VirtualizationMode="Recycling"

👉 Reuses containers:

Scroll down:
Item #1 container → reused for Item #21

💡 Huge benefit:

  • Less allocation
  • Less GC pressure
  • smoother scrolling

6. Real-World Example

Example 1: Inspection Machine UI

You display:

10,000 inspection results
(each with image + status + metadata)

Without virtualization:

  • UI freeze on load
  • memory spike
  • scroll lag

With virtualization:

  • only ~20–50 items rendered
  • smooth scrolling
  • stable memory


Example 2: Log Viewer

Real-time logs streaming:
100 logs/sec
total = 50,000 rows

Without virtualization:

  • UI dies quickly

With virtualization:

  • UI stays responsive
  • only visible logs rendered

7. Common Mistakes (VERY important)


❌ 1. Using StackPanel

xml
<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel /> <!-- BAD -->
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

👉 This disables virtualization completely.


❌ 2. Wrapping in ScrollViewer

xml
<ScrollViewer>
    <ListBox ... />
</ScrollViewer>

👉 Breaks virtualization

Because:

  • ListBox can no longer control scrolling

❌ 3. Using ItemsControl instead of ListBox

ItemsControl:

  • ❌ no virtualization by default

ListBox/ListView:

  • ✅ virtualization enabled

❌ 4. Complex DataTemplates

xml
<DataTemplate>
    <Grid>
        <Image ... />
        <Charts ... />
        <Heavy controls ... />
    </Grid>
</DataTemplate>

👉 Even with virtualization:

  • heavy rendering = slow scrolling

❌ 5. Binding to non-virtualizing collections

  • using LINQ .ToList() repeatedly
  • recreating collections every frame

8. Performance & Tuning


✅ Ensure virtualization is ON

xml
<ListBox
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingStackPanel.VirtualizationMode="Recycling"
    ScrollViewer.CanContentScroll="True"/>

✅ Keep templates lightweight

  • avoid deep visual trees
  • avoid heavy controls inside items

✅ Use UI virtualization + data virtualization

UI virtualization:

  • only render visible UI

Data virtualization:

  • only load data when needed

✅ Batch updates

Bad:

csharp
foreach (var item in items)
    collection.Add(item);

Better:

  • suspend notifications
  • bulk add

✅ Avoid frequent collection reset

csharp
collection = new ObservableCollection(...)

👉 causes full UI rebuild


9. Practical Guidance


When to use what

ControlUse case
ItemsControlsmall lists, custom layout
ListBoxgeneral lists
ListViewlists with columns
DataGridtabular large data

Design scalable templates

Good:

xml
<TextBlock Text="{Binding Name}" />

Bad:

xml
<Grid>
    <Image Source="{Binding HugeImage}" />
    <3 nested panels>
</Grid>

Monitor performance

Use:

  • Visual Studio Diagnostic Tools
  • Live Visual Tree
  • PerfView (advanced)

Watch:

  • UI thread usage
  • memory growth
  • layout time

10. Summary

Here’s what you should remember:


Core mental model

Collection → Container → Template → UI

Key truths

  • ItemsControl = repeat UI per item
  • Each item = real UI object (expensive)
  • Virtualization = only create visible UI

Performance rule

“UI elements are expensive. Avoid creating too many.”


Virtualization essentials

  • Use ListBox/ListView/DataGrid
  • Use VirtualizingStackPanel
  • Enable Recycling mode
  • Avoid breaking it (StackPanel, ScrollViewer)

Real-world mindset

When you see:

large collections (1000+ items)

Always ask:

  • Is virtualization enabled?
  • Is template lightweight?
  • Are containers reused?

If you want next level, we can go deeper into:

👉 “Why virtualization sometimes silently stops working” 👉 “UI virtualization vs data virtualization (very important for backend-heavy apps)” 👉 “How WPF scrolling pipeline actually works internally”

Just tell me 👍

Docs-first project memory for AI-assisted implementation.