Nested ObservableCollection filtering - c#

I have a question regarding the filtering of an ObservableCollection (and its children).
I have the following class:
public class SomeClass
{
public string Description { get; set; }
public string CodeFlag { get; set; }
public double Price { get; set; }
public List<SomeClass> Children { get; set; }
public SomeClass()
{
this.Children = new List<SomeClass>();
}
public SomeClass Search(Func<SomeClass, bool> predicate)
{
// the node is a leaf
if (this.Children == null || this.Children.Count == 0)
{
if (predicate(this))
return this;
else
return null;
}
else // the node is a branch
{
var results = Children.Select(i => i.Search(predicate)).Where(i => i != null).ToList();
if (results.Any())
{
var result = (SomeClass)MemberwiseClone();
result.Children = results;
return result;
}
/*
* this is where I'm struggling
*/
return null;
}
}
}
And in the view model the following properties:
private ObservableCollection<SomeClass> originalDataSource;
public ObservableCollection<SomeClass> TreeViewDataSource { get; set; }
The originalDataSource is set in the constructor whilst the TreeViewDataSource is the collection bound to the TreeView.
I'm certain that there are better ways to accomplish this, (i.e. have just the one collection) but I'm happy with this for now.
Initially, all of the items in the collection are to be shown - I simply show the Description, Code and Price properties for each item, so far so good.
Now, the view model is informed that the current filter has changed so I want to be able to filter as such.
An example could be to show all items where “CodeFlag” is “ABC” or “XYZ”.
If the filter has changed, I set the TreeViewDataSource as such:
this.TreeViewDataSource = _getFilteredList(this.originalDataSource);
private ObservableCollection<SomeClass> _getFilteredList(ObservableCollection<SomeClass> originalList)
{
var filteredItems = new ObservableCollection<SomeClass>();
SomeClass filterResults = null;
switch (this.SelectedFilter)
{
case SomeEnum.SomeFilterOption:
filterResults = originalList[0].Search(x => x.CodeFlag.Equals("ABC") || x.CodeFlag.Equals("XYZ"));
break;
default:
break;
}
filteredItems.Add(filterResults);
return filteredItems;
}
This almost works as expected.
Where it is not working as expected is if an item has children where the filter does NOT apply.
In this scenario, even though the item itself matches the filter, as its children do not, null is returned.
The
/*
* this is where I'm struggling
*/
comment is where I believe I need additional logic.
Please note, the credit for the original Search method goes to #tono-nam
As it's the Weekend and I may be in a different time zone as that of the vast majority of you, please do not be offended if I do not respond straight away!
Have a great weekend!

You don't need an ObservableCollection for the items you're going to show, since the entire collection changes at once. You can simply use e.g. an array, and let the parent class implement INotifyPropertyChanged to notify the fact that the entire collection has changed.
To answer your question about what to return instead of null, use the same logic you use for leaves: return the item if it matches the predicate and null otherwise.
You can simplify your code by reordering the conditions: first get all children that satisfy the predicate, and if there are none (either because there are no children, or because there are children but they don't match - doesn't matter) then treat the collection as a leaf.

Related

Create an ObservableCollection by filtering two ObservableCollections of the same type using LINQ and MVVM

I am attempting to create an ObserverableCollection to populate a grid view by viewing other collections and adding any object from the target collection that has different values from the source collection.
I have a model:
public class LayerModel
{
public string Name { get; set; }
public string OnOff { get; set; }
public string Freeze { get; set; }
public string Color { get; set; }
public string Linetype { get; set; }
public string Lineweight { get; set; }
public string Transparency { get; set; }
public string Plot { get; set; }
}
And two ObservableCollections of that model in my viewmodel:
public ObservableCollection<LayerModel> SourceDrawingLayers { get; set; }
public ObservableCollection<LayerModel> TargetDrawingLayers { get; set; }
A collection for the conflicts:
public ObservableCollection<LayerModel> ConflictLayers {get; set;}
And lastly, a collection of target drawings to compare against:
ObservableCollection<TargetDrawingModel> TargetDrawings {get; set;}
In the application, the user is asked to select a source drawing. The user is prompted to an OpenFileDialog and selects a drawing file. At that point, SourceDrawingLayers is created from a method that looks at every layer in that file and creates the collection.
Next the user has to select a group of Target Drawings, represented by TargetDrawingModel. The selection is added to TargetDrawings.
Now the fun part.
I need open each Target Drawing and read the layers, then compare those layers against the SourceDrawingLayers and, if any property is different, I need to add it to ConflictLayers.
So far I have tried a nasty triple nested foreach statement that didn't work right so I started digging into LINQ since it seems like there's an easy solution to my problem but my results are strange.
This is where I'm at currently. I attempted to use a "where" statement looking only at the OnOff property inside my LayerModel but the resulting ConflictLayers ObservableCollection is simply populating with every layer inside of the TargetDrawing and displaying their settings as they are.
private void PopulateConflictLayers()
{
foreach (TargetDrawingModel targetDrawingModel in TargetDrawings)
{
DataAccess da = new DataAccess();
TargetDrawingLayers = da.GetDrawingLayers(targetDrawingModel.DrawingPath);
ConflictLayers = TargetDrawingLayers.Where(y => SourceDrawingLayers.Any(z => z.OnOff == y.OnOff));
}
}
My goal is for ConflictLayers to be a collection of only the LayerModels inside TargetDrawingLayers, where any property does not match what is in the SourceDrawingLayers.
I have also tried using the Any method but got the exact same result, where my ConflictLayers just diplayed every LayerModel inside my Target Drawing, regardless of the setting or whether it matched anything inside of SourceDrawingLayers.
Any ideas would be hugely appreciated!
Update: I tried the solution provided by reggaeguitar below and the result was my datagrid simply displayed all layers, unfiltered, in both the source drawing and the target drawing i added to the collection.
I implemented IEquatable on my LayerModel
public bool Equals(LayerModel other)
{
if (other == null)
return false;
return
Name == other.Name
&& OnOff == other.OnOff
&& Freeze == other.Freeze
&& Color == other.Color
&& Linetype == other.Linetype
&& Lineweight == other.Lineweight
&& Transparency == other.Transparency
&& Plot == other.Plot;
}
And updated my method as follows:
private void PopulateConflictLayers()
{
foreach (TargetDrawingModel targetDrawingModel in TargetDrawings)
{
DataAccess da = new DataAccess();
TargetDrawingLayers = da.GetDrawingLayers(targetDrawingModel.DrawingPath);
var s = TargetDrawingLayers.Except(SourceDrawingLayers).Union(SourceDrawingLayers.Except(TargetDrawingLayers));
ObservableCollection<LayerModel> list = new ObservableCollection<LayerModel>(s);
ConflictLayers = list;
}
}
Something I did wrong?
I found a solution that I want to post.
I ended up modifying my code as such:
ConflictLayers = TargetDrawingLayers.Where(i => !SourceDrawingLayers.Contains(i)).ToList();
I also implemented IEquatable on my LayerModel and overrode GetHashCode() on it as well. The result is my ConflictLayers is now correctly populated with LayerModels whose properties are different from SourceDrawing.

Can't change value in foreach from IEnumerable<Model>

How to change value of an object in foreach from IEnumerable<Model>.
Code:
public IEnumerable<Model> ListDaftarPjsp()
{
IEnumerable<Model> list = from x in db.PJSPEvaluation
select new Model
{
foo = x.Foo,
bar = x.Bar
};
foreach (Model item in list) {
item.condition = "example";
}
return list;
}
public class Model{
public string foo{ get; set; }
public string bar { get; set; }
public string condition{ get; set; }
}
I already create Model. Then I am looping result using foreach, then set it. But the Return for conditionstill not changing? how to set condition inside foreach then return it for result
IEnumerable<T> is a query, not a collection. While there is some sort of collection at the other end, the query itself is not the collection. The nature of the collection you are targeting will determine whether or not you can modify the contents.
The general rule of thumb is that you can't expect an IEnumerable<T> to return the same list of objects twice, or even expect that you will be able to enumerate across it more than once - it is perfectly valid (if unusual) for an IEnumerable<T> to enumerate once only and refuse to enumerate a second or third time.
In this case what you have is actually a database query of type IQueryable<Model> that is cast to IEnumerable<Model>. It's still an IQueryable<Model> which means that each time you enumerate across it you will get (probably) the same list of data but in completely new objects. Changing one of the objects won't change all of the objects for the same source record, nor change the contents of the underlying record itself.
If you are trying to modify the returned objects without changing the underlying records (seems to be the case) then you need to materialize the query into a collection in memory. There are a few ways to do this depending on what you're expecting to do with the returned data.
The simplest is to convert the query to an array using the .ToArray() extension method:
public Model[] ListDaftarPjsp()
{
var query = from x in db.PJSPEvaluation
select new Model
{
foo = x.Foo,
bar = x.Bar
};
var list = query.ToArray();
foreach (Model item in list)
{
item.condition = "example";
}
return list;
}
Now the records are in an array in memory and enumeration of that array can be done multiple times returning the same exact objects instead of fetching new copies of the data from the database every time.
Here you are trying to create a list of Model using LINQ, then you are iterating the same for adding an additional property to each item. Then why don't you add the property at the time of creation of the list instead for an additional loop? Make things simple by try something like this:
from x in db.PJSPEvaluation
select new Model
{
foo = x.Foo,
bar = x.Bar,
condition = GetCondition(x.Foo)
};
Where the GetCondition() can be defined as :
private string GetCondition(int foo)
{
if(item.foo == 1)
{
return "a";
}
else if(item.foo == 2)
{
return "b";
}
else
{
return "xx";
}
}
There is already for this topic but there is more efficient way to do this.
Just use List<> instead of Array[].
public List<Model> ListDaftarPjsp()
{
List<Model> list = from x in db.PJSPEvaluation
select new Model
{
foo = x.Foo,
bar = x.Bar
};
foreach (Model item in list)
{
item.condition = "example";
}
return list;
}
public class Model{
public string foo{ get; set; }
public string bar { get; set; }
public string condition{ get; set; }
}
I case you dont want to load items in memory with a .ToArray or .ToList
You can use .Select from Linq.
return myEnumeration.Select(item => {
item.condition = "example";
return item;
})

Improve efficiency of modifying object properties in a list

I have a list of custom objects that I am working with. I need to find matching objects, and save two attributes to the object, and move on. I can't help but think that my method of working with these objects is sub-optimal. Given I am working with large volumes of data (in this instance a list with ~ 10000 objects, but in other instances significantly larger), I would appreciate any information that might help me optimize the process.
List<WebListingVerification> listings = new List<WebListingVerification>(); //This list is fully populated, and is actually passed into the function.
string sku = reader["vsr_sku"].ToString();
string vendorName = reader["v_name"].ToString();
string vendorSku = reader["vsr_vendor_sku"].ToString();
WebListingVerification listing = listings.Find(x => x.SKU == sku);
if(listing != null)
{
listings.Remove(listing);
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
listings.Add(listing);
}
As you can see above, I first remove the listing, then edit it, and then re-add it. I imagine there is a way to safely edit the object in the list without running Remove / Add which would help a great deal, but I can't seem to find how to do it. I'm not sure if you could do a compound function off of the listings.Find call (listings.Find(x => x.SKU == sku).Vendor = "vendor") but it would be unsafe, as there will be null returns in this circumstance anyways so..
Any help optimizing this would be greatly appreciated.
Edit
Thank you for the comments, I did not understand the fact that the result of the List.Find function call is in fact a pointer to the object in the list, and not a copy of the object. This clears up my issue!
In addition, thank you for the additional answers. I was looking for a simple improvement, predominantly to remove the Add / Remove routines, but the additional answers give me some good ideas on how to write these routines in the future which may net some significant performance improvements. I've been focused on reporting tasks in the past few months, so this example snippet is very similar to probably 100 different routines where I am gathering data from various source databases. Again, I very much appreciate the input.
public class WebListingVerification
{
public string Sku { get; set; }
public string VendorName { get; set; }
public string VendorSku { get; set; }
}
public class ListingManager : IEnumerable <WebListingVerification>
{
private Dictionary<string, WebListingVerification> _webListDictionary;
public ListingManager(IEnumerable <WebListingVerification> existingListings)
{
if (existingListings == null)
_webListDictionary = new Dictionary<string, WebListingVerification>();
else
_webListDictionary = existingListings.ToDictionary(a => a.Sku);
}
public void AddOrUpdate (string sku, string vendorName, string vendorSku)
{
WebListingVerification verification;
if (false == _webListDictionary.TryGetValue (sku, out verification))
_webListDictionary[sku] = verification = new WebListingVerification();
verification.VendorName = vendorName;
verification.VendorSku = vendorSku;
}
public IEnumerator<WebListingVerification> GetEnumerator()
{
foreach (var item in _webListDictionary)
yield return item.Value;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
If your items are unique, might I suggest a HashSet<T>?
HashSet<WebListingVerification> listings = new HashSet<WebListingVerification>();
string sku = reader["vsr_sku"].ToString();
string vendorName = reader["v_name"].ToString();
string vendorSku = reader["vsr_vendor_sku"].ToString();
if(listings.Contains(listing))
{
listings.Remove(listing);
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
listings.Add(listing);
}
You'd have to roll your own IEqualityComparer<T> interface on the WebListingVerification object and match on the SKU, which I assume is unique.
public class WebListingVerification : IEqualityComparer<WeblistingVerification>
{
public string Sku { get; set; }
public bool Equals(WebListingVerification obj, WebListingVerification obj2)
{
if (obj == null && obj2 == null)
return true;
else if (obj == null | obj2 == null)
return false;
else if (obj.Sku == obj2.Sku)
return true;
else
return false;
}
public int GetHashCode(WebListingVerification obj)
{
return Sku.GetHashCode();
}
}
HashSet.Contains() performance is phenomenal on large datasets like this.
To speed up the lookup you could first convert your list into a dictionary. Note though if your update method is a method, you should not do the conversion inside the method, but outside the update loop.
var dictionary = listings.ToDictionary(l => l.SKU);
And get the item from the dictionary with the sku value.
WebListingVerification listing;
if (dictionary.TryGetValue(sku, out listing))
{
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
}
No need to remove and add back the object into the list. Just;
if(listing != null)
{
listing.Vendor = vendorName;
listing.VendorSKU = vendorSku;
}

Function which will return particular node from tree structure

I am writing the function which will return particular node from tree structure. But when I search in a tree using LINQ it is searching in the first branch and finally when it reaches to leaf it is throwing null reference exception as leaf don't have any child.
Here is my class,
public class Node
{
public int Id { get; set; }
public string Name { get; set; }
public string Content { get; set; }
public IEnumerable<Node> Children { get; set; }
public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
{
return new[] { this }
.Concat(Children.SelectMany(child => child.GetNodeAndDescendants()));
}
}
This is how I am calling this function,
var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Name.Contains("string to search"));
OR
var foundNode = Location.GetNodeAndDescendants().FirstOrDefault(node => node.Id==123)
What would be the correct way to do this? and any sample code would be grateful
Nothing wrong to write your own function, but implementation based on LINQ or recursive iterator is not a good idea (performance!). But why depending on external libraries? A lot of code that you don't need, implementing interfaces, modifying your classes etc. It's not hard to write a generic function for pre-order tree traversal and use it for any tree structure. Here is a modified version of my participation in How to flatten tree via LINQ? (nothing special, ordinary iterative implementation):
public static class TreeHelper
{
public static IEnumerable<T> PreOrderTraversal<T>(T node, Func<T, IEnumerable<T>> childrenSelector)
{
var stack = new Stack<IEnumerator<T>>();
var e = Enumerable.Repeat(node, 1).GetEnumerator();
try
{
while (true)
{
while (e.MoveNext())
{
var item = e.Current;
yield return item;
var children = childrenSelector(item);
if (children == null) continue;
stack.Push(e);
e = children.GetEnumerator();
}
if (stack.Count == 0) break;
e.Dispose();
e = stack.Pop();
}
}
finally
{
e.Dispose();
while (stack.Count != 0) stack.Pop().Dispose();
}
}
}
and your function inside the class Node becomes:
public IEnumerable<Node> GetNodeAndDescendants() // Note that this method is lazy
{
return TreeHelper.PreOrderTraversal(this, node => node.Children);
}
Everything else stays the way you did it and should work w/o any problem.
EDIT: Looks like you need something like this:
public interface IContainer
{
// ...
}
public class CustomerNodeInstance : IContainer
{
// ...
}
public class ProductNodeInstance : IContainer
{
// ...
}
public class Node : IContainer
{
// ...
public IEnumerable<IContainer> Children { get; set; }
public IEnumerable<IContainer> GetNodeAndDescendants() // Note that this method is lazy
{
return TreeHelper.PreOrderTraversal<IContainer>(this, item => { var node = item as Node; return node != null ? node.Children : null; });
}
}
If you don't mind taking a dependency on a third party solution I have a lightweight library that I have been working on that can do this and many other things with just about any tree. It is called Treenumerable. You can find it on GitHub here: https://github.com/jasonmcboyd/Treenumerable; and the latest version (1.2.0 at this time) on NuGet here: http://www.nuget.org/packages/Treenumerable. It has good test coverage and seems to be stable.
It does require that you create a helper class that implements an ITreeWalker interface with two methods: TryGetParent and GetChildren. As you might guess TryGetParent gets a node's parent so your Node class would have to be modified in a way that it is aware of its parent. I guess you could just throw a NotSupported exception in TryGetParent as that method is not necessary for any of the traversal operations. Anyway, regardless of which way you go the following code would do what you want:
ITreeWaler<Node> walker;
// Don't forget to instantiate 'walker'.
var foundNode =
walker
.PreOrderTraversal(Location)
.FirstOrdefault(node => node.Name.Contains("string to search"));
One difference worth mentioning between my implementation and yours is that my implementation does not rely on recursion. This means you don't have to worry about a deep tree throwing a StackOverflowException.

Creating a simple NSOutlineView datasource with MonoMac

I cant seem to figure out how to create a simple NSOutlineView with 2 columns, and a datastructure that is more than 1 level deep (a hierachy).
I've been researching this for days, and all I can find is Objective C examples, which I really can't use for anything.
I understand there are different patterns for doing this, one being the DataSource pattern. I tried creating a class that inherited from NSOutlineViewDataSource, however thats all I got, I have no clue on what I should do next!
Lets say I would like to display the following class in my NSOutlineView:
public class Person
{
public string Name {get;set;} // First column
public int Age {get;set} // Second column
public List<Person> Children {get;set} // Children
}
What would be the most trivial approach to accomplishing this?
Brace yourselves... A level-independant NSOutlineView in MonoMac!
After hundreds of google searches, and looking through ObjC as well as C# code, I finally figured out how to do it! I will post my solution here, in case someone else needs it.
This may or may not be the best way to do it, but it works for me.
Step 1: In Interface Builder, add an NSOutlineView. Add 2 columns to it, and set their Identifier to colName, and colAge.
Also, while you're at it, add a button to your form.
Step 2: Create an outlet for the NSOutlineView - I called mine lvMain because I come from a VCL background. Also, create an action for your button (this will be the onClick handler).
Step 3: Save your XIB file, and return to Mono - it will update your project file. Now, we want to create the model we wish to use for our view.
For this example, I will use a simple Person object:
public class Person:NSObject
{
public string Name {
get;
set;
}
public int Age {
get;
set;
}
public List<Person> Children {
get;
set;
}
public Person (string name, int age)
{
Name = name;
Age = age;
Children = new List<Person>();
}
}
Nothing overly complicated there.
Step 4: Create the datasource. For this example, this is what I made:
public class MyDataSource:NSOutlineViewDataSource
{
/// The list of persons (top level)
public List<Person> Persons {
get;
set;
}
// Constructor
public MyDataSource()
{
// Create the Persons list
Persons = new List<Person>();
}
public override int GetChildrenCount (NSOutlineView outlineView, NSObject item)
{
// If the item is not null, return the child count of our item
if(item != null)
return (item as Person).Children.Count;
// Its null, that means its asking for our root element count.
return Persons.Count();
}
public override NSObject GetObjectValue (NSOutlineView outlineView, NSTableColumn forTableColumn, NSObject byItem)
{
// Is it null? (It really shouldnt be...)
if (byItem != null) {
// Jackpot, typecast to our Person object
var p = ((Person)byItem);
// Get the table column identifier
var ident = forTableColumn.Identifier.ToString();
// We return the appropriate information for each column
if (ident == "colName") {
return (NSString)p.Name;
}
if (ident == "colAge") {
return (NSString)p.Age.ToString();
}
}
// Oh well.. errors dont have to be THAT depressing..
return (NSString)"Not enough jQuery";
}
public override NSObject GetChild (NSOutlineView outlineView, int childIndex, NSObject ofItem)
{
// If the item is null, it's asking for a root element. I had serious trouble figuring this out...
if(ofItem == null)
return Persons[childIndex];
// Return the child its asking for.
return (NSObject)((ofItem as Person).Children[childIndex]);
}
public override bool ItemExpandable (NSOutlineView outlineView, NSObject item)
{
// Straight forward - it wants to know if its expandable.
if(item == null)
return false;
return (item as Person).Children.Count > 0;
}
}
Step 5 - The best step: Bind the datasource and add dummy data! We also wanna refresh our view each time we add a new element. This can probably be optimized, but I'm still in the "Oh my god its working" zone, so I currently don't care.
// Our Click Action
partial void btnClick (NSObject sender)
{
var p = new Person("John Doe",18);
p.Children.Add(new Person("Jane Doe",10));
var ds = lvMain.DataSource as MyDataSource;
ds.Persons.Add(p);
lvMain.ReloadData();
}
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
lvMain.DataSource = new MyDataSource();
}
I hope this information can help the troubled souls of the MonoMac newcomers like myself.
It took me a little while to track this down, but Xamarin has an example of how to do this Here

Categories