Xamarin forms ViewModels Inheritance - c#

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.

Related

C# User Control (MVVM) surface a property in MV

I feel though i may be missing somethig here, or its not doable (which i find hard to belive).
I have a UserControl thats using MVVM archtitecture.
The UserControl looks like this.
public partial class UserControl1 : UserControl
{
private string _labelContents;
public UserControl1()
{
InitializeComponent();
DataContext = new UserControl1_VM();
}
}
The VM looks like this.
public class UserControl1_VM : INotifyPropertyChanged
{
private string _labelContents = "Set from VM";
public string LabelContent
{
get => _labelContents;
set { _labelContents = value; OnPropertyChanged("LabelContent"); }
}
public event PropertyChangedEventHandler? PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I want to be able to put in a MainView or similar this:
<mv:UserControl1 LabelContent="My Text"></mv:UserControl1>
But VS states that it "Cannot relsolve symbol LabelContent". Which is understandable as its in the view model. Without putting that Property in to the code behind in the UserControl, and passing the value through it seems impossible. I may just be looking for the wrong thing.
This is a very basic exaple, but LabelContent i think needs to be a dependancy properties because it is bound to it self i.e ulimately.
<mv:UserControl1 LabelContent="{Binding LabelText}"></mv:UserControl1>
Any help with this would be great, as it has me scratching my head, and making me bald!!
Just to let you know if its not a Dependancy Property this works, but seems very clumsy.
Cheers
James
you don't need any view models for UserControl, just a dependency property:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set { SetValue(LabelContentProperty, value); }
}
public static readonly DependencyProperty LabelContentProperty = DependencyProperty.Register
(
nameof(LabelContent),
typeof(string),
typeof(UserControl1),
new PropertyMetadata(String.Empty)
);
}
then put it in a view and assign or bind property:
<mv:UserControl1 Name="uc1" LabelContent="My Text"/>
<mv:UserControl1 Name="uc2" LabelContent="{Binding Path=LabelContent, ElementName=uc1}"/>

WPF ObservableCollection binding to DataGrid not refreshing UI

I currently facing the issue that my DataGrid binding is not refreshing the UI.
My ViewModel and Object inherit from INotifyPropertyChanged.
Here is my code:
XAML:
<DataGrid Grid.Row="2" DataContext="{StaticResource MainViewModel}" ItemsSource="{Binding TestCollection, Mode=OneWay}" AutoGenerateColumns="True"/>
ViewModel:
public class MainViewModel: ViewModelBase
{
private ObservableCollection<ProductDisplayItem> _testCollection;
public ObservableCollection<ProductDisplayItem> TestCollection
{
get => _testCollection;
set => SetProperty(ref _testCollection, value);
}
private async void SendSearch()
{
//MyCode
.....
IEnumerable<ProductDisplayItem> displayItems = DisplayItemHelper.ConvertToDisplayItems(products);
TestCollection = new ObservableCollection<ProductDisplayItem>(displayItems);
}
}
My Object:
public class ProductDisplayItem: ViewModelBase
{
private string _mfrPartNumber;
private double _unitPrice;
private int _stock;
public string MfrPartNumber
{
get => _mfrPartNumber;
set => SetProperty(ref _mfrPartNumber, value);
}
public double UnitPrice
{
get => _unitPrice;
set => SetProperty(ref _unitPrice, value);
}
public int Stock
{
get => _stock;
set => SetProperty(ref _stock , value);
}
public ProductDisplayItem()
{
}
public ProductDisplayItem(string mfrp, double unitPrice, int stock)
{
MfrPartNumber = mfrp;
UnitPrice = unitPrice;
Stock = stock;
}
}
And my ViewModelBase:
public abstract class ViewModelBase: IDisposable, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
}
}
I also tried to add the items to the ObservableCollection instead of creating a new one, but with the same result.
I hope anyone can help me with that.
Thanks in advance
The most common cause of such errors is confusion about ViewModel instances: UI elements are bound to one instance, and you are modifying a collection in another instance.
Since WPF MVVM usually provides for using the main ViewModel in only one instance, try using Singleton.
Fresh topic with a similar question: Is it a correct approach to create static viewModel in MVVM?
First implementation option from there:
1) If:
in general, in principle, under no circumstances is it assumed that a ViewModel can have several instances at the assembly level in which it is created;
if this does not create any security problems, since the static instance can be accessed by everyone;
if static values are sufficient to create a single instance. In most cases, this means that the ViewModel has only one non-parameterized constructor.
Then in this case it is worth using Singleton.
Example:
public class MainWindowViewModel : ViewModelBase
{
// The only instance available outside of this class.
public static MainWindowViewModel Instanse { get; }
= new MainWindowViewModel();
// All constructors must be MANDATORY HIDDEN.
private MainWindowViewModel()
{
// Some code
}
// Some code
}
To get this instance in XAML, x: Static is used.
You can get the entire instance, or create a binding to a separate property.
<SomeElement
DataContext="{x:Static vm:MainWindowViewModel.Instance}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={x:Static vm:MainWindowViewModel.Instance}}"/>
Ok I figured it out. It's the DataContext...
Works fine after removing it from xaml.

Xamarin Forms watch ViewModel property from .xaml.cs class

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.

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";

Dependency property not updating the UI

I am new to the binding concept and got stuck with the following.
public sealed partial class MainPage : Page
{
Model model;
public MainPage()
{
this.InitializeComponent();
model = new Model();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.Name = "My New Name";
}
}
class Model : DependencyObject
{
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Model), new PropertyMetadata("My Name"));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
I have bound the Name property to Text property of TextView. All I need to do is, on the button click I want to update the Name value that will have to update the text box value. I thought, if I use dependency property instead of normal CLR property, I dont need to implement INotifyPropertyChanged.
But the value in the UI is not updating as expected. Am I missing something?
Thanks in advance.
There are a couple things that need to be addressed with your question. First of all, your model does not need to inherit from DependencyObject, rather it should implement INotifyPropertyChanged:
public class Model : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An object that implements INotifyProperty can then be used as a DependencyProperty in your page/window/object:
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model",
typeof(Model), typeof(MainWindow));
public Model Model
{
get { return (Model)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
Finally, then, you can bind your TextBox.Text property to that in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name}"/>
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Grid>
The INotifyPropertyChanged is still necessary here because there needs to be a way for the UI to know that the model object has been updated.

Categories

Resources