Tracking items checked while looping through a ListView - wpf

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
}
});
});

Related

ListView items do not display

I have a rather simple ListView with GridView style. Cells are binded to some properties of an ObservableCollection elements. I have no idea why thaey are not displaying. I were thinking about implementing INotifyPropertyChanged in a class of element contained by collection, but once an element is constructed and added to a collection it is never changed so it should be working just fine.
C#:
public ObservableCollection<SceneElement> SceneObjects { get; set; }
...
SceneObjects.Add(new Camera());
SceneObjects.Add(new Cuboid(20, 30, 40));
Camera and Cuboid both inherit from SceneObject (SceneObject <- Figure <- Cuboid and SceneObject <- Camera)
XAML:
<ListView Grid.Column="2" Margin="5"
ItemsSource="{Binding }"
DataContext="{Binding SceneObjects}"
ScrollViewer.CanContentScroll="True">
<ListView.Resources>
<GridView x:Key="StyleOne">
<GridViewColumn Header="Type" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="1" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Position" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="X: " />
<TextBlock Text="{Binding Position.X, RelativeSource={RelativeSource Mode=FindAncestor}}" />
<TextBlock Text=" Y: " />
<TextBlock Text="{Binding Position.Y}" />
<TextBlock Text=" Z: " />
<TextBlock Text="{Binding Position.Z}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.Resources>
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="View" Value="{StaticResource StyleOne}" />
</Style>
</ListView.Style>
</ListView>
Position property is defined inside an abstract SceneElement class.
Position's X, Y and Z are properties as well.
EDIT
This has solved the issue:
private ObservableCollection<SceneElement> sceneObjects;
public ObservableCollection<SceneElement> SceneObjects
{
get
{
return sceneObjects;
}
set
{
sceneObjects = value;
NotifyPropertyChanged("SceneObjects");
}
}
Can anyone explain why this was necessary? I've used ObservableCollection severeal time so far and it always worked without notifying.
Reason why your application wasn't displaying the information about the collection is because the UI wasn't aware of any changes.
Using ObservableCollection doesn't guarantee the results will be displayed if said collection is not initialized before it is used. As it is a plain property on the ViewModel it needs to notify if said object, in this case ObservableCollection has been changed, but not in the sense items inside of the collection.
This is due to the fact of NOT raising the PropertyChanged event for SceneObjects, which is crucial for WPF apps using Bidning.
at the point where the wpf system attempts to create the binding, your observablecollection was null, as xamimax above said.
instantiate your observablecollection in your constructor, and later you can add the items and they will appear.
for example, the following works because i first instantiated the observable collection and then added items 5 seconds later.
public MainWindow()
{
InitializeComponent();
this.SceneObjects = new ObservableCollection<MyClass>();
Task.Delay(5000).ContinueWith((old) =>
{
this.Dispatcher.Invoke(() =>
{
this.sceneObjects.Add(new MyClass("name"));
this.sceneObjects.Add(new MyClass("name"));
});
});
this.DataContext = this;
}
and here is an example that doesnt work, because i instantiate the observable collection 5 secs after the wpf system tried to bind to it:
public MainWindow()
{
InitializeComponent();
Task.Delay(5000).ContinueWith((old) => { this.SceneObjects = new ObservableCollection<MyClass> { new MyClass("name"), new MyClass("name") }; });
this.DataContext = this;
}
and as you already realized, you can throw propchanged to make wpf system display those items, but i do think you should simply instantiate the observablecollection earlier so that you dont need to throw propchanged later.

How to bind a GridViewColumn to a Property inside it's Collection Item in WPF

I created an attached property for a GridView's GridViewColumn to hide/show the whole column.
<GridViewColumn att:Visibility.IsVisible="{Binding ...}">
This works as long the property the attached property binds to exists in my main view model (the datacontext of my gridView).
In one case, the visibility of the column depends on a property of my list item (an item inside the collection that is the itemsource of my gridview).
How do I bind here?
I can access the item properties inside the cell template of the gridviewcolumn but not in the gridviewcolumn itself.
What I'm trying to achieve is, that I can bind the attached property to a property of my list item.
Is there a way to do this?
Edit:
Let's say I have a class:
public class Item : INotifyPropertyChanged
{
private string m_Text;
public string Text
{
get
{
return m_Text;
}
set
{
m_Text= value;
RaisePropertyChanged("Text");
}
}
private bool m_IsVisible;
public bool IsVisible
{
get
{
return m_IsVisible;
}
set
{
m_IsVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Now I have an ObservableCollection of this class as ItemsSource of my ListView/GridView.
public ObservableCollection<Item> Items;
In my xaml:
<ListView ItemsSource={Binding Items}>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding /*What goes here? Should bind to IsVisible of my Collection Item*/}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text={Binding Text"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView/>
The visibility of my gridviewcolumn should now depend on the value of "IsVisible" of my collection item. Is that event possible?
You can bind your attached property to the IsVisible property of your collection. I noticed that your xaml syntax is incorrect cause compilation errors. Your xaml should look like this.
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding IsVisible}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Also you don't need to implement INotifyPropertyChanged on your Item class. Your ObservableCollection already implements INotifyCollectionChanged to it already will update the UI. Your Item class should just like like this
public class Item
{
public string Text { get; set; }
public bool IsVisible { get; set; }
}

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>

Button inside a WPF List view/data grid

I am trying to get the value/ID of the clicked row. If the row is selected, this works fine. But if I just try to click the button inside, the selected customer is null. How do I do Command Parameters here.
I tried this see the answers to the following questions:
ListView and Buttons inside ListView
WPF - Listview with button
Here is the code:
<Grid>
<ListView ItemsSource="{Binding Path=Customers}"
SelectedItem="{Binding Path=SelectedCustomer}"
Width="Auto">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Address">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<Button Content="Address" Command="{Binding Path=DataContext.RunCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
public class VM : ViewModelBase
{
public RelayCommand RunCommand { get; private set; }
private ObservableCollection<Customer> _Customers;
public ObservableCollection<Customer> Customers
{
get { return _Customers; }
set
{
if (value != _Customers)
{
_Customers = value;
RaisePropertyChanged("Customers");
}
}
}
private Customer _SelectedCustomer;
public Customer SelectedCustomer
{
get { return _SelectedCustomer; }
set
{
if (value != _SelectedCustomer)
{
_SelectedCustomer = value;
RaisePropertyChanged("SelectedCustomer");
}
}
}
public VM()
{
Customers = Customer.GetCustomers();
RunCommand = new RelayCommand(OnRun);
}
private void OnRun()
{
Customer s = SelectedCustomer;
}
}
in the OnRun Method, selected Customer is coming in as null. I want the data row(customer) here. How do I do this?
Three possible solutions that you can choose.
Pass current row object as a CommandParameter. In this case, you will modify OnRun method a little. (recommended)
<Button Content="Address" Command="{Binding Path=DataContext.RunCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" CommandParameter={Binding} />
I think CommandParameter={Binding} works fine because the DataContext of each row is each Customer object.
and OnRun method needs to be modified in order to get a parameter as an argument.
private void OnRun(object o){
if(!(o is Customer)) return;
// Do something
}
Or, Write a little code-behind with SelectionChanged event handling. (not recommended)
Or, Use EventToCommand in MVVM-light toolkit. (not recommended)
CommandParameter does NOT entirely support data binding.
http://connect.microsoft.com/VisualStudio/feedback/details/472932/wpf-commandparameter-binding-should-be-evaluated-before-command-binding

Data added to datagrid not visible in UI in WPF in MVP

In a WPF application,
I have to display data to a log window using data grid..
Every log message should be added to the logwindow and needs to be displayed.
My Xaml is :
<ListView x:Name="lstViewLogWindow" ItemsSource="{Binding}" Height="152" IsSynchronizedWithCurrentItem="True" MouseEnter="lstViewLogWindow_MouseEnter" MouseLeave="lstViewLogWindow_MouseLeave" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="White"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView x:Name="grdViewLogWindow" >
<GridViewColumn x:Name="Message" Header="MessageDetails" Width="1000" DisplayMemberBinding="{Binding Path= MessageDetails}"/>
<GridViewColumn x:Name="LogDate" Header="DateTime" Width="275" DisplayMemberBinding="{Binding Path= DateTime}" />
</GridView>
</ListView.View>
</ListView>
I have a LogMessage.cs class as
public class LogMessage
{
public string Message_Name { get; set; }
public DateTime LogTime { get; set; }
}
In the code behind...
public void showmsg(string msg) {
List<LogMessage> messages = new List<LogMessage>();
messages.Add(new LogMessage() { LogTime = DateTime.Now, Message_Name = msg });
lstViewLogWindow.DataContext = messages;}
I am able to see the data available in the 'messages'... but I couldnt see it in the UI...
my presenter has _view.showmsg(msg)........
But I cant see the data int he log window..
Please help..
thanks
Ramm
Your ListView's ItemsSource is set to {Binding}. However, you did not show where you are setting the DataContext of your view. So, first, in your code-behind file, you need the following:
this.DataContext = messages;
In addition, your columns are bound to incorrect property names. Currently:
DisplayMemberBinding="{Binding Path= MessageDetails}"
DisplayMemberBinding="{Binding Path= DateTime}"
Should be:
DisplayMemberBinding="{Binding Path=Message_Name}"
DisplayMemberBinding="{Binding Path=LogTime}"

Resources