Let's say, I have a class that looks like this:
public class Item
{
public string Name { get; set; }
public List<Item> SubItems { get; set; }
}
I want to display a list of such items in such a way that top-level items are arranged horizontally, and their sub-elements are arranged vertically below them:
Item1 Item2 Item3 Item4
SubItem2.1
SubItem2.2
I'm trying to achieve that using a ListView, and my XAML looks like this:
<ListView x:Name="ItemsList">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" VerticalAlignment="Top">
<TextBlock Text="{Binding Name}" Background="#FF8686FF" Width="25" Height="25"/>
<ListView ItemsSource="{Binding SubItems}" BorderBrush="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
However, my list doesn't look exactly the way I want:
As you can see, the items are vertically aligned to the center of their enclosing stack panels (which, in turn, occupy all available space). How do I properly align elements to the top? Or maybe there is a better way to achieve what I want with different containers?
You have to set container style:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Top" />
</Style>
</ListView.ItemContainerStyle>
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 have a listView in which I show a collection of Vehicles which are grouped by their MaintenanceState. If the MaintenanceState of the Vehicle updates I expect it to change group. The collection itself is correctly updated, however the view does not update accordingly. Below is some of my code, maybe someone can help me getting this to work.
This is my CollectionViewSource managing my groupings
<CollectionViewSource x:Key="GroupedVehicles" IsLiveGroupingRequested="True" Source="{Binding ItemCollection}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="MaintenanceState" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
Here is my ListView
<ListView ItemContainerStyle="{DynamicResource VehicleItemContainerStyle}"
ItemsSource="{Binding Source={StaticResource GroupedVehicles}}"
SelectedItem="{Binding SelectedItem}"
SelectionMode="Single"
Style="{DynamicResource VehiclesListViewStyle}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Expander Header="{Binding Path=Name}"
IsExpanded="True"
Style="{DynamicResource VehicleListSectionExpanderStyle}">
<ItemsPresenter />
</Expander>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Number}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is what I do on my ViewModel
Vehicle updatedVehicle = new Vehicle(vehicleNumber, MaintenanceStateEnum.Running);
ItemCollection[index] = updatedVehicle;
The ItemCollection is of type ObservableCollection<Vehicle> and I make sure to only add, remove or replace Vehicles.
The MaintenanceStateEnum has the following values: InMaintenance, MarkedForMaintenance and Running.
This is what my Vehicle looks like
public class Vehicle
{
public Vehicle(int number, MaintenanceStateEnum state) {}
public int Number { get; private set; }
public MaintenanceStateEnum MaintenanceState { get; private set; }
}
So my problem:
If I have Vehicle(3, MaintenanceStateEnum.MarkedForMaintenace) and it is updated to Vehicle(3, MaintenanceStateEnum.InMaintenance) it does not change from the grouping MarkedForMaintenance to the grouping InMaintenance.
Interesting is that it does get removed from the MarkedForMaintenance grouping (the view even leaves a space as if the object is still there).
Does anyone know how I can fix my problem?
I think the issue here is that the view does not know that the collection has changed. You could try to change your container from ItemCollection to ObservableCollection which implements both INotifyCollectionChanged and INotifyPropertyChanged.
I have the following class:
public class MyClass
{
public MyClass()
{
OtherClass = new List<OtherClass>();
}
public List<OtherClass> OtherClass { get; set; }
}
OtherClass contains:
public class OtherClass
{
public OtherClass ()
{
}
public string Name { get; set; }
}
and the following xaml MyView:
<DataTemplate DataType="{x:Type Framework:MyClass}">
<StackPanel>
<Label FontSize="20" Content="{Binding Path=OtherClass.Name}" />
</StackPanel>
</DataTemplate>
in MyWindow referencing MyView:
<TabItem Header="My Class">
<Views:MyView DataContext="{Binding Path=MyClass}" />
</TabItem>
I have seen other examples of binding nested properties which suggest that Binding Path this way (ie, OtherClass.Name) works fine for a single object. However, I am binding a list of objects rather than a single object (in my example, a list of OtherClass).
Is it possible to bind a list of objects?
If you want to create DataTemplate for MyClass then you need to use some form of ItemsControl to display OtherClass list property
<DataTemplate DataType="{x:Type Framework:MyClass}">
<ItemsControl ItemsSource="{Binding OtherClass}" DisplayMemberPath="Name"/>
</DataTemplate>
also OtherClass.Name must be a public property and not private as it is at the moment
public class OtherClass
{
public OtherClass ()
{
}
public string Name { get; set; }
}
EDIT
DisplayMemberPath is the easiest way to display single property but if you want to display more then one property from OtherClass class, or change how it's formatted then you need to define ItemsControl.ItemTemplate instead and tell ItemsControl how to display each item
<ItemsControl ItemsSource="{Binding OtherClass}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<!-- more properties that you want to display -->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What you'll likely want to do here is make a ItemsControl, where each item of your OtherClass list will be prepresented by one item. Your ItemTemplate will dictate what each item in that list is to display, in your case the ItemTemplate will contain a Label which is bound to the Name property. See below:
<ItemsControl ItemsSource="{Binding OtherClass}" DataType="{x:Type Framework:MyClass}">
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
(See a more complete example here)
If you are binding to a "list" of objects, the list must be bound to a control that has an ItemsSource property. These types of controls are able to bind to collections so that the inner DataContext of the control is the list's type. In your case, if you bind to the OtherClass property (the list), the DataContext scope will be of Type 'OtherClass' where you can then bind to its properties explicitly.
As others have mentioned, you can bind to an ItemsControl, but can also bind to a ListBox, ListView, DataGrid, TreeView, etc.
<!-- Using ItemsControl -->
<ItemsControl ItemsSource="{Binding OtherClass}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="20"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataTemplate.Resources>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Using ListBox-->
<ListBox ItemsSource="{Binding OtherClass}">
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="20"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataTemplate.Resources>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have The following structure
QPage class Object contains a List<List<QLine>>
QLine object contains List<Qword>
every list of words constructs a line and every list of lines consists a group(paragraph) and every list of paragraphs consists a page.
I want to bind the page to structure like this in XAML
<ListView>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock>
</TextBlock>
</StackPanel>
</StackPanel>
</ListView>
where each item of the ListView is a paragraph(List<QLine>) and each vertical stack panel holds an item of the List<QLine> and each item of the horizontal stack panel holds an item of the List<Qword> and the texblock is bound to Qword.text property. I have no idea how to do such binding from the XAML code.
Hopefully I did not miss some list but this should work. Basically it's a ListBox that hosts List<List<QLine>> (called it QPageList). Then you have ItemsControl that hosts each List<QLine> in vertical panel and finally there is another ItemsControl that hosts List<Qword> from QLine (called it QwordList) where each QWord is displayed as TextBlock on horizontal StackPanel
<!-- ItemsSource: List<List<QLine>>, Item: List<QLine> -->
<ListBox ItemsSource="{Binding QPageList}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: List<QLine>, Item: QLine -->
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: QLine.List<QWord>, Item: QWord -->
<ItemsControl ItemsSource="{Binding QwordList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Item: QWord -->
<TextBlock Text="{Binding text}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What you are looking for is ListView.ItemTemplate. Basically, you need to provide your list with a way to understand the nested data structure of your rows.
Here is a good tutorial to get you started on ItemTemplates.
Once your list has an item template then you just bind the ListView directly to your data source and that's it.
Hopefuly this will prove helpful. Taken from here
Authors (list)
- - Name
- - Books (list)
| - - Title
| - - Contents
Sample code :
<Window.Resources>
<DataTemplate x:Key="ItemTemplate1">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate2">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource AuthorDataSource}}">
<ListBox x:Name="AuthorList" ItemTemplate="{DynamicResource ItemTemplate1}" ItemsSource="{Binding Authors }" >
<ListBox x:Name="BookList" DataContext="{Binding SelectedItem, ElementName=AuthorList}" ItemsSource="{Binding Books }" ItemTemplate="{DynamicResource ItemTemplate2}" />
<TextBlock x:Name="BookContent" DataContext="{Binding SelectedItem, ElementName=BookList}" Text="{Binding Contents }" />
</Grid>
Forward to what Alex is saying, it'd be a good idea to Encapsulate (If that's the correct word here) the object within classes, i.e.:
public class Page
{
public List<Paragraph> Paragraphs { get; set; }
}
public class Paragraph
{
public List<QWord> Sentences { get; set; }
}
public class Sentence
{
public List<QWord> Words { get; set; }
}
That'd help when you bind data in your XAML, i.e. if you look at the tutorial which Alex provided:
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
Hope that helps you.
I am trying to bind a list of objects to a list of HyperLinks that are comma-separated, and clickable.
I can do it if it's only one of them per cell, but I don't know how to bind a collection per cell.
Something like this in a single GridViewColumn cell:
Effect01, Effect02, Effect02, Shader01. Shader02
where clicking any of them will call the binded object's Execute method.
Each element is:
public class EffectLink : IExecutable
{
public string Name {get; set;}
public void Execute ();
}
as enforced by IExecutable.
I do it this way because the behaviour of Execute will depend on whether it's a shader, an effect, etc.
Any ideas?
EDIT:
I found that I could do this:
<TextBlock>
<Hyperlink>Effect01</Hyperlink>
<Hyperlink>Effect02</Hyperlink>
</TextBlock>
So this shows 2 space-separate hyperlinks in a single cell.
Also this question might be related, but it shows how do it in code, not xaml:
Nested TextBlocks and Hyperlinks, How do you replicate this XAML in C#?
put an ItemsControl in your GridViewColumn.CellTemplate
<GridViewColumn >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="commaTextBlock" Text=", " />
<TextBlock><Hyperlink><Run Text="{Binding Path=Name}" /></Hyperlink></TextBlock>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource PreviousData}}" Value="{x:Null}">
<Setter Property="Visibility" TargetName="commaTextBlock" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>