Xamarin View not Binding from viewModel after Constructor - mvvm

I have a simple View that displays a label with a Question that is being bound from my ViewModel. now if I set the property in my constructor I see the Label displaying whatever I set it to. if I populated from my command function I do not see the label changed. The funny thing is that if I set the Title property (a simple string that has a get and set), then that changes no matter where I set it. but for some reason this particular property does not want to show the changes to it. I have tried simplifying this as much as I can. I tried to define a public string property in my ViewModel and again if I set it in the Constructor than it binds other wise if it is being set in my Command Function then it does not change.
here is my XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Pre.MyPage"
Title="{Binding Title}"
Icon="about.png">
<StackLayout VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="{Binding MyClassObj.Question, Mode=TwoWay}"/>
</StackLayout>
</ContentPage>
Here is my Code behind
public partial class MyPage : ContentPage
{
MyViewModel vm;
MyViewModel ViewModel => vm ?? (vm = BindingContext as MyViewModel);
public MyPage()
{
InitializeComponent();
BindingContext = new MyViewModel(Navigation);
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadQuestionCommand.Execute("1");
}
}
Here is my ViewModel
public class MyViewModel : ViewModelBase
{
public MyClass MyClassObj {get;set;}
ICommand loadQuestionCommand;
public ICommand LoadQuestionCommand =>
loadQuestionCommand ?? (loadQuestionCommand = new Command<string>(async (f) => await LoadQuestion(f)));
public MyViewModel(INavigation navigation) : base(navigation)
{
Title = "My Title";
}
async Task<bool> LoadQuestion(string id)
{
if (IsBusy)
return false;
try
{
IsBusy = true;
MyClassObj = await StoreManager.QuestionStore.GetQuestionById(id);
//MyClassObject is populated when I break here
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
return true;
}

I don't see where you are firing the INofityPropertyChanged event for your MyClassObj property.
Instead of just:
public MyClass MyClassObj {get;set;}
you should have something like:
MyClass myClassObj;
public MyClass MyClassObj
{
get {return myClassObj;}
set
{
//if they are the same you should not fire the event.
//but since it's a custom object you will need to override the Equals
// of course you could remove this validation.
if(myClassObj.Equals(value))
return;
myClassObj = value;
//This method or something has to be in your VieModelBase, similar.
NotifyPropertyChanged(nameof(MyClassObj));
}
}
Where the last method
NotifyPropertyChanged(nameof(MyClassObj));
is who notifies the View about the changes.

Related

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

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

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

UWP Binding to AutoSuggestBox in MVVM

i am invoking the QuerySubmitted command of the AutoSuggestBox control in UWP.
the command binds to ICommand in the view model.
the problem is it requires to accept AutoSuggestBoxQuerySubmittedEventArgs which is pure UI and it's not acceptable in MVVM.
my code looks like that:
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find"
>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="QuerySubmitted">
<core:InvokeCommandAction Command="{x:Bind ViewModel.SearchCommand}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AutoSuggestBox>
and my view model looks like that:
public DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs> SearchCommand { get; }
public MainPageViewModel()
{
SearchCommand = new DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs>(ExecuteMethod);
}
private void ExecuteMethod(AutoSuggestBoxQuerySubmittedEventArgs o)
{
// CODE HERE
}
ofcours AutoSuggestBoxQuerySubmittedEventArgs is not acceptable in the view model.
looking for alternatives...
same goes to SuggestionChosen...
InvokeCommandAction has a parameter named InputConverter which you can use to convert the event args to some other parameter that can be passed to your ViewModel.
First create a IValueConverter class to extract what you need from your event args like this:-
public class AutoSuggestQueryParameterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// cast value to whatever EventArgs class you are expecting here
var args = (AutoSuggestBoxQuerySubmittedEventArgs)value;
// return what you need from the args
return (string)args.ChosenSuggestion;
}
}
Then use that converter in your XAML like this:
<Page.Resources>
<converters:AutoSuggestQueryParameterConverter x:Key="ArgsConverter" />
</Page.Resources>
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="QuerySubmitted">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.SearchCommand}"
InputConverter="{StaticResource ArgsConverter}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</AutoSuggestBox>
Finally in your viewmodel change your command to accept a string as parameter. So you would have the following in your vm:
public DelegateCommand<string> SearchCommand { get; }
public MainPageViewModel()
{
SearchCommand = new DelegateCommand<string>(ExecuteMethod);
}
private void ExecuteMethod(string o)
{
// CODE HERE
}
You can bind the search string (Text property) to a view model property and the events to parameter-less methods. This way the view model wont have to deal with UI objects:
XAML:
<AutoSuggestBox Header="What's your name?"
TextChanged="{x:Bind ViewModel.FilterUsuals}"
QuerySubmitted="{x:Bind ViewModel.ProcessQuery}"
SuggestionChosen="{x:Bind ViewModel.ProcessChoice}"
ItemsSource="{x:Bind ViewModel.Usuals, Mode=OneWay}"
Text="{x:Bind ViewModel.SearchText, Mode=TwoWay}"
QueryIcon="Find" />
Code behind:
public class MainPageViewModel : SomeViewModelBaseClass
{
/* Boilerplate code and constructor not included */
private string _SearchText;
public string SearchText {/* getter and setter INotyfyPropertyChange compliant */ }
private List<string> _Usuals; // Initialized on constructor
public string Usuals {/* getter and setter INotyfyPropertyChange compliant */ }
public void FilterUsuals()
{
// the search string is in SearchText Example:
Usuals = _UsualsStore.Where(u => u.Contains(_SearchText.ToLower())).ToList();
}
public void ProcessQuery() { /* TODO - search string is in SearchText */ }
public void ProcessChoice() { /* TODO - search string is in SearchText */ }
}
If you don't mind doing non pure MVVM way.
MainPage.xaml :
<AutoSuggestBox Name="SearchAutoSuggestBox"
PlaceholderText="Search by keywords"
QueryIcon="Find" QuerySubmitted="{x:Bind ViewModel.SearchQuerySubmitted}" IsEnabled="{x:Bind ViewModel.CanExecuteSearchCommand, Mode=TwoWay}"
>
</AutoSuggestBox>
MainPageViewModel.cs :
public class MainPageViewModel : INotifyPropertyChanged
{
private bool _canExecuteSearchCommand;
public MainPageViewModel()
{
this.CanExecuteSearchCommand = true;
}
public bool CanExecuteSearchCommand
{
get { return _canExecuteSearchCommand; }
set
{
bool changed = _canExecuteSearchCommand != value;
_canExecuteSearchCommand = value;
if(changed)
this.OnPropertyChanged();
}
}
public void SearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
// Just example disabling SearchBox
this.CanExecuteSearchCommand = false;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.cs :
MainPageViewModel ViewModel = new MainPageViewModel();
UWP Binding Command/Delegate to AutoSuggestBox in MVVM
For UWP Mobile Application
Make a DelegateCommand class
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> executeAction;
Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> executeAction)
: this(executeAction, null)
{
//var a = ((Page)(((Func<object, bool>)(executeAction.Target)).Target)).Name;
//((ViewModel.VMBranchSelection)(executeAction.Target)).;
}
public DelegateCommand(Action<T> executeAction, Func<object, bool> canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public void Execute(object parameter)
{
executeAction((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
In View Model
public ICommand SuggessionSelectCity_QuerySubmitted
{
get { return new DelegateCommand<AutoSuggestBoxQuerySubmittedEventArgs>(this.SuggessionSelectCityQuerySubmitted); }
}
private void SuggessionSelectCityQuerySubmitted(AutoSuggestBoxQuerySubmittedEventArgs obj)
{
if (obj.ChosenSuggestion != null)
{
AutosuggestionTextBoxName.Text = ((ModelName) (obj.ChosenSuggestion)).Model's Property name;
//or
AutosuggestionTextBoxName.Text =(obj.ChosenSuggestion).property name
}
else
{
}
}
In XAML Code
<AutoSuggestBox Grid.Column="1" x:Name="SuggessionSelectCity"
PlaceholderText="Search by keywords" QueryIcon="Find"
ItemsSource="{Binding PApplicantCityList}"
HorizontalAlignment="Center" VerticalAlignment="Center" DisplayMemberPath="Description" Width="250" Height="45">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="TextChanged">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCityTextChange}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="QuerySubmitted">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCity_QuerySubmitted}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="SuggestionChosen">
<Core:EventTriggerBehavior.Actions>
<Core:InvokeCommandAction Command="{Binding SuggessionSelectCitySuggestionChosen}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</AutoSuggestBox>
</Grid>
Create a list in View Model for Autosuggestion TextBox Itemssource
private ObservableCollection<ResultMasterModel> ApplicantCityList;
public ObservableCollection<ResultMasterModel> PApplicantCityList
{
get { return ApplicantCityList; }
set { this.SetProperty(ref this.ApplicantCityList, value); }
}
add some hard code value in above list
Create a Model In Model Folder
public class ResultMasterModel
{
public string Code { get; set; }
public string Description { get; set; }
}

View values are not inserted but retrieved from ViewModel

I am trying to create an application on the basis of the WAF framework following the MVVM pattern. Currently, my solution consists of two projects (each equipped with MEF and MAF references):
*.Application (holding controllers and viewmodels)
*.Presentation (holding the actual view files)
I am creating the binding between view and viewmodel via the ViewModel interface - see code fragments below. Further, all classes are made available via the MEF framework inside the App.xaml.cs file. Here, the controller is also initialized. In the easiest case, I want to show a string value in a label of the main window.
Here is the problem: If I start the application, the value of the second label only shows the fallback value, but the get method of the property is being called properly (checked via debugging mode). The binding between View and ViewModel seems to be correct - if I change the binding path in the xaml to a non existent property, I get an output that the property can not be found in the ViewModel. My impression is that there could be a problem with the events for view updating? Any suggestions on this strange behaviour?
Here is the expert of the ViewModel:
[Export]
public class MainWindowViewModel : ViewModel<IMainWindowView>
{
private string _labelContent;
public string LabelContent
{
get { return _labelContent; }
set { SetProperty(ref _labelContent, value); }
}
[ImportingConstructor]
public MainWindowViewModel(IMainWindowView view) : base(view)
{
}
}
Here is the exerpt of the controller:
[Export(typeof(IMainWindowController))]
public class MainWindowController : IMainWindowController
{
private MainWindowViewModel _mainWindowViewModel;
public MainWindowViewModel MainWindowViewModel
{
get { return _mainWindowViewModel; }
set { _mainWindowViewModel = value; }
}
[ImportingConstructor]
public MainWindowController(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public void Initialize()
{
_mainWindowViewModel.LabelContent = "stfu";
}
}
The view interface:
public interface IMainWindowView : IView
{
}
And the view itself:
[Export(typeof(IMainWindowView))]
public partial class MainWindow : Window, IMainWindowView
{
public MainWindow()
{
InitializeComponent();
}
}
<Window x:Class="MyCompany.Product.Redesign.Presentation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Test" />
<Label Name="MyLabel" Content="{Binding Path=LabelContent, FallbackValue=Fallback}" />
</StackPanel>
</Window>
Are you sure, that the view that is displayed really is the instance with the ViewModel-Instance you are setting the property on?
First, make sure that you don't have a view set as the Application's StartupUri-Property in the App.xaml. Then make sure, that you call View.Show() through your ViewModel. You are then certain that you really set the property on the instance that is being displayed:
App.xaml
<Application <!-- note: no StartupUri Property -->
x:Name="App" x:Class="YourProject.Presentation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShutdownMode="OnMainWindowClose">
</Application>
MainViewController.cs (with method declaration in IMainViewController.cs)
public void Run()
{
_mainWindowViewModel.Show();
}
App.xaml.cs
_controller = mainExportProvider.GetExportedValue<IMainViewController>();
_controller.Initialize();
_controller.Run();
MainViewModel.cs (with method declaration in IMainViewModel.cs)
public void Show()
{
ViewCore.Show();
}
This should do the trick. Otherwise, you might be seeing a view instance that you don't have a reference to. Thus you are setting a property on a ViewModel whos view isn't being displayed.

BindingMode.TwoWay does not work with UserControl (not update source property)

I have created Custom User Control which contain TextBox and PasswordBox. it is binding completely work but when i changed any value inside TextBox or PasswordBox of user control then my source property does not getting refreshed.
Following are the code for my Custom User Control
RestrictedBox.xaml
<UserControl.Resources>
<Converters:EnumToVisibilityConverter x:Key="enumToVisibilityConverter" />
<Converters:EnumToVisibilityConverterReverse x:Key="enumToVisibilityConverterReverse" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Transparent" >
<StackPanel>
<TextBox x:Name="txtTextBox" Width="50" Height="25" />
<PasswordBox x:Name="txtPasswordBox" Width="50" Height="25" />
</StackPanel>
</Grid>
RestrictedBox.xaml.cs
public partial class RestrictedBox : UserControl
{
public RestrictedBox()
{
InitializeComponent();
txtTextBox.SetBinding(TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay });
txtTextBox.SetBinding(TextBox.VisibilityProperty, new Binding("Type")
{
Source = this,
Converter = new EnumToVisibilityConverter()
});
txtPasswordBox.SetBinding(PasswordBox.PasswordProperty, new Binding { Source = this, Path = new PropertyPath("Value"), Mode = BindingMode.TwoWay });
txtPasswordBox.SetBinding(TextBox.VisibilityProperty, new Binding("Type")
{
Source = this,
Converter = new EnumToVisibilityConverterReverse()
});
}
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(RestrictedBox), new PropertyMetadata("", ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public Mode Type
{
get { return (Mode)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(Mode), typeof(RestrictedBox), new PropertyMetadata(Mode.Text, TypeChanged));
private static void TypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Following are the code for LoginView where i have used my custom User Control (RestrictedBox).
LoginView.xaml
<control:RestrictedBox Type="Text" Value="{Binding Path=UserName}" />
LoginView.xaml.cs
[Export(typeof(LoginView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class LoginView : UserControl, IPageTitle
{
#region Constuctors
public LoginView()
{
InitializeComponent();
}
[Import]
public LoginViewModel ViewModel
{
get
{
return this.DataContext as LoginViewModel;
}
set
{
DataContext = value;
}
}
#endregion
}
LoginViewModel.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class LoginViewModel : INotifyPropertyChanged, IRegionMemberLifetime
{
private string _UserName = "";
public string UserName
{
get { return _UserName; }
set
{
_UserName = value;
OnPropertyChanged("UserName");
}
}
[ImportingConstructor]
public LoginViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
}
}
Please help me to resolved this because i am trying to resolve since last 1.5 days without any luck.
Your comments and suggestions would be highly appreciated.
Note:- I am able to bind value of the UserName to TextBox but i update TextBox and click on submit i couldn't getting updated value from TextBox.
Thanks,
Imdadhusen
You are missing Mode=TwoWay in you LoginView.xaml:
<control:RestrictedBox Type="Text" Value="{Binding Path=UserName,Mode=TwoWay}" />