Wednesday, February 20, 2008

Paging Wrap Panel in WPF

This is the way how I implemented paging among a wrap panel in WPF. For this example I used simple buttons inside the wrap panel. My example dynamically takes the height of the control inside the wrap panel to determine the quantity of controls that the wrap panel must show.

In this example the columns always stay static, 1 column Min and 2 columns Max. You can also create a column function to show dynamically the quantity of columns you want.


XAML:

<Window x:Class="PagingWrapPanel.PagingWithDuff"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PagingWithDuff" Height="250" Loaded="Window_Loaded"
MaxHeight="500" MinHeight="120" MaxWidth="500" MinWidth="500">

<Grid>

<Grid.RowDefinitions>

<RowDefinition />

<RowDefinition Height="24" />

<RowDefinition Height="26" />

</Grid.RowDefinitions>

<ItemsControl Grid.Row="0" Name="RowsContainer" Background="Transparent" ItemsSource="{Binding}" SizeChanged="RowsContainer_SizeChanged" >

<ItemsControl.ItemTemplate>

<DataTemplate>

<Label />

</DataTemplate>

</ItemsControl.ItemTemplate>

<ItemsControl.ItemsPanel>

<ItemsPanelTemplate>

<WrapPanel Orientation="Vertical" IsItemsHost="True"

Width="Auto" HorizontalAlignment="Center" />

</ItemsPanelTemplate>.

</ItemsControl.ItemsPanel>

</ItemsControl>


<Grid Grid.Row="1" HorizontalAlignment="Center" >

<Label Name="label1" VerticalAlignment="Top" Height="Auto" >Page 1 out of 1</Label>

</Grid>


<Grid Grid.Row="2" >


<Grid.ColumnDefinitions>

<ColumnDefinition Width="*"/>

<ColumnDefinition Width="*" />

</Grid.ColumnDefinitions>

<Button Grid.Column="0" x:Name="btnBack" Click="btnBack_Click" MaxHeight="25" >Back</Button>

<Button Grid.Column="1" x:Name="btnNext" Click="btnNext_Click" MaxHeight="25" >Next</Button>

</Grid>

</Grid>

</Window>

By the way, you must change in the App.xaml the StartupUri string to - StartupUri="PagingWrapPanel.xaml" -


using System;
using
System.Collections.Generic;
using
System.Linq;
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.Navigation;
using
System.Windows.Shapes;
using
System.Collections.ObjectModel;

namespace PagingWrapPanel

{

///

/// Interaction logic for Window1.xaml

///

public partial class PagingWithDuff : Window

{

ObservableCollection<Label> collection;

WrapPanel currentPanel;

int rowsInPanel, pagingPressed;

double height;


//RowsContainer SizeChanged event is raised before the Loaded event for this class

//We need to raise Loaded event first, using this variable

bool isLoaded;


public PagingWithDuff()

{

InitializeComponent();

collection = new ObservableCollection<Label>();

isLoaded = false;

//Generate 100 Labels to Page

for (int generator = 1; generator <= 100; generator++)

{

Label newLbl = new Label();

newLbl.Name = "Label" + generator;

newLbl.Content = "Label " + generator;

newLbl.Width = 160;

newLbl.Background = Brushes.Black;

newLbl.Foreground = Brushes.White;

newLbl.HorizontalContentAlignment = HorizontalAlignment.Center;

collection.Add(newLbl);

}


btnBack.IsEnabled = false;

btnNext.IsEnabled = false;

pagingPressed = 1;


RowsContainer.DataContext = collection;

}


//Calculate how many rows will be showed in the container

public int rowsToShow()

{

currentPanel = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(RowsContainer, 0), 0), 0) as WrapPanel;

int rows = 0;

height = this.currentPanel.ActualHeight;

double RowControlHeight = 0.0;


//Get the first Visible Label in the Panel and get its Height

for (int iterator = 0; iterator <= collection.Count - 1; iterator++)

{

Visibility isVisible = (Visibility)(currentPanel.Children[iterator] as Label).Visibility;

//If is visible get height and break iteration

if (isVisible == Visibility.Visible)

{

RowControlHeight = (double)(currentPanel.Children[iterator] as Label).ActualHeight;

break;

}

}


rows = (int)Math.Floor((decimal)height / (decimal)RowControlHeight);

return rows;

}


//Shows the controls inside the wrap panel

public void setRowsControlsToShow()

{

bool endOfList = false;

bool startOfList = false;

bool medOfList = false;

bool overflow = false;

int collectionCount = 0;


collectionCount = collection.Count;

//Once the datacontext is displayed, we need to show up only the rowcontrols we need


if (RowsContainer.Items.Count > 0)

{


int start = PagingWithDuff.startFromIndex(rowsToShow(), pagingPressed);

int end = PagingWithDuff.endInIndex(start, rowsToShow() * 2);



if (start > collectionCount - 1)

//If this happens, means that the panel have no rowcontrols to show

{

end = collectionCount - 1;

start = (collectionCount - 1) - rowsToShow();

overflow = true;

}


if (start == 0) startOfList = true;


if (end > collectionCount)

{

end = collectionCount - 1;

endOfList = true;

}

else

{

if (!startOfList && !endOfList) { medOfList = true; }

}


for (int iter = 0; iter <= collectionCount - 1; iter++)

{

Label currentRow = currentPanel.Children[iter] as Label;


if ((iter <> end))

{

currentRow.Visibility = Visibility.Collapsed;

}

else

{

currentRow.Visibility = Visibility.Visible;

}

}


if (endOfList)

{

btnNext.IsEnabled = false;

if (pagingPressed > 1) btnBack.IsEnabled = true;

}

else

btnNext.IsEnabled = true;


if (medOfList)

{

if (end <= collectionCount - 1)

{

btnBack.IsEnabled = true;

}

}


if (startOfList)

{

btnBack.IsEnabled = false;

if ((rowsInPanel * 2) <= collectionCount)

btnNext.IsEnabled = true;


}


if (overflow)

{

btnNext.IsEnabled = false;

pagingPressed--;

}

}


else

{

btnNext.IsEnabled = false;

btnBack.IsEnabled = false;

}


}


public void setPagingInfo()

{

int totalQty = collection.Count;

rowsInPanel = rowsToShow();

int totalPages = (int)Math.Floor((double)totalQty / rowsInPanel);


if (totalPages == 0)

{

totalPages = 1;

btnNext.IsEnabled = false;

btnBack.IsEnabled = false;

}


if (pagingPressed > totalPages)

this.label1.Content = "Page " + totalPages + " out of " + totalPages;

else

this.label1.Content = "Page " + pagingPressed + " out of " + totalPages;

}


private static int startFromIndex(int qtyToShow, int page)

{

if (page == 1) return 0;


return ((page - 1) * qtyToShow);

}


private static int endInIndex(int start, int qtyToShow)

{

return (start + qtyToShow) - 1;


}


private void Window_Loaded(object sender, RoutedEventArgs e)

{

setRowsControlsToShow();

setPagingInfo();

isLoaded = true;

}


private void RowsContainer_SizeChanged(object sender, SizeChangedEventArgs e)

{

if (isLoaded)

{

setRowsControlsToShow();

setPagingInfo();

}

}


private void btnNext_Click(object sender, RoutedEventArgs e)

{

pagingPressed++;

setRowsControlsToShow();

setPagingInfo();

}


private void btnBack_Click(object sender, RoutedEventArgs e)

{

pagingPressed--;

setRowsControlsToShow();

setPagingInfo();

}

}

}


Each time the Window is resized we need to recalculate how many rows fit in the wrap panel doing the same procedure used in the Loaded event.