I have some collections. For example, List 1 and List 2. Both are List<Object>.
What I need to do:
1) Insert them into Datagrid:
2) Add new items for Lists. For example, there is some button on form. I click it and new item is adding to first list. Datagrid now look like this:
3) In some way. When I want to pass content of Datagrid to my class object, program must know that List 1 now contain 2 items, but List 2 - 1 item.
How can I in best way perform such features?
Here's a sample...
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SomeItem> VmList { get; set; }
List<SomeItem> List1 = new List<SomeItem>();
List<SomeItem> List2 = new List<SomeItem>();
public ViewModel()
{
// VmList is the item source for the grid
VmList = new ObservableCollection<SomeItem>();
// create two lists
for (int i = 0; i < 10; i++)
{
List1.Add(new SomeItem{ID = "1", Name = "Name " + i});
}
for (int i = 0; i < 10; i++)
{
List1.Add(new SomeItem { ID = "2", Name = "Name (2) " + i });
}
// merge the two separate lists
VmList.AddRange(List1);
VmList.AddRange(List2);
// get the view
var lcv = CollectionViewSource.GetDefaultView(VmList);
// apply a filter
lcv.Filter = o =>
{
var someItem = o as SomeItem;
if (someItem != null)
{
return someItem.ID == "2";
}
return false;
};
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
public class SomeItem:INotifyPropertyChanged
{
private string _id;
public string ID
{
[DebuggerStepThrough]
get { return _id; }
[DebuggerStepThrough]
set
{
if (value != _id)
{
_id = value;
OnPropertyChanged("ID");
}
}
}
private string _name;
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
public static class Extensions
{
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> list)
{
foreach (T t in list)
{
collection.Add(t);
}
}
}
In this example (which is contrived), the View Model constructor creates two lists and adds them to the observable collection which is bound to the data grid.
The underlying collection view source is then retrieved and a filter is attached to it.
In your application, the filter would be applied in a button event handler instead of the Vm constructor. This is just a sample to explain how it works. In my original comment, I noted that you could also use a LINQ zip operator, but instead I included an extension method which is probably more valuable at the moment. It's called "AddRange".
This approach will allow you to present two lists as a single collection while maintaining their separate identities behind the scenes. It also shows how to use a filter.
The docs for Collection View Source are here http://msdn.microsoft.com/en-us/library/System.Windows.Data.CollectionViewSource.aspx
You can use CompositeCollection it give an easy ability to bind to multiple collections.
CompositeCollection doesn't have DataContext so if you want to databind one of of your collections, you must reference a FrameworkElement with the desired DataContext.
fo example you can create CollectionViewSource and bind is a source like that:
<DataGrid.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource Collection1}}" />
<CollectionContainer Collection="{Binding Source={StaticResource Collection2}}"/>
</CompositeCollection>
</DataGrid.ItemsSource>
Related
My question is similar to this one: Bind to Count of List where Typeof
But how does this work for Classes?
In my MainWindow I have the following Collection and a Selected Count Property
private ObservableCollection<MyClass> _myClassCollection = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass>
{
get => _myClassCollection;
set
{
if(_myClassCollection == value) return;
_myClassCollection = value;
OnPropertyChanged("MyClassCollection");
}
}
public int SelectedCount
{
get => MyClassCollection.Where(x => x.IsSelected == true).Count();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
My MyClass:
public class MyClass : INotifyPropertyChanged
{
// .. Properties
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if(_isSelected == value) return;
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
So how can I "run" the SelectedCount Property, if the IsSelected Property of MyClass changed ? I want to show the number of Selected Items of the ObservableCollection in real time.
You can just add an OnPropertyChaned for SelectedCountin the setter of the other operations where a change may have occurred. For instance on setting the my class collection. This will tell stuff listening to that particular property something may have changed, get the value again.
set
{
if(_myClassCollection == value) return;
_myClassCollection = value;
OnPropertyChanged("MyClassCollection");
OnPropertyChanged("SelectedCount"); // Make the change Here.
}
From the comments it would seem you need to listen to each element's property changed explicitly. Here is an example of how that would look in your setter with an event handler.
set
{
// remove subscriptions
if(_myClassCollection != null)
{
foreach(var element in _myClassCollection)
{
element.PropertyChanged -= ElementChanged;
}
}
// set to new collection
_myClassCollection = value;
// subscribe to new elements.
if(_myClassCollection != null)
{
foreach(var element in _myClassCollection)
{
element.PropertyChanged += ElementChanged;
}
}
OnPropertyChanged("MyClassCollection");
OnPropertyChanged("SelectedCount"); // Make the change Here.
}
private void ElementChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(MyClass.IsSelected))
{
OnPropertyChanged("SelectedCount");
}
}
Now if you are adding or removing elements from your collection without creating a new collection, you will need to subscribe or remove subscriptions inside a CollectionChanged event handler.
You can use DynamicData to make it more readable:
var allObjects = new SourceList<MyClass>(); // this is what you populate with your objects
SelectedObjects = allObjects.Connect().Filter(x => x.IsSelected).AsObservableList();
If SelectedObjects is a public property, you can bind like:
<TextBloc Text="{Binding SelectedObjects.Count}"/>
You have to handle the CollectionChanged from your ObservableCollection.
There you have to call OnPropertyChanged("SelectedCount") like in the linked Question.
In your set:
_myClassCollection.CollectionChanged += Handle_CollectionChanged;
In the event handler:
private void Handle_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("SelectedCount");
}
I have a gridview shown as below in XAML
<ListView x:Name="listTasks">
<ListView.View>
<GridView x:Name="gridTasks">
<GridViewColumn Header="ID" HeaderStringFormat="Lowercase" Width ="26" DisplayMemberBinding="{Binding id}"/>
<GridViewColumn Header="Something" Width="113" DisplayMemberBinding="{Binding something}"/>
<GridViewColumn Header="State" Width="179" DisplayMemberBinding="{Binding currentState}"/>
</GridView>
</ListView.View>
</ListView>
and i have a button which adds to this gridview using the below
m.myList.Add(new mylistview.myitems
{
id = m.id,
something= m.something,
currentState = m.currentState,
});
This button works perfectly by adding the row into the gridview. However I would like to modify theCurrentState using a method that is running. How would I locate for example, ID = "8" and then modify theCurrentState for that row?
UPDATED CODE SHOWN
I've now replaced my list<Task> with ObservableCollection and managed to get it to add to my listview when I click onto my button. However, I am struggling to implement the iNotifyPropertyChanged into my code and getting it to work correctly... Below is my listview class
public class mylistview : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _currentState;
public string currentState
{
get { return _currentState; }
set
{
_currentState = value;
OnPropertyChanged();
}
}
public ObservableCollection<myitems> _myList = new ObservableCollection<myitems>();
public ObservableCollection<myitems> myList
{
get { return _myList; }
}
private static int _id = 0;
public class myitems
{
public int id { get; set; }
public string something{ get; set; }
public string currentState { get; set; }
}
public int id
{
get { return _id; }
set { _id = value; }
}
}
So I see you're using data bindings already, that's good. But your question makes me think you haven't quite grasped everything it can do for you yet.
My recommendation would be to forget about adding items directly to listOfTasks.Items. Instead you should make an ObservableCollection to hold that list and bind the listOfTasks to it. Like so:
ObservableCollection tasks = new ObservableCollection<mylistview.myitems>();
ListOfTasks.ItemsSource = tasks;
With that binding in place you should be able to simply add new items to the tasks list when they click your button:
tasks.Add(new mylistview.myitems
{
id = theId,
something= something,
currentState = theCurrentState,
});
and it should automatically update the GUI.
The last step is to make sure that the class mylistview.myitems implements INotifyPropertyChanged. This is easier than it sounds; you just need to have it trigger an event any time the property is set. Something like so:
public class exampleProperties: INotifyPropertyChanged
{
//this is the event you have to emit
public event PropertyChangedEventHandler PropertyChanged;
//This is a convenience function to trigger the event.
//The CallerMemberName part will automatically figure out
//the name of the property you called from if propertyName == ""
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
//Any time this property is set it will trigger the event
private string _currentState = "";
public string currentState
{
get { return _currentState; }
set
{
if (_currentState != value)
{
_currentState = value;
OnPropertyChanged();
}
}
}
}
Now that the gridview is bound to an ObservableCollection and the items held in that collection can notify interested GUI controls that their properties have changed, you should simply be able to update the GUI simply by changing the appropriate item in the collection.
And here's an example of a form that uses the whole technique: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).asp
edit
I forgot that you specifically need to bind to the ItemSource property of the ListView. The way I have done it in the past is to set ItemsSource={binding} in the ListView's xaml and then assign an ObservableCollection to ListView.DataContext. However I have found an easier way and updated the original post with it. Here's a reference: http://www.wpf-tutorial.com/listview-control/listview-with-gridview/
Edit 2
Aha, you're adding the iPropertyChangedNotify to the wrong thing. It goes on the myitems class like so:
public class myitems : iNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string something{ get; set; }
public string currentState { get; set; }
}
I leave updating the current state and something properties as an excersize. They also need to trigger the OnPropertyChanged event when their value is set.
Maybe with
listOfTasks.Items.Cast<ListViewItem>().First(item => item.ID == "8").theCurrentState = newState;
//I'm not sure about the Cast stuff, because I don't know what types the ListView uses for its items
Of course you could iterate through the items with a loop and check manually for the ID as well.
I have an Item class with two properties "quantity" and "price" and implements INotifyPropertyChanged
public class Item:INotifyPropertyChanged
{
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int QuantityOnHand
{
get
{
return this._quantityOnHand;
}
set
{
if (value > 0)
{
this._quantityOnHand = value;
NotifyPropertyChanged();
}
else
{
throw new System.ArgumentException("Quantity must be a positive value!");
}
}
}
.....
}
And I have a collection class of items named "Inventory" with a property of TotalRetailPrice:
public class Inventory {
private List<Item> _inventoryList = new LinkedList<Item>();
public decimal TotalRetailPrice
{
get
{
decimal totalRetailPrice = 0M;
foreach (var item in _inventoryList)
{
totalRetailPrice += item.QuantityOnHand * item.RetailPrice;
}
return totalRetailPrice;
}
}
I am trying to find out a way to automatically update this property TotalRetailPrice, whenever I change either the quantity or the price of any item(s) in the list. How can I do that? Right now with my code, every time I tried to get this totalRetailPrice property, I will have to go through the list and recalculate it.
thanks!
Since the interface INotifyPropertyChanged exposes an event called PropertyChanged you can just subscribe to that in the 'inventory' class.
You will also want to listen for changed events in the list since you will need to know when items are added/removed so you can add/remove event handlers as necessary. I'd suggest using ObservableCollection<T> as this supports some 'collection changed' events. Is there any reason you are using LinkedList<T>?
e.g.
public class Inventory
{
private ObservableCollection<Item> _inventoryList = new ObservableCollection<Item>();
public decimal _total;
// You probably want INPC for the property here too so it can update any UI elements bound to it
public decimal Total { get { return _total; } set { _total = value; } }
// Constructor
public Inventory()
{
WireUpCollection();
}
private void WireUpCollection()
{
// Listen for change events
_inventoryList.CollectionChanged += CollectionChanged;
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Check what was added - I'll leave this to you, the e.NewItems are the items that
// have been added, e.OldItems are those that have been removed
// Here's a contrived example for when an item is added.
// Don't forget to also remove event handlers using inpc.PropertyChanged -= Collection_PropertyChanged;
var inpc = e.NewItems[0] as INotifyPropertyChanged;
if(inpc != null)
inpc.PropertyChanged += Collection_PropertyChanged;
}
private void Collection_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
RecalculateTotal();
}
private void RecalculateTotal()
{
// Your original code here which should feed the backing field
}
}
Check out the MSDN docs here:
http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx
For info on ObservableCollection<T>. The events section is what you are after. Also note you can use anonymous functions to handle the events if you prefer the syntax or want to capture variables in a certain scope etc. It helps to understand them fully (not sure what's available for Java as I've not really touched it save a couple of Android mess-about projects) so it might be worth reading up as there are a small caveats to be aware of when capturing, but that's another story!
e.g.
_inventoryList.CollectionChanged += (o,e) =>
{
// Anonymous method body here
// o = the first param (object sender), e = args (NotifyCollectionChangedEventArgs e)
};
As it was told in another answer in order to "observe" collection items you have to use ObservableCollection which has a special event CollectionChanged. It is raised when the list is changed somehow (item added, removed, replaced). However, that event won't be raised (obviously) when some property of the existing item is changed, for example, inventory.InventoryList[0].QuantityOnHand = 8;.
So, to get the solution worked you need to observe both the collection changes (CollectionChanged event) and each collection item changes (PropertyChanged event). Though, implementing that logic correctly is not so easy. The Charlen answer is just a sketch of the solution but not the full solution.
It might be easier to use DependenciesTracking lib which solves the issue. See the example below:
public class Item : INotifyPropertyChanged
{
private int _quantityOnHand;
private decimal _retailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public int QuantityOnHand
{
get => this._quantityOnHand;
set
{
if (_quantityOnHand == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "QuantityOnHand must be a positive value");
_quantityOnHand = value;
OnPropertyChanged();
}
}
public decimal RetailPrice
{
get => _retailPrice;
set
{
if (_retailPrice == value) return;
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), value, "RetailPrice must be a positive value");
_retailPrice = value;
OnPropertyChanged();
}
}
}
public class Inventory : INotifyPropertyChanged
{
private static readonly IDependenciesMap<Inventory> _dependenciesMap =
new DependenciesMap<Inventory>()
.AddDependency(i => i.TotalRetailPrice,
i => i.InventoryList?.Sum(item => item.QuantityOnHand * item.RetailPrice) ?? 0.0m,
i => i.InventoryList.EachElement().QuantityOnHand, i => i.InventoryList.EachElement().RetailPrice);
private ObservableCollection<Item>? _inventoryList;
private decimal _totalRetailPrice;
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public ObservableCollection<Item>? InventoryList
{
get => _inventoryList;
set
{
if (_inventoryList == value) return;
_inventoryList = value;
OnPropertyChanged();
}
}
public decimal TotalRetailPrice
{
get => _totalRetailPrice;
private set
{
if (value == _totalRetailPrice) return;
_totalRetailPrice = value;
OnPropertyChanged();
}
}
public Inventory()
{
_dependenciesMap.StartTracking(this);
}
}
public class Tests_SO_20767981
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test_SO_20767981()
{
var inventory = new Inventory();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList = new ObservableCollection<Item>();
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(0.0m));
inventory.InventoryList.Add(new Item { QuantityOnHand = 3, RetailPrice = 5 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(15));
inventory.InventoryList.Add(new Item { QuantityOnHand = 1, RetailPrice = 7 });
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(22));
inventory.InventoryList[0].QuantityOnHand = 8;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(47));
inventory.InventoryList[0].RetailPrice = 12;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(103));
inventory.InventoryList.RemoveAt(1);
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(96));
var newInventoryList = new ObservableCollection<Item>
{
new Item() { QuantityOnHand = 10, RetailPrice = 0.5m},
new Item() { QuantityOnHand = 6, RetailPrice = 1.5m}
};
inventory.InventoryList = newInventoryList;
Assert.That(inventory.TotalRetailPrice, Is.EqualTo(14m));
}
}
I have an ObservableCollection of items bound to a listbox as the ItemsSource.
Some of these items are also located in another collection on the same ViewModel (call it CollectionTwo).
I want to be able to take the count of the item in Collection2 and display it for the respective item in CollectionOne. When CollectionTwo properties change (ie the Count), it must also be reflected back to CollectionOne.
I would guess the best way to do this in MVVM is to wrap items in CollectionOne with a viewmodel class with an extra Count property on it. Can someone point me to a good example of this? Or perhaps another method to tackle this problem that won't hugely weigh down my ItemsSource performance.
Thanks!
You can use inheritance to create a custom collection along these lines...
public class MyCollection<T> : ObservableCollection<T>, INotifyPropertyChanged
{
// implementation goes here...
//
private int _myCount;
public int MyCount
{
[DebuggerStepThrough]
get { return _myCount; }
[DebuggerStepThrough]
set
{
if (value != _myCount)
{
_myCount = value;
OnPropertyChanged("MyCount");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
This is a class that wraps an Observable Collection and puts a custom property in it. The property participates in change notification, but that depends upon your design.
To wire it up, you can do something like this...
public MyCollection<string> Collection1 { get; set; }
public MyCollection<string> Collection2 { get; set; }
public void Initialise()
{
Collection1 = new MyCollection<string> { MyCount = 0 };
Collection2 = new MyCollection<string> { MyCount = 0 };
Collection2.CollectionChanged += (s, a) =>
{
// do something here
};
}
You can also do something like...
Collection1.PropertyChanged += // your delegate goes here
I have created WPF MVVM application, and set WPFToolkit DataGrid binding to DataTable so I want to know how to implement DataTable property to notify changed. Currently my code is like below.
public DataTable Test
{
get { return this.testTable; }
set
{
...
...
base.OnPropertyChanged("Test");
}
}
public void X()
{
this.Test.Add(...); // I suppose this line will call to getter first (this.Test = get Test) and then it will call add letter, this mean that setter scope will never fire.
base.OnPropertyChanged("Test"); // my solution is here :) but I hope it has better ways.
}
Is it has another solution for this problem?
There are 2 ways your Table data could change: Either an element could be added/removed from the collection, or some properties from within an element could change.
The first scenario is easy to handle: make your collection an ObservableCollection<T>. Invoking .Add(T item) or .Remove(item) on your table will fire a change notification through to the View for you (and the table will update accordingly)
The second scenario is where you need your T object to implement INotifyPropertyChanged...
Ultimately your code should look something like this:
public class MyViewModel
{
public ObservableCollection<MyObject> MyData { get; set; }
}
public class MyObject : INotifyPropertyChanged
{
public MyObject()
{
}
private string _status;
public string Status
{
get { return _status; }
set
{
if (_status != value)
{
_status = value;
RaisePropertyChanged("Status"); // Pass the name of the changed Property here
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Now set the datacontext of your View to be an instance of your ViewModel, and bind to the collection, like:
<tk:DataGrid
ItemsSource="{Binding Path=MyData}"
... />
Hope this helps :)
Ian