Navigating records in a databound WPF window - c#

I'm fairly new to WPF and I've been reading many tutorials and yet while I could find many guides that showed how to bind data to textboxes and such, I couldn't find anything about navigating such data through back/forward/etc. buttons.
This is my current situation: I have a Customer class containing data on a single customer and a Customers class which is an ObservableCollection of customer.
Then data is loaded from an sqlite database (and this opens another can of worms because I don't know the exact approach for working this out but it doesn't really pertain to the current issue since I more or less got it to work) and every customer is added to the collection.
Then in the ViewModel for the main form I have the following stuff:
private Customer _objCustomer;
private Customers _customers;
private Customer _selectedCustomer;
public Customer Selection { get { return _selectedCustomer; }
set
{
if (object.ReferenceEquals(value, _selectedPartecipante)) { return; }
_selectedCustomer = value;
base.OnPropertyChanged("Selection");
}
}
public Customers customers { get { return _partecipanti; }
set { _customers = value; base.OnPropertyChanged("customers"); } }
public Customer customer { get { return _objCustomer; }
set { _objCustomer = value; base.OnPropertyChanged("customer"); } }
public string Name { get { return _objCustomer.Name; } set { _objCustomer.Name = value; base.OnPropertyChanged("Name"); } }
public int Id { get { return _objCustomer.Id; } }
public SubscriptionsViewModel()
{
_customers = Customers.LoadCustomers(); //This one loads the items from the database
_objCustomers = _customers.First();
_selectedCustomer = _objCustomer;
}
This is probably wrong but I still can't find a way to fix it, what am I supposed to work to get navigation working?
And how do I get the data in the current record to be saved when pressing a certain button on the form?

Try to put the exact property in OnPropertyChanged("Customers");
In your case, the property is lower case.

Related

How to set listview itemssource to a viewmodel in Xamarin?

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'

Xamarin forms - MVVM observable collection always null

I have the strangest issue with an observable collection. I set my collection with some dummy data and it loads on the content page as expected however when I attempt to get the data from the data context its always null.
I debugged the code in the ViewModel and I can see the collection as null. Its clearly not null because I populates on the form.
Is there something im missing here !
private ObservableCollection<Company> _CompanyCollection;
public ObservableCollection<Company> CompanyCollection
{
get { return _CompanyCollection; }
set
{
if (value != null)
{
_CompanyCollection = value;
OnPropertyChanged();
}
}
}
Loading data
public void LoadTestCompanies()
{
CompanyCollection = new ObservableCollection<Company>()
{
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"}
};
}
Calling Viewmodel from event in page.cs
CompaniesVM viewModel = (CompaniesVM)BindingContext;
var results = viewModel.CompanyCollection.Where(x => x.Name.ToLower().Contains(searchBar.Text.ToLower()));
This is the code behind
public Companies ()
{
InitializeComponent ();
BindingContext = new CompaniesVM(this.Navigation);
}
ViewModel calls loatTestCompanies
public CompaniesVM(INavigation navigation)
{
// Navigation
Navigation = navigation;
LoadTestCompanies();
}
Ive tried many other ways of initialising the collection and use .Add(object> but nothing seems to be working.
Any ideas would be great.
Thank you
Two advises that may solve your problem:
1
Use a self-declared readonly property when referring to collections:
public ObservableCollection<Company> CompanyCollection { get; }
2
This change will force you to create the instance of CompanyCollection directly in the constructor:
public CompaniesVM(INavigation navigation)
{
Navigation = navigation;
CompanyCollection = new ObservableCollection<Company>();
LoadTestCompanies();
}
And then...:
public void LoadTestCompanies()
{
CompanyCollection.AddRange(new[]
{
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"}
});
}
I believe that changing the reference itself for bound properties implies in ViewModel using an object instance and the View using another one. So the view 'stops' to listen to VM changes for that property.
I've never got into the deep of ItemsSources Views implementations, but I guess they kind of observe the items when binding collections - or the collection instance's properties in some cases - when getting changes notification.
With this changes, I guess your code should work fine.
Hope it helps.

How to call one viewmodel's method from another viewmodel

I am developing Windows Universal app. I have one GridView which has one textblock and a button. The gridview gets data of un-purchased objects from a service. The button is for purchasing particular object. So if user clicks on button that object is purchased & gridview gets refreshed to remove purchased item from it.
I am illustrating my requirement in simplified manner. I tried two ways, both are not working. Can you please suggest me solution regarding it.
First way I used is to inherit Model class with ViewModel class so I can access methods of ViewModel class, but it throws StackOverflowException in ViewModelBase at SetProperty<T> method.
P.S. - I don't want to migrate to any framework like MVVMLight, etc.
ViewModel.cs
public class ViewModel : ViewModelBase
{
public ViewModel()
{
DataCollection = new ObservableCollection<Model>();
for (int i = 1; i < 10; i++)
{
DataCollection.Add(new Model { Number = i });
}
}
private ObservableCollection<Model> _DataCollection;
public ObservableCollection<Model> DataCollection
{
get { return _DataCollection; }
set { this.SetProperty(ref this._DataCollection, value); }
}
}
Model.cs
public class Model : ViewModel
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
var obj = DataCollection.Where(varNum => varNum.Number == x).FirstOrDefault();
if (obj != null)
{
DataCollection.Remove(obj);
}
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
2nd way I keep that isolated, so I was not able to access the methods.
ViewModel.cs is same as above
Model.cs
public class Model : ViewModelBase
{
public RelayCommand<int> DeleteCommand { get; set; }
public Model()
{
DeleteCommand = new RelayCommand<int>((x) => DeleteNumber(x));
}
private void DeleteNumber(int x)
{
// How to access ViewModel's DataCollection property or
// a method which sets un-purchased objects in DataCollection property
}
private int _Number;
public int Number
{
get { return _Number; }
set { this.SetProperty(ref this._Number, value); }
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Well, in the first example you're getting a StackOverflowException because your ViewModel instantiates 9 Models each time - and since your Model is an extension of ViewModel, each one of those instantiates 9 more Models and an infinite recursion happens. That doesn't answer your main question, though :)
Your class names are confusing to me, because in MVVM a "Model" is simply a representation of the data and methods to manipulate it, whereas the ViewModel requests this data from the Model and presents it via publicly accessible properties that are retrieved from the View via binding. The View knows about the ViewModel, the ViewModel knows about the Model and the Model just knows about the data. In any case you shouldn't be binding directly from the View to the Model!
You'll want to house the RelayCommand in your ViewModel so your View can bind to it, and depending on what you want to happen when a user purchases an item (store it in a database, track this in another variable, simply remove from the view without doing anything else, etc.) you may or may not need to write additional logic for when this occurs. Generally you'll want the ViewModel to handle user input and update both the presentation object as well as notify the Model a change was made, if this is something your app requires. Think of it as the Model holds the actual data whereas the ViewModel only holds what the user sees.
Unfortunately, without knowing what you're trying to do in a little more detail it's hard to give more specific advice than this!

How do I create a user control with 3 combo boxes and a different data binding for each?

I've done some extensive searching before finally deciding to ask this question. I've followed the MSDN tutorials on creating User Controls that use Simple, Complex and Lookup Data Binding.
Walkthrough: Creating a User Control that Supports Simple Data Binding
Walkthrough: Creating a User Control that Supports Complex Data Binding
Walkthrough: Creating a User Control that Supports Lookup Data Binding
And they work great...for a User Control that only uses a single Combobox or Gridview.
Now I want to create a User Control with three different Comboboxes. I want to bind each one to a different Table. The tables are 'Names', 'Types', and 'Products'.
The MSDN tutorials involve creating DataBindingProperties for a single Combobox, but do not show how to do the same for a user control that contains more than one.
using System.Windows.Forms;
namespace CS
{
[System.ComponentModel.LookupBindingProperties(
"DataSource", "DisplayMember", "ValueMember", "LookupMember")]
public partial class LookupBox : UserControl
{
public object DataSource
{
get{ return comboBox1.DataSource; }
set{ comboBox1.DataSource = value; }
}
public string DisplayMember
{
get{ return comboBox1.DisplayMember; }
set{ comboBox1.DisplayMember = value; }
}
public string ValueMember
{
get{ return comboBox1.ValueMember; }
set{ comboBox1.ValueMember = value; }
}
public string LookupMember
{
get{ return comboBox1.SelectedValue.ToString(); }
set{ comboBox1.SelectedValue = value; }
}
public LookupBox()
{
InitializeComponent();
}
}
}
Now, as you can see, there is only one Combobox mentioned in the code. I need to have three Comboboxes, each bound to a different table as mentioned above.
Please, I'm banging my head against the wall. I'm not too well versed in User Controls (although I have used them in ASP.NET), but it seems like a good idea make one since I'm going to be using these three Comboboxes together quite a lot in different places in my application.
You can simply extrapolate what you know with what you need:
public object DataSource1
{
get{ return comboBox1.DataSource; }
set{ comboBox1.DataSource = value; }
}
public object DataSource2
{
get{ return comboBox2.DataSource; }
set{ comboBox2.DataSource = value; }
}
public object DataSource3
{
get{ return comboBox3.DataSource; }
set{ comboBox3.DataSource = value; }
}
Though you probably want to make better descriptive names than ..1, ..2, ..3.
I would create a UserControl that contained three of your LookupBox's. For example:
public partial class MyLookupBoxes : UserControl
{
public LookupBox()
{
// Add the 3 LookupBox to this UserControl using the designer
InitializeComponent();
SetupDataSources();
}
private void SetupDataSources()
{
namesLookupBox1.DataSource = names_data_source_1;
// ...
typesLookupBox2.DataSource = types_data_srouce_2;
// ...
productsLookupBox3.DataSource = products_data_srouce_2;
// ...
}
}

How to avoid redundant second query to database with using MVVM pattern?

How to avoid redundant second query to database with using MVVM pattern on view model:
public class DataFormViewModel : INotifyPropertyChanged
{
private int companyId
public int CompanyId
{
get { return companyId; }
set
{
companyId = value;
RaisePropentyChanged("FindingStatuses");
RaisePropentyChanged("StatusCount");
}
}
public List<FindingStatus> FindingStatuses
{
get
{
return FindingStatusService.GetAvalableStatuses(CompanyId);
}
}
public int StatusCount
{
get { return FindingStatuses.Count; }
}
}
i.e. when CompanyId was changed by DataBinder FindingStatuses will be executed and then StatusCount will be executed, that will execute FindingStatuses again.
I'm not sure I'd bind the property directly to a database operation in the first place. Why not have a local List<FindingStatus> representing the "last fetched" statuses, and then explicitly refresh it?
Apart from anything else, property access is usually expected to be reasonably cheap - making a database call every time you access either of those properties sounds like a bad idea to me.
Like Jon already mentioned, accessing properties are expected to be cheap, something you can do a thousand times without any sideeffect.
I would cache the result of your database access and return the cached object on any following request. Ie
private IList<FindingStatus> _findingStatuses;
public IList<FindingStatus> FindingStatuses
{
get
{
if (_findingStatuses == null)
{
_findingStatuses = FindingStatusService.GetAvalableStatuses(CompanyId);
}
return _findingStatuses;
}
}
And then you would of course have to clear your cache before raising the notification
public int CompanyId
{
get { return companyId; }
set
{
companyId = value;
_findingStatuses = null;
RaisePropentyChanged("FindingStatuses");
RaisePropentyChanged("StatusCount");
}
}
The best way to avoid multiple (and useless) queries to the database, is implement a simple cache layer in the Data Access Layer.
1- Ask the cache if he already has an updated result
2- Else query the database
Here is a cache class you can try: http://www.codeproject.com/KB/recipes/andregenericcache.aspx

Categories

Resources