Arrangement of Items in a List Box Using WPF

Introduction

This article describes how items can be arranged in a List Box (or any Items Control subclass). Here I have used the Items Panel property for the customization.

Background

I have used the Items Panel property of the Items Control that allows us to choose the layout panel to arrange items displayed in the Items Control or any other control derived from it, for example List Box. This feature allows you to completely redefine how the items in the list should be arranged, relative to one another.

Solution

In this application I populated a List Box with images of nature's beauty. Initially the images are listed from the top of the List Box down to the bottom, that is the normal behavior. After the customization is complete, the images will be displayed in a left-to-right top-to-bottom layout, like text on a page (for us left-to-right readers).
This custom layout is done using a Wrap Panel to arrange the images for us.

Procedure

Step 1

Putting a List Box in a Window

The following is a samle of putting a List Box in a window:

<Window x:Class="CustomItemsPanel.Window1"

    xmlns="http://schemas.microsoft.com/winx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winx/2006/xaml"

    xmlns:local="clr-namespace:CustomItemsPanel" 

    Title="Custom Items Panel" Height="600" Width="280"

    WindowStartupLocation="Center Screen"

    >

<ListBox Items Source="{Binding}" />

</Window>

 

On compiling and running the project you will see an empty window. The List Box is displayed here but it has no items yet.

Step 2

Filling the List Box with pictures

Now here we will create a class named Image Loader declared as static. This class populates a List Box with images. The project also contains a folder named "images" that contains the images stored in JPEG format (with .jpg extension).

using System;

using System.Collections.Generic;

using System.IO;

using System. Text;

using System. Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

 

namespace CustomItemsPanel

{

public partial class Window1 : Window

{

public Window1()

{

InitializeComponent();

}

}

 

public static class ImageLoader

{

public static List<BitmapImage> LoadImages()

{

List<BitmapImage> Images = new List<BitmapImage>();

DirectoryInfo ImageDir = new DirectoryInfo@"..\..\images" );

foreachFileInfo ImageFile in ImageDir.GetFiles( "*.jpg" ) )

{

Uri uri = new Uri( ImageFile.FullName );

Images.Add( new BitmapImage( uri ) );

}

return Images;

}

}

}

 

We use the Image Loader class with the following markup in the Window class, declared as above.

 

 <Window.DataContext>

    <ObjectDataProvider 

      ObjectType="{x:Type local:ImageLoader}" 

      MethodName="LoadImages" 

      />

  </Window.DataContext>

 

The XAML above indicates that the implicit data source for all visual elements in the window will, by default, be the object returned when calling the static ImageLoader.LoadImages method.

Step 3

Creating the template to display pictures

Now we need to define how a List Box should render a Bitmap Image. To do this we will apply Style to the List Box. The Style will set the List Box Item Template property to a DataTemplate,That specifies that an Image element wrapped in a Border should be displayed when trying to render a Bitmap Image Object.

<Window x:Class="CustomItemsPanel.Window1"

    xmlns="http://schemas.microsoft.com/winx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winx/2006/xaml"

    xmlns:local="clr-namespace:CustomItemsPanel" 

    Title="Custom ItemsPanel" Height="600" Width="280"

    WindowStartupLocation="CenterScreen"  >

  <Window.Resources>

    <Style TargetType="{x:Type ListBox}">

      

      <Setter Property="ItemTemplate">

        <Setter.Value>

          <DataTemplate>

            <Border 

              BorderBrush="Black" 

              BorderThickness="4" 

              CornerRadius="5"

              Margin="6">

 

              <Image 

                Source="{Binding Path=UriSource}" 

                Stretch="Fill"

                Width="100" Height="120" />

 

            </Border>

          </DataTemplate>

        </Setter.Value>

      </Setter>
 

      <Setter Property="ItemsPanel">

        <Setter.Value>

          <ItemsPanelTemplate>

            <WrapPanel />

          </ItemsPanelTemplate>

        </Setter.Value>

      </Setter>

 

       <Setter 

        Property="ScrollViewer.HorizontalScrollBarVisibility" 

        Value="Disabled" 

        />

      </Style>

     </Window.Resources>

 

    <Window.DataContext>

    <ObjectDataProvider 

      ObjectType="{x:Type local:ImageLoader}" 

      MethodName="LoadImages"  />

  </Window.DataContext>

   <ListBox ItemsSource="{Binding}" />

</Window>

 

If you now run the application you will see this output:

 

Output

 
im2

Step 4

Replacing the default Items Panel

Here we used the Wrap Panel instead of VirtualizingStackPanel (which is the default Stack Panel used by the List Box) to host the List Box items. The VirtualizingStackPanel is a Stack Panel that only creates visual objects for the items that are currently viewable in the control. For items that are scrolled out of view, the panel throws away the visual objects used to render them.
This technique can drastically improve performance and memory consumption when the control has a large number of items.

Output

Now, on running the application you will get this window:

 im4

If you were to resize the window, the Wrap-panel would adjust the layout to accommodate the new dimensions, your output will look like this.

 im3

It is necessary to specify that the Scroll Viewer inside the List Box disables its horizontal scrollbar. Doing so ensures that the width of the Wrap Panel is constrained to the viewable width of the Scroll Viewer. It also prevents the horizontal scrollbar from ever appearing, that is desirable in this scenario.

Here's the XAML in the <Style> that sets that property:

<Setter 

        Property="ScrollViewer.HorizontalScrollBarVisibility" 

        Value="Disabled" />