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 )
Related
I'm trying to make a listview in xamarin show data from a restapi but have the option to filter the list or sort it based upon last name.
I've set the bindingcontext equal to the apiviewmodel which works. But I want to set the itemssource to a list which can be manipulated later instead of the binding context.
Here is the code that works:
Xaml:
<ListView x:Name="DirectoryListView" ItemsSource="{Binding ContactsList}" IsPullToRefreshEnabled="True">
Xaml.cs:
LocalAPIViewModel = new APIViewModel();
BindingContext = LocalAPIViewModel;
APIViewModel.cs:
private List<MainContacts> _ContactsList { get; set; }
public List<MainContacts> ContactsList
{
get
{
return _ContactsList;
}
set
{
if(value != _ContactsList)
{
_ContactsList = value;
NotifyPropertyChanged();
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
This all works fine. It's only when I add the following lines that it stops displaying the data in the listview:
xaml.cs:
LocalList = LocalAPIViewModel.ContactsList;
DirectoryListView.ItemsSource = LocalList;
I think I need to add these lines so that I can manipulate the list that's being displayed. Why is the list not being displayed? Is this not how it should be done?
According to your description and code, you use MVVM to bind ListView firstly, it works fine, now you want to use Viewmodel to bind ListView itemsource in xaml.cs directly, am I right?
If yes,I do one sample according to your code, that you can take a look, the data can display successfully.
public partial class Page4 : ContentPage
{
public APIViewModel LocalAPIViewModel { get; set; }
public Page4 ()
{
InitializeComponent ();
LocalAPIViewModel = new APIViewModel();
listview1.ItemsSource = LocalAPIViewModel.ContactsList;
}
}
public class APIViewModel
{
public ObservableCollection<MainContacts> ContactsList { get; set; }
public APIViewModel()
{
loadddata();
}
public void loadddata()
{
ContactsList = new ObservableCollection<MainContacts>();
for(int i=0;i<20;i++)
{
MainContacts p = new MainContacts();
p.ID = i;
p.FirstName = "cherry"+i;
ContactsList.Add(p);
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
so I suggest you can check ContactsList if has data.
Update:
I want to be able to search the list with a search bar and also order it by first or last names. I also want to be able to click on one of the contacts and open up a separate page about that contact
I do one sample that can meet your requirement, you can take a look:
https://github.com/851265601/xf-listview
So, to answer all your questions...
First, the binding.
Once you set the ItemsSource="{Binding ContactsList}" this means that anytime you signal that you have changed your ContactsList by calling OnPropertyChanged(), that is going to be reflected on the ItemsSource property (so, update the UI - that is why we put the OnPropertyChanged() into the setter). Thus, you do not need to manually set the ItemsSource every time you change it. (Especially from the View, as the View should have no knowledge of how the ContactsList is defined in the ViewModel.)
So you can completely remove those lines from the View's code-behind.
Next, the ordering and searching.
What OnPropertyChanged() does, is that it re-requests the bound property from the ViewModel, and updates the View according to that. So, just after OnPropertyChanged() is called, the getter of the bound property (ContactsList) is called by the View.
So, a good idea is to put the sorting mechanism into the getter of the public property. (Or the setter, when resetting the property.) Something like this:
public class ViewModel {
private ObserveableCollection<MainContacts> contactList { get; set; }
public ObserveableCollection<MainContacts> ContactList {
get {
return new ObservableCollection<MainContacts>(contactList
.Where(yourFilteringFunc)
.OrderBy(yourOrderingFunc));
}
set {
contactsList = value;
OnPropertyChanged();
}
}
//...
}
So, whenever your public property is called, it will sort the private property and return the collection that way.
Change public List<MainContacts> ContactsList to public ObservableCollection<MainContacts> ContactsList
in xaml.cs
instead of LocalList = LocalAPIViewModel.ContactsList;, put
ContactsList = new ObservableCollection(LocalAPIViewModel.ContactsList);
I think this will work, instead of setting ListView's Itemsource to 'LocalList'
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 building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:
<ComboBox ItemsSource="{Binding ReasonsForTest}"
SelectedItem="{Binding Path=ReasonsForTestVm,
UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" />
The ReasonsForTest is of type ReasonForTestViewModel class:
public class ReasonForTestViewModel: ViewModelBase
{
private int _ReasonForTestId;
private string _ReasonForTestAbbr;
private string _description;
public int ReasonForTestId
{
get { return _ReasonForTestId; }
set
{
if (value == _ReasonForTestId) return;
_ReasonForTestId = value;
RaisePropertyChanged();
}
}
public string ReasonForTestAbbr
{
get { return _ReasonForTestAbbr; }
set
{
if (value == _ReasonForTestAbbr) return;
_ReasonForTestAbbr = value;
RaisePropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged();
}
}
}
I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox:
public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
using (var context = new MyEntities())
{
var query = new ObservableCollection<ReasonForTestViewModel>
(from rt in context.ReasonForTests
orderby rt.description
select new ReasonForTestViewModel
{
ReasonForTestId = rt.ReasonForTestID,
ReasonForTestAbbr = rt.ReasonForTestAbbr,
Description = rt.description,
});
return await Task.Run(() => query);
}
}
The view model populates the ComboBox using this:
var dataService = new TestDataService();
ReasonsForTest = await dataService.GetReasonsForTest();
The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem (ReasonsForTestVm) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.
Here's the property for ReasonsForTestVm:
public ReasonForTestViewModel ReasonForTestVm
{
get
{
return _reasonForTestVm;
}
set
{
if (Equals(value, _reasonForTestVm)) return;
_reasonForTestVm = value;
RaisePropertyChanged();
}
}
What am I doing wrong here? I'm about to lose my mind!
Update: Sorry for the confusing name in the property above. Fixed.
Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem.
When you bind a collection to ItemsSource, a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource. If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.
When you select one of these representations, the SelectedItemproperty now holds the instance from the collection that was bound to that representation.
This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically.
It's a very common pattern to bind these properties to similar properties in your view model.
public class MuhViewModel
{
public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };
// I don't want to show INPC impls in my sample code, kthx
[SuperSlickImplementINotifyPropertyChangedAttribute]
public MuhSelectedItem {get;set;}
}
bound to
<ComboBox ItemsSource="{Binding MuhItems}"
SelectedItem="{Binding MuhSelectedItem}" />
If you try to manually update the selected item this way...
muhViewModel.MuhSelectedItem = new Item(2);
The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)
What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource. In our example,
var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;
Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.
So, I am quite new at all of this stuff.
I got a IEnumerable<BasicClient> return type from a REST call.
I have vague memories of there being some way to bind a
list to .NET controls but for the life of me I can't remember how to do it.
Anybody's got a pointer for me, please?
This is a very basic example how to bind a collection to a ListBox. Since I don't know BasicClient I demonstrate it with this class:
public class Poco
{
public string Name { get; set; }
public string Description { get; set; }
}
And I have a Form with a ListBox named listBox1. The Form has a field _pocos as List<Poco>. To use an IEnumerable as in your question I declared a property IEnumerable<Poco> Pocos that returns that field.
public partial class Form1 : Form
{
private readonly List<Poco> _pocos = new List<Poco>();
public IEnumerable<Poco> Pocos { get { return _pocos; }}
public Form1()
{
_pocos.AddRange(new[] {
new Poco {Name = "Poco1", Description = "Description1"},
new Poco {Name = "Poco2", Description = "Description2"}
});
InitializeComponent();
listBox1.DataSource = Pocos;
listBox1.DisplayMember = "Name";
}
As you can see, I set the listBox1.DataSource to this property Pocos and use the listBox1.DisplayMember property to tell the ListBox which member it shall display.
This is the result:
For multi-column controls this might be a little more complicated, but there are examples on the MSDN. Search for keywords DataSource and DisplayMember.
I have looked at a lot of places and I'm struggling to do this supposedly simple thing. I have a Windows form on which I've to show a simple DataGridView in this form:
| (CheckBoxColumn) | FilePath | FileState |
Now, there are a couple of problems. The data I need to bind to them is in a List of objects like this:
class FileObject
{
string filePath;
string fileState;
}
//Now here's the list of these objects which I populate somehow.
List<FileObject> listFiles;
Is there any efficient way to bind this directly to the DataGridView
so that different members of Object in the list are bound to
different columns, and for each there's checkbox?
I saw the examples of adding checkbox column to a datagrid, but none of them showed how
to add it to the 'header' row as well, so that it can behave as a 'check all'/'uncheck all' box.
Any help in how to achieve this would be great! I do write simple applications in C# but never had to work with datagrids etc. :(
Thanks!
The DataGridView control has a feature to automatically generate columns that can be set by the AutoGenerateColumns property. This property defaults to true - that is columns are by default auto generated.
However, columns are only automatically generated for public properties of the object you bind to the grid - fields do not show up.
Auto generation also works for check box columns when there is a public boolean property on the bound object.
So the simplest way to achieve your first two requirements is to change your FileObject class to this:
public class FileObject
{
public string FilePath { get; set; }
public string FileState { get; set; }
public bool Selected { get; set; }
}
If you cannot modify that class then next best would be the create a wrapper object that holds a file object:
public class FileObjectWrapper
{
private FileObject fileObject_;
FileObjectWrapper()
{
fileObject_ = new FileObject();
}
FileObjectWrapper(FileObject fo)
{
fileObject_ = fo;
}
public string FilePath
{
get { return fileObject_.filePath; }
set { fileObject_.filePath = value; }
}
public string FileState
{
get { return fileObject_.fileState; }
set { fileObject_.fileState= value; }
}
public bool Selected { get; set; }
}
You can then create your list to bind to (a BindingList is usually best) doing something like:
var fowList = new BindingList<FileObjectWrapper>();
foreach (FileObject fo in // here you have your list of file objects! )
{
fowList.Add(new FileObjectWrapper(fo));
}
dataGridView1.DataSource = fowList;
There are many ways to do the above but that is a general idea.
You can also add an unbound DataGridViewCheckBoxColumn to the grid, though I find it easier to have in the the bound list. Here is how if you do need to:
DataGridViewCheckBoxColumn c = new DataGridViewCheckBoxColumn();
c.Name = "Selected";
dataGridView1.Columns.Add(c);
Finally, for having a "SelectedAll" option in the header you will need to use custom code.
The article on CodeProject that Umesh linked to (CheckBox Header Column for DataGridView) looks quite easy to implement - they create a custom DataGridViewHeaderCell overriding the Paint and OnMouseClick methods.
Please refer the the below example, showing exactly what you are looking for
http://www.codeproject.com/Articles/20165/CheckBox-Header-Column-For-DataGridView