I want to create a query with linq on my ObservableCollection but it doesn't really work how T tried it.
I have a Model Entry which has {note, information, isActive} as parameters. So I now want to simply just get all the Entries where isActive is true. I don't use it on my dataprovider (once the data gets loaded) because I need to load every entry into the program.
So I thought about to override the getter inside my entries ObservableCollection:
public ObservableCollection<Note> _entries { get; set; }
public ObservableCollection<Note> entries
{
get
{
return new ObservableCollection<Note>(from entry in this._entries
where entry.isActive == true
select entry);
}
set { this._entries = value; }
}
But as you might guess this doesn't work.
Regards
Try
get
{
List<Notes> list = _entries.Where(e=>e.isActive).ToList();
return new ObservableCollection<Note>(list) ;
}
Rather than editing it in the get, try updating the refinedEntries in the entries' setter. My Linq statement may need work but it encapsulates what I'm trying to suggest.
Essentially keep a copy of everything even the inactive records in entries and another collection to contain only the active records. In this case I'm calling it refinedEntries.
private ObservableCollection<Note> _entries;
public ObservableCollection<Note> entries
{
get{return _entries;}
set
{
_entries = value;
RefinedEntries = new ObservableCollection(_entries.Where(e=>e.isActive).Select(e => e));
}
}
public ObservableCollection<Note> refinedEntries {get;set;}
I would also suggest updating refinedEntries when CollectionChangedEvent fires. In this case the only time refinedEntries is updated is when entries is set to a new instance.
When you instantiate an new collection for entries, subscribe to its CollectionChangedEvent. For example if you instantiate the collection in the Model's constructor you could use the following..
entries = new ObservableCollection<Note>();
entries.CollectionChangedEvent += new NotifyCollectionChangedEventHandler((sender,args) =>
{
RefinedEntries = new ObservableCollection(_entries.Where(e=>e.isActive).Select(e => e));
//Notify the UI that an update has been made.
OnPropertyChanged("RefinedEntries");
});
Related
I have a view model DetailViewModel contains one IList of a sub view model EntryDetailViewModel.
public class DetailViewModel
{
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries;
[JsonIgnore]
public new IList<EntryDetailViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<=EntryDetailViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
}
My issue is when adding a EntryDetailViewModel entryDetailModel into the collection, the collection simply remains count = 0.
detailModel.Entries.Add(entryDetailModel);
Any ideas? Is it because entryDetailModel at the moment has no Id so it cannot be added? Or what can it be?
Additional Info
I tried query an existing DetailViewModel dvm1, and a existing EntryDetailViewModel from another DetailViewModel to add into dvm1, it also failed. Does that mean I can't add view model into another view model?
Nope, it won't work that way.
When you call:
detailModel.Entries.Add(entryDetailModel);
This triggers the Getter for your Entries, which is doing this:
return _entries.Where(m => !m.Deleted).ToList();
_entries may have been set to a new List<Entry> however, by returning the .Where(...).ToList(), the getter will return a newly initialized list containing just non-deleted entries. You're adding to that temporary list of results, not _entries.
If you want your Getters to filter only undeleted rows, that is fine, but you should consider a more of a DDD approach:
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries = new List<EntryDetailViewModel>();
[JsonIgnore]
public new IReadOnlyList<EntryDetailViewModel> Entries
{
get
{
return _entries.Where(m => !m.Deleted).ToList().AsReadOnly();
}
}
public void AddEntry(EntryDetailViewModel entry)
{
// TODO: Validate entry doesn't already exist, is complete/valid...
// then...
_entries.Add(entry);
}
With collection sets it's beneficial to initialize them on creation to avoid needing that extra boiler plate to check for null or constructor initialization. Then the getter is marked as a ReadOnlyList (or ReadOnlyCollection) which returns your filtered data. Using ReadOnly variants isn't required but it gives your future consumers at least some warning that says "Hey, you shouldn't try and modify this collection." DDD involves exposing suitable add/update/delete methods to regulate when and how the state of this object can be changed. There is no Setter for this collection so it cannot be overwritten by some rogue code added later. This involves planning out methods to serve as actions to regulate how state is modified rather than relying on setters.
Thanks to Steve Py for pointing out calling model.entries.add(entry) will always trigger the getter, which result to returning a new list every time.
However, the solution will always return a new list of the viewmodel when query and mapped. My original code doesn't post this issue, so I integrated them together by adding the AddEntry function, which will not trigger the getter and still able to query all the necessary data. My solution is down below.
[AutoMap(typeof(Manifest), ReverseMap = true)]
public class ViewModel
{
......other stuff
[NotMapped]
[JsonIgnore]
private IList<EntryViewModel> _entries;
[JsonIgnore]
public virtual IList<EntryViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<EntryViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
public void AddEntry(EntryViewModel entry)
{
_entries.Add(entry);
}
}
[AutoMap(typeof(Manifest), ReverseMap = true)]
public class DetailViewModel : ViewModel
{
......other stuff
[NotMapped]
[JsonIgnore]
private IList<EntryDetailViewModel> _entries;
[JsonIgnore]
public new IList<EntryDetailViewModel> Entries
{
get
{
if (_entries == null)
_entries = new List<EntryDetailViewModel>();
return _entries.Where(m => !m.Deleted).ToList();
}
set => _entries = value;
}
public void AddEntry(EntryDetailViewModel entry)
{
_entries.Add(entry);
}
}
When populating an observable collection, I can see that the "return" is not being called when I "set" the new data in the collection. It does work if I set the data from a different location in the program so I must be not understanding some nuance of the way it works. The part that works is when I take out the commented code under "This works", "ChooseFile()" does not. In the debugger I can see the OptionsToChoose has data in both cases. When it works the XAML is updated correctly.
class ScripterViewModel : BindableBase
{
public ScripterViewModel()
{
ScripterModel scripterModel = new ScripterModel();
ObservableCollection<string> tabsChoice = new ObservableCollection<string>();
tabsChoice.Add("Tabs");
tabsChoice.Add("Buttons");
Tabs = tabsChoice;
this.OpenFileBtn = new DelegateCommand(chooseFile, canChooseFile).ObservesProperty(() => OpenFile);
this.SaveFileBtn = new DelegateCommand(saveFile, canSaveFile).ObservesProperty(() => SaveFile);
//This works
//var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
//OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public void chooseFile()
{
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
_optionsToChoose = value;
}
}
}
When you are creating the OptionsToChoose in the constructor it will be initialized when the viewmodel is used by the view.
In the example that is not working, you are just replacing the ObservableCollection with a new one instead clearing it and adding the items. Therefore you need to notify that the property has been changed like V.Leon pointed out in his answer.
Or just clear the existing collection and populate it with the values from the json.
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose.Clear();
foreach (var item in myJSONDoc.TabbedBtns)
{
OptionsToChoose.Add(item);
}
You are not raising PropertyChanged event in the setter of OptionsToChoose. You already extend BindableBase, so raising PropertyChanged event can be done by replacing your current OptionsToChoose property implementation with the following:
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
SetProperty(ref _optionsToChoose, value);
}
}
See BindableBase.SetProperty Method
Ideally, you should not change the whole reference of ObservableCollection after it is binded. Instead clear items in it and then add new items in it.
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
}
OptionsToChoose.Clear();
OptionsToChoose.Add(foo);
As has already been brought up, given your code you would need to make the property for your collection raise PropertyChanged if you were resetting the collection. That said ObservableCollection is really not an ideal collection type to use. What I would recommend is including MvvmHelpers in your project and using the ObservableRangeCollection
public class MyPageViewModel : BindableBase
{
public MyPageViewModel()
{
OptionsToChoose = new ObservableRangeCollection<Tabbed>();
SomeCommand = new DelegateCommand(OnSomeCommandExecuted);
}
public DelegateCommand SomeCommand { get; }
public ObservableRangeCollection<Tabbed> OptionsToChoose { get; }
private void OnSomeCommandExecuted()
{
// get some updated data
IEnumerable<Tabbed> foo = DoFoo();
OptionsToChoose.ReplaceRange(foo);
}
}
You get a couple of benefits there. One you're not allocating and deallocating your collection. Also the ObservableRangeCollection updates the full list before raising PropertyChanged or CollectionChanged events this results in few UI notifications and better app performance.
I have a method that queries a database using entity framework and places the results in an ICollectionView. The ICollectionView acts as the ItemsSource for a DataGrid. Everything works fine on the first query, but upon querying a second time, the data is not properly sorted, despite the application of the correct SortDescriptions.
Here is my code for trying querying and grouping/sorting the data:
CollectionViewSource cvsRS;
private ObservableCollection<productorder> rs;
public ObservableCollection<productorder> RS
{
get { return rs; }
set
{
if (rs != value)
{
rs = value;
OnPropertyChanged("RS");
}
}
}
private ICollectionView rsView;
public ICollectionView RSView
{
get { return rsView; }
set
{
if (rsView != value)
{
rsView = value;
OnPropertyChanged("RSView");
}
}
}
public void QueryDatabase()
{
RS = new ObservableCollection<productorder>(DatabaseEntities.productorders.Where(o => o.month.id == CurrentMonth.id));
if (RS != null)
{
cvsRS.Source = RS;
RSView = cvsRS.View;
RSView.GroupDescriptions.Clear();
RSView.GroupDescriptions.Add(new PropertyGroupDescription("producttype.productcategory.name"));
RSView.GroupDescriptions.Add(new PropertyGroupDescription("producttype.name"));
RSView.SortDescriptions.Clear();
RSView.SortDescriptions.Add(new SortDescription("producttype.productcategory.sortorder", ListSortDirection.Ascending));
RSView.SortDescriptions.Add(new SortDescription("client.name", ListSortDirection.Ascending));
RSView.Refresh();
CurrentRecord = null;
SelectedRecords = null;
}
}
The grouping works fine, but the groups aren't in the correct order based on the sorting. I've tried a number of possible "fixes" with no success (e.g. adding sort/group descriptions directly to the CollectionViewSource, sorting before grouping, removing some of the sorting/grouping, removing the SortDescriptions per CollectionViewSource does not re-sort on property change).
Does anyone know how to maintain the sort order regardless of how many queries are performed? I'm open to alternative methods of querying displaying the data in the DataGrid if that may work.
Try binding your CollectionViewSource.Source property to your ObservableCollection<T> property. Set up the binding in the viewmodel constructor. Then, just leave it alone. Update the ObservableCollection<T>, replace it, etc. As long as it's an ObservableCollection<T> and its public property raises PropertyChanged whenever you replace it, the whole thing will work.
public MyViewModel()
{
BindCollectionViewSource();
}
protected void BindCollectionViewSource()
{
cvsRS = new CollectionViewSource();
var binding = new Binding
{
Source = this,
Path = new PropertyPath("RS")
};
BindingOperations.SetBinding(cvsRS, CollectionViewSource.SourceProperty, binding);
}
// Since we're not going to be messing with cvsRS or cvsRS.View after the
// constructor finishes, RSView can just be a plain getter. The value it returns
// will never change.
public ICollectionView RSView
{
get { return cvsRS.View; }
}
You can't just assign a binding to Source; there's more to it than that. The Source="{Binding RSView}" stuff you see in XAML may look like an assignment, but some details are being hidden for convenience. The Binding actively does stuff. It needs to know who the target object is.
I did see one funny thing: I gave my test code one PropertyGroupDescription and one SortDescription. When I added items to the collection, it sorted them within the groups. Then when I called RSView.Refresh(), it resorted them without reference to the groups. Not sure I understood what it was doing there.
I want to filter an ObservableCollection of Person object by name for my Xamarin Form application.
The goal is to filter this ObservableCollection to just display a part of it.
Here is my Person object class :
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}
I tried to make a filter like this :
private ObservableCollection<Person> personItems = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonItems
{
get { return personItems; }
set { personItems = value; OnPropertyChanged(); }
}
public void FilterPerson(string filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
PersonItems = personItems;
}
else
{
PersonItems = personItems.Where((person) => person. Name.ToLower().Contains(filter));
// Error here
}
}
I have this error :
Cannot not explicitly convert type :
'System.Collections.Generic.IEnumerable' to
'System.Collections.ObjectModel.ObservableCollection
Basically, there are two solutions:
If your PersonsItems list is not huge, you may recreate a whole collection each time a new filter string arrives. You don't even need an ObservableCollection in this case (due to the fact that you don't change the collection itself, you change a reference to a collection). All the UI elements will be recreated in this case
PersonItems = originalItems.Where((person) => person. Name.ToLower().Contains(filter)).ToList();
If your PersonsItems list is big enough, the first solution is not an option. In this case you need to manually call Add/Remove methods on the objects that should be added/removed. ObservableCollection has an imperative API and it fires an event each time Add/Remove is called. This event, in turn, can be observed by the ItemsControl that will make corresponding UI changes. Linq has a declarative API. That's why u need to sync a list to show with a filtered list manually.
PersonItems = personItems.Where((person) => person. Name.ToLower().Contains(filter));
is returning IEnumerable.
replace it with PersonItems = new ObservableCollection<Person>(personItems.Where((person) => person.Name.ToLower().Contains(filter)));
You have to recreate the observable using the filtered results.
To do this in the past I've used James Montemagno's ObservableRangeCollection and Grouping helper functions. You can find them in this plugin https://github.com/jamesmontemagno/mvvm-helpers
I'm creating an application using WPF, MVVM and LINQ to SQL. I have a collection of notes on an object of type Calculation. I have therefore created a ViewModel-class for this called vmCalculation. My problem is, that when I try to add a note to this object and submit the changes the "note" isn't submittet to the database.
Content of vmCalculation
public class vmCalculation : vmBase
{
Calculation calc;
public ObservableCollection<Note> Notes { get; private set; }
public vmCalculation(Calculation calc)
{
this.calc = calc;
Notes = new ObservableCollection<Note>();
foreach (var n in calc.Notes) Notes.Add(n);
}
public void AddNote()
{
Notes.Add(new Note
{
NoteText = "New note",
NoteType = 1
});
}
internal void Save()
{
foreach (var n in Notes.Where(n => n.NoteId == 0))
calc.Notes.Add(n);
}
}
Method in vmNotes (ViewModel for the "NoteWindow")
public void SaveChanges()
{
CurrentCalc.Save();
DC.SubmitChanges();
}
CurrentCalc is a property that gets/sets a vmCalculation that I use in the databinding (binding a DataGrid to CurrentCalc.Notes).
When I run AddNote() on CurrentCalc the view is updated just fine with a "New note"-note. But, when I run SaveChanges() the note isn't written to the database.
Any thoughts on this problem?
A possible cause for the the problem could be, that I don't initialize the DataContext (DC) in vmNotes. I get the DataContext from another ViewModel so that I don't destroy the MVVM-structure.
You must add your new entities to the datacontext before you submit it.
Example:
DC.Notes.InsertOnSubmit(NewNote);
DC.SubmitChanges();
Thought of a possible solution for my problem.
I updated the SaveChanges()-method on the vmNotes class a bit.
public void SaveChanges()
{
var newNotes = currentCalc.Notes.Where(n => n.NoteId == 0);
DC.Notes.InsertAllOnSubmit(newNotes);
DC.SubmitChanges();
}
}
UPDATE 03/09/2011:
Above code is not needed anyway.
I discovered that I had multiple (and static) instances of my DataModel-class.
I cut away some of these and now my original code works just fine!