Binding a nested Collection to listview Columns - c#

I have a Object like this:
public class DummyVm {
public string Name { get; set; }
public List<int> DummyList { get; set; }
}
and create a Collection to bind it to the view
public List<DummyVm> VmList { get; set; } = new List<DummyVm>() {
new DummyVm() { Name = "test1", DummyList = new List<int>() { 5, 6 } },
new DummyVm() { Name = "test2", DummyList = new List<int>() { 1, 2 } }
};
how can i bind each element of the DummyList-Property to an own column?
In the End, my result shoud be a grid with 3 colums and 2 rows:
test1 | 5 | 6
test2 | 1 | 1
i am using mvvm

If VmList is declared in MainWindow.xaml.cs, add this to your MainWindow.xaml:
<Window.Resources>
<DataTemplate x:Key="DummyVmTemplate">
<StackPanel Orientation="Horizontal">
<Label FontSize="14" Content="{Binding Name}" />
<Label Margin="20,0,0,0" Content="{Binding DummyList[0]}" />
<Label Margin="20,0,0,0" Content="{Binding DummyList[1]}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
Inside the <Grid> in MainWindow.xaml add:
<ListView x:Name="DummyListView"
ItemTemplate="{StaticResource DummyVmTemplate}" />
Then in the MainWindow.xaml.cs constructor add:
Loaded += OnLoaded;
The OnLoaded() implementation should look like this:
private void OnLoaded(object sender, RoutedEventArgs e)
{
DummyListView.ItemsSource = VmList;
}

Related

WPF Binding second ListBox in relation to selected item in first ListBox

I'm new to WPF and MVVM ... i created a class WorkstationItem
public class WorkstationItem
{
public WorkstationItem() { }
public string Name { get; set; }
public string OS { get; set; }
public List<UpdateItem> Updates { get; set; }
}
UpdateItem is another class:
public class UpdateItem
{
public UpdateItem() { }
public string Title { get; set; }
public string KB { get; set; }
}
I create some dummy data:
private List<WorkstationItem> workstations = new List<WorkstationItem>();
workstations.Add(new WorkstationItem
{
Name = "PC01",
OS = "Windows Server 2019",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test", KB = "KB123123" },
new UpdateItem { Title = "Test2", KB = "KB123123" }
}
});
workstations.Add(new WorkstationItem
{
Name = "PC02",
OS = "Windows Server 2016",
Updates = new List<UpdateItem>{
new UpdateItem { Title = "Test5", KB = "KB123123" },
new UpdateItem { Title = "Test3", KB = "KB123123" }
}
});
Now i show the workstations in a listbox:
<ListBox x:Name="lbPCs" ItemsSource="{Binding WorkstationItemList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Name}" FontSize="12" FontWeight="Bold" />
<TextBlock Text="{Binding OS}" FontSize="9" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public ObservableCollection<WorkstationItem> WorkstationItemList { get; set; }
WorkstationManager workstationmanager = WorkstationManager.GetInstance();
WorkstationItemList = new ObservableCollection<WorkstationItem>();
foreach (var k in workstationmanager.GetUpdatelist())
{
WorkstationItemList.Add(k);
}
This is working fine ... but how can i show the List<UpdateItem> Updates in another list in relation to the selected workstation?
So i select a workstation in list1 and want to show the related updates in list2?
Thanks in advance!
Basically add another ListBox or ItemsControl that refers to SelectedItem of the existing ListBox and bind to the SelectedItem's Updates collection; roughly like (untested):
<ListBox ItemsSource="{Binding ElementName=lbPCs, Path=SelectedItem.Updates}">
<ListBox .ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- TODO improve alignment+layout -->
<TextBlock Text="{Binding KB}" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
<ListBox .ItemTemplate>
</ListBox>
In case you want to do something more complex with the selected item but to display its Updates, it might be more appropriate to bind the SelectedItem of lbPCs to some new ViewModel property and to bind the new ListBox' itemsource to that VM property's Updates collection.

Getting the ListView in ListView SelectedIndex Value

It looks a little more complicated than it actually may be, but I had to use the ListView inside Listview the window. I brought in a DataTemplate as a Resource, but the problem is I can't access anything in the resource section, foreach and do go and check but I thought I didn't know where to start, I'll have to get the selectedIndex of a ListView (maybe with messagebox, say you clicked xxx index). I used this example, how Can I do it?
Update:
My XAML Window Resource
<Window.Resources>
<DataTemplate x:Key="InsideListTemplate">
<StackPanel Orientation="Vertical">
<Grid MinWidth="300" MinHeight="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<Border Background="{x:Static SystemParameters.WindowGlassBrush}" Grid.ColumnSpan="5" Opacity=".8"/>
<!--0-->
<Grid Grid.Row="0" Grid.Column="0" MinWidth="50" MaxWidth="70" HorizontalAlignment="Left" Margin="3,0,0,0">
<TextBlock Margin="5,0,0,0" Text="{Binding CodeNumber}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center"/>
</Grid>
<!--1-->
<Grid Grid.Row="0" Grid.Column="3" MinWidth="300" MaxHeight="50" MinHeight="20" HorizontalAlignment="Left" Margin="5,0,0,0">
<Grid>
<TextBox Text="{Binding Code}"/>
</Grid>
</Grid>
</Grid>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CodeListTemplate">
<StackPanel Orientation="Horizontal">
<Grid Background="{x:Static SystemParameters.WindowGlassBrush}" MinHeight="296" MinWidth="400" MaxHeight="296" MaxWidth="400">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding CodeGroupName}" Foreground="White" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
</Grid>
<ListView HorizontalAlignment="Stretch" Background="#FFBB9B45" HorizontalContentAlignment="Stretch" MinWidth="100" MinHeight="25" MaxHeight="300" ItemsSource="{Binding CodeList}" ItemTemplate="{StaticResource InsideListTemplate}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
Main Listview
<ListView x:Name="KodListeleri" Visibility="Visible" Grid.Row="1" Background="CadetBlue" Margin="20,20,20,20" Foreground="#FFFBF7F7" BorderBrush="Transparent" ItemTemplate="{StaticResource CodeListTemplate}" VirtualizingStackPanel.IsVirtualizing="False"/>
Classes
public sealed class CodeLibrary
{
public int CodeGroupNumber { get; set; }
public string CodeGroupName { get; set; }
public List<Codes> CodeList { get; set; }
}
public class Codes
{
public int CodeNumber { get; set; }
public string Code { get; set; }
}
Last add some value
List<CodeLibrary> AllCodesList = new List<CodeLibrary>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
AllCodesList.Add(new CodeLibrary()
{
CodeGroupNumber = 1,
CodeGroupName = "My Code Group 1",
CodeList = new List<Codes>
{
new Codes {CodeNumber = 1, Code = "Code 1"},
new Codes {CodeNumber = 2, Code = "Code 2"},
new Codes {CodeNumber = 3, Code = "Code 3"},
new Codes {CodeNumber = 4, Code = "Code 4"},
new Codes {CodeNumber = 5, Code = "Code 5"},
new Codes {CodeNumber = 6, Code = "Code 6"},
new Codes {CodeNumber = 7, Code = "Code 7"},
new Codes {CodeNumber = 8, Code = "Code 8"},
new Codes {CodeNumber = 9, Code = "Code 9"},
new Codes {CodeNumber = 10, Code = "Code 10"},
new Codes {CodeNumber = 11, Code = "Code 11"},
new Codes {CodeNumber = 12, Code = "Code 12"},
new Codes {CodeNumber = 13, Code = "Code 13"},
new Codes {CodeNumber = 14, Code = "Code 14"},
new Codes {CodeNumber = 15, Code = "Code 15"},
new Codes {CodeNumber = 16, Code = "Code 16"},
new Codes {CodeNumber = 17, Code = "Code 17"},
new Codes {CodeNumber = 18, Code = "Code 18"},
new Codes {CodeNumber = 19, Code = "Code 19"},
new Codes {CodeNumber = 20, Code = "Code 20"},
}
}
);
KodListeleri.ItemsSource = AllCodesList;
}
It must look like this:
The quickest way to do this is to give the ListView a SelectionChanged event handler:
<ListView
SelectionChanged="CodeListView_SelectionChanged"
ItemsSource="{Binding CodeList}"
ItemTemplate="{StaticResource InsideListTemplate}"
...
And here's what the handler looks like in the window class:
private void CodeListView_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
var listView = sender as ListView;
var selectedCodes = listView.SelectedItem as Codes;
if (selectedCodes != null)
{
// Do stuff with selectedCodes
}
}
But you can do it MVVM style instead:
public sealed class CodeLibrary
{
public int CodeGroupNumber { get; set; }
public string CodeGroupName { get; set; }
public List<Codes> CodeList { get; set; }
private Codes _selectedCodes;
public Codes SelectedCodes {
get { return _selectedCodes; }
set {
if (value != _selectedCodes) {
_selectedCodes = value;
// Do other stuff here if you want
MessageBox.Info("You selected " + _selectedCodes.Code);
}
}
}
}
XAML:
<ListView
SelectedItem
ItemsSource="{Binding CodeList}"
ItemTemplate="{StaticResource InsideListTemplate}"
...

How to show ListBox Items when TreeView Item is selected

So I have a task of building a mail application for school. I am having a problem with one of the parts.
I have a TreeView and a ListBox. TreeView has few items in it (Inbox, trash, draft). Now what I am trying to do, is that when I select and TreeView item certain ListBox Items will appear in the ListBox. (purpose of the ListBox is to show the mails in that "folder").
I have been looking into this, and there are some suggestions with ListAray and DataBinding, but I am very new and have no idea how to implement any of those.
What I have at this poit is:
<TreeView Grid.Row="2" Grid.ColumnSpan="1" VerticalAlignment="Stretch" HorizontalAlignment="Left" Margin="10,10,0,10" Name="treeView1" Width="100" FontSize="14" SelectedItemChanged="treeView1_SelectedItemChanged">
<TreeViewItem Header="Prejeto" IsSelected="True">
<TreeViewItem Header="Prebrano" />
<TreeViewItem Header="Neprebrano" />
</TreeViewItem>
<TreeViewItem Header="Poslano" />
<TreeViewItem Header="Osnutki" />
<TreeViewItem Header="Izbrisano" />
<TreeViewItem Header="Nezaželeno" />
<TreeViewItem />
</TreeView>
XAML ListBox:
<ListBox Name="seznamSporocil" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" Margin="10,10,0,10" VerticalAlignment="Stretch" Width="100" FontWeight="Bold" FontFamily="Arial" MouseDoubleClick="seznamSporocil_MouseDoubleClick" />
SelectedItemChanged:
private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
When working with WPF, data binding is your best friend. Just bind ItemsSource of list box with some collection property of tree view's selected item.
Update.
Here's the complete sample (just create WPF Application project).
Model:
public class MailFolder
{
public string Name { get; set; }
public ObservableCollection<MailItem> Items
{
get
{
return items ?? (items = new ObservableCollection<MailItem>());
}
}
private ObservableCollection<MailItem> items;
public ObservableCollection<MailFolder> SubFolders
{
get
{
return subFolders ?? (subFolders = new ObservableCollection<MailFolder>());
}
}
private ObservableCollection<MailFolder> subFolders;
}
public class MailItem
{
public string Subject { get; set; }
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TreeView x:Name="MailTreeView" ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MailFolder}" ItemsSource="{Binding SubFolders}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<ListBox Grid.Column="1" ItemsSource="{Binding Path=SelectedItem.Items, ElementName=MailTreeView}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:MailItem}">
<TextBlock Text="{Binding Subject}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And this is data context setup:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new MailFolder
{
Name = "Prejeto",
SubFolders =
{
new MailFolder
{
Name = "Prebrano",
Items =
{
new MailItem { Subject = "A" },
new MailItem { Subject = "B" },
new MailItem { Subject = "C" },
}
},
new MailFolder
{
Name = "Neprebrano",
Items =
{
new MailItem { Subject = "D" },
new MailItem { Subject = "E" },
}
},
},
Items =
{
new MailItem { Subject = "M" },
new MailItem { Subject = "N" },
}
},
new MailFolder
{
Name = "Poslano",
Items =
{
new MailItem { Subject = "F" },
new MailItem { Subject = "G" },
}
},
new MailFolder
{
Name = "Osnutki",
Items =
{
new MailItem { Subject = "H" },
}
},
new MailFolder
{
Name = "Izbrisano",
Items =
{
new MailItem { Subject = "I" },
new MailItem { Subject = "J" },
new MailItem { Subject = "K" },
}
},
new MailFolder
{
Name = "Nezaželeno",
Items =
{
new MailItem { Subject = "L" },
}
}
};
}
}
Note, that if you want to reflect changes, made to properties of your model classes, you need to implement INotifyPropertyChanged interface.

Displaying hierarchical data associated with checkboxes in Silverlight

Hello and thanks in advance for the assistance,
I have a list of parent objects, in which each parent object has a list of children objects. I would like to display the data in a fashion in which the user may select child objects, press a button and then I will save the selected children objects, along with their parent, likely in xml through serializing the objects with an xml serializer. An idea of what the display should look like is:
<sdk:Label x:Name="ParentLabel" content="ParentNameString" />
<CheckBox x:Name="ChildCheckBox1" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox2" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox3" Content="ChildNameString" />
<sdk:Label x:Name="ParentLabel2" content="ParentNameString" />
<CheckBox x:Name="ChildCheckBox4" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox5" Content="ChildNameString" />
<CheckBox x:Name="ChildCheckBox6" Content="ChildNameString" />
I know that there are options for a checkbox column in the DataGrid control, but would I be able to display the hierarchical relationship there via Header/children? or is there an option for datatemplating in a Listbox control that would allow headings and associated children elements? What would you recommend?
Thanks again for the help.
If you have a single level of Parent and Child you can do something like this.
A simple codebehind with some sample data
namespace SilverlightApplication2
{
public partial class MainPage : UserControl
{
public ObservableCollection<Parent> ParentList { get; set; }
public MainPage()
{
Populate();
InitializeComponent();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
foreach (var child in ParentList
.SelectMany(p => p.Children)
.Where(c => c.IsSelected))
{
//Save the child
Debug.WriteLine(string.Format("Child {0} saved", child.Name));
}
}
private void Populate()
{
ParentList = new ObservableCollection<Parent>();
ParentList.Add(new Parent
{
Name = "John",
Children = new List<Child> { new Child { Name = "Paul" }, new Child { Name = "Pat" } }
});
ParentList.Add(new Parent
{
Name = "Mike",
Children = new List<Child> { new Child { Name = "Bob" }, new Child { Name = "Alice" } }
});
ParentList.Add(new Parent
{
Name = "Smith",
Children = new List<Child> { new Child { Name = "Ryan" }, new Child { Name = "Sue" }, new Child { Name = "Liz" } }
});
}
}
public class Parent
{
public string Name { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public string Name { get; set; }
public bool IsSelected { get; set; }
}
}
Your xaml would be something like this.
<UserControl x:Class="SilverlightApplication2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:SilverlightApplication2"
x:Name="MainUserControl"
Width="400"
Height="300"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="ChildTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ParentTemplate">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ListBox ItemsSource="{Binding Path=Children}" ItemTemplate="{StaticResource ChildTemplate}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="White">
<ListBox ItemsSource="{Binding ElementName=MainUserControl, Path=ParentList}" ItemTemplate="{StaticResource ParentTemplate}"/>
</StackPanel>
</UserControl>
This results in a view like this. I have omitted all styling for simplicity, I am sure you can make it more sexy ;)
Now you can in the code behind only process Child with IsSelected true
If you have more than one level... ie..Your Children have children you will have to use the HierarchicalDataTemplate

WPF: Changing a ComboBox's ItemTemplate removes the ability to jump down the list as you type. Any way to fix this?

PersonVM.cs
public class MainWindowVM
{
public MainWindowVM()
{
PersonList = new ObservableCollection<Person>(Employees);
}
private Person[] Employees = new Person[]
{
new Person { ID = 1, Name = "Adam" },
new Person { ID = 2, Name = "Bill" },
new Person { ID = 10, Name = "Charlie" },
new Person { ID = 15, Name = "Donna" },
new Person { ID = 20, Name = "Edward" }
};
public ObservableCollection<Person> PersonList { get; set; }
}
Person.cs
public class Person
{
public string Name { get; set; }
public int ID { get; set; }
}
MainWindow.xaml (Functionally working version -- Not what I want to display)
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ComboBox Height="23" Width="300"
ItemsSource="{Binding Path=Objects}"
DisplayMemberPath="Name"
>
</ComboBox>
</Grid>
</Window>
MainWindow.xaml (Displays correctly -- Doesn't function properly)
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ComboBox Height="23" Width="300"
ItemsSource="{Binding Path=Objects}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{} {0} | {1}">
<Binding Path="ID" />
<Binding Path="Name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
The second code displays what I want the ComboBox to display {ID} | {Name}, but it takes away a common function of the ComboBox. In the first example, when the ComboBox is selected the user can start typing into it and have it jump down the list. For example, if you press the letter A it jumps to "Adam", B jumps to "Bill", etc. This is how a ComboBox is supposed to function. But, when I override the ComboBox ItemTemplate it loses that functionality. Is there another way to Bind what I need and keep that functionality or to reenable it? Perhaps the ItemTemplate is setup wrong?
See my answer to this question: Can I do Text search with multibinding
Unfortunately TextSearch.Text doesn't work in a DataTemplate. I think you have two options here
Option 1. Set IsTextSearchEnabled to True for the ComboBox, override ToString in your source class and change the MultiBinding in the TextBlock to a Binding
<ComboBox ...
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
public class Person
{
public override string ToString()
{
return String.Format("{0} | {1}", Name, ID);
}
public string Name { get; set; }
public int ID { get; set; }
}
Option 2. Make a new Property in your source class where you combine Name and ID for the TextSearch.TextPath. Also, you should call OnPropertyChanged for NameAndId whenever you do it for Name or ID
<ComboBox ...
TextSearch.TextPath="NameAndId"
IsTextSearchEnabled="True">
public string NameAndId
{
return String.Format("{0} | {1}", Name, ID);
}

Categories