ListView isn't working properly with ObservableCollection - wpf

Here's the code I'm working with. In xaml I've expanded the ListView like this:
<ListView x:Name="lv" ItemsSource="{Binding}">
<ListView.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding Edit}" CommandParameter="{Binding SelectedItem, ElementName=lv}"/>
<KeyBinding Key="Return" Command="{Binding Edit}" CommandParameter="{Binding SelectedItem, ElementName=lv}"/>
</ListView.InputBindings>
</ListView>
and implemented INotifyPropertyChanged in Master class to see updates in ListView when I edit an item. In Person class I've added one more Command
public Command Edit { get; set; }
intialized it in constructior:
Edit = new Command(CanClick, EditItem);
and implemented those callbacks like this:
bool CanClick(object arg) => Count > 0;
void EditItem(object obj) {
if(obj != null{
var item = obj as Master;
item.FirstName = "Edited";
item.LastName = "Edited";
SetItem(IndexOf(item), item);
}
}
When I select an item and hit Return it updates the collection BUT I don't see any change in ListView. If I double click on an item, it neither updates the collection nor the ListView!
Another question is why do I have to set a name for the ListView to use in nested InputBindings like this ElementName=lv? Can I get rid of that?
EDIT
If I expand the ListView further like this:
<ListView x:Name="lv" ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name" Width="200"
DisplayMemberBinding="{Binding FirstName}"/>
<GridViewColumn Header="Last Name" Width="200"
DisplayMemberBinding="{Binding LastName}" />
</GridView>
</ListView.View>
<ListView.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding Edit}" CommandParameter="{Binding SelectedItem, ElementName=lv}"/>
<KeyBinding Key="Return" Command="{Binding Edit}" CommandParameter="{Binding SelectedItem, ElementName=lv}"/>
</ListView.InputBindings>
</ListView>
ListView reflects the update I make through void EditItem(object obj) BUT MouseBinding doesn't work in this way either. Why would one bind individual property like this for a collection?

There is no reason to call SetItem to update an item.
Setting the properties is enough provided that the Master class implements INotifyPropertyChanged correctly.
When it comes to handling double-clicks, you could add a reference to System.Windows.Interactivity and use an EventTrigger:
<ListView x:Name="lv" ItemsSource="{Binding}" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Edit}" CommandParameter="{Binding SelectedItem, ElementName=lv}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn Header="First Name" Width="200" DisplayMemberBinding="{Binding FirstName}"/>
<GridViewColumn Header="Last Name" Width="200" DisplayMemberBinding="{Binding LastName}" />
</GridView>
</ListView.View>
</ListView>

Related

How to auto update the selected row in listview after save sucess in wpf mvvm

I'm using a listview in WPF like this:
<ListView x:Name="lv" ItemsSource="{Binding Path=xyz}" SelectedItem="{Binding SelectedRow}" >
<ListView.View>
<GridView>
<GridViewColumn Width="50" DisplayMemberBinding="{Binding }" />
<GridViewColumn Width="140" DisplayMemberBinding="{Binding }" />
<GridViewColumn Width="140" DisplayMemberBinding="{Binding }" />
<GridViewColumn Width="120" DisplayMemberBinding="{Binding }" />
</GridView>
</ListView.View>
</ListView>
On click of save in the form the selected row should jump to the next row.Like currentselectedrow+1.
How do I achieve this in WPF MVVM?
At first, I would suggest using SelectedIndex instead of SelectedItem.
<ListView x:Name="lv" ItemsSource="{Binding Path=xyz}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" />
Then in your Save Method in your Viewmodel, you can simply increase the SelectedIndex by 1 (Remember to Check if the Index is not the last)
private void Save()
{
// Your Save Logic...
if (SelectedIndex + 1 < xyz.Count)
SelectedIndex++;
}
if you want to keep that SelectedItem, than you can do something similar like
private void Save()
{
//Your Save Logic...
var index = xyz.IndexOf(this.SelectedItem);
if (index - 1 < xyz.Count)
SelectedItem = xyz.ElementAt(index + 1);
}

MVVM light listview selecteditem

How can I get the selecteditem from my listview in mvvm light wpf?
I have a collection and created a selected item property but I cant get the binding right for the selected item.
This is my viewmodel:
ObservableCollection<DTO.Dossier.Dossier> _dossiers;
public ObservableCollection<DTO.Dossier.Dossier> Dossiers
{
get { return _dossiers; }
set
{
_dossiers = value;
RaisePropertyChanged("Dossiers");
}
}
private DTO.Dossier.Dossier _selectedDossier;
public DTO.Dossier.Dossier SelectedDossier
{
get { return _selectedDossier; }
set
{
if (_selectedDossier != value)
_selectedDossier = value;
RaisePropertyChanged("SelectedDossier");
}
}
And this is the xaml for the listview:
<ListView ItemsSource="{Binding Dossiers}" Margin="0,5,0,0" Name="LstDossiers" SelectedItem="{Binding SelectedDossier, Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectDossierCommand}"
CommandParameter="{Binding SelectedDossier,
ElementName=LstDossiers}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.View>
<GridView>
<GridViewColumn Header="Id"
DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Omschrijving"
DisplayMemberBinding="{Binding Omschrijving}" />
</GridView>
</ListView.View>
</ListView>
The command is working but the binding to the SelectedDossier isn't.
You have to use mode TwoWay in your binding:
<ListView ItemsSource="{Binding Dossiers}" Margin="0,5,0,0" Name="LstDossiers" SelectedItem="{Binding SelectedDossier, Mode=TwoWay}">

How to set SelectedItem in listview control?

I have UserControl(AllCustomerView) in project and its Viewmodel as ALlCustomerViewModel consists a property as SearchText.
SearchText is selected value for TextBox inside a listview in UserControl.
SearchItem is set as customerViewmdel for SearchText.
But in listview , SearchItem is not set as selected
in AllCustomerView.xaml
<TextBlock>
<TextBox
Width="150"
Text="{Binding Path=SearchText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</TextBox>
<Button
Command="{Binding Path=SearchCommand}"
Content=" Search ">
</Button>
</TextBlock>
<ListView
SelectedValue="{Binding Path=SearchText}"
ItemsSource="{Binding Path=AllCustomers}"
FontFamily="Tahoma"
FontSize="14"
ItemContainerStyle="{StaticResource CustomerItemStyle}"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding ElementName=Root, Path=DataContext.DeleteCommand}"
Content="x"
FontFamily="Tahoma"
FontSize="10"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
FontFamily="Tahoma"
FontSize="10"
Content="Edit"
Command="{Binding Path=DataContext.Editcommand,ElementName=Root}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
Header="CustomerID"
Width="130"
DisplayMemberBinding="{Binding Path=CustomerID}"/>
<GridViewColumn
Header="Contact Name"
Width="130"
DisplayMemberBinding="{Binding Path=ContactName}"/>
<GridViewColumn
Header="Company Name"
Width="130"
DisplayMemberBinding="{Binding Path=CompanyName}"/>
<GridViewColumn
Width="130"
Header="Contact Name"
DisplayMemberBinding="{Binding Path=ContactTitle}"
/>
</GridView>
</ListView.View>
</ListView>
and its viewmodel(AllcustomerViewModel.cs)
public ICommand SearchCommand
{
get
{
if (_search == null)
_search = new Relaycommand(SearchCustomer);
return _search;
}
}
public void SearchCustomer(object o)
{
System.Windows.Forms.MessageBox.Show(SearchItem.ToString());
}
public string searchText;
public string SearchText
{
get { return searchText; }
set
{
searchText = value;
var customerExsits = AllCustomers.Where(q => q.CustomerID.Trim().Equals(searchText));
if (customerExsits.Any())
{
SearchItem = customerExsits.Single();
}
}
}
public CustomerViewModel SearchItem
{
get;
set;
}
what should i set in SelectedValue in ListView, whether to set Customerviewmodel(as SelectedItem) or to set CustomerID(as SearchText)?
You should do the following:
Use this binding in your ListView: SelectedItem="{Binding Path=SearchItem }". Don't use SelectedValue.
Implement INotifyPropertyChanged in your ViewModel and raise the PropertyChanged event in the setter of the SearchItem property. Obviously, you need to change this property from an automatic one to a classical one with a backing field:
public CustomerViewModel SearchItem
{
get { return _searchItem; }
set
{
if(value == _searchItem)
return;
_searchItem = value;
RaisePropertyChanged("SearchItem");
}
}

Binding textbox in ListView's header datatemplate to a filter property

I am creating a customized listview header that has the header text but also has a textbox that you can enter to filter the content of that column. My code currently looks like this:
<UserControl.Resources>
<DataTemplate x:Key="myHeaderTemplate">
<StackPanel>
<TextBlock FontSize="14" Foreground="DarkBlue" Margin="20,4" Text="{Binding}" />
<TextBox Text="" Margin="4,2" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
which is the definition for the header datatemplate containing the texbox; and the listview
<ListView ItemsSource="{Binding Path=MyData}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="Last Name" HeaderTemplate="{StaticResource myHeaderTemplate}"
DisplayMemberBinding="{Binding Path=Something}" />
<GridViewColumn Header="First Name" HeaderTemplate="{StaticResource myHeaderTemplate}"
DisplayMemberBinding="{Binding Path=Something}" />
<GridViewColumn Header="Address" HeaderTemplate="{StaticResource myHeaderTemplate}"
DisplayMemberBinding="{Binding Path=Tube}" />
</GridView>
</ListView.View>
</ListView>
I want to be able to build up a filter statement that I can apply to the listview rows, but to do that I have to get the data from each filter textbox in the header template.
Can I somehow bind the textboxes in the headers to properties on my viewmodel? If not is there some other way to get the text?
Thanks for any help.
You should be able to bind the header to a property like this:
<GridViewColumn
Header="{Binding LastNameFilter, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
HeaderTemplate="{StaticResource myHeaderTemplate}"
DisplayMemberBinding="{Binding Path=Something}" />
The RelativeSource is needed to get to the DataContext of the ListView - you could also give it a name and use ElementName instead.
Now you can make a HeaderFilter class:
public class HeaderFilter
{
public string Name { get; set; }
public string Filter { get; set; }
}
Obviously you would need to extend that class to hook into the event when Filter is changed to perform the filtering.
Put a property for each column header on the object which is the DataContext for your ListView (same object which provides MyData probably)
public class SomeClass
{
....
public HeaderFilter LastNameFilter { get; set; }
....
}

WPF - Listview with button

i have a listview template and one column is a button. I need selected item when i click in this button. How i can do this ??
To cature the selected ListView item inside a button pressed event you can leverage the MVVM pattern. In my ListView, in the XAML, I bind the ItemsSource and SelectedItem to a ViewModel class. I also bind my button Command in the template to RunCommand in the ViewModel.
The tricky part is getting the binding correct from the template to the active DataContext.
Once you do this you can capture the SelectedCustomer inside the RunCommand that
gets executed when the button gets pressed.
I've included some of the code to help get you started.
You can find implementations of ViewModelBase and DelegateCommand via Google.
Here is the XAML:
<Window x:Class="ListViewScrollPosition.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main Window" Height="400" Width="400">
<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 FirstName}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Last Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding LastName}"/>
</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>
</Window>
Here is the ViewModel:
using System.Collections.ObjectModel;
using System.Windows.Input;
using ListViewScrollPosition.Commands;
using ListViewScrollPosition.Models;
namespace ListViewScrollPosition.ViewModels
{
public class MainViewModel : ViewModelBase
{
public ICommand RunCommand { get; private set; }
public MainViewModel()
{
RunCommand = new DelegateCommand<object>(OnRunCommand, CanRunCommand);
_customers = Customer.GetSampleCustomerList();
_selectedCustomer = _customers[0];
}
private ObservableCollection<Customer> _customers =
new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customers
{
get
{
return _customers;
}
}
private Customer _selectedCustomer;
public Customer SelectedCustomer
{
get
{
return _selectedCustomer;
}
set
{
_selectedCustomer = value;
OnPropertyChanged("SelectedCustomer");
}
}
private void OnRunCommand(object obj)
{
// use the SelectedCustomer object here...
}
private bool CanRunCommand(object obj)
{
return true;
}
}
}
Here is where I link in the ViewModel to the View:
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
Example with a regular click event in the code behind:
<ListView Height="167.96" VerticalAlignment="Top" ItemsSource="{Binding FulfillmentSchedules}" SelectedItem="{Binding SelectedFulfillmentSchedule}">
<ListView.View>
<GridView>
<GridViewColumn Header="Request">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}-{1}-{2}">
<Binding Path="Template.ProjectNumber" />
<Binding Path="Template.JobNumber" />
<Binding Path="Template.RequestId" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Template" DisplayMemberBinding="{Binding Template.Name}"/>
<GridViewColumn Header="Start Date" DisplayMemberBinding="{Binding StartDate}"/>
<GridViewColumn Header="Records" DisplayMemberBinding="{Binding Parameters.Records}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Name="BtnYourButton" Content="Your Button" Click="BtnYourButton_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code behind:
private void BtnYourButton_Click(object sender, RoutedEventArgs e)
{
var boundData= (YourBoundDataType)((Button)sender).DataContext;
//do what you need to do here, including calling other methods on your VM
}
Note: While I certainly appreciate MVVM, I've come to accept that there is a pretty steep slope of dimminishing returns once you cross into actions and messaging between the form and the VM, so I use it only in cases of complex relationships between VMs or large singular VMs. For CRUD style data-centric applications I prefer to handle actions and message relay with the code behind.

Resources