How to make a recursive ListView in XAML - wpf

My model looks like
public class MyVm
{
public string MyTitle { get; set; }
public List<MyVm> Children { get; set; }
public MyVm()
{
this.Children = new List<MyVm>();
}
}
I want to be able to list through all the children and children's children, which I think is recursive.
The MainWindow code behind is
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Kids = new List<MyVm>();
var m = new MyVm();
m.MyTitle = "Title1";
var m2 = new MyVm();
m2.MyTitle = "Title2";
var m3 = new MyVm();
m3.MyTitle = "Title3";
var m4 = new MyVm();
m4.MyTitle = "Title4";
m.Children.Add(m2);
m2.Children.Add(m3);
m3.Children.Add(m4);
this.Kids.Add(m);
}
public List<MyVm> Kids { get; set; }
and finally the MainWIndow view is
<Grid.Resources>
<Style x:Key="MyStyle" TargetType="ListViewItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<HierarchicalDataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding MyTitle}" />
<ListView ItemsSource="{Binding Children}" ItemContainerStyle="{Binding MyStyle}" />
</StackPanel>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListView ItemsSource="{Binding Kids}" ItemContainerStyle="{StaticResource MyStyle}" />
As you can see I've tried to re-use the same resource for each 'children' to achieve the recursive bit, but sadly, the only thing I see rendered is a single TextBlock with Title2

For these purposes you would use a HierarchicalDataTemplate (which has its own ItemsSource property that you would bind to Children), i am not sure if a ListBox supports it. If not use a TreeView and change the control templates to remove the indentation and the collapse toggle button if you don't want that.

Fixed it
<Grid.Resources>
<DataTemplate DataType="{x:Type a:MyVm}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding MyTitle}" />
<ListView ItemsSource="{Binding Children}">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding }" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListView ItemsSource="{Binding Kids}" />

Related

Image icon and string do not appear in Listview in WPF

this is XAMl code
Image Icon and string don't appear in ListView in WPF
What's wrong?
Not only the image, but also other text strings do not appear. Trying other methods says that the ItemSource is overlap.
Is the binding not being applied?
<GroupBox Grid.Column="1" Grid.RowSpan="2" Background="Aqua">
<StackPanel Orientation="Vertical" Background="Aquamarine" Margin="5">
<StackPanel.Resources>
<Style x:Key="ListviewStyle" TargetType="{x:Type ListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListView}">
<Border x:Name="corner"
CornerRadius="9"
Background="White"
Height="1000">
<ContentPresenter
VerticalAlignment="Center"
HorizontalAlignment="Center"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<TextBlock Text="I/O Input"
FontSize="30"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10"/>
<ListView Style="{StaticResource ListviewStyle}"
x:Name="lv_input">
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="InputStatus">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding InputStatus}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="200" Header="InputName"
DisplayMemberBinding="{Binding InputName}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</GroupBox>
this is behind code
Image Icon and string don't appear in ListView in WPF
What's wrong?
Not only the image, but also other text strings do not appear. Trying other methods says that the ItemSource is overlap.
Is the binding not being applied?
enter code here
public partial class IOSetPage : Page
{
public ObservableCollection<ListViewItemsData> ListViewItemsCollections { get { return ListViewItemsCollections; } }
ObservableCollection<ListViewItemsData> _ListViewItemsCollections = new ObservableCollection<ListViewItemsData>();
public IOSetPage()
{
InitializeComponent();
ListViewItemsCollections.Add(new ListViewItemsData()
{
InputStatus = "/Image/inputdisconnect.png",
InputName = "ASFWQEFAasdfqerwefc"
});
lv_input.ItemsSource = ListViewItemsCollections;
}
public class ListViewItemsData
{
public string InputStatus { get; set; }
public string InputName { get; set; }
}
}
Considering the example you've provided, it appears you have a typo on the 3rd line of your C# code. The property getter should be returning _ListViewItemsCollections like this:
public ObservableCollection<ListViewItemsData> ListViewItemsCollections { get { return _ListViewItemsCollections; } }
Also, I noticed that you are using ItemsSource directly, rather than setting the DataContext on the lv_input ListView. Whilst ItemsSource may work after fixing the typo, you may encounter unexpected behavior when trying to use item templates etc...
A more correct (but not perfect) way of doing what you are attempting would look like this:
public partial class IOSetPage : Page
{
IOSetPageView view;
public IOSetPage()
{
InitializeComponent();
view = new IOSetPageView();
view.Items.Add(new ListViewItemsData()
{
InputStatus = "/Image/inputdisconnect.png",
InputName = "ASFWQEFAasdfqerwefc"
});
lv_input.DataContext = view;
}
}
public class IOSetPageView
{
public ObservableCollection<ListViewItemsData> Items { get; }
= new ObservableCollection<ListViewItemsData>();
}
public class ListViewItemsData
{
public string InputStatus { get; set; }
public string InputName { get; set; }
}
With the XAML for the ListView looking like this:
<ListView Style="{StaticResource ListviewStyle}" ItemsSource="{Binding Items}" x:Name="lv_input">
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="InputStatus">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding InputStatus}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="200" Header="InputName"
DisplayMemberBinding="{Binding InputName}"/>
</GridView>
</ListView.View>
</ListView>
Notice how the ItemSource is now bound to the Items property of the view class I created. To improve further, you could implement the INotifyPropertyChanged interface for your ListViewItemsData class which would allow the ListView to update in real time when you modify a property.

Data in TreeView.ItemTemplate HierarchicalDataTemplate gets erased when switching the Selected TreeItem

I have the following code:
Template.XAML
<Style TargetType="{x:Type HeaderedContentControl}">
<Setter Property="Header">
<Setter.Value>
<ContentControl Foreground="Red"
FontFamily="Segoe UI"
Margin="0,0,0,20"
Content="{Binding Tag, RelativeSource={RelativeSource AncestorType=HeaderedContentControl}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type local:ViewModel}">
<HeaderedContentControl xmlns:sys="clr-namespace:System;assembly=mscorlib"
Tag="{Binding Header}"
Background="SteelBlue"
BorderBrush="DarkSlateBlue">
</HeaderedContentControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<HeaderedContentControl xmlns:sys="clr-namespace:System;assembly=mscorlib"
Tag="{Binding Header}"
Background="SteelBlue"
BorderBrush="DarkSlateBlue">
</HeaderedContentControl>
</DataTemplate>
Windows.XAML:
<Window.DataContext>
<local:WindowsVM x:Name="viewModel"/>
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Templates.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding AllContents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<Label Content="{Binding Header}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<StackPanel Orientation="Vertical">
<ContentControl Content="{Binding SelectedItem}" />
</StackPanel>
</StackPanel>
Windows.XAML.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
viewModel.SelectedItem = (ViewModel)e.NewValue;
}
}
ViewModel.cs
public class WindowsVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public WindowsVM()
{
AllContents = new ObservableCollection<ViewModel>();
AllContents.Add(new ViewModel("Item 1"));
AllContents.Add(new ViewModel2("Item 2")); //using ViewModel("Item 2") will show the Header as it should
SelectedItem = AllContents.First();
}
private ViewModel _selectedItem;
public ViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
}
}
private ObservableCollection<ViewModel> _allContents;
public ObservableCollection<ViewModel> AllContents
{
get { return _allContents; }
set
{
_allContents = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AllContents)));
}
}
}
public class ViewModel:INotifyPropertyChanged
{
private string _header;
public event PropertyChangedEventHandler PropertyChanged;
public string Header
{
get { return _header; }
set
{
_header = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Header)));
}
}
public ViewModel(string header)
{
Header = header;
}
}
public class ViewModel2 : ViewModel
{
public ViewModel2(string header) : base(header)
{
}
}
If you run the application, the "Item 1" will be shown as the header of the selected item, as it should.
If you click on the second tree node, I would expect "Item 2" shown as the header of the selected item, but it is not.
Here comes the interesting part, if for "Item 2", it is of the type ViewModel instead of ViewModel2, then the "Item 2" header will be shown. How come this is happening? Is this a WPF treeview bug?
I would also welcome workaround, in the spirit of MVVM model.
You should bind the Header property of the HeaderedContentControl to your Header source property and then define a HeaderTemplate in the HeaderedContentControl style.
If you implement Templates.xaml like this, your example works as expected:
<Style TargetType="{x:Type HeaderedContentControl}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Label Foreground="Red"
FontFamily="Segoe UI"
Margin="0,0,0,20"
Content="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type local:ViewModel}">
<HeaderedContentControl Header="{Binding Header}"
Background="SteelBlue"
BorderBrush="DarkSlateBlue">
</HeaderedContentControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<HeaderedContentControl Header="{Binding Header}"
Background="SteelBlue"
BorderBrush="DarkSlateBlue">
</HeaderedContentControl>
</DataTemplate>

Custom Control for a ComboBox with Default Element

What I want:
I'm trying to create a ComboBox which has a 'Default' element seperated from the rest of the items.
What I have:
So by following the countless threads about grouping the items and overwritting the ItemTemplate I came to this result:
.CS
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ObservableCollection<GroupItem>()
{
new GroupItem() { Name = "Default Item", GroupName = "Default"},
new GroupItem() { Name = "Item 1", GroupName = "Item"},
new GroupItem() { Name = "Item 2", GroupName = "Item"},
new GroupItem() { Name = "Item 3", GroupName = "Item"}
};
}
public class GroupItem
{
public string Name { get; set; }
public string GroupName { get; set; }
}
}
}
XAML
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="GroupedDataCollView">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="GroupHeaderTemplate">
<TextBlock Text="{Binding GroupName}" Margin="10,0,0,0" Foreground="#989791" />
</DataTemplate>
<!--Here, we tell that the URL's description should be displayed as item text.-->
<DataTemplate x:Key="NameTemplate">
<TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
</DataTemplate>
<Style x:Key="GroupStyleContainerStyle" TargetType="{x:Type GroupItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Separator />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window>
<!-- ... -->
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource GroupedDataCollView}}"
Width="200" Margin="10">
<ComboBox.GroupStyle>
<GroupStyle
ContainerStyle="{StaticResource GroupStyleContainerStyle}"
HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- ... -->
</Window>
The Problem: Since we use Dictionaries for styling and templates which lie in another project, I can't use this 'solution' because this would require the style to know what kind of items he has to expect (Destroys the isolation of the custom styles and templates). Furthermore it's not really a group that I want to create rather a 'special' item, which is seperated by the others visually.
So I tried to create a custom UserControl to solve the problem so I could use it like this:
<ComboBox ItemsSource="{Binding Items}" DefaultItem="{Binding DefaultItem}" SelectedItem="{Binding SelectedItem}" />
In this CustomControl I would create the necessary grouping and handling hidden from the user. But how can I do this? And how can I overwrite the whole selection behaviour since I don't want to return a Key-Value-Pair (GroupItem) instead of the item the user expects.
So in short: How can I create a CustomControl which provides the necessary functionality shown at the start combined with the simplicity shown above?

Binding to a CollectionViewSource with expander style

I have a CollectionViewSource bound to an ObservableCollection of my ViewModels. I then have a style on the GroupItem which has an expander in it. I want to be able to have a 'collapse all' and an 'expand all' on the group headers, but am having trouble binding to the isExpanded on the expander in the ControlTemplate. I have this code:
<Style x:Key="GroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="{Binding Path=**????**}" Template="{StaticResource DAExpander}">
<Expander.Header>
<DockPanel VerticalAlignment="Center">
<Image DockPanel.Dock="Left" x:Name="scItemIcon" Source="./groupIcon.ico" Height="18" Width="18"/>
<TextBlock DockPanel.Dock="Left" Text="{Binding Name}" VerticalAlignment="Center" Margin="4,0,6,0" />
</DockPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have an IsExpanded property on my view model but i cant figure out how to access it. I have found examples such as:
<Expander IsExpanded="{Binding Path=Items[0].IsExpanded}"
<Expander IsExpanded="{Binding Path=Name.IsExpanded}"
None of which seem to work. Is there a way to accomplish this or do i need to take a different approach?
Edit: more xaml, and view model code:
<ListBox ItemsSource="{Binding Source={StaticResource MyCVS}}" ItemTemplate="{StaticResource ItmesTemplate}" ItemContainerStyle="{StaticResource ItemChildStyle}" SelectedItem="{Binding SelectedItem}" >
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupStyle}" />
<GroupStyle ContainerStyle="{StaticResource Group2Style}" />
</ListBox.GroupStyle>
</ListBox>
public class ItemViewModel : ViewModelBase
{
public string GroupName { get; set; }
public string SubGroupName{ get; set; }
public string ItemName { get; set; }
public bool IsExpanded {get; set;}
}
I solved this without binding by adding an event to the click on my 'Expand all' button
private void expandAll_Click(object sender, EventArgs e)
{
// get each listBoxItem by index from the listBox
for (int i = 0; i < MyListBox.Items.Count; i++)
{
ListBoxItem item = (ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromIndex(i);
// find its parents expander
var exp = FindParent<Expander>(item);
if (exp != null)
{
//if its not null expand it and see if it has a parent expander
exp.IsExpanded = true;
exp = FindParent<Expander>(exp);
if (exp != null)
exp.IsExpanded = true;
}
}
}
The FindParent function is a helper that walks up the visual tree looking for the control specified starting at the control you pass in.

WPF: display a collection of datagrids in a custom format (with image)

I have 2 classes, one inside the other, and a prop with an ObservableCollection of the class with the sub-class collection. But I'm having a serious trouble in displaying the whole thing.
First my data, this is what I've got: (it may clarify my issue)
public class MyItem
{
public string Id { get; set; }
public string Front { get; set; }
public Props.StateSemaphore Semaphore{ get; set; } // this is an enum w/ints
public string ToolTip { get; set; }
public string Architect { get; set; }
public string Status { get; set; }
public MyItem(){}
public MyItem(string id, string front,
Props.StateSemaphore semaphore, string toolTip,
string architect, string status)
{
Id = id;
Front = frente;
Semaphore = semaphore;
ToolTip = toolTip;
Architect = architect;
Status = status;
}
}
public class MyTab
{
public List<MyItem> MyItems { get; set; }
public string Environment { get; set; }
public MyTab() { }
public MyTab(string environment)
{
Environment = environment;
MyItems = new List<MyItem>();
}
}
And a prop on the PageExample.xaml.cs
private ObservableCollection<MyTab> myPanel;
public ObservableCollection<MyTab> MyPanel
{
get { return myPanel; }
set { myPanel = value; }
}
The idea is to display for each Environment a Grid of MyItems with an image(Red, Yellow or Green) on the semaphore enum
#Edit: This is almost working! Only the images won't display.
This is My XAML but im newbie on wpf so It's obvious I’m missing something.
<Page x:Class="MyBoard.PageMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyBoard"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="PageMain">
<Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Center">
<DataGrid Name="EnvironmentDataGrid" IsReadOnly="True" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
<DataGridTextColumn Binding="{Binding Front}" Header="Front"/>
<DataGridTemplateColumn Header="Semaphore">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Semaphore}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding ToolTip}" Header="ToolTip"/>
<DataGridTextColumn Binding="{Binding Architect}" Header="Architect"/>
<DataGridTextColumn Binding="{Binding Status}" Header="Status"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Environment}" FontWeight="Bold" Padding="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}" Margin="8,0,4,0"/>
<TextBlock Text="Element(s)"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Page>
These are my questions:
How is the correct way to write the XAML? #Edit: DONE!
How and where to bind
the semaphore image to the datagrid?
#Edit: Semaphore is now a RelativeUri, because I didnt understand this answer.
I mean, I get the idea but not this thing:
<MultiBinding Converter={StaticResource catMultiConverter}>
<Binding .../>
<Binding .../>
</MultiBinding>
With the RelativeUri and all It still does not display.
See here to find out how to set DataGrid.Columns and how to Bind them.
Check here how to convert semaphore enums into Images thru Converter and DataGridTemplateColumn.CellTemplate.
See here how to use grouping in DataGrid to group on the Environment property so that same Environment items are shown arranged under one group.

Resources