Xamarin Forms MVVM Databinding failing when I'm binding to a single object - mvvm

I'm having an issue with data not binding correctly on a details page when I have clicked through from a ListView via a button. The ListView binds perfectly and the object gets passed through to the details page. The Id of the object is read and a full version of the object is called from an API and set to a new instance of the object. When I add a breakpoint, the full object is available, but Labels on the view aren't populated. Here is the ViewModel:
DetailsViewModel.cs
public class DetailsViewModel
{
public Deal Deal { get; set; }
public int DealId { get; set; }
public DetailsViewModel(int id)
{
Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if(deal != null)
{
Deal = deal;
}
}
}
The codebehind looks like this:
DetailPage.Xaml.cs
DetailsViewModel viewModel;
int dealId;
public DetailPage(int id)
{
InitializeComponent();
dealId = id;
viewModel = new DetailsViewModel(dealId);
BindingContext = viewModel;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.GetDeal();
}
And the Xaml file is
DetailPage.Xaml
<ContentPage.Content>
<ScrollView>
<StackLayout x:Name="detailsLayout">
<Label Text="{Binding Deal.Name}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
When I put a breakpoint in Deal = deal on DetailsViewModel, the Deal object exists and has the correct data, but I just get a blank screen. I have tried Labels with Text="{Binding Name}" and Text="{Binding Deal.Name}".
I have also tried manually creating a deal in the GetDeal function of the ViewModel and still nothing is bound.

1) Ensure your property Notifies the UI of a change implementing the INotifyPropertyChanged interface. See https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm
2) Ensure the set is done on the UI thread using Device.BeginInvokeOnMainThread. https://learn.microsoft.com/fr-fr/dotnet/api/xamarin.forms.device.begininvokeonmainthread?view=xamarin-forms
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace YourNamespace
{
public class DetailsViewModel : INotifyPropertyChanged
{
private Deal _deal;
public Deal Deal
{
get => _deal;
set
{
if (_deal != value)
{
_deal = value;
OnPropertyChanged();
}
}
}
public int DealId { get; set; }
public DetailsViewModel(int id)
{
//!! useless assignation
//Deal = new Deal();
DealId = id;
}
public async void GetDeal()
{
var deal = await Deal.GetDeal(DealId);
if (deal != null)
{
//Ensure we are on UI thread
Device.BeginInvokeOnMainThread(() => Deal = deal);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Xamarin Forms View only renders when Added as a child in the codebehind

I have a custom View that I am trying to pass as List<T> to. For some reason when trying to load the page the app throws a System.ArrayTypeMismatchException.
Here is the class:
public class DiaryCalendarCustomView : View
{
MiscFunctions misctools = new MiscFunctions();
private List<DiaryNextContactEventModel> _eventList = new List<DiaryNextContactEventModel>();
public List<DiaryNextContactEventModel> EventList
{
get { return _eventList; }
set { _eventList = value; }
}
public void SetSelectedDate (DateTime selectedDate)
{
SelectedDate = selectedDate;
Settings.Current.NextContactContactDate = selectedDate.ToLocalTime();
}
public DateTime SelectedDate { get; set; }
public DiaryCalendarCustomView()
{
}
}
View Model:
private List<DiaryNextContactEventModel> _eventList = new List<DiaryNextContactEventModel>();
public List<DiaryNextContactEventModel> EventList
{
get { return _eventList; }
set { SetProperty(ref _eventList, value); }
}
When I add static data to the EventList object it works fine and when I remove the Binding from the XAML view it works as well. So the issue appears to be that xamarin is trying to convert my list into another type of enumerable and that's where it is failing.
XAML:
<Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.Children>
<partials:DiaryCalendarCustomView EventList="{Binding EventList}"/>
</Grid.Children>
</Grid>
Debugging and searching around hasn't really offered anything useful. Any help would be appreciated.
If you want to use custom property in XAML, you need to declare it in your view. Your code seems fine, just follow some tutorial like this: Creating Custom Controls with Bindable Properties in Xamarin.Forms and add the missing pieces, so the property definiton and propertyChanged method:
public static readonly BindableProperty EventListProperty = BindableProperty.Create(
propertyName: "EventList",
returnType: typeof(List<DiaryNextContactEventModel>),
declaringType: typeof(DiaryCalendarCustomView),
defaultValue: "",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: EventListPropertyChanged);
and also:
private static void EventListPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = (DiaryCalendarCustomView) bindable;
view.EventList = (List<DiaryNextContactEventModel>) newValue;
}
Also make sure that your class implements INotifyPropertyChanged interface, so when you change EventList in EventListPropertyChanged, the view will get reloaded

Create Wizard with MVVM Pattern - Databinding and GUI Update not working

I am creating a Wizard for automatic Report generating. Therefore the user enters a couple of Issues to the Mainwindow and after he finished, he can create an automatically filled report by clicking a button. Also he need to switch between the entered Issuses (Therefore I put the issues to a list of the Type "Compliant".
The MainWindow contains different controls, in which the user can enter some information.
All controls are binded to the Model like this way (example for a textbox):
Text="{Binding Path=SingleCompliant.Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
"SingleCompliant" is an object of the ViewModel of the type "Compliant" (part of the Model), which contains all attributes needed for one Issue.
When I load the Application for the first time, everything works fine.
But when I click on "SaveCompliantAndNext", the binding between "SingleCompliant" Object gets lost and the GUI is not updating. In this Method (raised by a command) I create a new SingleCompliant Object.
What do I need to do for getting a new Object with an empty gui, so the user can continue entereing the next Issue?
This is the ViewModel with implemented "PropertyChanged" Handling:
public class Compliant_ViewModel : ObservableCollection<Compliant> //INotifyPropertyChanged
{
/// <summary>
/// MainWindow Controls are binded to Attributes of this object
/// </summary>
private Compliant _SingleCompliant;
public ObservableCollection<Compliant> CompliantListReport
{
get { return _CompliantListReport; }
private set { _CompliantListReport = value; }
}
/// <summary>
/// Beandstandungs-Objekt
/// </summary>
public Compliant SingleCompliant
{
get { return _SingleCompliant; }
set
{ _SingleCompliant = value;
OnPropertyChanged("SingleCompliant");
}
}
/// <summary>
/// Load next Compliant to the GUI
/// </summary>
public NextCompliant_Command NextCompliant_command { get; private set; }
/// <summary>
/// Load previous Compliant to the GUI
/// </summary>
public PreviousCompliant_Command PreviousCompliant_Command { get; private set; }
/// <summary>
/// Constructor
/// </summary>
public Compliant_ViewModel()
{
SingleCompliant = new Compliant();
CompliantListReport = new ObservableCollection<Compliant>();
NextCompliant_command = new NextCompliant_Command(SaveCompliantAndNext);
PreviousCompliant_Command = new PreviousCompliant_Command(LoadPreviousCompliant);
CreateReport_Command = new CreateReport_Command(CreateReport);
}
public void SaveCompliantAndNext()
{
CompliantListReport.Add(SingleCompliant);
// >>>>> Not working - databinding get lost and gui not updating
SingleCompliant = new Compliant();
}
public void LoadPreviousCompliant()
{
if (this.SingleCompliant.CompliantID > 0)
{ this.SingleCompliant = this.CompliantListReport[this.SingleCompliant.CompliantID - 1]; }
else
{ this.SingleCompliant = this.CompliantListReport[0]; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{ handler(this, new PropertyChangedEventArgs(propertyname)); }
}
}
This is the Model:
public class Compliant : INotifyPropertyChanged
{
public string _Text;
public string Text
{
get { return _Text; }
private set
{
_Text = value;
OnPropertyChanged("Text");
}
}
#region Konstruktoren
public Compliant()
{ }
#region Interface
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
#endregion Interface
}
XAML:
Example for one Textbox:
<TextBox Text="{Binding Path=SingleCompliant.Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" x:Name="Text" />
XAML Buttons to switch between the Compliants (Issues):
<!--Previous Compliant (Issue)-->
<Button x:Name="BtnBack_Compliant" Content="{DynamicResource Back}" Command="{Binding PreviousCompliant_Command}"/>
<!--Next Compliant (Issue)-->
<Button x:Name="BtnForeward_Compliant" Content="{DynamicResource Foreward}" Command="{Binding NextCompliant_command}" />
What do I make wrong?

Prism proper handling of viewmodel collections

I'm using the prism framework for my Xamarin.Forms application.
This is a common scenario, but it caused me headache.
MainPage
- MainPageViewModel
- ObserveableCollection<SomePageViewModel>
public class MainPageViewModel : BaseViewModel
{
private ObservableCollection<SomePageViewModel> viewModels;
public MainPageViewModel(INavigationService navigationService) : base(navigationService)
{
SomePageSelectedCommand = DelegateCommand.FromAsyncHandler(NavigateToSomePage);
}
public ICommand SomePageSelectedCommand { get; private set; }
public ObservableCollection<SomePageViewModel> ViewModels
{
get { return viewModels; }
set { SetProperty(ref viewModels, value); }
}
private async Task NavigateToSomePage(SomePageViewModel viewModel)
{
var navParams = new NavigationParameters
{
{viewModel.typeof(SomePageViewModel).Name, viewModel}
};
await Navigation.NavigateAsync(NavigationConstants.SomePageUri, navParams, false);
}
}
public class SomePageViewModel : BaseViewModel
{
protected SomeModel someModel;
public SomePageViewModel(INavigationService navigationService) : base(navigationService)
{
someModel = new SomeModel();
EditCommand = DelegateCommand.FromAsyncHandler(Edit);
}
public ICommand EditCommand { get; private set; }
public string Name
{
get { return SomeModel.Name; }
set { SetProperty(ref SomeModel.Name, value); }
}
public string Description
{
get { return SomeModel.Description; }
set { SetProperty(ref SomeModel.Description, value); }
}
public override void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey(typeof(SomePageViewModel).Name))
{
var viewModel = (SomePageViewModel)parameters[typeof(SomePageViewModel).Name];
Name = viewModel.Name;
Description = viewModel.Name;
}
}
private async Task Edit()
{
var navParams = new NavigationParameters
{
{viewModel.typeof(SomePageViewModel).Name, this}
};
await Navigation.NavigateAsync(NavigationConstants.SomePageEditUri, navParams, false);
}
}
public class SomePageEditViewModel : BaseViewModel
{
public SomePageEditViewModel(INavigationService navigationService) : base(navigationService)
{
SaveCommand = DelegateCommand.FromAsyncHandler(Save);
}
public ICommand SaveCommand { get; private set; }
private async Task Save()
{
App.ContentService.Save(someModel);
await Navigation.GoBackAsync();
}
}
So lets navigate from the MainPage to a SomePage. We want to edit it so we navigate to SomePageEdit afterwards and save finally.
What is a proper way to make the changes visible to the SomePage and the MainPage according mvvm/prsim? For the first one I could pass the changes as NavigationParameter into GoBackAsync. But what about the MainPage?
Well it appears you have a bit of a design problem. To properly architect your app you want something closer to:
Model
public class TodoItem : ObservableObject
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private bool _done;
public bool Done
{
get { return _done; }
set { SetProperty(ref _done, value); }
}
}
Model Collection Page ViewModel
public class TodoItemListPageViewModel : BaseViewModel, INavigationAware
{
private INavigationService _navigationService { get; }
public TodoItemListViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
TodoItems = new ObservableRangeCollection<TodoItem>();
AddTodoItemCommand = new DelegateCommand(OnAddTodoItemCommandExecuted);
EditTodoItemCommand = new DelegateCommand<TodoItem>(OnEditTodoItemCommandExecuted);
}
public ObservableRangeCollection<TodoItem> TodoItems { get; }
public DelegateCommand AddTodoItemCommand { get; }
public DelegateCommand<TodoItem> EditTodoItemCommand { get; }
public void OnNavigatingTo(NavigationParameters parameters)
{
// Initialize your collection
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if(parameters.GetValue<NavigationMode>(KnownNavigationParameters.NavigationMode) == NavigationMode.Back)
{
// Option 1
// Fetch an updated list of TodoItems from your data source
TodoItems.ReplaceRange(updatedTodoItems);
// Option 2
// Replace the updated item or add a new item
}
}
Edit Model Page ViewModel
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
private async void OnAddTodoItemCommandExecuted() =>
await _navigationService.NavigateAsync("AddTodoItemPage");
private async void OnEditTodoItemCommandExecuted(TodoItem item) =>
await _navigationService.NavigateAsync("EditTodoItemPage", new NavigationParameters { { "item", item } });
}
public class EditTodoItemPageViewModel : BaseViewModel
{
private INavigationService _navigationService { get; }
public EditTodoItemPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
SaveCommand = new DelegateCommand(OnSaveCommandExecuted, () => IsNotBusy)
.ObservesProperty(() => IsBusy);
}
private TodoItem _model;
public TodoItem Model
{
get { return _model; }
set { SetProperty(ref _model, value); }
}
public DelegateCommand SaveCommand { get; }
public void OnNavigatingTo(NavigationParameters parameters)
{
Model = parameters.GetValue<TodoItem>("item");
}
private async void OnSaveCommandExecuted()
{
IsBusy = true;
// Persist any changes
// Option 1
await _navigationService.GoBackAsync();
// Option 2
await _navigationService.GoBackAsync(new NavigationParameters { { "updatedItem", Model } });
IsBusy = false;
}
}
The Why...
Your ObservableCollection should be where T : TModel not where T : TViewModel. Another issue you would have immediately is that the INavigationService is dependent on knowing what Page you're navigating to/from. So you cannot follow the pattern you're doing there.
Now a couple of notes here.
You'll notice this sample is actually using some helpers from the MvvmHelpers library. The BaseViewModel class from that library gives you the IsBusy/IsNotBusy property as well as a Title property and the ObservableRangeCollection.
ObservableRangeCollection vs ObservableCollection
The ObservableRangeCollection gives you a little better performance particularly when working with larger datasets. You may have noticed the Option 1 where we simply get the updated dataset and replace the entire dataset. This is where the ObservableRangeCollection really shines in my opinion since you're able to ensure you have an up to date dataset while minimizing the notifications to the UI resulting in fewer CPU cycles taken up.
Models, Views, ViewModels
I do not mean for this to an authoritative answer but to at least provide food for thought. From a high level overview of MVVM patterns you generally are working with a View which provides the UX, a ViewModel which provides the business logic for who/what/why/when/where/etc, and a Model which is the data we want to work with. In some cases it can become necessary to introduce a DTO which further abstracts our raw data from the Model we want to work with as a logical unit.

Pass CommandParameter to Command in Silverlight using MVVM

I'm just learning Silverlight and looking at MVVM and Commanding.
Ok, so I have seen the basic RelayCommand implementation:
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
How can I pass a parameter down with a Command using this?
I've seen that you can pass a CommandParameter like this:
<Button Command="{Binding SomeCommand}" CommandParameter="{Binding SomeCommandParameter}" ... />
In my ViewModel, I need to create the Command, but RelayCommand is expecting an Action delegate. Can I implement RelayCommand<T> using Action<T> - if so, how do I do it and how to I use it?
Can anyone give me any practical examples on CommandParameters with MVVM that don't involve using 3rd-party libraries (e.g. MVVM Light) as I want to understand it fully before using existing libraries.
Thanks.
public class Command : ICommand
{
public event EventHandler CanExecuteChanged;
Predicate<Object> _canExecute = null;
Action<Object> _executeAction = null;
public Command(Predicate<Object> canExecute, Action<object> executeAction)
{
_canExecute = canExecute;
_executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
if (_canExecute != null)
return _canExecute(parameter);
return true;
}
public void UpdateCanExecuteState()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
public void Execute(object parameter)
{
if (_executeAction != null)
_executeAction(parameter);
UpdateCanExecuteState();
}
}
Is the Base Class for Commands
And this is your Command Property in ViewModel:
private ICommand yourCommand; ....
public ICommand YourCommand
{
get
{
if (yourCommand == null)
{
yourCommand = new Command( //class above
p => true, // predicate to check "CanExecute" e.g. my_var != null
p => yourCommandFunction(param1, param2));
}
return yourCommand;
}
}
in XAML set Binding to Command Property like:
<Button Command="{Binding Path=YourCommand}" .../>
Maybe this article explains what you're looking for. I also had the same problem just minutes ago.
http://www.eggheadcafe.com/sample-code/SilverlightWPFandXAML/76e6b583-edb1-4e23-95f6-7ad8510c0f88/pass-command-parameter-to-relaycommand.aspx

Getting MVVM ViewModel to bind to the View

I have the following code (changed object names, so syntax/spelling errors ignore).
public class ViewModel
{
ViewModelSource m_vSource;
public ViewModel(IViewModelSource source)
{
m_vSource= source;
m_vSource.ItemArrived += new Action<Item>(m_vSource_ItemArrived);
}
void m_vSource_ItemArrived(Item obj)
{
Title = obj.Title;
Subitems = obj.items;
Description = obj.Description;
}
public void GetFeed(string serviceUrl)
{
m_vFeedSource.GetFeed(serviceUrl);
}
public string Title { get; set; }
public IEnumerable<Subitems> Subitems { get; set; }
public string Description { get; set; }
}
Here is the code I have in my page's codebehind.
ViewModel m_vViewModel;
public MainPage()
{
InitializeComponent();
m_vViewModel = new ViewModel(new ViewModelSource());
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
this.DataContext = m_vViewModel;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
m_vViewModel.GetItems("http://www.myserviceurl.com");
}
Finally, here is a sample of what my xaml looks like.
<!--TitleGrid is the name of the application and page title-->
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock Text="My Super Title" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
<TextBlock Text="{Binding Path=Title}" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
</Grid>
Is there anything I'm doing wrong here?
I think your ViewModel should implement the INotifyPropertyChanged interface:
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then your property would look like that:
private title;
public string Title
{
get
{
return this.title;
}
set
{
if (this.title!= value)
{
this.title= value;
this.RaisePropertyChanged("Title");
}
}
}
Michael
Well, go figure, 10 mins after I post it, I figure it out.
I was missing the INotifyProperty implementation. Thanks if anyone is looking at this.