Add multiple bindingcontexts to xaml file - xaml

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.

Related

How to bind viewModel property in a ResourceDictionary

I have the static resource named ArticoliDTSelector defined in the ResourceDictionary and I need to bind the property MostraGriglia to a value which is in the viewModel.
But, when I try to run, I get following error: "No property, BindableProperty, or event found for "MostraGriglia", or mismatching type between value and property."
Why can't the compiler find the property CatalogoVisualizzato?
I try to bind like so:
<base:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:base="clr-namespace:Vendo.Views"
xmlns:model="clr-namespace:Vendo.Models"
xmlns:viewmodels="clr-namespace:Vendo.ViewModels"
x:DataType="viewmodels:DettaglioArticoliViewModel"
xmlns:selector="clr-namespace:Vendo.DataTemplatesSelector" >
<base:BasePage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="GridLinearList">
<!--foo-->
</DataTemplate>
<DataTemplate x:Key="GridImages">
<!--foo-->
</DataTemplate>
<selector:ArticoliDTSelector x:Key="ArticoliTemplateSelector"
MostraGriglia="{Binding CatalogoVisualizzato, Source={StaticResource viewmodels:DettaglioArticoliViewModel}}"
ElencoTemplate="{StaticResource GridLinearList}"
GrigliaTemplate="{StaticResource GridImages}"/>
</ResourceDictionary>
</base:BasePage.Resources>
</base:BasePage>
DataTemplateSelector class implementation:
internal class ArticoliDTSelector : DataTemplateSelector
{
public DataTemplate ElencoTemplate { get; set; }
public DataTemplate GrigliaTemplate { get; set; }
public bool MostraGriglia { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (MostraGriglia)
return GrigliaTemplate;
else
return ElencoTemplate;
}
}
ViewModel implementation:
class DettaglioArticoliViewModel : BaseViewModel
{
static bool catalogoVisualizzato;
public bool CatalogoVisualizzato
{
get => catalogoVisualizzato;
set => SetProperty(ref catalogoVisualizzato, value);
}
}

Stacklayout backgroundColor binding with MVVM

I'm attempting to get my head around MVVM with XamarinForms and I'm slightly confused with regards to proper partitioning of functionality:
I have a main page, MainPage.xaml, which includes a stacklayout:
<StackLayout x:Name="MainPageStackLayout">
...
</StackLayout>
Within this stacklayout I have Picker which is bound as follows:
<Picker Title="Select a background colour"
TitleColor="Black"
TextColor="Black"
ItemsSource="{Binding MyColours}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding selectedBackGroundColour}" SelectedIndexChanged="BackGroundColourPicker_SelectedIndexChanged"/>
Following the article from microsoft (https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/userinterface-monkeyapppicker/):
I have a "View" which basically defines the layout of my page.
A "ViewModel" which holds an IList "MyColours" and a variable "SelectedBackGroundColour".
A "Model" which defines the MyColour class. A MyColour has a string name and a Xamarin.Forms.Color (from a hex value, both populated on start up).
This all works fine. I can start up the app and the Picker populates with the colours I add to "MyColours". If I change the index then my SelectedBackGroundColour also updates, has the correct name and a different RGB value.
However, I'm lost as to where I would tie in the updating of the actual background colour of the MainPageStackLayout. The View (MainPage.xaml.cs) picks up the "BackGroundColourPicker_SelectedIndexChanged" event but what is the standard practice for reading from the view model (where SelectedBackGround colour is actual defined ?)
I have a feeling I can bind Background colour in the MainPageStackLayout xaml view so I wont have to catch the selected index change event.
Thanks all.
According to your description, I guess that you want to change MainPage StackLayout BackGround color by Picker value, am I right?
If yes, please follow the steps below.
Firstly, please confirm that you implement INotifyPropertyChanged interface to notify SelectedBackGroundColour changed.
Then there are full code, please take a look:
<StackLayout x:Name="MainPageStacklayout" BackgroundColor="{Binding selectedBackGroundColour.color}">
<Picker
x:Name="picker1"
Title="Select a background colour"
ItemDisplayBinding="{Binding name}"
ItemsSource="{Binding MyColours}"
SelectedItem="{Binding selectedBackGroundColour}"
TextColor="Black"
TitleColor="Black" />
</StackLayout>
public partial class Page5 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<MyColour> MyColours { get; set; }
private MyColour _selectedBackGroundColour;
public MyColour selectedBackGroundColour
{
get { return _selectedBackGroundColour; }
set
{
_selectedBackGroundColour = value;
RaisePropertyChanged("selectedBackGroundColour");
}
}
public Page5()
{
InitializeComponent();
MyColours = new ObservableCollection<MyColour>()
{
new MyColour(){name="red",color=Color.Red},
new MyColour(){name="gray",color=Color.Gray},
new MyColour(){name="BlueViolet",color=Color.BlueViolet}
};
selectedBackGroundColour = MyColours[0];
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyColour
{
public string name { get; set; }
public Color color { get; set; }
}
The screenshot:

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);
}

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

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

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