I have a ListView bounded to a List of a class I created. When doing an operating, it was supposed to add/remove items from the list, but my ListView wasn't updated even though I used INotifyPropertyChanged.
If I use ObservableCollection, it works but I need to have the list sorted, and ObservableCollection doesn't do sorting for WPF4.0 :(
Any way I can make the List binding work? Why didn't it work even though I used INotifyPropertyChanged?
XAML:
<ListView BorderThickness="0" ItemsSource="{Binding SelectedValues, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" Padding="5">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding Value}"></GridViewColumn>
VM:
private List<CheckBoxItem> _selectedValues = new List<CheckBoxItem>();
public List<CheckBoxItem> SelectedValues
{
get { return _selectedValues; }
set
{
_selectedValues = value;
OnPropertyChanged();
}
}
private void UnselectValueCommandExecute(CheckBoxItem value)
{
value.IsSelected = false;
SelectedValues.Remove(value);
//OnPropertyChanged("SelectedValues");
OnPropertyChanged("IsAllFilteredValuesSelected");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
The CheckBoxItem class contains 2 properties, Value and IsChecked, which I don't think is relevant here.
So basically, I have a button which uses the UnselectValueCommandExecute to remove items from the list, and I should see the list updated in the UI, but I'm not.
When I debug, I can see the SelectedValues list updated, but not my UI.
You need a CollectionViewSource in your UI.
The XAML:
<Window x:Class="WavTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource Source="{Binding TestSource}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Order"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="Description"/>
</Window>
The code behind:
namespace WavTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
this.DataContext = vm;
vm.TestSource.Add(new TestItem { Description="Zero", Order=0 });
}
}
public class ViewModel
{
public ObservableCollection<TestItem> TestSource { get; set; }
public ViewModel()
{
TestSource = new ObservableCollection<TestItem>();
TestSource.Add(new TestItem { Description = "Second", Order = 2 });
TestSource.Add(new TestItem { Description = "Third", Order = 3 });
TestSource.Add(new TestItem { Description = "First", Order = 1 });
}
}
public class TestItem
{
public int Order { get; set; }
public string Description { get; set; }
}
}
Explanation:
The ObservableCollection raises the PropertyChanged event as you expect, but you cannot sort it.
So, you need the CollectionView to sort it and bind the sorted collection to you ListView/ListBox.
As you can see, adding an element after the DataContext initialization affects the UI sorting the last added item ("Zero") correctly.
You need to use ObservableCollection because this raises a collection changed event which your wpf ListView will pick up on.
How about doing
Public ObservableCollection<object> MyList
{
get
{
return new ObservableCollection<object>(MySortedList);
}
}
and then whenever you change your sorted list raise a property changed event for MyList.
This obviously depends how you would like to sort your list as it might be possible to sort the ObservableCollection your question needs more info.
Related
So I've setup a viewmodel to where it binds an ObservableCollection<string> to my DataGrid.
It prints out the value just fine but it also prints out the Length of the property? I don't recall ever setting that in any binding whatsoever. Why is it doing that?
My MainWindow.cs
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
MasterViewModel.cs
class MasterViewModel
{
public Users Users { get; } = new Users();
public Achievements Achievements { get; } = new Achievements();
}
Users.cs
class Users : INotifyPropertyChanged
{
public Users()
{
newList.Add("Hello there");
}
private ObservableCollection<string> newList = new ObservableCollection<string>();
public ObservableCollection<string> NewList
{
get { return newList; }
set { newList = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<DataGrid ItemsSource="{Binding Users.NewList}" Width="400" Height="200" Margin="182,158,210,61">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
DataGrid has property AutoGenerateColumns which is set to True by default and makes DataGrid to create a column for each property defined in items.
DataGrid is bound to NewList which contains items of type string which has Length property. So it makes Length column
you can disable auto-generation by setting <DataGrid AutoGenerateColumns="False" ...
I forgot to add the property AutoGenerateColumns="False".
Not sure why it was set to true by default or why it woudl choose the length property of all the properties but I guess I got it fixed.
I have found some similar questions, but none have fully solved my problem, so I have put together a small example.
I want to be able to press the D key, and delete that item from the ObservableCollection. This works as expected.
I then want to be able to continue maipulating the datagrid using the arrow keys and D key from the row after the one I just deleted (i.e. the index of the updated datagrid is equal to the index that the deleted item had).
The most useful answer I have found is this one - Focus on DataGridCell for SelectedItem when DataGrid Receives Keyboard Focus - however I'm not sure when I should be calling it as I want to call it after the view has been updated, I'm currently using the SelectionChanged event but obviously this is being called far too often to use.
Any advice would be much appreciated, I hope I have provided enough code below to enable anyone to recreate the project and replicate the problem.
Many Thanks,
Mike
My XAML code:
<Window x:Class="DataGridProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding MyItems}"
SelectedItem="{Binding SelectedItem}">
<DataGrid.InputBindings>
<KeyBinding Key="D" Command="{Binding Delete}"/>
</DataGrid.InputBindings>
</DataGrid>
</Grid>
</Window>
My view model:
namespace DataGridProblem
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MyItem> _myItems;
private ICommand _delete;
public MyItem SelectedItem { get; set; }
public ICommand Delete { get { return _delete; } }
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value;
OnPropertyChanged("MyItems");
}
}
public ViewModel()
{
_myItems = new ObservableCollection<MyItem>();
_myItems.Add(new MyItem() { name = "John" });
_myItems.Add(new MyItem() { name = "Mike" });
_myItems.Add(new MyItem() { name = "Phil" });
_delete = new RelayCommand(DeleteSelected);
}
private void DeleteSelected(object obj)
{
MyItems.Remove(SelectedItem);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MyItem:
namespace DataGridProblem
{
public class MyItem
{
public string name { get; set; }
}
}
I would try something like this:
private void DeleteSelected(object obj)
{
var index = MyItems.IndexOf(SelectedItem);
MyItems.Remove(SelectedItem);
if (MyItems.Count > 0)
SelectedItem = MyItems[index >= MyItems.Count ? index-1 : index];
}
What I am trying to do here is databind the Itemsource of a DataGridComboBoxColumn to a collection of strings declared as property of my item view model.
The Datagrid itself is bound to another viewmodel which has a collection of viewModels that represent the rows on the datagrid.
All my other bindings work properly. The collection is also filled, but the combobox remains empty.
XAML:
<Window.Resources>
<ResourceDictionary>
<local:GeneralDataGridViewModel x:Key="generalDataGridVm"/>
</ResourceDictionary>
</Window.Resources>
<Grid>
<DataGrid DataContext="{StaticResource generalDataGridVm}"
ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="chbCodes"
Header="Code"
ItemsSource="{Binding Path=DataContext.Collection, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
C# ItemViewModel:
public class ItemViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> _collection;
public ObservableCollection<string> Collection
{
get
{
return _collection;
}
}
public Model Model { get; set; }
public string Code
{
get { return Model.Code; }
set { Model.Code = value; }
}
public ItemViewModel()
{
}
public ItemViewModel(Model model)
{
Model = model;
_collection = new ObservableCollection<string>();
_collection.Add(model.Code);
Model.PropertyChanged += Model_PropertyChanged;
}
public void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
}
c# DataGridViewModel:
public class GeneralDataGridViewModel
{
private ObservableCollection<ItemViewModel> _collection;
public ObservableCollection<ItemViewModel> Collection
{
get { return _collection; }
set
{
_collection = value;
NotifyPropertyChanged("Collection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public GeneralDataGridViewModel()
: base()
{
_collection = new ObservableCollection<ItemViewModel>();
}
public GeneralDataGridViewModel(List<Model> models)
{
_collection = new ObservableCollection<ItemViewModel>((from m in models
select new ItemViewModel(m)).ToList());
}
}
C# Model:
public class Model: INotifyPropertyChanged
{
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public override string ToString()
{
return Code;
}
}
The code you have posted does not compile, but looking at it the issue might be with the data context, which you are setting to a static resource. If you are going to do this, the view model must be in your resource dictionary. The bindings in XAML are fine, see below for an example of this working:
XAML:
<Window.Resources>
<ResourceDictionary>
<local:GeneralDataGridViewModel x:Key="generalDataGridVm"/>
</ResourceDictionary>
</Window.Resources>
<StackPanel Orientation="Vertical">
<DataGrid DataContext="{StaticResource generalDataGridVm}" Name="DataGrid1" ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="chbCodes"
Header="Code"
ItemsSource="{Binding Path=DataContext.Collection, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
By declaring generalDataGridVm in XAML, the constructor is called in XAML, let's assume that construction supplies the values for the collection:
public GeneralDataGridViewModel() : base()
{
_collection = new ObservableCollection<ItemViewModel>();
_collection.Add(new ItemViewModel(new Model("code1")));
_collection.Add(new ItemViewModel(new Model("code2")));
_collection.Add(new ItemViewModel(new Model("code3")));
_collection.Add(new ItemViewModel(new Model("code4")));
}
With this code, it results in a populated list:
So I think you just need to make sure that you are declaring your view model properly (I would suggest not creating this in XAML unless there is some good reason).
Then ensure that the collection is kept up to date properly in that particular instance of your view model.
I have an ObservableCollection and want to bind a Textbox to a specific element of that collection. The Items in the ObservableCollection are of a Type that implements INotifyPropertyChanged.
I have thought about creating a Property that selects the right element from the ObservableCollection, but then I would have to make this Property realise when the corresponding element in the Collection changes and I am not sure if this is the right way to do this.
Usually, especially if you use MVVM, you'll have a viewModel with your ObservableCollection and a property for the SelectedItem that you update with data binding.
For example, your viewModel could look like this:
class ProductsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> Products { get; set; }
private Product _selectedProduct;
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
}
}
public ProductsViewModel()
{
Products = new ObservableCollection<Product>();
Products.Add(new Product() { Name = "ProductA" });
Products.Add(new Product() { Name = "ProductB" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
Your window object xaml:
<Window x:Class="ProductsExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox HorizontalAlignment="Left" Height="171" Margin="32,29,0,0" VerticalAlignment="Top" Width="176"
ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
DisplayMemberPath="Name"
/>
<TextBox HorizontalAlignment="Left" Height="33" Margin="36,226,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="172"
Text="{Binding SelectedProduct.Name, Mode=TwoWay}"/>
</Grid>
</Window>
and the code-behind where you just set the datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProductsViewModel();
}
}
Whenever you select a product in the listbox, the textbox is updated with the selected product, and if you change the product in the textbox (if product correctly implements INotifyPropertyChanged) the item in the listbox will also be updated.
Obviously you can achieve all this only using the code-behind, but for several reasons explained here:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx, it is better to have a ViewModel
If the item you need is specific by index you can access using the index
<TextBlock Text="{Binding MyItemsSource[2]}" />
To solve my problem I created a Property that selects the right element from the ObservableCollection and created an event Handler that is added to the CollectionChanged event of the ObservableCollection and raises the PropertyChanged Event for my SelectionProperty.
In code that looks something like this in the constructor of the class containing the ObservableCollection and the SelectionProperty:
myObservableColleciton.CollectionChanged +=
new NotifyCollectionChangedEventHandler(
myObservableCollection_CollectionChanged);
somewhere else in the class define this event handler:
void myObservableCollection_CollectionChanged(
Object sender, NotifyCollectionChangedEventArgs e){
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectionProperty"));
}
}
my selectionProperty looks something like this:
public User SelectionProperty
{
get { return myObservableCollection.First( user => user.id == 0); }
}
if the SelectionProperty depends on more than the ObservableCollection (maybe we want to find a user closest to a certain age, that is set elsewhere) then it needs to be made sure that the PropertyChanged event for SelectionProperty is raised as well, when those other properties change.
I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}