Sorting ObservableCollection Using Custom Logic - c#

New to WPF and also the ObservableCollection I need to sort it in my own way, and keep it sorted everytime something adds or removes from it.
ObservableCollection<User> users = new ObservableCollection<User>();
The user object is like so :
class User
{
public string Name { get; set; }
public bool IsOp { get; set; }
public bool IsAway { get; set; }
}
I would like all the IsOp's at the top of the list, in alphabetical order. Then all the non-ops in alphabetical order following them.
What is the correct way to achieve this?
Many thanks in advance.

The easiest way is to use a CollectionView:
ICollectionView view = CollectionViewSource.GetDefaultView(users);
view.SortDescriptions.Add(new SortDescription("IsOp", ListSortDirection.Descending));
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
When you will bind the view to that list, the items will appear in the specified order.
Using the trick shown here, you can even use Linq:
var query =
from u in users.ShapeView()
orderby u.IsOp descending, u.Name ascending
select u;
query.Apply();

Related

Filter an ObservableCollection of object C#

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

LongListSelector grouping, JumpList

I have the LongListSelector that's working as it should, I want only to enable the grouping now. Like it is in the PeopleHub and the JumpList too. How do I do that ? I have checked an example on MSDN but it's complicated and it didn't work for me, maybe I don't understand it right.
I don't fill the LongListSelector with xaml or C# code, but with xml parsing.
First I parse the xml:
XDocument xml = XDocument.Load("xmlfile.xml");
var data = from query in xml.Descendants("country")
select new Country
{
Name = (string)query.Element("name"),};
and set the itemsSource:
countriesList.ItemsSource = data.ToList();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
}
I have the Country class:
public class Country
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}}
Now I would like to group this countries by name. How can I do that ?
Thanks for your help.
In the sample they create a fancy-pants helper class called AlphaKeyGroup<T>. Really though, you just need a class to contain each grouping:
public class CountryGrouping : List<Country>
{
public CountryGrouping(IEnumerable<Country> items) : base(items) { }
public string Key { get; set; }
}
Bind the ItemsSource to this:
countriesList.ItemsSource = data
.GroupBy(country => country.Name)
.Select(grp => new CountryGrouping(grp.ToArray()) { Key = grp.Key })
.ToList();
I'm guessing that the LongListSelector looks for a property called "Key" as the group header (magic strings!).
Also, don't forget to set IsGroupingEnabled="true" on the control.
Take a look at this wiki about the LongListSelector on Nokia Developer site: http://developer.nokia.com/Community/Wiki/LongListSelector_with_bindable_SelectedItem_and_better_scrolling
Because it contains a nice example you can use, but also talks about other things you may be needing if you go further with the LongListSelector ( like getting the selecteditem and other stuff )

Force a group in ListCollectionView to be first

I am using a ListCollectionView as a datacontext to a tab control. I have added a GroupDescription to it based on an enum and I want a particular group to appear as the first tab in the tab control however now it is always being put on the bottom.
Profiles = new ListCollectionView(_profiles);
Profiles.GroupDescriptions.Add(new PropertyGroupDescription("ProfileType"));
Profiles.SortDescriptions.Add(new SortDescription("ProfileName", ListSortDirection.Ascending));
_profiles is an ObservableCollection of my profile ViewModels.
My Enum looks like:
public enum ProfileTypeEnum
{
CurrentSettings,
CustomSettings,
DefaultSettings
}
So how can I force the CurrentSettings group to always be first?
Try to use an auxiliary property:
public class Item
{
public ProfileTypeEnum ProfileType { get; set; }
public string ProfileName { get; set; }
public int ProfileTypeValue { get { return (int)ProfileType; } }
}
give values to the enumeration:
public enum ProfileTypeEnum
{
CurrentSettings=0,
CustomSettings=1,
DefaultSettings=2
}
and add a sort description:
Profiles.SortDescriptions.Add(new SortDescription("ProfileTypeValue", ListSortDirection.Ascending));
Profiles.SortDescriptions.Add(new SortDescription("ProfileName", ListSortDirection.Ascending));
this way you can use enumeration's values to alter the order and force one on top.

How to edit an item in a list: C# and WPF

OK, so I have learned how to create a list, view items in the list, and use the items in the list. I now want to learn how to edit the information that is in the list.
Here is my list:
class ObjectProperties
{
public string ObjectNumber { get; set; }
public string ObjectComments { get; set; }
public string ObjectAddress { get; set; }
}
List<ObjectProperties> Properties = new List<ObjectProperties>();
This is how I am adding values to the list:
ObjectProperties record = new ObjectProperties
{
ObjectNumber = txtObjectNumber.Text,
ObjectComments = txtComments.Text,
ObjectAddress = addressCombined,
};
Properties.Add(record);
I am wanting the user to enter which number they want to update by using a textbox(txtUpdateObjectNumber). I then want to compare that number to the values that are stored in record.ObjectNumber and then if it exist I want to replace the information in record.ObjectNumber and record.ObjectComments where record.ObjectNumber == txtUpdateObjectNumber. If you need me to elaborate on anything just let me know. Any help would be appreciated. Thank You :)
To find the list item, use linq:
ObjectProperties opFound = Properties.Find(x => x.ObjectNumber == txtUpdateObjectNumber.Text);
Or the delegate form:
ObjectProperties opFound = Properties.Find(delegate (ObjectProperties x) { return x.ObjectNumber == txtUpdateObjectNumber.Text; });
Once you've found the item in the list, any changes you make to opFound, including the ObjectNumber, will persist in the list.

Using 2 maps on the same source?

I have 2 maps that I want to throw into the same source. But it seems one source overrides the second source even though I am targeting different fields.
public class FormViewModel
{
public List<SelectListItem> Items { get; set; }
public string SelectedItem { get; set; }
public string SomeField {get; set;}
// I have many more
}
List<Items> items = Service.GetItems();
FormViewModel viewModel = new FormViewModel()
{
Items = Mapper.Map<List<Items>, List<SelectListItem>>(courses);
};
var fields = Service.GetFields();
viewModel = Mapper.Map<Fields, FormViewModel>(fields);
So now when I do the second map. It will wipe out my first map. How can I stop this?
Edit
I guess I can see why it is happening. I thought it was just filling in those the fields but now I am looking at it and seeing that it is return a new FormViewModel.
I guess I can rearrange my code so that I first do the last map first then add my other map after.
List<CalendarAppointmentReminderVM> vm = Mapper.Map<List<CalendarAppointment>, List<CalendarAppointmentReminderVM>>(taskReminders.Select(x => x.CalendarAppointment).ToList());
Mapper.Map<List<AppointmentReminder>, List<CalendarAppointmentReminderVM>>(taskReminders, vm);
Separately they work. But together the first result gets wiped out by the first one.
pass the output of the first mapping into the second mapping so it doesn't create a new one...
viewModel = Mapper.Map<Fields, FormViewModel>(fields, viewModel);

Categories

Resources