Irregular layout ItemsControl - wpf

I have a strange layout for an ItemsControl.
I have a 4x6 grid with the following pattern:
1 2 3 4
13 14 15 16
5 6 7 8
17 18 19 20
9 10 11 12
21 22 23 24
Is there an easy way to do this? should I be using 6 Items Controls and take "sections" of my list? is there a good way to do this? What about notification?
It's important to note that I may, or may not, have all 24 entries present, but the layout needs to be maintained (think of it like filled slots on a bingo card or something)
Edit:
Ideally, I'd like to be able to take a list, and do some fun sorting/padding type stuff off properties on the items in the list.
for instance, if I have an ObservableCollection with a few units, and Unit has a property "Index", I'd like to have a view consumable Collection generated that automatically uses Index to make a padded list. I guess an observable dictionary could work, but that seems gross. Maybe a new custom layout panel is in order?

There is a clever way of doing this in pure XAML using a custom template for your ItemsControl. It's easiest if all your "cards" have a fixed size, say 100x100:
<!-- Wrap each card in a decorator twice as high as the card cell -->
<DataTemplate x:Key="ItemInDoubleHighBox">
<Decorator Width="100" Height="200">
<Decorator Width="100" Height="100" ClipToBounds="True">
<ContentPresenter />
</Decorator>
</Decorator>
</DataTemplate>
<!-- Define a template for use with WrapPanel -->
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
<WrapPanel />
</ItemsPanelTemplate>
<!-- Now the actual ItemsControl template -->
<ControlTemplate TargetType="ItemsControl">
<Grid Width="600" Height="600" ClipToBounds="True">
<!-- Items 1 to 12 -->
<ItemsControl ItemsSource="{TemplateBinding ItemsSource}"
ItemsPanel="{StaticResource WrapPanelTemplate}"
ItemTemplate="{StaticResource ItemInDoubleHighBox}" />
<!-- Items 13 to 24 -->
<ItemsControl ItemsSource="{TemplateBinding ItemsSource}"
ItemsPanel="{StaticResource WrapPanelTemplate}"
ItemTemplate="{StaticResource ItemInDoubleHighBox}"
RenderTransform="1 0 0 1 0 -500" />
</Grid>
</ControlTemplate>
How it works: The DataTemplate causes the items to be "double-spaced" with only 1-12 visible, and the RenderTransform on the second ItemsControl makes items 13-24, which are also "double-spaced" appear in the spaces between the first rows of items.
Note: You can make the height and width data-bindable, but it takes more XAML. Just add ScaleTransforms everywhere "200", "500" or "600" appears in the XAML. For example, to deal with the "200" you can set a scale transform on the inner decorator with ScaleY="0.5" and on each ItemsControl with ScaleY="2". Now the outer decorator's height will be 100, which can be data-bound. The other constants can be dealt with via similar pre- and post- scaling of the content. And because WPF combines all the transforms before rendering anyway, the extra transforms will cost basically nothing.

WPF makes this pretty trivial. Basically you just need to specify an ItemsPanelTemplate.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="4" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Now whatever items you add to the ListBox will be arranged according to the layout logic of the panel which in this case is a UniformGrid.
Note that you'll still need to keep the items in your collection in the order that you want them to appear. So I would sort them out first before adding them to the ListBox. If you need to create "holes" in the collection then I would use some type of placeholder object (maybe new object() will do) instead of trying to use complex layout logic to spread the items.

Related

WPF ContentControl width grows but doesn't shrink when wrapped in a ScrollViewer

I'm trying to figure out how to make my ContentControl to correctly scroll Horizontally (Vertically its fine at the moment). By correctly i mean i would like to see the content to stretch (expand infinitely) while having minimum sizes to which a scrollbar would appear in order for the content not to overflow behind the ContentControl's area, so here's a quick introduction:
The main window is structured in this way:
Grid (2 columns of .3* and .7*)
Border
Grid (7 rows, one set to * where ContentControl is)
ScrollViewer with StackPanel (purely for test) wrapping a ContentControl that has Auto Width
ContentControl's Template:
Grid (Width set to UserControl's ActualWidth, 6 rows with one set to Auto where ItemsControl go
ItemsControl that describes an ItemTemplate of a type DataTemplate which contains a Grid inside of which i have a DataGrid
The actual problem is that the ContentControl grows as you resize the window, but does not shrink with window resize.
Main View XAML (truncated for clarity):
<ScrollViewer Grid.Row="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Grid.Row="5" Background="Transparent" Focusable="False" Margin="0,5,0,0"
Content="{Binding CurrentSection}" ContentTemplateSelector="{StaticResource templateSelector}/>
</ScrollViewer>
Tempate XAML (truncated for clarity):
<Grid>
...
<ItemsControl Grid.Row="4" ItemsSource="{Binding Data.QualifyingDistributionsDividends}" x:Name="QualifyingItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="DTLayoutGrid">
...
<Grid Grid.Row="1" x:Name="DataLayout" Width="{Binding ElementName=DTLayoutGrid, Path=ActualWidth}" HorizontalAlignment="Stretch">
...
<DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="8" HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Payments}" Style="{StaticResource DataGridStyle}" CellStyle="{StaticResource DataGridNormalCellStyle}">
</DataGrid>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
So what happens? Datagrid assumes width of the entire DataTemplate (well its underlying controls that are set to be DataTemplates size, then * column assumes all empty space. When you try to resize the entire window that holds this code it will grow correctly, expanding the * column but it seems shrinking is not "registered" and it keeps the size you expanded it to, applies a scrollbar over that and forgets about it.
What i've tried so far was to set widths for ItemsControl, its underlying parents like Grid etc, also setting size to ContentControl, StackPanel, ScrollViewer and parent Grid of that.
I've also tried using scrollviewers directly on the Datagrid which produces an epileptic "1 million resizes a second" scenario. I've also played around with HorizontalAlignments
Under certain situations i DID managed to get the horizontal scrollbar to appear correctly but unfortunately that makes my DataGrid's * column to assume Auto Width rather then Star so DataGrid starts having an empty area to the right (unacceptable unfortunately...)
I understand that in order for horizontal scrollbar to work the parent or child of the scrollviewer needs Width set, i guess i can't work out where exactly do i need to restrict it. DataGrids NEED to infinitely expand with the main window while having first column fill all the available space.
Do let me now if you need more information on this and I will gladly answer.
It seems to me that this is just another case of the dreaded StackPanel layout problem. This problem comes up again and again and I confess that I had the very same problem when I started learning WPF. The StackPanel does not take the available size of its parent into consideration whereas other Panels such as a DockPanel or a Grid (yes, that's actually a Panel too) do.
It's explained in the How to: Choose Between StackPanel and DockPanel page on MSDN:
Although you can use either DockPanel or StackPanel to stack child elements, the two controls do not always produce the same results. For example, the order that you place child elements can affect the size of child elements in a DockPanel but not in a StackPanel. This different behavior occurs because StackPanel measures in the direction of stacking at Double.PositiveInfinity; however, DockPanel measures only the available size.
The StackPanel should only really be used to align a number of items, such as Buttons or other controls in a straight line where available space is not a concern. So anyway, the solution should be simple... just remove the StackPanel from the ScrollViewer. It doesn't appear to serve any purpose there anyway.
UPDATE >>>
After looking again, it seems as though you're saying that the problem is inside the DataTemplate, right? You might be able to fix that by setting the ItemsControl.HorizontalContentAlignment property to Stretch. That would ensure that each item remains within the boundary of the ItemsControl.
I'd also remove your Binding on the Grid.Width as you don't need it... a child Grid will take up the full space of a parent Grid by default. If these ideas don't work, just simplify your problem. Seriously, if you follow the advise in the linked page from the Help Center that I gave you in the comments, then you'll either fix the problem, or be able to come back here and provide a complete, but concise example that we could test.
I've found the behavior I was looking for by using a UniformGrid as the ItemsPanel, with its rows bound to the count of the ItemsSource model:
<ScrollViewer>
<ItemsControl ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding MyCollection.Count}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
As #Sheridan pointed out above, it seems the StackPanel is causing trouble. Also, credit to https://stackoverflow.com/a/23375262/385273 for pointing out the UniformGrid option.

split DataTemplate in different grids using ItemTemplateSelector

I am working on WPF-XAML. My requirement is :
I need to add collection of Trunks(which consists of Border & TexBlocks) in a Tab.
there will be 2 types of such Trunks (say RSPTrunkTemplate and ASPTrunkTemplate). now I need to add collection of Trunks of type RSPTrunkTemplate in one grid. then there will be GridSplitter and then I need to add another collection of Trunks of type ASPTrunkTemplate in another grid.
I am using ItemTemplateSelector as follows :
<Grid>
<ItemsControl Name="TrunkList"
ItemsSource="{Binding RSPTrunks}"
ItemTemplateSelector="{StaticResource TrunkItemTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
this TrunkItemTemplateSelector is as follows :
<Helpers:TrunkItemTemplateSelector x:Key="TrunkItemTemplateSelector"
RSPTrunkTemplate="{StaticResource RSPTrunkTemplate}"
SPTrunkTemplate="{StaticResource ASPTrunkTemplate}" />
Now, RSPTrunkTemplate should be in one grid and ASPTrunkTemplate shoulb be in another grid.
How to do this. Do I have to change my approach.?
I seek your help guys.
ItemTemplateSelector, as the name suggests, is used to specify a different template for the objects inside a ItemsControl, not to do filtering. If i understand correctly you want to apply a grouping maybe this link can help you http://msdn.microsoft.com/en-us/library/ms742542.aspx

ItemsControl with only custom subelements

I would like to build a custom component that layouts its childs in either a StackPanel or a Grid (with variable row count, which makes me consider the StackPanel instead). The items are custom elements/objects that just hold some configuration, based on which a few controls are created to display them (some labels and text boxes).
Ideally, the component should be used somehow like this (where SpecializedCustomPanelItem is a subtype of CustomPanelItem):
<CustomPanel>
<CustomPanelItem Param1="value A" Param2="value B">Text</CustomPanelItem>
<CustomPanelItem Param1="value C">Other text</CustomPanelItem>
<SpecializedCustomPanelItem>More text</SpecializedCustomPanelItem>
<!-- The number of items is variable -->
</CustomPanel>
I’ve read on the ItemsControl for a while now, and it fits my needs rather well. I would create simply types for the items, and make data templates for them available from inside the ItemsControl. Then they should already render fine.
However I would like to require the items inside that ItemsControl to be of a specific type (i.e. CustomPanelItem or a subtype). I actually thought that the ItemsControl would allow this, just like you within a ComboBox or a MenuItem, but it turns out that it actually allows any subtype, and if necessary wraps them in a item container.
So I have been thinking if an ItemsControl is actually what I am looking for, as I do not want any “fancy” things like selection or scrolling which most of those controls implement. I actually only want to build a simple interface to a common pattern in the application that auto generates those components and layouts them in a Grid/StackPanel.
Should I still be using the ItemsControl or rather build some more custom component?
In this case you don't really need a custom component. Changing the ItemsPanel type to whatever type you need + multiple templates for the Items should do the trick.
However to answer the question in the heading: If you want to force an items control to only accept a certain type of items, you will have to create
a. A CustomItemsControl
b. A CustomItemsControlItem
Then for the CustomItemsControl you should declare the attribute
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CustomItemsControlItem))]
Then you also will need to
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomItemsControlItem();
// You can throw an exception here
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is CustomItemsControlItem;
}
If memory serves this should force the ItemsControl to not allow other types to be added as children and should throw exceptions. You could then do some magic inside CustomItemsControlItem by defining some DependencyProperties which you can then set when adding the items in XAML.
But yet if you have multiple types in your ViewModel that you want to display correctly, the correct way is still to provide multiple templates for the CustomItemsControlItem targetting your ViewModel types.
Hope this helps.
This sounds perfect for an ItemsControl
You can set it's ItemsPanelTemplate to define the kind of panel which will hold your items, and set the ItemContainerTemplate to define how to draw each item.
If items should be drawn differently based on what type they are, I'd suggest using implicit DataTemplates instead of setting the ItemContainerTemplate
<Window.Resources>
<DataTemplate DataType="{x:Type my:BasePanelItem}">
<my:CustomPanelItem Param1="{Binding Param1}" Param2="{Binding Param2}" Content="{Binding SomeValue}" />
</DataTemplate>
<DataTemplate DataType="{x:Type my:SpecializedPanelItem}">
<my:SpecializedCustomPanelItem Content="{Binding SomeValue}" />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding MyItems}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:CustomPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You mentioned that you wanted to perhaps use a dynamically created Grid instead of a StackPanel as well. If you do, you might be interested in some GridHelpers I have posted on my blog. This would allow you to bind the number of Columns/Rows on the Grid in the ItemsPanelTemplate
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

adding rows dynamically based on the records in database in listbox mvvm mvvm light

I have a listbox in which I am displaying the records from the database. Its coming one after the other in a single column. How to put the records programitically as 3 rows and 3 columns i.e. 3 records in a row, after that 3 records in a row. Kindly suggest?
Thanks.
If the records are (or can be) a fixed width, it is easy: Just change your ListBox.ItemsPanel to a WrapPanel and set the ListBox width to be just over 3x the width of the items:
<DataTemplate DataType="{x:Type my:CustomItem}">
<Border Width="100" ...>
...
</Border>
</DataTemplate>
...
<ListBox ... Width="320">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
...
</ListBox>
If you need to divide the available width by three and you know that all of your items are exactly the same height (or if you are ok with all your rows being the max height of any item), there is a very simple solution using UniformGrid with no Height specified:
<ListBox ... >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Width="3" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
...
</ListBox>
If you want to divide the available width by 3 to get the item width but stack the items as closely as possible vertically, it is possible but tricky in pure XAML. (I wouldn't recommend doing it in pure XAML but if you want to do it that way, you can create a hidden Grid with three star-sized columns and a fixed-width column, then use a binding to the ActualWidth of the first column of the hidden grid.)
An easier way is to create a simple custom panel that acts like UniformGrid horizontally but StackPanel vertically. This is a very simple Panel subclass:
MeasureOverride computes desired width as 3* max width of any child
MeasureOverride computes desired height as sum(max(height of each group of 3))
ArrangeOverride lays out children 3 per row, each DesiredWidth/3 to the right of the previous one, and the next row being max(height) down from the current row
Obviously if you design a custom panel you wouldn't hard-code the "3" - you would make it a property, probably a DependencyProperty, so you can easily change it and so that you can reuse your panel elsewhere if need be.

If you were going to build an org chart builder in Silverlight, what base class would you use for creating the boxes of the chart?

For this question, let us assume that we will want to show the face of the employee, title, department, and whether they like Piña coladas/getting caught in the rain.
Perhaps it might look something like the following:
http://www.edrawsoft.com/images/examples/Photo-Org-Chart-Full.png
Would you use a...
System.Windows.Control.UserControl?
FrameworkElement?
UIElement?
Canvas
Why? As always, thank you for your advise! I greatly appreciate it!
If I had to create a org chart control with advanced layout I would probably derive from Control, and create a "real" templated control in a similar manner as e.g. the TreeView control. This is probably the most advanced route to create a new control, but also the most powerful.
You may also be able to modify the control template of a TreeView, and make it grow downwards from the center instead of left and down from the upper left corner, but it will probably be difficult or impossible to customize the layout of the various levels as the TreeViewItem doesn't carry any extra information to describe the layout of a particular node.
In fact I did recently some experiments modifying the TreeView control template, but I stumbled upon something I didn't understand. Luckily I figured out what I did wrong, and you can see how it is possible to change the orientation of TreeView child items from vertical to horizontal in my question here on Stack Overflow.
I've seen a website that uses TreeViewItem and ControlTemplates, but I can't find it at the moment, I think it was on CodeProject.
Another idea I was playing with recently is use 2 usercontrols, itemcontrols and stackpanels.
Here's an example of a an OrgBar rectangle with text under it and it renders it's children in OrgGroup control by setting the ItemSource to it's children collection recursively. You can put the root orgbar on a canvas and play around with paths for the arrows. I tried to point out the basics but if you need more I can fill in the blanks.
Public Class OrgBarDataNode
Public Property BarColor as New SolidColorBrush(Colors.Red)
Public Property BarName As String
Public Property Children as New ObservableCollection(Of OrgBarDataNode)
End Class
Class MainPage
...
Public Sub Loaded
Dim Root as New OrgBarDataNode With {.BarName = "Root"}
Dim Child1 as New OrgBarDataNode With {.Barname = "Child1"}
Root.Children.Add(Child1)
LayoutRoot.Children.Add(Root)
End Sub
...
End Class
<UserControl x:Class="OrgBar">
<Grid>
<StackPanel ToolTipService.ToolTip="{Binding BarName}" Cursor="Hand">
<Rectangle Fill="{Binding BarColor}" Style="{StaticResource RecStyle}"/>
<TextBlock Text="{Binding BarName}" HorizontalAlignment="Center"
Margin="0,10,0,0" />
<local:OrgGroup Margin="0,20" HorizontalAlignment="Center"
DataContext="{Binding Children}" />
</StackPanel>
</Grid>
</UserControl>
<UserControl x:Class="OrgGroup">
<Grid>
<!-- this {Binding} to nothing means bind to DataContext}-->
<ItemsControl ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:OrgBar Style="{StaticResource OrgBarStyle}"
DataContext="{Binding}" />
<!-- this {Binding} refers to the the child node this time} -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
That's essentially a tree structure, so like Paully suggested, I would start with a TreeView (Silverlight Toolkit) and customize the control template and treeview itself.

Resources