Xamarin Forms watch ViewModel property from .xaml.cs class - c#

As the title suggests, on Xamarin Forms, I am trying to watch from a View when a property on the ViewModel changes.
This is my ViewModel class
public class RegisterViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool AutomaticVerificationDone { get; set; }
public ICommand AutomaticVerification
{
get
{
return new Command(async () =>
{
AutomaticVerificationDone = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AutomaticVerificationDone"));
});
}
}
}
This is my Register.xaml.cs class
public partial class Register : ContentPage
{
public static readonly BindableProperty AutomaticVerificationDoneProperty = BindableProperty.Create(nameof(AutomaticVerificationDone), typeof(bool), typeof(Register), false);
public bool AutomaticVerificationDone
{
get { return (bool)GetValue(AutomaticVerificationDoneProperty); }
set
{
SetValue(AutomaticVerificationDoneProperty, value);
if (value)
accessButton.Opacity = 1;
else
accessButton.Opacity = 0.8f;
}
}
public Register()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
this.BindingContext = new RegisterViewModel();
}
}
Doing in this way nothing happens.
What am I missing?

Bindable properties don't use your setter; they go directly through the bindable property system.
Instead, you need to pass a propertyChanged callback to BindableProperty.Create.
But actually, you should bind Opacity in your XAML (using a converter) instead.

Related

Xamarin forms ViewModels Inheritance

I work on developing a Xamarin forms application about video games using MVVM for my pet project. I am new in Xamarin forms, I need your advice.
I had a few ViewModels with the same code in it. I decided to create one base ViewModel and inherit others from that.
I have ViewModelBase with PropertyChanged event:
public class ViewModelBase : INotifyPropertyChanged
{
private string _title;
public string Title
{
get => _title;
set => Set(ref _title, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a base GamesViewModel from which others are inherited, there is a lot of code, that is why I will show only that I inherit everything properly:
public class GamesViewModel : ViewModelBase
Below are derived ViewModels:
public class NewGamesViewModel : GamesViewModel
and
public class SearchViewModel : GamesViewModel
Problem is I have SearchGame property in the base GamesViewModel:
private string _searchGame;
public string SearchGame
{
get => _searchGame;
set => Set(ref _searchGame, value);
}
When the program is running I put the value inside SearchGame property,and in GamesViewModel I can see that value assigned, but in derived ViewModels value is null:
For example, in debugging in SearchViewModel which is inherited from the GamesViewModel I check the value and it's null.
var test = SearchGame; - value is null here
I don't create any object of GamesViewModel in the project.
In the pages Code-behind files in BindingContext I do like this:
public partial class SearchGamePage : ContentPage
{
public SearchGamePage()
{
InitializeComponent();
BindingContext = new SearchViewModel();
}
}
I tried to explain as more as I can. Maybe in Xamarin forms inheritance with ViewModels work not obviously.
Thank you in advance for the help!
Have a nice day!
First of all, I made a ViewModelBase like following code.
public class ViewModelBase : INotifyPropertyChanged
{
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((propertyName)));
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
I set a text for SearchGame in the SearchViewModel.
public class SearchViewModel : GamesViewModel
{
public SearchViewModel()
{
SearchGame = "test";
}
}
Here is code about GamesViewModel.
public class GamesViewModel:ViewModelBase
{
private string _searchGame;
public string SearchGame
{
get => _searchGame;
set => SetProperty(ref _searchGame, value);
}
}
Then I make a Label to binding the SearchGame property like the xaml.
<Label Text="{Binding SearchGame}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
Here is layout background code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext = new SearchViewModel();
}
}
Here is running screenshot.
I sent a message to the GamesViewModel with search game value like this:
MessagingCenter.Send(this, "search_game", titleViewModel.SearchGame);
var detailPage = (Application.Current.MainPage as MasterDetailPage)?.Detail;
await detailPage.Navigation?.PushAsync(new SearchGamePage());
I subscribe to this message in GamesViewModel constructor and assigned to SearchGame property message value like this:
MessagingCenter.Subscribe<CustomTitleView, string>(this, "search_game", (sender, message) =>
{
SearchGame = message;
});
But as you can see in the code above I create SearchGamePage instance. SearchGamePage constructor calls and I create in it new SearchViewModel instance:
public partial class SearchGamePage : ContentPage
{
public SearchGamePage()
{
InitializeComponent();
BindingContext = new SearchViewModel();
}
}
That's why GamesViewModel constructor calls again and in SearchGame property assign null.
I solved this problem using DependencyService.Register in App.xaml.cs file:
public App()
{
InitializeComponent();
DependencyService.Register<MockDataStore>();
DependencyService.Register<IGameApiClient, GameApiClient>();
DependencyService.Register<IFavoriteGameService, FavoriteGameService>();
DependencyService.Register<GamesViewModel>();
DependencyService.Register<SearchViewModel>();
DependencyService.Register<NewGamesViewModel>();
DependencyService.Register<TitleViewModel>();
MainPage = new MainPage();
}
And finally, in SearchGamePage constructor I assign to BindingContext SearchViewModel like this:
public SearchGamePage()
{
InitializeComponent();
BindingContext = DependencyService.Get<SearchViewModel>();
}
Now everything is fine when I assign value to SearchGame property in GamesViewModel using messenger I can see the same value in the SearchGame property in SearchViewModel because this ViewModel wasn't recreated.
I understand that I didn't give full info when I asked a question. But I hope that if somebody else will face the same problem this answer will be useful.

Two Way binding not working when set via Code behind

I am creating dynamic control in code behind and setting it's visibility property binding to the property in the code behind. But when the property value is changed, it's not updating visibility of the control.
Binding:
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
Property(Using fody):
public bool IsLocalSearchEnabled { get; set; }
maybe your class which contains the property needs to implement the interface
INotifyPropertyChanged
Let's assume your class name be A
then snippet will be
class A : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool isLocalSearchEnabled = false;
public bool IsLocalSearchEnabled
{
get { return isLocalSearchEnabled ;}
set { isLocalSearchEnabled = value; this.OnPropertyChanged("IsLocalSearchEnabled");
}
}
What happens here when you implement INotifyPropertyChanged is the event PropertyChanged
is triggered when the value of isLocalSearchEnabled is set (regardless of old value and new value) and OnPropertyChanged is called with the name of Public property
It seems you have not implemented INotifyPropertyChanged interface, please see the detailed example INotifyPropertyChanged
Did you set the assetsStackPanel DataContext the binding need the source ,that you should set the DataContext ont only set the source.
If you set the property in this in xaml.cs that you should make it public.
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.DataContex=this;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
For I have not read frameWork ,I think you can try use property by INotifyPropertyChanged to know whether code is right.
And you can use BindingOperations.SetBinding
Try use resharper in xaml and write Visibility="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=IsLocalSearchEnabled}",mode=TwoWay.If it can work that is mean the FrameWork can work.
Thanks. But as mentioned in the post I am actually using
Fody(github.com/Fody/PropertyChanged). Which automatically implements
that
I have checked the complied class, by using Fody PropertyChanged the property changed notification wasn't successfully implemented.
[ImplementPropertyChanged]
public sealed partial class MainPage : Page
{
public bool IsLocalSearchEnabled { get; set; }
public MainPage()
{
this.InitializeComponent();
SetBinding();
this.DataContext = this;
}
public void SetBinding()
{
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
}
}
I would suggest you reporting issue to Fody to fix it.
The standard way as follows:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
bool isLocalSearchEnabled;
public bool IsLocalSearchEnabled
{
get { return isLocalSearchEnabled; }
set
{
if (value != isLocalSearchEnabled)
{
isLocalSearchEnabled = value;
OnPropertyChanged("IsLocalSearchEnabled");
}
}
}
public MainPage()
{
this.InitializeComponent();
SetBinding();
this.DataContext = this;
}
public void SetBinding()
{
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Or you can easily use a wrapper class: BindableBase

UWP MVVM Data Binding for dummies (textbox.text from String)

Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";

How to navigate in WPF using PRISM and bind DataContext

There must be a lot of questions surrounding this area but I couldn't find anything to help in my instance.
The problem I'm experiencing is getting my ViewModel, and specifically a property within ViewModel, to be updated to my View. Below is my implementation. I think I understand where I'm going wrong but not sure how to resolve it.
I have a Module that has a list and edit view. Quite simply lists domain objects and then ability to edit a domain object.
My xaml binds the DataContent to a ViewModel property in my View.
I then use the INavigationAware.NavigateTo method to navigate to my ViewModel and this is where I load the domain object.
The problem is that obviously this is not reflected back to the View. The view already has an instance of the ViewModel. This method worked fine when the ViewModel was using a list of objects using ObservableCollection. However, this did not work when using a simple object or even an ObservableObject.
Could someone please help my understanding or point me to some links with a better implementation of what I am trying to achieve?
MyModule
public class MyModule : IModule
{
private readonly IRegionManager _regionManager;
public MyModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyListView));
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyEditView));
}
}
XAML
<UserControl
DataContext="ViewModel">
...
<TextBlock Text="{Binding Path=MyDomainObject.AProperty}" />
...
View
public partial class MyEditView
{
public readonly static string ViewName = "MyEditView";
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
}
ViewModel
public class MyViewModel : INavigationAware
{
private readonly IRegionManager _regionManager;
public MyDomainObject MyDomainObject { get; set; }
public void Load(ViewModelKey key)
{
// get domain object
// this method worked when MyDomainObject was
// ObservableCollection<T> as just adding elements to list
// where this is creating a new instance of MyDomainObject
var id = parameter from navigationContext;
MyDomainObejct = server.GetDomainObject(id);
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
var key = key from navigationContext;
Load(key);
}
}
SOLUTION
public class MyEditViewModel : INavigationAware
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyDomainObject _myDomainObject;
public MyDomainObject MyDomainObject
{
get
{
return _myDomainObject;
}
set
{
if (value != _myDomainObject)
{
_myDomainObject = value;
NotifyPropertyChanged();
}
}
}
View
public partial class MyEditView
{
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!(sender is MyEditViewModel))
return;
ViewModel = (MyEditViewModel)sender;
}
}
For your binding to update you need to implement INotifyPropertyChanged and raise PropertyChanged Event on the set accessor of your domain object.
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public MyDomainObject MyDomainObject
{
get
{
return myDomainObject;
}
set
{
if(value != myDomainObject)
{
myDomainObject = value;
RaisePropertyChanged("MyDomainObject");
}
}
}
private void RaisePropertyChanged(String p)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Or as in the Prism book, inherit NotificationObject and call RaisePropertyChanged(()=> PropertyName) which is refactoring-safe

DataContext, DependencyProperties and Bindings

I have a UserControl, we'll call it "Header". It has a DependencyProperty called ProjectID, this control has a View Model and I set it to be the DataContext:
public BillingInfoHeaderControlVM VM
{
get
{
return (BillingInfoHeaderControlVM)DataContext;
}
set
{
DataContext = value;
}
}
public static readonly DependencyProperty ProjectIDProperty =
DependencyProperty.Register("ProjectID", typeof(int), typeof(BillingInfoHeaderControl), new PropertyMetadata();
public int ProjectID
{
set
{
SetValue(ProjectIDProperty, value);
}
get
{
return (int)GetValue(ProjectIDProperty);
}
}
Now what I want to do, is to bind the ProjectID of a control to this control's ProjectID:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay}"></controls:Header>
Now when I run this, I get an error in the InitializeControl() method that states "
Property Get method was not found.
From what I'm reading, I'm seeing this is because the Binding ProjectID is relative to the data context of the control. Of course I could set the ElementName within the binding:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay, ElementName=ParentControl}"></controls:Header>
But this is ugly, and to be honest we don't want to have to remember to do this for this control whenever we use it. What other options do I have? Is there a way to set the source of the binding to use the DataContext of the parent?
I duplicated your concept in code and it compiles and runs fine.
I have included the control code and the viewmodel below in case you are doing something different.
*Note: I kept the viewmodel ProjectID as a simple update property.:
namespace Demo1
{
public partial class BillingInfoHeaderControl : UserControl
{
public BillingInfoHeaderControl()
{
InitializeComponent();
this.DataContext = new BillingInfoHeaderControlVM();
}
public int ProjectId
{
get { return (int)GetValue(ProjectIdProperty); }
set { SetValue(ProjectIdProperty, value); }
}
public static readonly DependencyProperty ProjectIdProperty =
DependencyProperty.Register("ProjectId", typeof(int), typeof(BillingInfoHeaderControl),
new PropertyMetadata(0));
}
}
namespace Demo1
{
public class BillingInfoHeaderControlVM : INotifyPropertyChanged
{
private int _projectId;
public int ProjectId
{
get { return _projectId; }
set
{
if (_projectId != value)
{
_projectId = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProjectId"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

Categories

Resources