How to set the number of ListView columns dynamically - wpf

I got a window containing two usercontrols, a usercontrol with a listview and a usercontrol with some other controls.
Something like this:
+-------------------------------------------------+
| header stuff |
+---------------------------+---------------------+
| usercontrol with listview | another usercontrol |
+-------------------------------------------------+
| footer stuff |
+-------------------------------------------------+
Problem: on screens with low resolution its only possible to see around 2 of the 3 columns and you need to scroll to see the thrid column. I want to avoid the horizontal scrolling by dynamically setting whether to show 1,2 or 3 columns depending on the width. Another problem is that the names can
be very long, so the width of the items in the listview all have same width as the the longest name.
Code for listview:
<ListView Name="lstContacts"
ItemsSource="{Binding Path=Contacts}"
IsSynchronizedWithCurrentItem="True"
ItemContainerStyle="{StaticResource RoundedItem}"
SelectionMode="Single"
HorizontalContentAlignment="Center"
VerticalAlignment="Top"
HorizontalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="3">
<TextBlock FontWeight="Bold" FontSize="20" Text="{Binding Path=Identifier}" HorizontalAlignment="Center" />
<TextBlock FontSize="16" Text="{Binding Path=Name}" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Any ideas on how to sort this out ?
Perhaps the listview is a bad choice ?
Thanks.

You could use a WrapPanel as the ItemsPanel:
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
Note that there is no native virtualizing WrapPanel, so this will not perform well for a large collection of items.

since your names can be long you can :
1- use textbox having textwrapping and Max/Min Height.
2- decrease fonts size for Names.
3- put Names in a ViewBox for them to always fit. (wrap also)

Related

How to setup a vertical WPF WrapPanel to use as much columns as possible

I have an ItemsControl with WrapPanel as ItemsPanel. The ItemsControl is inside a ScrollViewer.
Depending on how wide the window is, the ItemsControl/WrapPanel shall have more columns to make more use of the screen (and that the user has to scroll less).
If I set the WrapPanel to Orientation="Horizontal" it works as expected. But if I set the Orientation to "Vertical" the ItemsControl/WrapPanel only show one column.
The difference is, that I like the columns to be like newspaper columns, so like:
A E I
B F J
C G
D H
But if the WrapPanel is set to Horizontal the columns are like:
A B C
D E F
G H I
J
How can I setup the WrapPanel to behave like that?
I could of course limit the height. Of course I could realize this with some clever logic, that takes the number of items from the source into account as well as the width of the window. But this would be complicated and probably a mess, that no one else would ever understand (or even myself, If I review this in a couple of years).
I hope that something like this already exists in WPF.
Here is the Pseudo-XAML-Code
<ScrollViewer>
<StackPanel>
<!-- Some other stuff -->
<ItemsControl ItemsSource="{Binding … }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Some more other stuff -->
</StackPanel>
</ScrollViewer>
It works with Orientation="Horizontal" (but then cells are in wrong order), but only shows one column, when Orientation="Vertical".
I believe Orientation="Vertical" is the option you are looking for. It will attempt to fill a column before wrapping to a new column.
A __D __G
| | | | |
B | E | .
| | | | .
C_| F_| .->
The trouble you are having is that the WrapPanel is being allowed an infinite amount of vertical layout space by the StackPanel and, as a result, does not have to wrap.
To fix this issue, change your StackPanel to a WrapPanel or another control that does not allow infinite layout space.
Example
<WrapPanel>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Rectangle Height="50" Width="50" Fill="#F000"/>
<Rectangle Height="50" Width="50" Fill="#F111"/>
<Rectangle Height="50" Width="50" Fill="#F222"/>
<!- ... ->
<Rectangle Height="50" Width="50" Fill="#F111"/>
<Rectangle Height="50" Width="50" Fill="#F000"/>
</ItemsControl>
</WrapPanel>
Result
Ok, I came up with a solution, that is pure XAML.
So basically I have a Grid. With two rows. Inside the grid are two almost identical ItemsControls with bindings and WrapPanels.
The first has a WrapPanel with Orientation="Horizontal". It is only there to calculate the boundaries for the second ItemsControl. Therefor its Visibility="Hidden". Also it only spans over the first row.
The second ItemsControl has the WrapPanel with Orientation="Vertical". Its Height is bound to the ActualHeight of the first ItemsControl. Therefor it is forced to use multiple Columns, because the height can't get higher then of the height of ItemsControl #1.
The second ItemsControl spans over both rows (this is important, otherwise the height can get stuck, wenn resizing the window*).
It's not a very nice solution. More like Brute-force. But of course it works. The first ItemsControl calculates the outer size. I force this size to the second one, which than can distribute the items in a nice pattern.
<ScrollViewer>
<StackPanel>
<!-- Some other stuff -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- first ItemsControl, to calculate the outer boundaries with -->
<ItemsControl ItemsSource="{Binding … }"
Name="Calculating_Size"
Visibility="Hidden">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Second ItemsControl with fixed size for Vertical WrapPanel -->
<ItemsControl ItemsSource="{Binding … }"
Height="{Binding ActualHeight, ElementName=Calculating_Size}"
Grid.RowSpan="2">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="300"><!-- … --></Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Some more other stuff -->
</StackPanel>
</ScrollViewer>
* The reason for the second row is, that if the first WrapPanel reorganizing, because the width increases, it still fixed with the height because the row cannot get smaller, that the second ItemsControl. But, if the second ItemsControl spans over two rows, the first row can get smaller independent of the second ItemsControl. The second gets smaller immediteately afterwards. But this seems to be an extra step.

How to make the items in a WPF ListBox wrap horizontally and vertically

I want to show a list of thumbnails, and allow the user to choose one. A ListBox seemed an obvious choice, setting the template to include the thumbnail and description.
The following code does this correctly...
<ListBox Margin="5"
Name="BackgroundsLb">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="5"
BorderBrush="Black"
BorderThickness="1">
<StackPanel Margin="5">
<Image Source="{Binding Path=Data, Converter={StaticResource BytesToImageVC}}"
Width="150"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding Path=Description}"
HorizontalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
However, this displays the thumbnails vertically, one per row, as is normal for a ListBox. Given that the thumbnails are only 150 pixels wide, I would like to show them in something more like a grid, but (ideally) in a way so that the number of columns adapts to the size of the window.
I tried replacing the ListBox with an ItemsControl, adding in the following...
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
...and it displayed exactly how I wanted it, but the ItemsControl does not allow selection, so is no use for my purpose.
Is there a way to achieve a flexible, selectable display that fills the horizontal space, and breaks onto a new row according to the size of the window?
Thanks
You can use an ItemsPanelTemplate in a ListBox just the same as you are using one in the ItemsControl. The difference I think you're seeing is that ListBox uses scroll bars by default rather than wrapping the content. Basically the content is allowed to grow forever, so you never get the wrap. Instead you get a scrollbar. The good news is you can disable this behavior. The following should give you a horizontal wrap, where new rows are created as needed.
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

StackPanel items don't wrap to new lines

I have an ItemsControl with a StackPanel as the ItemsPanelTemplate and the items which get displayed inside (the Button) are not wrapped to new lines. I've tried adding the HorizontalContentAlignment property to the ItemsControl and play with the options but it doesn't help. What am I doing wrong?
<ItemsControl ItemsSource="{Binding RecipientsNames}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="btnContact" Click="BtnContact_Click"
Width="Auto" Height="14" Padding="0" BorderThickness="0" Margin="0 0 6 0" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBlock Text="{Binding Path=Name}" FontSize="12" Margin="0 -2 0 -2"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
you are getting your panels mixed up,
A StackPanel expands infinity in a linear direction.
a WrapPanel stacks elements until they hit the edge of the visible
area then starts a new stack under the previous expanding
perpendicular to the stacking direction
a UniformGrid sizes itself to fit the entire area and then splits it
into a set number of rows and columns, so if you set it to 3 columns
and add 4 items the 4th will appear in column 1 of a second row,
because the uniform grid unlike the WrapPanel this isn't affected by item size i generally find it produces more visually appealing layouts to the wrap panel but does need to be configured to make best use of the available space

Silverlight 4 - simple control template

<DataTemplate x:Key="dirtSimple">
<TextBlock Margin="10,0,0,0" Text="{Binding Path=CurrentBook.Published, StringFormat=d}"></TextBlock>
</DataTemplate>
<ControlTemplate x:Key="lbWrapPanelTemplate">
<StackPanel Orientation="Horizontal" Margin="2" Background="Aqua">
<ItemsPresenter></ItemsPresenter>
</StackPanel>
</ControlTemplate>
...
<ListBox Template="{StaticResource lbWrapPanelTemplate}" x:Name="bookListBox" Grid.Row="0" ItemsSource="{Binding Path=BookSource}" ItemTemplate="{StaticResource dirtSimple}" >
</ListBox>
The list box is displaying correctly, with a beautiful "Aqua" background, and each item is boringly displayed with just a date. For some reason though the items are not flowing horizontally. I originally tried it with the Silverlight Toolkit's WrapPanel, with the same problem, but I can't even get it to work with a built-in StackPanel, so I suspect I'm missing something.
Are you trying to get selection-based behavior that a ListBox provides? If not, use an ItemsControl (and supply an ItemsPanel as below).
The reason it's not going horizontal is the ItemsPresenter ultimately has its own panel it lays out items in. It's not inserting each item separately into your StackPanel (or WrapPanel), it's putting them in its own panel
What you want to do is specify a value for ItemsPanel like so:
<ListBox ItemTemplate="{StaticResource dirtSimple}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

How to space items evenly in a horizontal Silverlight Listbox

In Silverlight, I have a Vertical ListBox that has a Horizontal ListBox for each item. I want the items in the HorizontalListbox to space evenly across the width of the parent (Vertical) ListBox. How can I do this?
<ListBox x:Name="MachineListBox" Background="Green">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding CoilList}" Background="Red" HorizontalAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
HorizontalAlignment="Stretch" />
</ItemsPanelTemplate >
</ListBox.ItemsPanel>
<ListBox.ItemTemplate >
<DataTemplate>
<TextBlock
Text="{Binding Coil}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'll take the liberty of suggesting you use a custom control of my own invention. It's called a ProportionalPanel and it does just what you need - spaces items evenly. You could use it for the ItemsPanel in the inner ListBox instead of the StackPanel. I also provide the source code, so you can tweak the logic any way you like. The relevant post on my blog is here.
I think the proportional sizing operator will do what you're looking for. Haven't tried it but it sounds like an option. Width="*" and Margin="2*".

Resources