Let's say there is a model:
public class MyCustomModel
{
public bool parameter { get; set; }
....
}
and there are also several ViewModels:
public class MainViewModel : INotifyPropertyChanged
{
private MyCustomModel model;
private bool _missingValue = true;
public bool MissingValue
{
get => _missingValue;
set
{
if (_missingValue == value) return;
_missingValue = value;
OnPropertyChanged(nameof(MissingValue));
}
}
public MainViewModel(MyCustomModel model)
{
this.model = model;
}
}
public class AdvancedViewModel : INotifyPropertyChanged
{
private MyCustomModel model;
private bool _missingValue = true;
public bool MissingValue
{
get => _missingValue;
set
{
if (_missingValue == value) return;
_missingValue = value;
model.parameter = _missingValue;
OnPropertyChanged(nameof(MissingValue));
}
}
public AdvancedViewModel(MyCustomModel model)
{
this.model = model;
}
}
I need to change the value in my MainViewModel when the value changes in AdvancedViewModel (need to call setter). So how to do that properly? Any suggestions? Thanks in advance.
Related
I've started working on an application that allows the user to enter some data in different textboxes and then these data are serialized in an XML format, but it seems that I've understood the MVVM concept completely wrong because I've tried to serialize the ViewModel and one of my work colleagues said that is an incorrect way to do things and I should serialize the MODEL.
The "incorrect" implementation:
public class ExampleViewModel : ViewModelBase
{
private double lowerRange;
public double LowerRange
{
get { return lowerRange; }
set
{
lowerRange = value;
RaisePropertyChanged();
}
}
private double upperRange;
public double UpperRange
{
get { return upperRange; }
set
{
upperRange = value;
RaisePropertyChanged();
}
}
}
According to my colleague, serializing this is wrong, but then how should look my MODEL (actually this is the question)
This is correct?
public class ExampleModel
{
public double LowerRange { get; set; }
public double UpperRange { get; set; }
}
public class ExampleViewModel : ViewModelBase
{
private ExampleModel model;
public ExampleViewModel()
{
model = new ExampleModel();
}
private double lowerRange;
public double LowerRange
{
get { return model.LowerRange; }
set
{
model.LowerRange = value;
RaisePropertyChanged();
}
}
private double upperRange;
public double UpperRange
{
get { return model.UpperRange; }
set
{
model.UpperRange = value;
RaisePropertyChanged();
}
}
}
Or this is complety overhead? If you can provide me a meaningful example, I'll be grateful.
Also, I've seen some people that use "RaisePropertyChanged()" inside a MODEL, is this correct? that this model because a ViewModel, right?
Following my comment above, here is how I tend to do this:
public class ExampleModel : ViewModelBase
{
private double _lowerRange;
private double _upperRange;
public double LowerRange
{
get { return _lowerRange; }
set
{
_lowerRange = value;
RaisePropertyChanged();
}
}
public double UpperRange
{
get { return _upperRange; }
set
{
_upperRange= value;
RaisePropertyChanged();
}
}
}
public class ExampleViewModel : ViewModelBase
{
private ExampleModel model;
public ExampleViewModel()
{
Model = new ExampleModel();
}
// this is only needed if you change your complete model
// and need to update the change on the UI
public ExampleModel Model
{
get { return model; }
set
{
model = value;
RaisePropertyChanged();
}
}
}
Also, if you change your ViewModelBase class to something like the one shown here, you can simplify the property getters/setters a bit to the following for example:
public ExampleModel Model
{
get { return model; }
set { SetProperty<ExampleModel >(ref model, value); }
}
I'm having a problem with using MVVM for a Xamarin project.
I can not refresh the user interface if one of my objects in my viewModel is updated (after a PUT request, for example).
Let me explain :
My model :
public class MyObject
{
public string Id { get; private set; }
public string Name { get; private set; }
}
My viewmodel :
public class MyViewModel : BaseViewModel
{
public MyObject MyObject { get; private set; }
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
}
BaseViewModel implements INotifyPropertyChanged
public class BaseViewModel : INotifyPropertyChanged
{
public string PageTitle { get; protected set; }
LayoutViewModel() {}
// MVVM ----------------------------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
MyViewModel is defined as BindingContext for my page
My properties IdMvvm and NameMvvm are bind in Entry in my page in xaml
When I modify an Entry then the value is raised but if my MyModel object changes value, for example update (click on a button) then the value of the different Entry is not updated
Can you help me please? Because it seems that I missed something ...
If you need more explanation, tell me to know
Sorry if my english is not good
It is because when you change the model, your view is not aware about the change. Update your code so that you explicitly notify property changes when your model changes.
public class MyViewModel : BaseViewModel
{
private MyObject _myObject;
public MyObject MyObject
{
get { return _myObject; }
private set { _myObject = value; NotifyModelChange(); }
}
public string IdMvvm
{
set
{
if (this.MyObject.Id != value)
{
MyObject.Id = value;
OnPropertyChanged(nameof(IdMvvm));
}
}
get { return MyObject.Id; }
}
public string NameMvvm
{
set
{
if (this.MyObject.Name != value)
{
MyObject.Name = value;
OnPropertyChanged(nameof(NameMvvm));
}
}
get { return MyObject.Name; }
}
private void NotifyModelChange()
{
OnPropertyChanged(nameof(IdMvvm));
OnPropertyChanged(nameof(NameMvvm));
}
}
Im using the MVVMCross 4.4.0 and my FirstView is a List View like WhatsApp. When I click an item, my SecondView is a common view with just a field to update a property and change my FirstView, but this is not happen.
My First View Code
public sealed partial class MyOrdersView : MvxViewController
{
UITableView tableView;
public MyOrdersView() : base("MyOrdersView", null)
{
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
tableView = new UITableView
{
Frame = new CoreGraphics.CGRect(0, 0, View.Bounds.Width, View.Bounds.Height),
CellLayoutMarginsFollowReadableWidth = true,
};
Add(tableView);
var tableSource = new MvxSimpleTableViewSource(tableView, OrderCell.Key, OrderCell.Key);
tableView.Source = tableSource;
var set = this.CreateBindingSet<MyOrdersView, Core.ViewModels.MyOrdersViewModel>();
set.Bind(tableSource).To(vm => vm.Tasks);
set.Bind(tableSource).For(s => s.SelectionChangedCommand).To(vm => vm.ItemSelectedCommand);
set.Apply();
tableView.ReloadData();
Title = "Meus Pedidos";
AppDelegate app = UIApplication.SharedApplication.Delegate as AppDelegate;
NavigationItem.SetLeftBarButtonItem(
new UIBarButtonItem(UIImage.FromBundle("threelines")
, UIBarButtonItemStyle.Plain
, (sender, args) => app.SidebarController.ToggleMenu()), true);
}
}
And ViewModel
public class MyOrdersViewModel : BaseViewModel
{
private IOrderService orderService;
public ObservableCollection<ValOrders2TO> tasks;
private MvxCommand<ValOrders2TO> _itemSelectedCommand;
public MyOrdersViewModel(IOrderService orderService)
{
this.orderService = orderService;
tasks = orderService.getAvailableOrders();
searchMyOrders();
}
private async Task<bool> searchMyOrders()
{
orderService.clearAvailableOrders();
orderService.addAllAvailableOrders(await orderService.searchMyOrders());
return true;
}
public ObservableCollection<ValOrders2TO> Tasks
{
get { return tasks; }
set { tasks = value; RaisePropertyChanged(() => Tasks); }
}
public ICommand ItemSelectedCommand
{
get
{
_itemSelectedCommand = _itemSelectedCommand ?? new MvxCommand<ValOrders2TO>(DoSelectItem);
return _itemSelectedCommand;
}
}
private void DoSelectItem(ValOrders2TO item)
{
ShowViewModel<MyOrdersDetailModel>(new { idOrder = item.id });
}
}
My Second View Code
public sealed partial class MyOrdersDetailView : MvxViewController<MyOrdersDetailModel>
{
public MyOrdersDetailView() : base("MyOrdersDetailView", null)
{
//ViewDidLoad();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var btnBack = UIButton.FromType(UIButtonType.System);
btnBack.Frame = new CGRect(2, 35, 50, 20);
btnBack.SetTitle("Voltar", UIControlState.Normal);
Add(btnBack);
var set = this.CreateBindingSet<MyOrdersDetailView, Core.ViewModels.MyOrdersDetailModel>();
set.Bind(txtItemDes).To(vm => vm.txtItemDes);
set.Bind(btnUpdate).To(vm => vm.update);
set.Apply();
}
}
And the ViewModel
public class MyOrdersDetailModel : MvxViewModel
{
public ValOrders2TO _valItem;
public INC<string> txtItemDes = new NC<string>();
public MyOrdersDetailModel(IMessageService messageService, IOrderService orderService)
{
}
public ValOrders2TO Item
{
get { return _valItem; }
set { _valItem = value; RaisePropertyChanged(() => Item); }
}
public void Init(ValOrders2TO item)
{
Item = item;
ConfigProp();
}
void ConfigProp()
{
txtItemDes.Value = Item.itemsDes;
}
public IMvxCommand Update
{
get
{
return new MvxCommand(() => updateOrder());
}
}
private bool updateOrder()
{
Item.itemsDes = txtItemDes.Value;
return true;
}
public IMvxCommand CloseCommand
{
get { return new MvxCommand(() => Close(this)); }
}
}
This is my Bean Code
public class ValOrders2TO
{
//[JsonProperty()]
public int id { get; set; }
//[JsonProperty()]
public string itemsDes { get; set; }
}
And this is my OrderCell
public partial class OrderCell : MvxTableViewCell
{
public static readonly UINib Nib = UINib.FromName("OrderCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString("OrderCell");
private readonly MvxImageViewLoader _loader;
public OrderCell(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<OrderCell, Core.ValOrders2TO>();
set.Bind(lblItemDes).To(item => item.itemsDes);
set.Apply();
});
}
public static OrderCell Create()
{
return (OrderCell)Nib.Instantiate(null, null)[0];
}
}
I know how to remove the items from my ListView like this:
orderService.deleteAvailableOrder(Item);
But I don't know how to update the items list.
Following attached my print screens
Try to implement RaisePropertyChanged for
public string itemsDes { get; set; }
I am stuck with a problem where I am checking network connectivity in an iOS app and trying to binding a boolean hasNetworkConnection in my view controller for it's view model.
View controller UpdateContentView.cs
// This file has been autogenerated from a class added in the UI designer.
using System;
using MvvmCross.iOS.Views;
using MvvmCross.Binding.BindingContext;
using Training.Core;
namespace EdwardsTraining.IOS
{
public partial class UpdateContentView : MvxViewController
{
public UpdateContentView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindingSet = this.CreateBindingSet<UpdateContentView, UpdateContentViewModel>();
bindingSet.Bind(NoConnectionView).For(x => x.Hidden).To(vm => vm.HasConnection).WithConversion("ReverseBoolean");
bindingSet.Bind(UpdateInProgressView).For(x => x.Hidden).To(vm => vm.InProgress).WithConversion("ReverseBoolean");
bindingSet.Bind(UpdateAvailableView).For(x => x.Hidden).To(vm => vm.HasContentUpdate).WithConversion("ReverseBoolean");
bindingSet.Bind(CancelButton).For(x => x.Hidden).To(vm => vm.CancelVisible).WithConversion("ReverseBoolean");
bindingSet.Bind(RetryButton).To(vm => vm.DoRetryUpdate);
bindingSet.Bind(ConfirmButton).To(vm => vm.DoUpdate);
//bindingSet.Bind(iOSNetworkConnectivitiy).For(x => x.HasNetworkConnection).To(vm => vm.NetworkConnectivitiy).TwoWay()
//.For(vm => vm.HasNetworkConnection);
bindingSet.Bind(iOSNetworkConnectivitiy).To(vm => vm.NetworkConnectivitiy).TwoWay();
bindingSet.Apply();
_iOSnetworkConnectivity = new NetworkConnectivity()
{
HasNetworkConnection = Reachability.IsNetworkAvailable()
};
}
private NetworkConnectivity _iOSnetworkConnectivity { get; set; }
public NetworkConnectivity iOSNetworkConnectivitiy {
get{return _iOSnetworkConnectivity;}
set { _iOSnetworkConnectivity = value;
}
}
}
}
I would like to check for connectivity using my iOS specific code and bind the boolean returned to a a public view model property.
View Model
using System;
using System.Threading.Tasks;
using EdwardsTraining.BusinessLayer.Interfaces.Services;
using MvvmCross.Core.ViewModels;
using MvvmCross.Platform;
namespace Training.Core
{
public class UpdateContentViewModel : BaseViewModel
{
private IApplicationContentService _applicationContentService;
private ITrainingContentService _trainingContentService;
public bool _isNetworkAvailable { get; set; }
public UpdateContentViewModel(IApplicationContentService applicationContentService, ITrainingContentService trainingContentService)
{
_applicationContentService = applicationContentService ?? Mvx.Resolve<IApplicationContentService>();
_trainingContentService = trainingContentService ?? Mvx.Resolve<ITrainingContentService>();
IntialSetup();
}
protected void IntialSetup()
{
_cancelVisible = false;
_hasContentUpdate = true;
_inProgress = false;
}
public void SetNoConnection()
{
_cancelVisible = true;
_hasContentUpdate = false;
_inProgress = false;
}
public void SetInProgress()
{
_cancelVisible = false;
HasContentUpdate = false;
InProgress = true;
}
public void SetProgessComplete()
{
InProgress = false;
Task.Run(async () => await FinishedUpdating());
}
public async Task UpdateContent()
{
if (_networkConnectivity.HasNetworkConnection)
{
SetInProgress();
await _trainingContentService.UpdateTrainingContentAsync();
await _applicationContentService.UpdateContent();
SetProgessComplete();
await FinishedUpdating();
}
return;
}
public async Task FinishedUpdating()
{
Close(this);
}
public MvxCommand DoUpdate
{
get { return new MvxCommand(async () => await UpdateContent()); }
}
public MvxCommand DoRetryUpdate
{
get { return new MvxCommand(async () => await UpdateContent()); }
}
public MvxCommand CancelUpdate
{
get { return new MvxCommand(async () => await FinishedUpdating()); }
}
private bool _hasContentUpdate;
public bool HasContentUpdate
{
get { return _hasContentUpdate; }
set
{
_hasContentUpdate = value;
RaisePropertyChanged(() => HasContentUpdate);
}
}
private bool _hasConnection;
public bool HasConnection
{
get { return _hasConnection; }
set
{
_hasConnection = value;
RaisePropertyChanged(() => HasConnection);
}
}
private bool _inProgress;
public bool InProgress
{
get { return _inProgress; }
set
{
_inProgress = value;
RaisePropertyChanged(() => InProgress);
}
}
private bool _cancelVisible;
public bool CancelVisible
{
get { return _cancelVisible; }
set
{
_cancelVisible = value;
RaisePropertyChanged(() => CancelVisible);
}
}
private NetworkConnectivity _networkConnectivity { get; set; }
public NetworkConnectivity NetworkConnectivitiy
{
get { return _networkConnectivity; }
set {
_networkConnectivity = value;
RaisePropertyChanged(() => NetworkConnectivitiy);
}
}
}
public class NetworkConnectivity
{
public bool HasNetworkConnection { get; set; }
}
}
I have a problem with this line of code:
public async Task UpdateContent()
{
if (_networkConnectivity.HasNetworkConnection)
{
SetInProgress();
await _trainingContentService.UpdateTrainingContentAsync();
await _applicationContentService.UpdateContent();
SetProgessComplete();
await FinishedUpdating();
}
return;
}
if (_networkConnectivity.HasNetworkConnection) is already null even though I set two way binding. I'm new to MVVM cross for this reason I don't know if my approach is correct.
Is there anyone who could provide some help?
Nick
You need to explicitly tell the binding what property you want to bind on your NetworkConnectivity like:
bindingSet.Bind(iOSNetworkConnectivitiy).For(v => v.HasNetworkConnection).To(vm => vm.NetworkConnectivitiy).TwoWay();
However, the binding does not have any way to get notified that your NetworkConnectivity class has gotten any of its values updated. Hence you would have to extend that class to have some kind of event where it can get notified.
Then, you would have to write and register a Target Binding class.
Lets say you simply implement INotifyPropertyChanged in your NetworkConnectivity class:
public class NetworkConnectivity : MvxNotifyPropertyChanged
{
private bool _hasNetworkConnection;
public bool HasNetworkConnection {
get { return _hasNetworkConnection; }
set {
_hasNetworkConnection = value;
RaisePropertyChanged();
}
}
}
Then you create the following class in your iOS project:
public class NetworkConnectivityTargetBinding
: MvxPropertyInfoTargetBinding<NetworkConnectivity>
{
public NetworkConnectivityTargetBinding(object target, PropertyInfo targetPropertyInfo)
: base(target, targetPropertyInfo)
{
var view = View;
if (view == null)
{
MvxBindingTrace.Trace(MvxTraceLevel.Error,
"NetworkConnectivity is null in NetworkConnectivityTargetBinding");
}
else
{
view.PropertyChanged += HandleValueChanged;
}
}
private void HandleValueChanged(object sender, System.EventArgs e)
{
var view = View;
if (view == null)
return;
FireValueChanged(view.HasNetworkConnection);
}
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
var view = View;
if (view != null)
{
view.PropertyChanged -= HandleValueChanged;
}
}
base.Dispose(isDisposing);
}
}
Then in Setup.cs override FillTargetFactories:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
registry.RegisterPropertyInfoBindingFactory(typeof(NetworkConnectivityTargetBinding),
typeof(NetworkConnectivity), "HasNetworkConnection");
base.FillTargetFactories(registry);
}
Now the TwoWay binding should work. You should also be able to remove the .For(v => v.HasNetworkConnection) from you binding expression.
I have a list item which has more or less around 10 objects. I could able to detect which item is selected and also I sending this item properties into the DetailViewModel,I am using messageprotocol in mvvmcross.
I could able to observe changes in the MainViewModel when user enters new value in the edittext in DetailViewModel.
I wonder how I am going to put these values back into the selected item and update list.
MainViewModel
private readonly IMvxMessenger _messenger;
private readonly MvxSubscriptionToken _token;
private MainViewModel _selectedItem;
public MainViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ShowViewModel<DetailViewModel>(
new DetailViewModel.Parameter
{
Age= _selectedItem.Age,
Category = _selectedItem.Category,
});
RaisePropertyChanged(() => SelectedItem);
}
}
public MainViewModel(IMvxMessenger messenger) {
_messenger = messenger;
_token = messenger.Subscribe<SelectedItemMessage>(OnMessageReceived);;
}
private void OnMessageReceived(SelectedItemMessage obj)
{
// I could observe the DetailView Changes in the MainViewModel
// I wonder how to put these value back to selectedItem
double? Age = obj.Age;
int? Category= obj.Category;
}
public virtual ICommand ItemSelected
{
get{ return new MvxCommand<TestViewModel>(item =>{ SelectedItem = item;});
}
}
private ObservableCollection<TestViewModel> _testViews;
private ObservableCollection<WellTestViewModel> _allTestItemViews;
public void Init(string Id)
{
List<Test> allTests = new List<Test>();
allTests = _TestService.GetAllTestById(Id);
foreach (var test in allTests)
{
_testViews.Add(TestViewModel.CreateViewModel(test, this));
}
_allTestItemViews = _testViews;
}
TestViewModel
public static TestViewModel CreateViewModel(Test entity, MainViewModel parent = null)
{
if (entity == null)
{
return null;
}
return new TestViewModel(parent)
{
Age = entity.Age,
Category= entity.Category,
};
}
public TestViewModel()
{
// parameterless constructor
}
readonly MainViewViewModel _mainViewModel ;
public TestViewModel(MainViewViewModel mainViewViewModel)
{
_mainViewModel = mainViewViewModel;
}
DetailViewModel
private readonly IMvxMessenger _messenger;
public class Parameter
{
public double? Age{ get; set; }
public int? Category { get; set; }
}
public void Init(Parameter param)
{
Age= param.Age;
Category= param.Category;
}
public DetailViewModel(IMvxMessenger messenger) {
_messenger = messenger;
}
public void UpdateMethod() {
var message = new SelectedItemMessage(this, age, category);
_messenger.Publish(message, typeof(SelectedItemMessage));
}
SelectedItemMessage
public SelectedItemMessage(object sender, double? age, int? category) : base(sender)
{
Age = age;
Category = category;
}
public double? Age { get; set; }
public int? Category{ get; set; }
}
Just use your _selectedItem and set the properties on it.
private void OnMessageReceived(SelectedItemMessage obj)
{
_selectedItem.Age = obj.Age;
_selectedItem.Category= obj.Category;
}
You need to update the collection inside the OnMessageReceived method:
var item = _allTestItemViews.FirstOrDefault(i => i.Id == id);
if (item != null)
{
item.Age = age;
item.Category = category;
}
You need to add Id to your model class so that you can uniquely identify the item you need to update.