Wpf ListView Grouping - wpf

I've been trying to replicate the Listview like the screenshot below
enter image description here
But my problem is I can't make it work. I'm pretty new to MVVM
This is my xaml code
<ListView Margin="10,10,10,6" x:Name="BusinessListView">
<ListView.View>
<GridView>
<GridViewColumn Header="Company" DisplayMemberBinding="{Binding Company}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" Text="{Binding BusinessType}"></TextBlock>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
And below is my viewmodel code
List<BusinessM> businesses = new List<BusinessM>();
businesses.Add(new BusinessM
{
BusinessType = "Enterprise",
ListBusiness = new List<BusinessInfo>
{
new BusinessInfo {Company = "Microsoft"}
}
});
BusinessListView.ItemsSource = businesses;
Model
public class BusinessM
{
public string BusinessType { get; set; }
public List<BusinessInfo> ListBusiness { get; set; }
public BusinessM()
{
ListBusiness = new List<BusinessInfo>();
}
}
public class BusinessInfo
{
public string Company { get; set; }
}
When I run the program it returns an error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Company' property not found
What's wrong with my code? What I understand is I'm not referencing properly the property Company into the Listview?
If it's that's the case. How can I do it properly. I've been trying to solve it myself but I can't make it work.
Thank you.

You are getting the error specified because of the way your data structure is bounded to listview's item source property. Typically, you would have a flattened list of model and then you would use ColllectionViewSource to apply Grouping and/or Sorting on the collection.
In posted code, you are binding ItemSource to list of BusinessM which means each list view item represents object of type BusinessM. So when you bind GridViewColumn to Company,it expects BusinessM type to have a property with similar name, which by the way, is not the case.
I have modified you code so that I can explain you how it should be done (IMO) with example.
Model:
public class BusinessM
{
public string BusinessType { get; set; }
public string Company { get; set; }
}
Code behind (xaml.cs):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CollectionViewSource cvs = new CollectionViewSource();
cvs.Source = GenerateData();
cvs.GroupDescriptions.Add(new PropertyGroupDescription(nameof(BusinessM.BusinessType)));
BusinessListView.ItemsSource = cvs.View;
}
private List<BusinessM> GenerateData()
{
List<BusinessM> businesses = new List<BusinessM>();
for (int i = 1; i <= 10; i++)
{
for (int j = 1; j <= 5; j++)
{
businesses.Add(new BusinessM
{
BusinessType = $"Type {i}",
Company = $"{i}{j}"
});
}
};
return businesses;
}
}
View (xaml):
<ListView Margin="10,10,10,6" x:Name="BusinessListView">
<ListView.View>
<GridView>
<GridViewColumn Header="" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Company" DisplayMemberBinding="{Binding Company}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True" Header="{Binding Name}">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>

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.

Change ItemsControl grouping dynamically

I've search over the Internet for changing ItemsControl dynamically, but no luck (maybe my query is not good enough), so I ask here. Anyone can help me about creating grouping feature for an ItemsControl that can change group type dynamically.
Example, I have a collection of songs which have properties: Name, Artirst, Singer, Album. Then I have 3 radio buttons to which property is used to group there songs.
I have thinked about creating multiple CollectionView, but I think there is another ways which is better.
(I'm new at WPF, so whould you please give a little more details answer. Thank you :))
Here is the most minimal example I can come up with in MVVM style.
The viewmodel:
enum GroupBy
{
Name,
Artist
}
class Song
{
public string Name { get; set; }
public string Artist { get; set; }
}
class SonglistViewModel : ViewModelBase
{
private GroupBy groupBy;
public GroupBy GroupBy
{
get => this.groupBy;
set
{
if(this.groupBy == value)
{
return;
}
this.groupBy = value;
this.UpdateGrouping(value);
this.OnPropertyChanged();
}
}
public ObservableCollection<Song> Songs { get; } = new ObservableCollection<Song>();
private void UpdateGrouping(GroupBy value)
{
var servicesView = CollectionViewSource.GetDefaultView(this.Services);
servicesView.GroupDescriptions.Clear();
switch (value)
{
case GroupBy.Name:
servicesView.GroupDescriptions.Add(
new PropertyGroupDescription(nameof(Song.Name)));
break;
case GroupBy.Artist:
servicesView.GroupDescriptions.Add(
new PropertyGroupDescription(nameof(Song.Artist)));
break;
}
}
}
The view:
<ListView ItemsSource="{Binding Songs}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ItemCount, StringFormat={}({0})}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListViewGroupStyle>
</ListView>

Tracking items checked while looping through a ListView

I have a list view like this:
<ListView Name="FinalListView"
SelectionMode="Multiple"
SelectionChanged="FinalListView_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Tag="{Binding ID}"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Path=IsSelected}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name" Width="400"/>
</GridView>
</ListView.View>
</ListView>
As you can see the list view's each item contains a checkbox, But below is my Parallel.Foreach loop to iterating with my loop and work with the items.
Please let me know, how can not which item is checked, and not checked in the loop as below :
FinalFileNames -> IS the Collection. i.e. the data source for the listview.
Parallel.ForEach(FinalFileNames,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
path =>
{
-----code ------------
});
});
To Find the Checked Items in your List
Have a Boolean property to bind the check Box,
foreach (TheObjectYouBindedForYourList item in FinalFileNames )
{
if (item.Checked== true)
{
}
}
You should add a Checked property to the objects in your FinalFileNames collection. I see you are already binding to the properties ID and Name in your XAML, so I will assume this is not a collection of primitive types. So if your class looks something like this:
public class FileName
{
public string Name { get; set; }
public int ID { get; set; }
}
add a Checked property to it
public bool Checked { get; set; }
Then in your XAML you can add a style which binds the IsSelected property of the ListViewItem to the Checked property of your object.
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding Checked}"/>
</Style>
</ListView.Resources>
Now the Checked property will be set on your FileName object whenever the ListViewItem is selected and it can be checked in your Parallel.ForEach code:
Parallel.ForEach(FinalFileNames,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
path =>
{
if (path.Checked)
{
// code
}
});
});

Binding ObservableCollection of ObservableCollections to nested ListView in TabControl TabItems

Rather than declaring a tab for each of the ObservableCollections explicitly as in the first TabControl below I need them to generate dynamically as in the second TabControl and have the ItemsSource of the nested ListView set to each of the nested ObservableCollections.
In other words: Why aren't the ItemSource bindings of the nested ListViews in the second TabControl working? Is there a way to set the index of the nested ObservableCollection to the index of the containing ObservableCollection?
Or: How do I make the second dynamic TabControl and nested ListViews look like the first static TabControl and nested ListViews?
using System.Collections.ObjectModel;
using System.Windows;
namespace GridViewColumns2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel viewModel = new ViewModel();
viewModel.ThingCollections = new ObservableCollection<ThingCollection>();
viewModel.ThingCollections.Add(new ThingCollection { Name = "One" });
viewModel.ThingCollections[0].Things = new ObservableCollection<Thing>();
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.One" });
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.Two" });
viewModel.ThingCollections[0].Things.Add(new Thing { Name = "One.Three" });
viewModel.ThingCollections.Add(new ThingCollection { Name = "Two" });
viewModel.ThingCollections[1].Things = new ObservableCollection<Thing>();
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.One " });
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.Two" });
viewModel.ThingCollections[1].Things.Add(new Thing { Name = "Two.Three" });
DataContext = viewModel;
}
}
public class ViewModel
{
public ObservableCollection<ThingCollection> ThingCollections { get; set; }
}
public class ThingCollection
{
public string Name { get; set; }
public ObservableCollection<Thing> Things { get; set; }
}
public class Thing
{
public string Name { get; set; }
}
}
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabControl>
<TabItem>
<TabItem.Header>One</TabItem.Header>
<TabItem.Content>
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</TabItem.Content>
</TabItem>
<TabItem>
<TabItem.Header>Two</TabItem.Header>
<TabItem.Content>
<ListView ItemsSource="{Binding Path=ThingCollections[1].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</TabItem.Content>
</TabItem>
</TabControl>
<TabControl Grid.Column="1" ItemsSource="{Binding Path=ThingCollections}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
When you're in a DataTemplate, the DataContext is already that of the item (in your case, a ThingCollection class.
So instead of:
<ListView ItemsSource="{Binding Path=ThingCollections[0].Things}">
Use:
<ListView ItemsSource="{Binding Path=Things}">
To diagnose future binding errors, hit F5 (start debugging) and then take a look in the Output Window. All binding errors will be recorded there. In this case, it would've indicated that it cannot find a property named ThingCollections in the object ThingCollection.
Remove ThingCollections[0]. in BindingPath. The DataContext will be set for you to the right ThingCollection.
<ListView ItemsSource="{Binding Path=Things}">

WPF Listview Item Clicked Name

I have a listview with 5 columns. In the first column i insert an image in wich i have an event MouseClicked event. What i want to accomplish is when i click the image from row 5 , to get the name of item in row 5 - column 2;
This is my code so far :
<ListView x:Name="HistoryListB">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="#FF515050"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="true">
<Setter Property="Background" Value="#FF515050"/>
<Setter Property="BorderBrush" Value="#FF3B3A3A"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridViewColumn Width="90" Header="Image" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Name="Favorite" Width="12" Height="12" Source="{Binding ImageSource}" MouseLeftButtonDown="Image_MouseLeftButtonDown" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="90" Header="Time Added" DisplayMemberBinding="{Binding Time}" />
<GridViewColumn Width="110" Header="Status" DisplayMemberBinding="{Binding Status}"/>
<GridViewColumn Width="290" Header="ItemTitle" DisplayMemberBinding="{Binding ItemTitle}"/>
<GridViewColumn Width="50" Header="Duration" DisplayMemberBinding="{Binding Duration}"/>
</GridView>
</ListView.View>
</ListView>
And i add items in listview like this. I have a class and a list like this :
public class HistoryItems
{
public string Time { get; set; }
public string Status { get; set; }
public string ItemTitle { get; set; }
public string Duration { get; set; }
public ImageSource ImageSource { get; set; }
}
IList<HistoryItems> SHistoryItems { get; set; }
And then add the items :
SHistoryItems = new List<HistoryItems>() {
new HistoryItems () {
Time = DateTime.Now.ToString("HH:mm:ss tt"),
Status = "Started Playing : ",
ItemTitle = StationL.StationName
}
};
foreach (var item in SHistoryItems)
HistoryListB.Items.Add(SHistoryItems);
And in my code i want to get the name of the item from same row but 2nd column :
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TextBlock1.Text = ?? THE NAME OF THE SECOND COLUMN IN THE SAME ROW ?? ;
}
You can use the Tag property of the Image to hold the HistoryItem then you can access through the sender argument in the event handler.
Example:
Xaml:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Name="Favorite" Width="12" Height="12" Source="{Binding ImageSource}" Tag="{Binding}" MouseLeftButtonDown="Image_MouseLeftButtonDown" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Code:
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var image = sender as Image;
if (image != null && image.Tag is HistoryItems)
{
TextBlock1.Text = (image.Tag as HistoryItems).ItemTitle;
}
}

Resources