Cannot show table in WPF from database with use of MVVM pattern - sql

The program works fine and doesn't crashing or something. But data is not showing on the table(datagrid)
Updated version:
View: Userperspective.xaml
I am getting errors in xaml file because of the binding path "Products" is unkown datacontext
<Grid Margin="0,0,0,-20">
<DataGrid Name="Producttable" ItemsSource="{Binding Path=Products}"
HorizontalAlignment="Left" Height="200" Margin="10,44,0,0"
VerticalAlignment="Top" Width="972" />
View: Userperspective.xaml.cs
public partial class Userperspective : Window
{
public Userperspective()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
}
ProductviewModel
private readonly Product _product;
private IBackend _backend;
public ICommand ProductCommand { get; set; }
public IList<Product> Products { get; set; }
public ProductViewModel()
{
_backend = new BackendService();
_product = new Product();
ProductCommand = new ProductCommand(this);
}
public Product Product()
{
return _product;
}
public void LoadProducts()
{
Products = _backend.GetProducts();
RaisePropertyChanged("Products");
}
Productcommand
private readonly ProductViewModel _vm;
public ProductCommand(ProductViewModel vm)
{
this._vm = vm;
}
public void Execute(object parameter)
{
_vm.LoadProducts();
}
BackendService
namespace _blabla
{
class BackendService : IBackend
{
public IList<Product> GetProducts()
{
using (var db = new NORTHWNDEntities())
{
var query = from p in db.Products
select new Product
{
Name = p.ProductName,
};
return query.ToList();
}
}
}
}
Ibackend
namespace _blabla.Commands
{
public interface IBackend
{
IList<Product> GetProducts();
}
}

Seeing as you are new to WPF and MVVM you should break the problem down into something a little more manageable. There is a lot going on in your code; MVVM, commands, database access and some abstraction. Your intentions are sound but it doesn't make solving this problem easy.
With the information you have given I'm not even 100% sure what the problem is but I suspect that it is either the binding or the database access. I will concentrate on demonstrating the binding aspect to you.
Seeing as I don't have access to your database code I have mocked up some classes to help me solve this problem.
Note: The command code is noise so I will remove it from my answer and concentrate on binding to a list of products (you can integrate it with your commanding solution once this is working).
Product
public class Product
{
public string Name { get; set; }
public string Description { get; set; }
public override string ToString()
{
return string.Format("Product: ({0}), {1}", Name, Description);
}
}
BackendService : This basically returns an array of products in lieu of being able to access a database.
class BackendService : IBackend
{
public IList<Product> GetProducts()
{
return new Product[]
{
new Product{ Name = "Laptop", Description = "Dell 17inch laptop" },
new Product{ Name = "Mobile Phone", Description = "iPhone" },
new Product{ Name = "Television", Description = "Samsung 32 inch plasma" },
new Product{ Name = "Car", Description = "Gran Torino" },
new Product{ Name = "Book", Description = "Effective C#" },
};
}
}
I have bound the list of products in the viewModel to a Listbox as I don't have access to the DataGrid but otherwise I have not modified the main window code.
Mainwindow.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Margin="5"
ItemsSource="{Binding Path=GetProducts}"/>
</Grid>
Mainwindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
}
Now If I use your viewModel, I get a NullReferenceException which originates from your call to _backend.GetProducts() because you have not instantiated an instance of your BackendService. If I update the constructor like so:
public ProductViewModel()
{
_backend = new BackendService();
_product = new Product();
ProductCommand = new ProductCommand(this);
}
and run the application, the list of products is displayed correctly.
You should be able to integrate the code I have supplied into your project and demonstrate that it is working. When you are happy with this, you should update the BackendService class to call the list of products from your database instead. I would recommend doing this as a matter of course for all bindings that way you know whether it is the binding that isn't working or the database call.

You are trying to execute GetProducts but that is a Property not a method - create a seperate method to load products and
Change your property name to something more meaningful
public IList<Product> Products {get;set;}
Then create a method to load your products
public void LoadProducts()
{
Products = _backend.GetProducts();
//You will need to notify of property change here
OnPropertyChanged("Products");
}
Then bind to Products in your xaml
<Window x:Class="_blabla.View.Userperspective"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UserPerspective" Height="500" Width="1000">
<Grid Margin="0,0,0,-20">
<DataGrid Name="Producttable" ItemsSource="{Binding Path=Products}"
HorizontalAlignment="Left" Height="200" Margin="10,44,0,0"
VerticalAlignment="Top" Width="972" />
</Grid>
</Window>
Then in your command call LoadProducts
public void Execute(object parameter)
{
_vm.LoadProducts();
}
You will need to implement INotifyPropertyChanged so the UI knows you have changed the Products Property

Related

EventToCommandBehavior does NOT call the Command

I'm in the process of trying to implement "Commanding" in my Xamarin Forms app, in order to follow MVVM architecture pattern.
For Buttons, it works just fine because you can just define Command in the XAML.
But for a Picker, for example, you have to do the following
(NOTE: xct:EventToCommandBehavior used the new Xamarin Community Toolkit):
<Picker x:Name="myBackgroundColorChoicePicker" SelectedItem="{Binding BGColorChoice, Mode=TwoWay}"
x:FieldModifier="public"
TextColor="{DynamicResource TextForegroundColor}"
WidthRequest="300" HorizontalOptions="CenterAndExpand">
<Picker.Items>
<x:String>User Selected</x:String>
<x:String>Totally White</x:String>
<x:String>Totally Black</x:String>
</Picker.Items>
<Picker.Behaviors>
<xct:EventToCommandBehavior
EventName="SelectedIndexChanged"
Command="{Binding ProcessBGColorChoiceCommand}"/>
</Picker.Behaviors>
</Picker>
So, I expect that when the user changes the selected item in the Picker, that the Command ProcessBGColorChoiceCommand will be called from the ViewModel.
But, the Command in the ViewModel is NOT getting called. I set a breakpoint at the beginning of the Command code, and the breakpoint is NEVER reached.
Is there something that I'm missing? I can't get this to work.
The Binding Context is set at the top of the XAML like so:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:MedLemnMobile.ViewModels"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="MedLemnMobile.Views.ColorPage"
Title="Color"
x:FieldModifier="public"
x:Name="myColorPage"
AutomationId="myColorPage"
BackgroundColor="{DynamicResource PageBackgroundColor}"
x:DataType="viewModels:ColorViewModel">
<ContentPage.BindingContext>
<viewModels:ColorViewModel/>
</ContentPage.BindingContext>
And, the Command is defined in the ViewModel like so:
using MedLemnMobile.Classes;
using MedLemnMobile.Models;
using MedLemnMobile.Views;
using System;
using System.Globalization;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.CommunityToolkit;
namespace MedLemnMobile.ViewModels
{
public class ColorViewModel : ViewModelBase
{
public ICommand GotoMenuCommand { get; }
public ICommand ProcessBGColorChoiceCommand { get; }
public ICommand ProcessFGColorChoiceCommand { get; }
public ICommand ProcessBGRedSliderCommand { get; }
public ICommand ProcessBGGreenSliderCommand { get; }
public ICommand ProcessBGBlueSliderCommand { get; }
public ICommand ProcessFGRedSliderCommand { get; }
public ICommand ProcessFGGreenSliderCommand { get; }
public ICommand ProcessFGBlueSliderCommand { get; }
public ColorViewModel()
{
IsBGColorChoiceUserSelected = EquationLibrary.MyCurrentEqSet.BackgroundColorChoice == "User Selected";
IsFGColorChoiceUserSelected = EquationLibrary.MyCurrentEqSet.ForegroundColorChoice == "User Selected";
GotoMenuCommand = new Command<ColorPage>(GotoMenu);
ProcessBGColorChoiceCommand = new Command<ColorPage>(ProcessBGColorChoice);
ProcessFGColorChoiceCommand = new Command<ColorPage>(ProcessFGColorChoice);
ProcessBGRedSliderCommand = new Command<ColorPage>(ProcessBGRedSlider);
ProcessBGGreenSliderCommand = new Command<ColorPage>(ProcessBGGreenSlider);
ProcessBGBlueSliderCommand = new Command<ColorPage>(ProcessBGBlueSlider);
ProcessFGRedSliderCommand = new Command<ColorPage>(ProcessFGRedSlider);
ProcessFGGreenSliderCommand = new Command<ColorPage>(ProcessFGGreenSlider);
ProcessFGBlueSliderCommand = new Command<ColorPage>(ProcessFGBlueSlider);
}
The command ProcessBGColorChoiceCommand was instantiated with a parameter ColorPage.
But no command parameter binding in your xaml. To fix that, change the command definition
ProcessBGColorChoiceCommand = new Command(ProcessBGColorChoice);
//also check other commands
And get selected value from property BGColorChoice in ProcessBGColorChoice method.

Accessing object's property from View

I have a Booking class inside Models folder. I already passed the Booking instance when I navigate to the class, now how do I access the Booking's property from the view?
My ViewModel:
public class BookingEditViewModel : ViewModelBase
{
public Booking Booking { get;set; }
public override void OnNavigatedTo(INavigationParameters parameters)
{
Booking = parameters.GetValue<Booking>("booking");
}
}
My View:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:booking="clr-namespace:HelloWorldPrism.Models"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="HelloWorldPrism.Views.BookingEdit">
<StackLayout>
<Label Text="{Binding Booking.Id}"/>
<Label Text="{Binding Booking.CustomerId}"/>
</StackLayout>
</ContentPage>
My Model:
public class Booking
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
I have added the Models namespace as part of the xmlns, what do I do next to access the Booking's property?
Edit:
If I just add any value to the Booking property inside the class's constructor, the Label will print out the values.
public BookingEditViewModel(BookingService bookingService, INavigationService navigationService) : base(navigationService)
{
this.bookingService = bookingService;
Booking = new Booking { Id = 999, CustomerId = 2222, };
}
So the problem seems to be that when the Booking is updated in OnNavigatedTo, the update isn't propagated back to the UI. I have changed the BindingMode to TwoWay, still not working. How do I solve this?
Okay, found the answer. Basically my issue is the same as per this question: Prism Xamarin Forms View is not updated after changing the model in OnNavigatedTo
I just need to change the property to this:
public Booking Booking
{
get => booking;
set => SetProperty(ref booking, value);
}

Add multiple bindingcontexts to xaml file

I try to realize my first MVVM-Project.
First I created model called "person.cs".
Then I created a modelview "AddPerson.cs", which should dynamically creates the data, which is stored in person.cs.
In my view (completely created with xaml) I have a button which should call a method "CreatePerson()" from my "AddPerson.cs". I like to bind the method.
Additionally I have created a label which should be bound to the class "person.cs" for example to the public string "Name".
How can I set the BindingContext of the Button to the "AddPerson.cs"-class and the BindingContext of the Label to the "person.cs"-class?
Yes this is possible.
Most of the Elements inherit BindablObject. Each BindableObjaect has a BindingContext Property.
See: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-binding-basics
MainViewModel
The Viewmodel for your entire page, that holds every sub-viewmodel.
public class MainViewModel
{
public AddPersonViewModel AddPersonViewModel { get; }
public PersonViewModel PersonViewModel { get; }
public MainViewModel()
{
// the passed action is just a fake action to simulate adding a person
AddPersonViewModel = new AddPersonViewModel(value => PersonViewModel.Name = value);
PersonViewModel = new PersonViewModel();
}
}
AddPersonViewModel
Contains your add logic.
public class AddPersonViewModel : INotifyPropertyChanged
{
public AddPersonViewModel(Action<string> onAction)
{
AddPerson = new Command(() =>
{
onAction(NewName); // call your update logic
NewName = ""; // reset name
});
}
public Command AddPerson { get; }
private string _name;
public string NewName
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewName)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
PersonViewModel
Contains your "new" Person.
public class PersonViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage
Create and set your MainViewModel.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
MainPage.xaml
Here we bind the BindingContext of Entry and Button to the AddPersonViewModel property of our ContentPage's BindingContext which is the MainViewModel. And then we bind the Text of the Label and the Command of the Button to NewName and AddPerson properties of the local BindingContext, which is AddPersonViewModel
Same for Label.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App5"
x:Class="App5.MainPage">
<StackLayout>
<Entry BindingContext="{Binding AddPersonViewModel}" Text="{Binding NewName}"
HorizontalOptions="FillAndExpand" />
<Button BindingContext="{Binding AddPersonViewModel}" Text="Click me!" Command="{Binding AddPerson}"
HorizontalOptions="Center" />
<Label Text="Added Person:" FontAttributes="Bold"
HorizontalOptions="Center"/>
<Label BindingContext="{Binding PersonViewModel}" Text="{Binding Name}"
HorizontalOptions="Center"/>
</StackLayout>
</ContentPage>
The example is very hacky, but I think you get the point. The key is the already mentioned property BindingContext
You are missing some essential concepts which result in your requests being strange.
You don't data bind to the class definition, but to the instance of the class. As one ViewModel is a class it may contain instances of other classes that you data bind to, and everything except that is in 99% of cases a wrong thing to do and your example is not one of those 1% of cases.
So basically your ViewModel should be something like:
public class PersonViewModel
{
public Person Person {get; set}
public ICommand AddPersonCommand {get; set}
}
Your BindingContext is then an instance of PersonViewModel and then on Label you bind to Person while on the button you would bind to AddPersonCommand.

Have complex object update in view when property changed - property changed event not fired - WinRT/XAML

I have a WinRT app with a number of Users, Projects, Meetings, etc.
I have a main screen, with a main screen view model, which should display CurrentUser and has a ListView bound to CurrentUser.ProjectList.
I initialise CurrentUser in the ViewModel using a UserProvider class that gets all the required information from the database.
My problem then becomes very similar to this: Subscribe to INotifyPropertyChanged for nested (child) objects
I have a user and project model:
public class User
{
public int id { get; set; }
public string ForeName { get; set; }
public string Surname { get; set; }
... etc ...
public ObservableCollection<Project> ProjectList { get; set; }
public ObservableCollection<User> FriendList { get; set; }
... constructor
}
public class Project
{
public String Name { get; set; }
public int Id { get; set; }
public List<User> Users { get; set; }
public List<Meeting> Meetings { get; set; }
.. constructor ...
}
A view model with the following:
class HomeScreenViewModel : INotifyPropertyChanged {
private User _currentUser;
public User CurrentUser
{
get { return this._currentUser; }
set
{
if (Equals(_currentUser, value)) return;
this._currentUser = value;
RaisePropertyChanged("CurrentUser");
}
}
//[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
... I have a method in this view model that gets the current user
public async Task<bool> GetLoggedInUserAsync()
{
int testId = 0;
CurrentUser = await userProvider.GetCurrentUser(testId);
UserProjects = await userProvider.GetUsersProject(CurrentUser);
CurrentUser.ProjectList = UserProjects;
return true;
}
That is called in the view's loadState
public MainPage()
{
this.InitializeComponent();
addMeeting = new AddMeetingFlyout();
_vm = new HomeScreenViewModel();
this.DataContext = _vm;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
await _vm.GetLoggedInUserAsync()
}
And my bindings in the XAML, for ProjectList and ForeName, for example, are as follows:
<CollectionViewSource
x:Name="projectsViewSource"
Source="{Binding CurrentUser.ProjectList}"/>
...
<ListView
x:Name="projectList"
ItemsSource="{Binding Source={StaticResource projectsViewSource}}"
Grid.Row="1"
SelectionMode="None"
Style="{StaticResource DraggableListView}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
IsItemClickEnabled="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource ProjectTileButton}" Content="{Binding Name}" Click="ProjectItem_Click" />
</DataTemplate>
</ListView.ItemTemplate>
<AddDeleteThemeTransition/>
</ListView>
...
<Button ...>
<TextBlock ...">
<Run Text="{Binding CurrentUser.ForeName}" />
</TextBlock>
</Button>
The button content, CurrentUser.ForeName fires an INotifyPropertyChanged event when CurrentUser is first initialised in the viewmodel. This is reflected in the view - but any further changes to CurrentUser.ForeName do not fire any subsequent INotifyPropertyChanged events. The ProjectList is also not displayed in the view and does not fire an INotifyPropertyChanged event even though I know it is there.
I have spent many days looking at implementing INotifyPropertyChanged so that changes to nested child complex objects (such as CurrentUser.ProjectList) will propagate up to the view. At the minute, the only way this happens is if I force a call to
this._currentUser = value;
RaisePropertyChanged("CurrentUser");
which I am testing with a button that calls a method called MakeChange() in the viewmodel
public void MakeChange()
{
User updatedCurrentUser = CurrentUser;
CurrentUser = updatedCurrentUser;
}
This works, so I know for a fact all the data is coming correctly from the database and all is as it should be - one less thing to worry about!
However, I simply cannot get the view to display user projects on page load, or when new projects are added.
I tried implementing this solution: https://gist.github.com/thojaw/705450, however, the WinRT reflection capabilites have changed and I am not sure how to get the following liens to work within the context of my project, as this is beyond me:
//from property
//in _type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
//where _inotifyType.IsAssignableFrom(property.PropertyType)
//select property;
Any help would be greatly appreciated - I honestly thought all I had to do was bind CurrentUser.ProjectList to a ListView.
As you are replacing the entire ObservableCollection itself, then you will also need to introduce another property changed event and backing field for the collection property.
There is a good example of this here

How to have DesignTime data in WinRT XAML?

How can I get DesignTime data in WinRT XAML so the designer shows sample data?
Simple enough.
Create a Model like this:
public class Fruit
{
public string Name { get; set; }
}
Create a base ViewModel like this:
public class BaseViewModel
{
public ObservableCollection<Fruit> Fruits { get; set; }
}
Create a real ViewModel like this:
public class RealViewModel : BaseViewModel
{
public RealViewModel()
{
if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
LoadData();
}
public void LoadData()
{
// TODO: load from service
}
}
Create a fake-data ViewModel like this:
public class FakeViewModel : BaseViewModel
{
public FakeViewModel()
{
this.Fruits = new ObservableCollection<Fruit>
{
new Fruit{ Name = "Blueberry"},
new Fruit{ Name = "Apple"},
new Fruit{ Name = "Banana"},
new Fruit{ Name = "Orange"},
new Fruit{ Name = "Strawberry"},
new Fruit{ Name = "Mango"},
new Fruit{ Name = "Kiwi"},
new Fruit{ Name = "Rasberry"},
new Fruit{ Name = "Blueberry"},
};
}
}
Do this in your XAML:
<Page.DataContext>
<local:RealViewModel />
</Page.DataContext>
<d:Page.DataContext>
<local:FakeViewModel />
</d:Page.DataContext>
Have fun!
PS: you can also attempt to use d:DesignData.
That approach also works. I feel it is not as straight forward.
In the end, it's up to you how to do it.
Either way, don't miss out on DeisgnTime data!
Here is the d:DesignInstance sample:
I will also use Jerry's Fruit class, but I won't use MVVM here as you don't need that to make it works.
Basically, we need to create the data model class (e.g., ViewModel or Model) that we want to have design data (e.g., in this case, I create a child class, but you don't need to).
public class Fruit
{
public string Name { get; set; }
}
public class SampleFruit : Fruit
{
public SampleFruit()
{
Name = "Orange (Sample)";
}
}
Then in our XAML, we can use d:DataContext to bind the child class.
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"
DataContext="{Binding}"
d:DataContext="{Binding Source={d:DesignInstance Type=local:SampleFruit, IsDesignTimeCreatable=True}}">
<TextBlock Text="{Binding Name}"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="42"/>
</Grid>
Please note this line:
d:DataContext="{Binding Source={d:DesignInstance Type=local:SampleFruit, IsDesignTimeCreatable=True}}"
Now you should see your design time data on both Visual Studio Designer and Blend.
P.S. In Blend 2013, there is a data tab that let you create sample data as well.