I have a ListView, where the item template is another ListView, which has an item template of a TextBox. The end result of this is that I'm able to bind my collection of "rows" to the first ListView and I get a 2D grid of my data. I'm essentially trying to implement a basic spreadsheet.
My problem is that I want the TextBoxes to stretch horizontally so that they are all the same width and take up all of the available space.
I've removed all extraneous style, event handling, context menu, etc - here is the code as it is:
<ListView ItemsSource="{Binding Rows}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Path=RowData}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've tried setting HorizontalContentAlignment="Stretch" on the ItemContainerStyle of the ListView, and HorizontalAlignment="Stretch" on the TextBox itself with no luck so far. I did have this "working" by binding the width of the TextBoxes to a dependency property on the control which was updated with an attempt to calculate the correct width for all boxes, but it was rather ugly and slow.
Does anyone know how I can get this done in the XAML alone?
Don't use StackPanel, use UniformGrid instead in your ItemsPanelTemplate and set its Columns to the number of your desired number of fields in each row.
<ItemsPanelTemplate>
<UniformGrid Columns="5"/>
</ItemsPanelTemplate>
Which will:
Provides a way to arrange content in a grid where all the cells in the grid have the same size.
Related
I would like to be able to use a WrapPanel to wrap by group header instead of by item.
I have implemented the following ICollectionView view for a ListView bound to an ObservableCollection studentlist:
view = CollectionViewSource.GetDefaultView(studentlist);
view.GroupDescriptions.Add(new PropertyGroupDescription("sectionoutput"));
view.SortDescriptions.Add(new SortDescription("sectionoutput", ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("displayname", ListSortDirection.Ascending));
The XAML includes a style resource to set the template for the group header:
<Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Name}" IsExpanded="{Binding Name, Mode=OneWay, Converter={StaticResource expanderconverter}}">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This Style is used as the GroupStyle for the ListView:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</ListView.GroupStyle>
...
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding displayname}">
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As is, the code presents a vertical list of student names grouped by a header listing their section number. But I would like to list the groups sequentially in a horizontal direction.
I know that I am able to organize the items in the ListView with a WrapPanel:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ItemWidth="150">
</WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
But this code merely displays the students in a group side by side rather than displaying the groups themselves side by side. What I would like to do is have the groups themselves wrapped horizontally. I've tried to implement a wrap panel in the style resource, but I haven't been successful and have been wondering if there is a way to wrap by group.
I figure it is possible to do this by switching to a TreeView and turning the groups into Items. But I would prefer to use a ListView if at all possible because my program implements a class that takes the ListView as an argument.
The GroupStyle has a Panel property that you can set to an ItemsPanelTemplate with a WrapPanel:
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
Suppose to have a ViewModel that is the DataContext of the XAML posted below that contains an ObservableCollection<GroupViewModel> named GroupsViewModel.
GroupViewModel merely has a property named GroupName that is the name of the Group to show and it has a property of ObservableCollection<StudentViewModel>.
StudentViewModel has the property displayName to display the name of the Student.
Said so, by using an Expander and 2 nested ListBox you can get what you asked, like the following code:
<ListBox ItemsSource="{Binding GroupsViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Expander HorizontalAlignment="Left" IsExpanded="True" Header="{Binding GroupName}">
<ListBox ItemsSource="{Binding StudentViewModel}" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding displayname}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Expander>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
I want to archive a vertical ListView that contains a UserControl in which i have some infos on the left and a second ListView that will scroll horizontal.
The problem is that my second listview will not start to scroll. It seems to pick unlimited space.
First of all some more infos about my setup:
My window is arranged via a grid basically just a row on top with text and the first listview in the second row.
The first listview uses a ItemTamplate like the following:
<ListView.ItemTemplate>
<DataTemplate>
<controls:MyControl />
</DataTemplate>
</ListView.ItemTemplate>
Beside that it has a ItemContainerStyle to display a horizontal line between the items:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem"
BasedOn="{StaticResource {x:Type ListViewItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<StackPanel>
<Rectangle x:Name="Separator"
Height="2"
SnapsToDevicePixels="True"
Fill="{DynamicResource AccentColorBrush}" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
(trigger that disables the unnecessary first line omitted)
MyControl is a UserControl which is also just a grid first column some text and second column is my second listview.
This one is special because it is horizontal. Since their are various approaches for this from arround the internet i will show the one i ended using with.
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"
IsItemsHost="True"
IsVirtualizing="True"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
Item template is again a UserControl (at the moment just a TextBlock) and again the separator lines but this time vertically.
The problem is now that the second ListView is not scrollable. Even if i explicit set them to be visible they are disabled (also after resizing window).
My approach solving this was binding MaxWidth of Stackpanel in ListViewItem Template to Actual with of my MetroWindow.
MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type metroControls:MetroWindow}}}"
But this and some other tries binding different sizes of different items does not work.
Can somebody give a hint to resolve this?
I have problem with my gridview. I'm displaying grouped data in it and using VariableSizedWrapGrid for displaying items. The problem is that every group has the same width equal to the most item. Even if a group contains only 1 item it has width like it was 20 items there. How to make these ItemsPanels have variable width?
My issue is almost the same as described here but when I use VirtualizingStackPanel as GridView.ItemsPanel my VariableSizedWrapGrid is displayed in one row and I need it to be displayed in two rows.
<SemanticZoom.ZoomedInView>
<GridView ScrollViewer.IsHorizontalScrollChainingEnabled="False"
ScrollViewer.IsVerticalScrollChainingEnabled="False"
ItemTemplate="{StaticResource PatientMediaFileBigItemTemplate}"
ItemsSource="{Binding Source={StaticResource PatientVisits} }"
IsItemClickEnabled="True"
SelectionMode="None"
Margin="0,0,0,0" ItemClick="MediaFileIcon_Click"
>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Button Content="{Binding DateOfVisit, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:dd MMM yyyy}'}" FontSize="28" Foreground="Black" BorderThickness="0" Click="ButtonVisit_OnClick"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0" Width="Auto" Background="BlueViolet"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
You might need to change the ItemsPanel of your GridView to a StackPanel, but note this will limit the number of items you can put in your GridView as it disables virtualization. A better option might be to keep showing a limited number of items in a group, but add a button (really the group header button with a chevron glyph should work) to navigate to a full view of the selected group.
I was wondering if its possible to somehow bind a list of lists to a cavas panel. For example having object "layer" which itself is a list of "rectangle" objects. Would if be possible to bind a list of layers to a canvas? So far I can only do it by binding a flattened INumerable of the nested list (using SelectMany function) to an itemcontrol, but this isnt good enough, Id like to keep the "layers" separate and have the Zindex of the rectangles vary according to the layer it is at, allowing to reorder the layers easily.
I have also tried having nested itemcontrols but as expected it only displays the first layer. The aim is to draw a rectangle for every object in every layer on the canvas allowing for layer manipulation, insertion of new objects in each layer, etc...
Thanks in advance! :)
How about making the ItemTemplate of the main ItemsControl another ItemsControl with yet another Canvas as ItemsPanel? (I do not see a reason why it should only display the first layer)
Actually the main ItemsControl does not need to be a Canvas, a Grid would do as well (at least unless your collections have coordinates of their own or a Z-Order, if you use a grid the layer order is he same as their appearance in the collection).
An example (a bit verbose but can't do anything about that):
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding LayerItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<!-- Here would the binding to some properties that take care of placement -->
<Setter Property="Canvas.Top" Value="{Binding Top}" />
<Setter Property="Canvas.Left" Value="{Binding Left}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<!-- Some template for the individual items -->
<DataTemplate>
<Border Padding="5" BorderThickness="1" BorderBrush="Red" CornerRadius="3"
Background="White">
<TextBlock Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Like H.B said, i too can't see a reason why it will not work. Having an ItemsControl in another ItemsContainer works perfectly fine.
<ItemsControl ItemsSource="{Binding Layers}">
<ItemsControl.ItemsContainerStyle>
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ItemsControl ItemsSource="{Binding Rectangles}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ItemsControl.ItemsContainerStyle>
</ItemsControl>
But it think it would be cleaner to put the sub ItemsControl inside an DataTemplate for your Items, without the need to modify the ItemsContainerStyle. I think its bad design if your control needs to know informations about the properties of the model, which is necessary for binding to Rectangles.
I have an ItemsControl with a DataTemplate that is bound to an ObservableCollection of integers.
<ItemsControl Name="DimsContainer" ItemTemplate="{StaticResource DimensionsTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And in the Windows Resources:
<Window.Resources>
<DataTemplate x:Key="DimensionsTemplate" >
<TextBlock Text="{Binding}"
Padding="5"
VerticalAlignment="Center"
FontSize="32"/>
</DataTemplate>
</Window.Resources>
My problem is that in code, I need to be able to determine the width of the TextBlocks (or whatever the element is if I change it later) in the ItemsControl. Does anyone have an idea how to do this?
When I do DimsContainer.Items[i] it gives me the bound item not the TextBlock.
You should be able to use instead:
DimsContainer.ItemContainerGenerator.ContainerFromIndex(i);
This won't give you the TextBlock itself, but it will give you the generated ContentPresenter that is wrapped around it by the ItemsControl to contain the ItemTemplate.