I have two advanced collection views from windows community toolkit and both of them are bound to same ObservableCollection with different filters and sorting, basically in one of them I need to show just the recent and limited number of items. How can I achieve that?
PeoplePrivate = new ObservableCollection<Person>();
var People = new AdvancedCollectionView(PeoplePrivate, true) { Filter = x => true };
People.SortDescriptions.Add(new SortDescription(nameof(Person.Name), SortDirection.Ascending));
var RecentPeople = new AdvancedCollectionView(PeoplePrivate, true) { Filter = x => true };
RecentPeople.SortDescriptions.Add(new SortDescription(nameof(Person.Modified), SortDirection.Descending));
As you can see in the code above recentPeople should only show recent 20 people according to the modified date. There doesn't seem to be any property to set max size on the advancedCollection view or do anything like "Take(20)".
I tried to return a new advancedCollection by creating a IEnumerable first with Take(20) but that doesn't look the right way because I need to keep it linked to the same ObservableCollection.
view or do anything like "Take(20)" I tried to return a new advancedCollection by creating a IEnumeralbe first with Take(20)
Currently AdvancedCollectionView has not provide this method to get recent number items. But you could remove all the items except top 20 of the source.
public static class AdvancedCollectionViewEx
{
public static void GetTopRang(this AdvancedCollectionView acv, int Range)
{
do
{
var LastIndex = acv.Source.Count - 1;
acv.Source.RemoveAt(LastIndex);
} while (acv.Source.Count > Range);
}
}
Usage
RecentPeople.GetTopRang(20);
I like the WPF answer provided here and use a Binding Converter to chop the end-result of the collection view when it's bound to the ListView. Then it should get updated when the collection changes and re-filter?
Related
I have a datagrid in my WPF c# application (MVVM pattern) whose ItemsSource is bound to a ICollectionView, something like :
public ICollectionView PendingBills { get; private set; }
public BillsViewModel()
{
_penBills = m.PBills;
PendingBills = new ListCollectionView(_penBills)
{
Filter = o => ((Bills)o).PaidOn==""
};
}
....
<DataGrid ItemsSource="{Binding PendingBills}"
Now I've bound the row count of the datagrid to a textblock text using something like (which shows the count of all rows after filter)
<TextBlock Text="{Binding PendingBills.Count, StringFormat='Total Pending Bills :\ {0}'}" ...
But I also want to bind the row count to a different textblock where say the BillNo column data of the database has a specific text or maybe its null or non-empty something like that.
How do I do that ? Do I need to use some sort of IValueConverter to this and if yes how ?
UPDATE
I've tried to a property to the viewmodel like
private int _cnt;
public int Cnt
{
get
{
if (_cnt == 0) _cnt = _penBills.Where(x => !String.IsNullOrEmpty(x.PaidOn.ToString())).Count();
return _cnt;
}
set
{
_cnt =value;
}
}
and then use it like Text="{Binding Cnt}" but it does not work.
While you could always use a converter yes (and this is my own opinion based on experience here) you may just want to do this in the code behind.
You could have a property stored that gets the count you are looking for based on a linq query and simply bind to that.
Let me give an example.
you could get back the elements that match on some text:
var countOfMatches = yourlist.Where(x => x.SOMEVALUE == SOMETEXT).Count();
or you could go by some numeric condition:
var countOfMatches = yourList.Where(x => x.Price > 400).Count();
I hope this is a helpful answer to you.
In short I am pretty much saying that if you want a custom count based on this list you are better off creating a property for it and setting it with a query.
Based on your edit:
change the query to:
_penBills.Where(x => x.PaidOn.ToString() != string.Empty).Count();
(which is just a nitpick really)
as for your binding can you confirm that the observable collection for bills and this new integer property are stored on a view model that is the data context for this xaml.cs view?
Is there any way that I can have multiple views on a single Observable Collection, with different filters, that I can Refresh?
I have an Observable Collection called Proficiencies. I have three list boxes, each should display a subset of the Proficiencies items, filtered on a value within the Proficiencies items. i.e. One list displays items of category A, one list displays items of category B & one list displays items of category C.
I am trying to filter the collection using CollectionViewSources (called the SkillsView, ToolsView, and LanguagesView, one for each list box, each with it's own filter. They are properties in my ViewModel class that the lists boxes Bind to. they are declared in the form of:
protected ICollectionView theSkillsView;
public ICollectionView SkillsView
{
get { return theSkillsView; }
protected set
{
theSkillsView = value;
OnPropertyChanged("SkillsView");
}
}
I have two ways of initialising them either (1):
theSkillsView = new CollectionViewSource { Source = Proficiencies }.View;
theToolsView = new CollectionViewSource { Source = Proficiencies }.View;
theLanguagesView = new CollectionViewSource { Source = Proficiencies }.View;
Or an alternative Ive found (2):
theSkillsView = CollectionViewSource.GetDefaultView(Proficiencies);
theToolsView = CollectionViewSource.GetDefaultView(Proficiencies);
theLanguagesView = CollectionViewSource.GetDefaultView(Proficiencies);
I then Apply the Filtering:
theLanguagesView.Filter = p => ((ProficiencyData) p).Category == ProficiencyCategoryType.Language;
theSkillsView.Filter = p => ((ProficiencyData) p).Category == ProficiencyCategoryType.Skill;
theToolsView.Filter = p => ((ProficiencyData) p).Category == ProficiencyCategoryType.Tool;
The problem is:
if I use Option (1) all the views have the same filter (which ever is applied last)
If I use option (2) when I call Refresh() I get the following error:
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=PresentationFramework
StackTrace:
at System.Windows.Data.ListCollectionView.PrepareLocalArray()
I could create three seperate Collections, but that a) means I don't have all the items in a single collection and b) seems to be not in the spirit of things when CollectionViewSources are available precisely for Sorting, grouping and Filtering.
Many thanks in advance for any suggestions.
You should create three separate views of the AllItemsInDataBase source collection:
theSkillsView = new ListCollectionView(AllItemsInDataBase);
theToolsView = new ListCollectionView(AllItemsInDataBase);
theLanguagesView = new ListCollectionView(AllItemsInDataBase);
You can then filter the views independently of each other.
Currently, I'm planning to add ~500K List to datagridview.
Class has multiple Fields, Each List has 13 fields.
but i bind only 7 fields for now.
Problem is,it seems like adding takes too much time. like 5sec for 15K which is awful.
Is there any ways to optimze this?
or should i throw away datagridview and consider some other views?
private void UpdateDataGrid()
{
this.dataGridView1.Rows.Clear();
for (int i = 0; i < gVar.gTagCount; i++)
{
this.dataGridView1.Rows.Add(new object[]
{
gVar.Tags[i].TagCount,
gVar.Tags[i].Name,
gVar.Tags[i].Score.Story,
gVar.Tags[i].Score.Drawing,
gVar.Tags[i].Score.Drawing,
gVar.Tags[i].Score.Memetic,
gVar.Tags[i].DupeCount
});
}
}
According to what we discussed my approach would be this:
First make a new Class, I would call it TagsMin this class should contain the 7 things you want in your datagridview.
Secondly i would populate that a list of that class like this (you need to complete it with what you want):
var tagList = gVar.Tags.Select(x => new TagsMin() { TagCount = x.TagCount, Name = x.Name... }).ToList()
And the last step, would be to bind it to the datagridview:
dataGridView1.DataSource = tagList;
Consider using paging so that you are not loading all of the data at once. The answer to the question linked to below provides an example.
How can we do pagination in datagridview in winform
Can you try to avoid the loop and directly bind the list with the standard way:
dataGridView1.DataSource = null;
dataGridView1.DataSource = gVar.Tags;
How to filter a combobox based on another combobox? ... again :)
I'm writing an web app to learn. I'm using Visual Studio 2012, Silverlight 5, C#, and SQl Server for the data source.
I have one table loading into a datagrid and comboboxes to filter the datagrid. Up to this point everything is working just right.
The comboboxes are "FilterState" and "FilterWaterWay". Note they are not in the datagrid.
I want to select a state and re-populate the FilterWaterWay with only those waterways in the state.
I've seen a lot of ways to do this but none of them seem to match my setup. I could be wrong and just not know it.
From a learning standpoint, I would like to know how to implement this in all 3 of the following query data examples but I'll settle for just one. The last one is my favorite.
Thanks for any and all help.
I would not mind using the following to load comboboxes, filtered or not, but I can't firgure out how to
Restirct the GetQuery to only one field
Make that field distinct
This loads all data from the GetQuery to the datagrid.
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(this._DocksContext.GetMASTER_DOCKSQuery());
DocksGrid.ItemsSource = loadOp.Entities;
This loads all data from the GetQuery to the datagrid after it's been filtered
EntityQuery<MASTER_DOCKS> query = _DocksContext.GetMASTER_DOCKSQuery();
query = query.Where(s => s.WTWY_NAME == WaterwaytoFilterBy && s.STATE == StateToFilterBy);
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(query);
DocksGrid.ItemsSource = loadOp.Entities;
This is how I am currently loading the comboboxes. This works fine for the load but I don't see how to filter.
The DomainService.cs does not know my other combobox (FilterState) that I want to use as the filter for this combobox (FilterWaterway).
If I could query the ObservableCollection in the xaml I might be able to get it to work but it seems kind of chunky.
Adapted from http://www.jonathanwax.com/2010/10/wcf-ria-services-datagrid-filters-no-domaindatasource-2/
XAML =
private ObservableCollection<string> waterWayFilterList;
public ObservableCollection<string> WaterWayFilterList
{
get { return waterWayFilterList; }
set { waterWayFilterList = value; }
}
private void DoPopulateFilter()
{
//Call Invoke Method to get a list of distinct WaterWays
InvokeOperation<IEnumerable<string>> invokeOp = _DocksContext.FillWaterWayList();
invokeOp.Completed += (s, e) =>
{
if (invokeOp.HasError)
{
MessageBox.Show("Failed to Load Category Filter");
}
else
{
//Populate Filter DataSource
WaterWayFilterList = new ObservableCollection<string>(invokeOp.Value);
//Add a Default "[Select]" value
WaterWayFilterList.Insert(0, "[Select WaterWay]");
FilterWaterWay.ItemsSource = WaterWayFilterList;
FilterWaterWay.SelectedItem = "[Select WaterWay]";
}
};
}
DomainService.cs =
[Invoke]
public List<string> FillWaterWayList()
{
return (from r in ObjectContext.MASTER_DOCKS
select r.WTWY_NAME).Distinct().ToList();
}
Here's the closest I've gotten so far and it seems straight forward.
It returns no errors but the displayed result reads System.Collections.Generic.List'1[System.Char]
The record count in the dropdown is correct which leads me to think it's on the right track.
Only what is displayed is wrong. A casting problem perhaps?
I would still have to get the result from the FilterState Combo box in where "TX" is.
var filter = from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME.Distinct().ToList();
MyComboBox.ItemsSource = filter;
Without parentheses, you're doing the .Distinct().ToList() on the string (which implements IEnumerable<char>, which is why those operations work), which results in a List<char> (which isn't what you're looking for). You need to add parentheses so you get the distinct waterways:
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME).Distinct().ToList();
Note that if two waterways might have the same name, but actually be distinct, you'll need to instead select distinct r, and then differentiate them in the dropdown somehow, e.g.
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r).Distinct().ToList();
// generated classes are partial, so you can extend them in a separate file
public partial class MASTER_DOCKS
{
// the dropdown uses the ToString method to show the object
public override string ToString()
{
return string.Format("{0} ({1})", WTWY_NAME, ID);
}
}
I have an ObservableCollection of about 1000 objects that needs to be filtered (searched) by the end user. The user must be able to search by name or employee id. The List Control consumes FilteredEmployees and Employees is loaded up with everything on page load.
I currently have it set up as such:
public ObservableCollection<EmployeeServicesData> Employees { get; set; }
public ObservableCollection<EmployeeServicesData> FilteredEmployees { get; set; }
internal void FilterEmployee(string searchText, bool isByName)
{
if (searchText.Length > 0)
{
IEnumerabe<EmployeeServicesData> filter;
if (isByName)
filter = Employees.Where(x => x.Name.Length >= searchText.Length).Where(x => x.Name.Substring(0, searchText.Length) == searchText.ToUpper());
else
filter = Employees.Where(x => x.EmployeeNumber.ToString().Length > searchText.Length).Where(x => x.EmployeeNumber.ToString().Substring(0, searchText.Length) == text);
foreach (EmployeeServicesData employee in filter)
FilteredEmployees.Add(employee);
}
}
Sanitation is handled before this method.
This doesn't smell very efficent. Should I use two methods for this, or is there a better way to handle filtering?
I'd like to keep Employees at an unchanged state so I can repopulate FilteredEmployees to the full list without hitting the DB again.
I know this is an old post but I was using it to help me with the filtering aspect and noticed that SlipFish was creating the ObservableCollection by looping round the IEnumerable collection.
As the ObservableCollection constructor accepts an IEnumerable collection the ObservableCollection could be created like this:
FilteredEmployees = new ObservableCollection<EmployeeServicesData>(filter);
It looks like you're trying to see if searchText is contained in the Employee name, or in the Employee Number.
You could do this instead:
x.Name.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0
x.EmployeeNumber.ToString().IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0
Or you could use StartsWith instead of IndexOf.
Edit: Another problem with List Controls with large amounts of data in them is that it takes a long time to render. So if you have it unfiltered when you start and Silverlight or WCF or whatever has to render all 1000 into the control even though you don't see all of them, it can take a little bit of time. Silverlight 3 has UI Virtualization, which would probably be the best optimization you could do here.
Take a look at this post for a filtered observable collection.
You can this through the PagedCollectionView.
Check out this blog post for an example: http://blogs.msdn.com/b/avip/archive/2009/10/30/real-time-list-filtering-with-silverlight-mvvm-and-pagedcollectionview.aspx