WPF ListView with horizontal arrangement of items? - wpf

I want to lay out items in a ListView in a similar manner to the WinForms ListView in List mode. That is, where items are laid out not just vertically but horizontally in the ListView as well.
I don't mind if the items are laid out like this:
1 4 7
2 5 8
3 6 9
Or like this:
1 2 3
4 5 6
7 8 9
As long as they are presented both vertically and horizontally in order to maximize the use of available space.
The closest I could find was this question:
How do I make WPF ListView items repeat horizontally, like a horizontal scrollbar?
Which only lays out the items only horizontally.

It sounds like what you are looking for is a WrapPannel, which will lay the items out horizontally until there is no more room, and then move to the next line, like this:
(MSDN)
alt text http://i.msdn.microsoft.com/Cc295081.b1c415fb-9a32-4a18-aa0b-308fca994ac9(en-us,Expression.10).png
You also could use a UniformGrid, which will lay the items out in a set number of rows or columns.
The way we get the items to arange using these other panels in a ListView, ListBox, or any form of ItemsControl is by changing the ItemsPanel property. By setting the ItemsPanel you can change it from the default StackPanel that is used by ItemsControls. With the WrapPanel we also should set the widths as shown here.
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
...
</ListView>

I recently research how to achieve this in WPF and found a good solution. What I wanted was to the replicate the List mode in Windows Explorer, i.e. top-to-bottom, then left-to-right.
Basically what you want to do override the ListBox.ItemsPanel property to use a WrapPanel with it's orientation set to Vertical.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
However this WILL be slow when loading a large data set as it the wrap panel is not virtualised. This is important. So this task now becomes a little more as now you need to write your own VirtualizedWrapPanel by extending VirtualizedPanel and implementing IScrollInfo.
public class VirtualizedWrapPanel : VirtualizedPanel, IScrollInfo
{
// ...
}
This is as far as I got in my research before having to go off to another task. If you want more information or examples, please comment.
UPDATE. Ben Constable's has a great series on how to implement IScrollInfo.
There are 4 articles in total. A really good read.
I have since implemented a virtualized wrap panel, it is not an easy task even with the help of the above series of articles.

In my case, the best option was to use:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"
MaxHeight="{Binding (FrameworkElement.ActualHeight), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
MinHeight="{Binding ItemHeight, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
This gave me a decent analog to Windows Explorer's List option

for left to right then top to bottom use
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
MaxWidth="{Binding ActualWidth, Mode=OneWay,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type er:MainWindow}}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>

In addition to #Dennis's answer, about the WrapPanel losing Virtualization, I have found a nice class that correctly implements this. While the suggested post by Ben Constable (Part 1, Part 2, Part 3, Part 4) is a nice introduction, I couldn't quite complete the task for a Wrap Panel.
Here is an implementation:
https://virtualwrappanel.codeplex.com/
I've tested it with total of 3.300 video's and photo's, loading the list itself is of course a bit long, but eventually it is correctly virtualizing the list, no scroll lag whatsoever.
There are some issues to this code, see the issues tab on the page above.
After adding the source code to your project, example source code:
<!--in your <Window> or <UserControl> tag -->
<UserControl
xmlns:hw="clr-namespace:Project.Namespace.ToClassFile" >
<!--...-->
<ListView x:Name="lvImages" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Margin="10" Height="auto"
ItemsSource="{Binding ListImages}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<hw:VirtualizingWrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5" MaxHeight="150">
<TextBlock Text="{Binding title}" FontWeight="Bold"/>
<Image Source="{Binding path, IsAsync=True}" Height="100"/>
<TextBlock Text="{Binding createDate, StringFormat=dd-MM-yyyy}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MVVM style back-end, so this is inside the ViewModel:
public ObservableCollection<Media> ListImages
{
get
{
return listImages;
}
set { listImages = value; OnPropertyChanged(); }
}
//Just load the images however you do it, then assign it to above list.
//Below is the class defined that I have used.
public class Media
{
private static int nextMediaId = 1;
public int mediaId { get; }
public string title { get; set; }
public string path { get; set; }
public DateTime createDate { get; set; }
public bool isSelected { get; set; }
public Media()
{
mediaId = nextMediaId;
nextMediaId++;
}
}

Related

Items Box with limit of objects in row (Large icons view style in Windows Explorer)

Can't find a solution to a pretty simple UI problem:
I have a model with a Images property. The Images property holds a collection of items Image.
As for now on - I have a ListBox and binding a ListBoxItem data template to Images.Image and all good. But I have each item on a new line. Not good.
What I am willing to achieve is, lets describe as, a Listbox with Horizontal items orientation and limit of items in a row. Just like Large icons view style in Windows Explorer.
Have somebody previously implemented such a solution? Any advice will be highly appreciated.
Thank you in advance.
Use a WrapPanel (or some other appropriate Panel) as the ListBox's ItemsPanel, and disable horizontal scrolling:
<ListBox ItemsSource="{Binding Images}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Width="100" Margin="5"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can set the ListBox's ItemPanelTemplate to WrapPanel, like this.
I am not sure why it is always like that - but as soon as I asked, I have found an alternative solution with usage of ListView:
<ListView ItemsSource="{Binding Images}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>

Using WPF/XAML to add dynamic controls to a form

I'm new to XAML/Wpf forms so maybe this project is a little outside of my ability. I would like to make a scheduler that essentially adds a bunch of "Jobs" to the form, each one has it's own abilities like click events and such but for the most part they are labels with a background and a set size.
Here is something like what I would like that I had made (with data removed) in Winforms, but it was very slow to add to the form. I was hoping some sort of databinding and user controls with xaml would help me out.
There will be a large number of these jobs, ideally I was thinking each row could be it's own usercontrol or each section (marked here as assembly cell).
I had some code that was able to place jobs onto the window, but there was no user control and no row or section labels. It looked like this in the xaml:
<Window.Resources>
<DataTemplate x:Key="Job">
<Label Content="{Binding}" Height ="100" Width="100" BorderThickness="1" BorderBrush="Black"/>
</DataTemplate>
<DataTemplate x:Key="wrkCenterPanel">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource job}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto" >
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
...
<Label Grid.Row="0" Grid.Column="0" Content="Production Scheduler" Width="Auto" Height="Auto" FontSize="40" FontWeight="Bold" HorizontalAlignment="Center" />
<ScrollViewer Grid.Row="1" Grid.Column="0" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="sched" ItemTemplate="{DynamicResource wrkCenterPanel}" />
</ScrollViewer>
In the code I just had a List(Of List(Of String)) And I added each row as a new list of strings and then binded it to sched. Is this even the right direction to be moving in? Any help at all would be greatly appreciated.
You do seem to be heading in the right direction, however I would suggest reading up on MVVM before you continue so that you can truly understand how DataBinding and the WPF template system will be of benefit. The Prism documentation on MVVM is a good place to start, but there are countless other places to learn about MVVM with a simple search.
The jist of it is that your DataTemplates are a good start, however you can go further. Rather than just using Strings and lists of strings, you can use CLR Objects (ViewModels, in MVVM terminology) and then define DataTemplates (Views) for visually representing those CLR Objects.
The result is that your XAML will look something like this (incomplete, but demonstrative):
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type vms:JobViewModel}">
<Label Content="{Binding Name}" Height ="100" Width="100" BorderThickness="1" BorderBrush="Black"/>
</DataTemplate>
<DataTemplate DataType="{x:Type WorkCenterViewModel}">
<ItemsControl ItemsSource="{Binding Jobs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding WorkCenter}"/>
</Window>
And your CLR Objects will look something like this:
public class JobViewModel : ViewModel
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public class WorkCenterViewModel : ViewModel
{
private ObservableCollection<JobViewModel> _jobs;
public ObservableCollection<JobViewModel> Jobs
{
get { return _jobs; }
}
}
When you define your DataTemplates without the x:Key property, but with a DataType property instead, it will automatically apply to any instances of that type it finds.

Binding ListView to ObservableCollection of Bitmaps

I have some problems with binding my ListView to ObservableCollection<Bitmap>...
It's my XAML:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemsSource="{Binding Path=FrameImages}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
Width="Auto"
ItemWidth="{Binding (ListView.View).ItemWidth, RelativeSource={RelativeSource AncestorType=ListView}}"
ItemHeight="{Binding (ListView.View).ItemHeight, RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Border Width="100" Height="75" BorderThickness="1" BorderBrush="DarkGray" VerticalAlignment="Center" Margin="7,5,7,5">
<Image Margin="5,5,5,5" Width="100" Height="75" Source="{Binding}" Stretch="Fill"></Image>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code behind:
public ObservableCollection<Bitmap> FrameImages { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
FrameImages = new ObservableCollection<Bitmap>();
Bitmap img = new Bitmap(#"E:\test\3047817.bmp");
FrameImages.Add(img);
}
When I add the element, it seems to appear in ListView, but it's transparent (just an empty frame):/ I tried to save the bitmap back into file and there were no problems (same as the original one). I don't know why it's not working:(
[edit]
Btw. my code works if I replace ObservableCollection<Bitmap> with ObservableCollection<BitmapSource>. But here's the additional conversion which affects on program's performance... That's why I need Bitmap.
You can't use System.Drawing.Bitmap that way in a WPF application. The class does not belong to WPF. It encapsulates a GDI+ bitmap, whereas WPF is based in DirectX. Hence you need to use BitmapSource.
You might however simply bind the ItemsSource property of your ListView to a collection of image path strings. The necessary conversion from string to ImageSource is performed automatically by WPF.
If you really need to manually create the bitmaps, you should define your collection as ObservableCollection<ImageSource> and create collection elements by something like this:
var img = new BitmapImage(new Uri(#"E:\test\3047817.bmp"));
FrameImages.Add(img);
You might want to take a look into Imaging Overview. And you should make yourself familiar with the BitmapSource class hierarchy in WPF.

Generic, bindable, uniform-stretch layout

Excuse the length, trying to ensure all info's contained!
I need a view of cells (a view with corresponding VM) almost in a circle like so:
********
**********
************
**************
**************
**************
**************
************
**********
********
A few of the complications:
Before we fire up I don't know how many cells to display. All cells must be equal in size. The entire view must be scalable.
In my simplified world, my window creates 20 RowViewModel objects and passes each constructor the number of columns it should create.
My main view:
<Viewbox Stretch="Uniform">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Path=Rows}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Path=Rows.Count}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
And my RowView:
<Grid>
<ItemsControl ItemsSource="{Binding Columns}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
My cell view:
<Border >
<Viewbox Stretch="Uniform" >
<Label FontSize="10" Content="{Binding Notice}" Foreground="White" />
</Viewbox>
</Border>
As it stands, all rows are the same width so rows with fewer cells have wider cells. If I tell each row there are 20 cells, each cell is the same size but everything aligns left despite HorizontalAlignment settings, presumably because it appends blank cells. I'm assuming I could insert blank cells where I want them but this feels like a fudge of the data to make the display right, which I'm sure you'll agree is B-A-D.
I've tried squillions of approaches and think this is the closest so far but I'm missing it. Can you help please?
Thanks for your patience.
Well, maybe it's impossible? I ended up adding a property to the ViewModel that builds the grid and assigns the appropriate row/column during the generation, then databinding a contentcontrol to that grid.
It remains unsolved - how to do it in XAML?
The view:
<Viewbox Stretch="Uniform">
<ContentControl Content="{Binding PrettyGrid}" />
</Viewbox>
In the ViewModel:
public Grid PrettyGrid
{
get
{
return BuildGrid(data);
}
}
and the BuildGrid snippets:
private static Grid BuildGrid(List<objects> cellData)
{
var localGrid = new Grid();
// ...
localGrid.RowDefintions.Add(...);
localGrid.ColumnDefinitions.Add(...);
cellData.ForEach(cell =>
{
ContentControl ctl = new ContentControl();
ctl.Content = cell;
Grid.SetRow(ctl, cell.Row);
Grid.SetColumn(ctl, cell.Column);
localGrid.Children.Add(ctl);
});
return localGrid;
}

Proper usage of Viewbox for WPF kiosk touch screen applications

Is there some kind of "best practices" manual for creating proper GUI for kiosk touch screens? These applications need to have consistent look and feel across different screen resolutions and more importantly screen ratios (since everything is rendered as vectors so screen resolution and DPI shouldn't be an issue with WPF).
Take for example this screenshot where I tried to create simple keyboard for touch screens. I've used UniformGrid so that each button gets cell of equal size:
Here is the code for this:
<TabItem Header="Test 1">
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Margin="5">
<Button Content="{Binding}"></Button>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TabItem>
Notice that all buttons are sized to content which makes them non-stretchable so each button has its own size... This is how Viewbox scales its content and of course this kind of GUI is out of question. This is not the keyboard I want to use on some kiosk application, so the next better version is following:
<TabItem Header="Test 2">
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5">
<Viewbox>
<TextBlock Text="{Binding}" />
</Viewbox>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</TabItem>
Now this is a bit better as Viewbox is now wrapping the content rather than the whole button. Notice however that because we are now wrapping the button's content rather the whole button the button's border is now not scaled. I want this to be scaled too, not just the content. In the first example we had this but the overall look of the GUI was horrible.
Also notice that in this version I've set Margin to the Button and in the first version on the Viewbox. This means that in the first version margin will scale too (I want that!) while in the second version it will be constant for any screen size. So for really big screens the white space between buttons will become relatively smaller though they are absolutely of constant size (not what I want!).
Here is the code for generating keyboard buttons:
public partial class MainWindow : Window
{
public List<string> KeyboardItems { get; set; }
public MainWindow()
{
KeyboardItems = new List<string>();
for (char c = 'A'; c <= 'Z'; c++)
{
KeyboardItems.Add(c.ToString());
}
KeyboardItems.Add("Space");
DataContext = this;
InitializeComponent();
}
}
Problems like this are all around development of WPF touch screen kiosks so I'd like to hear some ideas and solutions you came about while dealing with scaling issues.
You didn't show us Test 3, which I thought might be this:
<TabItem Header="Test 3">
<Viewbox>
<ItemsControl ItemsSource="{Binding KeyboardItems}" SnapsToDevicePixels="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</TabItem>
Does that have the desired effect?

Resources