C# WPF Loop & Delete Checked ListView Items - c#

As someone who has recently switched from WinForms to WPF, I'm still struggling and going nuts trying to figure out a way to loop through and delete checked ListView items.
This method gives me error: "ListView does not contain a definition for CheckedItems..."
if (lvFilesList != null)
{
foreach (ListViewItem lvItem in lvFilesList.CheckedItems)
{
lvItem.Checked = False;
}
}
My XAML Code:
<ListView Height="400" Width="400"
Name="lvFilesList"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chk" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Header="File" DisplayMemberBinding="{Binding File}"/>
<GridViewColumn Header="Author" DisplayMemberBinding="{Binding Author}"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" Text="Group"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>

It looks odd that the ItemsSource of your ListView is directly bound to the current DataContext by
ItemsSource="{Binding}"
The DataContext would usually hold a view model object with a collection-type property like
public class Item
{
public bool IsChecked { get; set; }
// other properties like ID, File, Author
}
public class ViewModel
{
public ObservableCollection<Item> Items { get; }
= new ObservableCollection<Item>();
}
and the Binding would be
ItemsSource="{Binding Items}"
Then the view model could have a method that deletes all checked items, like
public void DeleteCheckedItems()
{
var checkedItems = Items.Where(item => item.IsChecked).ToList();
checkedItems.ForEach(item => Items.Remove(item));
}
Note that you usually assign an instance of the view model class to the DataContext of your main view, e.g. in the MainWindow constructor:
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
viewModel.Items.Add(new Item { ID = 1, ... });
viewModel.Items.Add(new Item { ID = 2, ... });
}

Related

How to trigger converter when ObservableCollection is changed in WPF using MVVM?

I have two list like this:
<ListView Name="listView" Height="300" ItemsSource="{Binding Resultados}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Path=CommandParameter, RelativeSource={RelativeSource Self}, Converter={StaticResource CheckBoxCeldaConverter}, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
Command="{Binding Path=DataContext.SeleccionarCeldaCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="EDIFICIO" DisplayMemberBinding="{Binding NIVEL.PASILLO.EDIFICIO.NOMBRE}"/>
<GridViewColumn Header="PASILLO" DisplayMemberBinding="{Binding NIVEL.PASILLO.NOMBRE}"/>
<GridViewColumn Header="NIVEL" DisplayMemberBinding="{Binding NIVEL.NOMBRE}"/>
<GridViewColumn Header="CELDA" DisplayMemberBinding="{Binding CELDA.NOMBRE}"/>
<GridViewColumn Header="CLASIFICACIÓN" DisplayMemberBinding="{Binding CELDA_CATEGORIA.NOMBRE}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding Path=DataContext.MostrarDiasVisitasCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding CELDA.CELDA_DIAS_VISITA}"
IsEnabled="{Binding TieneDiasVisita}">
<materialDesign:PackIcon Kind="Eye"/>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<ListView Name="listView2" Height="300" ItemsSource="{Binding CeldasSeleccionadas}" Grid.Column="1" Margin="20 0 0 0">
<ListView.View>
<GridView>
<GridViewColumn Header="EDIFICIO" DisplayMemberBinding="{Binding NIVEL.PASILLO.EDIFICIO.NOMBRE}"/>
<GridViewColumn Header="PASILLO" DisplayMemberBinding="{Binding NIVEL.PASILLO.NOMBRE}"/>
<GridViewColumn Header="NIVEL" DisplayMemberBinding="{Binding NIVEL.NOMBRE}"/>
<GridViewColumn Header="CELDA" DisplayMemberBinding="{Binding CELDA.NOMBRE}"/>
<GridViewColumn Header="CLASIFICACIÓN" DisplayMemberBinding="{Binding CELDA_CATEGORIA.NOMBRE}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding Path=DataContext.BorrarCeldaCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<materialDesign:PackIcon Kind="Delete"/>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
In the first list, the elements have a CheckBox, if the CheckBox is pressed, the element is added or removed from the second list that it's ItemSource is binded to CeldasSeleccionadas ObservableCollection in the ViewModel. I have a converter in the binding of the isChecked property of the first list that checks if the element is present in the CeldasSeleccionadas ObservableCollection, if is present, put the isChecked property as true. This is because if the user update the View, the selected values keeps checked.
Now, in the second list, the one binded to CeldasSeleccionadas, I have a button to delete the element from CeldasSeleccionadas ObservableCollection. This works fine, the only problem I have is that when I remove the element from CeldasSeleccionadas ObservableCollection the CheckBox in the first list keeps isChecked property as true, the converter is not beign called again and is not checking if the element exist in the CeldasSeleccionadas ObservableCollection.
CeldasSeleccionadas looks like this in the ViewModel:
public ObservableCollection<CeldaModel> CeldasSeleccionadas { get; set; } = new ObservableCollection<CeldaModel>();
This is the Command to add elements to CeldasSeleccionadas:
private ICommand _SeleccionarCeldaCommand;
public ICommand SeleccionarCeldaCommand {
get {
if (_SeleccionarCeldaCommand == null) _SeleccionarCeldaCommand = new RelayCommand(param =>SeleccionarCelda((CeldaModel) param));
return _SeleccionarCeldaCommand;
}
}
void SeleccionarCelda(CeldaModel celda) {
if (CeldasSeleccionadas.Contains(celda)) {
CeldasSeleccionadas.Remove(celda);
}
else {
CeldasSeleccionadas.Add(celda);
}
}
And this for remove elements:
private ICommand _BorrarCeldaCommand;
public ICommand BorrarCeldaCommand {
get {
if (_BorrarCeldaCommand == null) _BorrarCeldaCommand = new RelayCommand(param =>BorrarCelda((CeldaModel) param));
return _BorrarCeldaCommand;
}
}
void BorrarCelda(CeldaModel celda) {
CeldasSeleccionadas.Remove(celda);
}
The Converter looks like this:
public class CheckBoxCeldaConverter : Freezable, IValueConverter
{
public CheckBoxCeldaConverter()
{
MyCollection = new ObservableCollection<CeldaModel>();
}
protected override Freezable CreateInstanceCore()
{
return new CheckBoxCeldaConverter();
}
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.Register(nameof(MyCollection),
typeof(ObservableCollection<CeldaModel>), typeof(CheckBoxCeldaConverter),
new FrameworkPropertyMetadata(null));
public ObservableCollection<CeldaModel> MyCollection
{
get { return GetValue(MyCollectionProperty) as ObservableCollection<CeldaModel>; }
set { SetValue(MyCollectionProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (MyCollection.Contains(value as CeldaModel))
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And I call it from my view inside the User.Resources like this:
<UserControl.Resources>
<local:CheckBoxCeldaConverter MyCollection="{Binding CeldasSeleccionadas}" x:Key="CheckBoxCeldaConverter"/>
</UserControl.Resources>
How can I trigger the converter when a item from the CeldasSeleccionadas ObservableCollection is added or removed?
Read all, it's more intuitive and explained than before.
The code will be much easier if CeldaModel was aware of "I'm selected", letting you bind the checkbox to "Selected" boolean property.
With that you can avoid the converter.
But I think there is a better choice.
You have "Resultados".
Then in you ViewModel creates two more properties ICollectionView
public class MasterViewModel : ViewModelBase //example{
ObservableCollection<CeldaModel> Resultados{get; set;}
ICollectionView TodosResultados{get; set;}
ICollectionView ResultadosSeleccionados {get; set;} //You create ICollectionView and filter it by "Selected" property in CeldaModel
...
}
Bind one listview to "TodosResultados" and the other to "ResultadosSeleccionados"
If you have bound everything correctly (checkbox with Selected, ListViews with ICollectionView), you don't have to worry about nothing. Everything will work like a charm. At least in the surface.
No need to create the "Commands" (unless the command make something in the back Model- don't mess up with the ViewModel).
Warning: this work fine if you only want "View", if you want two ObservableCollection is valid too.
PS: should you name "CeldaModel" "CeldaViewModel"? In a pure MVVM, Model classes must not interact in any way with the view, even if you include it in a collection.

WPF ListView Column of Text Some Rows Underlined Some Not

I need to display databound data in a listview such that some of the cells of the same column are underlined while others are not:
How do I create a data template that will display different control types in the same cell? (Visual Studio 2013, .NET 4.5.2)
XAML:
<ListView Grid.Column="3" ItemsSource="{Binding Results.MyItemSource, Mode=OneWay}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="Auto" Header=" " DisplayMemberBinding="{Binding Path=Caption, Mode=OneWay}"/>
<GridViewColumn Width="Auto" Header=" " DisplayMemberBinding="{Binding Path=Value, Mode=OneWay}" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
ItemSource:
public List<object> MyItemSource
{
get
{
return new List<object>
{
new CaptionValuePair {Caption = "Caption1", Value = new TextBlock {Text = "Value1"}},
new CaptionValuePair {Caption = "Caption2", Value = new TextBlock {Text = "Value2", TextDecorations = TextDecorations.Underline}}
};
}
}
Bound type:
public class CaptionValuePair
{
public string Caption { get; set; }
public object Value { get; set; }
}
Problem: The StackPanel is just displaying the typename of a textblock.
Instead of a StackPanel, using an ItemsControl and binding its ItemsSource property worked.
XAML:
<GridViewColumn.CellTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Value, Mode=OneWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Bound type:
public List<UIElement> Value{ get; set; }

Create columns dynamically in a GridView in WPF MVVM

My MVVM WPF application currently has a GridView that binds to a ViewModel property and has the columns defined in the XAML:
<ListView Grid.Row="0" ItemsSource="{Binding GroupedOrders}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Item2, Mode=OneWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Item1.Date, StringFormat={}{0:dd/MM/yyyy}}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Item1.Name}" />
<!-- lots more -->
</GridView>
</ListView.View>
</ListView>
GroupedOrders is an ObservableCollection of a Tuple of two different item types: an "Order" object and a Boolean that controls whether or not, for this particular view, it is "selected".
However, that "selected" property hasn't been modelled well. It's come to light that each Order needs multiple "selected" properties, depending on the number of dates in the order, which can be between one and five.
To model this in the UI, I need to replace that single Checkbox GridViewColumn with a dynamic construct that creates a similar Checkbox GridviewColumn for each date in the order. So GroupedOrders becomes a Tuple <Order, List<bool>> instead, and there will need to be one column for each bool in the List.
At any given instance, the size of that list will be the same for all the Orders in the grid. But if the user loads new data into the grid, the size of the list will change.
However, I cannot see how to do this in MVVM. Existing solutions seem to be for code-behind where the GridView can be grabbed as an object and manipulated on the fly. The only MVVM solution I've seen to this is to use a Converter to building the entire GridView on the fly, which would seem to be massive overkill for this situation, where there are a lot of columns in the GridView but only a small number need to be dynamic.
Is there another way?
Not sure this is what you expect, but this is what I can think of.
View
<ListView ItemsSource="{Binding Orders}">
<ListView.View>
<GridView>
<GridViewColumn Header="State">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding States}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value}" Content="{Binding Text}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="OrderNo" DisplayMemberBinding="{Binding OrderNo}" />
</GridView>
</ListView.View>
</ListView>
Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Orders.Add(new Order { OrderNo = "Order001" });
Orders.Add(new Order { OrderNo = "Order002" });
Orders.Add(new Order { OrderNo = "Order003" });
}
private readonly ObservableCollection<Order> _orders = new ObservableCollection<Order>();
public ObservableCollection<Order> Orders
{
get { return _orders; }
}
}
public class Order
{
public Order()
{
States.Add(new State { Text = "Is Paid", Value = false });
States.Add(new State { Text = "Is Delivered", Value = false });
}
public string OrderNo { get; set; }
private readonly ObservableCollection<State> _states = new ObservableCollection<State>();
public ObservableCollection<State> States
{
get { return _states; }
}
}
public class State
{
public string Text { get; set; }
public bool Value { get; set; }
}
Result

unable to set selected value of "comboBox" in "ListView"

I am unable to set selected value of "comboBox" in "ListView".
Here is XAML code.
Propertyname: LISTTOPICS
<ListView x:Name="gridTopics"
ItemsSource="{Binding Path=TOPICSINFO}" Width="310">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Associated Topics" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding LISTTOPICS}"
SelectedValue="{Binding SelectedTopic.SELECTEDTOPIC}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
And C# code is
public class SelectedTopic : ObservableObject
{
private static string selectedTopic;
public static string SELECTEDTOPIC
{
get { return selectedTopic; }
set { selectedTopic = value; }
}
}
You need to call RaisePropertyChanged in your setter.
You have two options (it's difficult to say precisely, because DataContext isn't clear from your question):
change binding expression to something like SelectedValue="{Binding SELECTEDTOPIC}" .
properly binds to static property: Binding to static class property.

WPF Listview databinding to generic List, dynamically filled by WCF service

In WPF app I have a WCF service which dynamically fills a generic List object from a backend database.
How in this case (List created in runtime), I could bind List items to a ListView object items?
It is the Data contract for my Web service:
....
[DataContract]
public class MeetList
{
[DataMember]
public string MeetDate;
[DataMember]
public string MeetTime;
[DataMember]
public string MeetDescr;
.....
static internal List<MeetList> LoadMeetings(string dynamicsNavXml)
{
...// Loads XML stream into the WCF type
}
Here in this event handler I read the WCF service and Loop through a List object:
private void AllMeetings()
{
Customer_ServiceClient service = new Customer_ServiceClient();
foreach (MeetList meet in service.ReadMeetList())
{
?????? = meet.MeetDate; // it's here that I bumped into a problem
?????? = meet.MeetTime; //
?????? = meet.MeetDescr;//
}
}
My Listview XAML:
<Grid>
<ListView Height="100" Width="434" Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" Width="100" HeaderTemplate="{StaticResource DateHeader}" CellTemplate="{DynamicResource DateCell}"/>
<GridViewColumn Header="Time" Width="100" HeaderTemplate="{StaticResource TimeHeader}" CellTemplate="{DynamicResource TimeCell}"/>
<GridViewColumn Header="Description" Width="200" HeaderTemplate="{StaticResource DescriptionHeader}" CellTemplate="{DynamicResource DescriptionCell}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
And data templates for this ListView:
<Window.Resources>
<DataTemplate x:Key="DateHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,0,0" Text="Date" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DateCell" DataType="Profile">
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<Binding Path="MeetDate" />
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
......
How in this case (List created in runtime), I could bind my generic List items to a ListView object items?
I tried to use lvItems.ItemsSource = profiles; , but it doesn't work in my event handler
List doesn't have behaviour to notify that items count is changed. You should use a list with support INotifyCollectionChanged.. for example: ObservableCollection<T>. ObservableCollection<T> will inform your lvItems that items count is changed and it will be properly display.
Using intermediate ObservableCollection:
ObservableCollection<Meets> _MeetCollection =
new ObservableCollection<Meets>();
public ObservableCollection<Meets> MeetCollection
{ get { return _MeetCollection; } }
public class Meets
{
public string Date { get; set; }
public string Time { get; set; }
public string Description { get; set; }
}
In the event handler loop we put:
private void AllMeetings()
{
Customer_ServiceClient service = new Customer_ServiceClient();
_MeetCollection.Clear();
foreach (MeetList meet in service.ReadMeetList())
{
_MeetCollection.Add(new Meets
{
Date = meet.MeetDate,
Time = meet.MeetTime,
Description = meet.MeetDescr
});
}
}
And XAML binding is changed to:
<Grid>
<ListView Height="100" Width="434" Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding MeetCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" Width="100" DisplayMemberBinding="{Binding Date}"/>
<GridViewColumn Header="Time" Width="100" DisplayMemberBinding="{Binding Time}"/>
<GridViewColumn Header="Description" Width="200" DisplayMemberBinding="{Binding Description}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>

Categories