I have a XAML view bound to a ViewModel
When the view loads it sets the binding context to be an object (type GAME_TBL)
When the ViewModel loads, I await a message arriving with a unique ID
I then use this to calll an API and return a game object which I cast to replace the one that is bound
However, even tho debug shows me this is all working, and the PropertyChanged fires, the UI never updates
From the View:
<ContentPage.BindingContext>
<viewmodels:VotingViewModel/>
</ContentPage.BindingContext>
.....
<Label Text="{Binding currentGame.GAME_NAME}" />
In the VotingViewModel:
public class VotingViewModel : BaseViewModel
{
GAME_TBL gameSelected;
public GAME_TBL currentGame
{
get {
return _currentGame;
}
set
{
_currentGame = value;
OnPropertyChanged(nameof(_currentGame));
}
}
public VotingViewModel()
{
MessagingCenter.Subscribe<Views.GamesView, string>(this, "Selected Game", (sender, arg) =>
{
gameID = Convert.ToInt32(arg);
currentGame = loadGameInfo(); // this interacts with an API to set object specifics
});
}
}
In the BaseViewModel
protected virtual void OnPropertyChanged(
string propertyName = null)
{
System.Diagnostics.Debug.WriteLine("Debug: Name of property: " + propertyName);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
You need to inherit BaseViewModel in the Model Class of GAME_TBL. And inside the class GAME_TBL, you need to specify each and every property in the following manner:
private string _gAME_NAME;
public string GAME_NAME
{
get => _gAME_NAME;
set
{
_gAME_NAME = value;
OnPropertyChanged(nameof(GAME_NAME));
}
}
Each property should have a an internal backing property, as above.
Related
I am using the design pattern mvvm, I have a view model locator and a view model base class.
The view model locator finds what view is associated to the view model. I also wrote a navigation service and one of my methods (NavigateTo) takes in a parameter (an object). the method is to navigate to a view model associated with the view.
namespace StudentData
{
public class StudentOverViewViewModel : ViewModelBase
{
private DataSet studentData;
public Icommand getDetails { get; set; }
public DataSet _data
{
get { return studentData; }
set
{
studentData = value;
RaisePropertyChanged(() => studentData);
}
}
public StudentOverViewViewModel (DataSet studentData)
{
this.studentData = studentData;
getDetails = new Command(Details);
}
public async Task getDetails()
{
// api calls done to retrieve data and set studentData to the current student data
await NavigationService.NavigateToAsync<StudentDetailViewModel>(studentData );
}
}
}
For the second view model I have :
namespace StudentData
{
public class StudentDetailViewModel: ViewModelBase
{
private DataSet Data;
public DataSet _Data
{
get
{
return Data;
}
set
{
Data= value;
RaisePropertyChanged(() => Data);
}
}
}
public StudentDetailViewModel(DataSet Data)
{
this.Data = Data;
}
public override async Task InitializeAsync(object navigationData)
{
if(navigationData is DataSet)
{
Data = (DataSet) navigationData; // after the page is initialized, the variables or properties/ models are not updated and is still null
}
}
}
My issue is that in my initializeAsync method in the second view model, I set the value and property for data, but after the method is done it set all the values back to null.
Thank you in advance for your help.
private async Task InternalNavigateToAsync(Type viewModelType, object
parameter)
{
Page page = CreatePage(viewModelType, parameter);
if (page is UserAuthenticateView)
{
Application.Current.MainPage = new CustomNiavigationView(page);
}
else
{
var navigationPage = Application.Current.MainPage as CustomNiavigationView;
if (navigationPage != null)
{
await navigationPage.PushAsync(page);
}
else
{
Application.Current.MainPage = new CustomNiavigationView(page);
}
}
await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
}
Listview not refresh when any changes on a listview items property
here is my PageModel:
Public MyPageModel()
{
// populating listview data
ListData=PopulateData();
ObservableCollection<MyClass> _listData = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass> ListData
{
get { return _listData; }
set
{
_listData = value;
RaisePropertyChanged();
}
}
}
foreach(MyClass item in ListData)
{
if(item.id==2)
{
item.Name="UpdatedName"
}
}
Inside XAML:
<ListView ItemSource="{Bindind ListData}">
..............
</ListView>
after changing item details it will not update the listview itemsource
Did you added the property notify change in 'MyClass' as well?
ex:
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
I'm new to MVVMCross, theoretically it's shouldn't be that hard until I started using UICollectionViewCell
I have multiple sections in my view controller, and each of them should bind to different types of data, what should I do?
in the ViewModel, it has a list and a property I want to use the following properties to fill my custom cells
List<ClassForCell1> list;
private int _valueForCell2;
public int ValueForCell2
{
get => _valueForCell2;
set => _valueForCell2 = value;
}
In MySource class, I registered different types of Cell, which looks like this
public MySource(UICollectionView collectionView, ViewModel viewModel) : base(collectionView)
{
_viewModel = viewModel;
collectionView.RegisterClassForCell(typeof(CustomCell1), CustomCell1.Key);
collectionView.RegisterNibForCell(CustomCell2.Nib, CustomCell2.Key);
//... some other cell registration
}
Here are my Cells
protected CustomCell1(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CustomCell1, ClassForCell1>();
set.Bind(NameLabel).To(m => m.name);
set.Apply();
});
}
protected CustomCell2(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
// how to bind this one?
var set = this.CreateBindingSet<CustomCell2, ???>();
set.Bind(NameLabel).To(a view model's value); // ValueForCell2 in ViewModel
set.Apply();
});
}
My questions are:
How can I bind viewModel's specific property or list to the specific cell(s) or section(s)?
here's the code snippet in my ViewController, in order to bind ViewController and ViewModel, and it seems like doesn't work at all
var source = new MySource(MyCollectionView, MyViewModel);
var set = this.CreateBindingSet<MyViewController, MyViewModel>();
For CustomCell2, how to bind a label's Text property to view model's property? (ValueForCell2)
There needs to be separate ViewModels for each of the cells as well as your parent ViewModel that exposes the List of items you want to display.
Since you want two different types of cells to show up, you need a common base class to describe them. To achieve that, you can create a BaseCellViewModel class as such:
public abstract class BaseCellViewModel : MvxViewModel
{
private string _name;
public string Name {
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
Now that you have a base class setup, you can create ViewModels for each of the cells you want to display:
public class FirstCustomCellViewModel : BaseCellViewModel
{
//add any properties that are specific to the first type of cell
}
public class SecondCustomCellViewModel : BaseCellViewModel
{
//add any properties that are specific to the second type of cell
}
Now that all the ViewModels for the cells are setup, you can setup your parent ViewModel like this:
public class ListViewModel : MvxViewModel
{
private ObservableCollection<BaseCellViewModel> _listItems { get; set; }
public virtual ObservableCollection<BaseCellViewModel> ListItems {
get { return _listItems; }
set { _listItems = value;
RaisePropertyChanged(() => ListItems);
}
}
}
Notice that the collection is constrained to the BaseCellViewModel type. This allows you to add both FirstCustomCellViewModel and SecondCustomCellViewModel objects into it:
ListItems = new ObservableCollection<CellViewModelBase>(){
new FirstCustomCellViewModel(),
new SecondCustomCellViewModel()
};
Now you define the bindings in your cells like this:
protected CustomCell1(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CustomCell1, FirstCustomCellViewModel>();
set.Bind(NameLabel).To(vm => vm.Name);
set.Apply();
});
}
protected CustomCell2(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CustomCell2, SecondCustomCellViewModel>();
set.Bind(NameLabel).To(vm => vm.Name);
set.Apply();
});
}
And in your ViewController that has the UICollectionView, you just bind the source:
var source = new MyCollectionViewSource(MyCollectionView, MyViewModel);
var set = this.CreateBindingSet<MyViewController, ListViewModel>();
set.Bind(source).To(vm => vm.ListItems);
set.Apply();
I have a TableView in iOS and, in my ViewModel, I have a property to Selected Item in TableView, but I don't know how to bind the Selected Item for this property. How can I do that? My project is cross-platform. I have an Android project and an iOS project. In Android project, I did the bind:
<Mvx.MvxListView
android:id="#+id/lstViewTasks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:focusableInTouchMode="true"
android:choiceMode="multipleChoice"
local:MvxBind="ItemsSource Tasks; SelectedItem SelectedTask; ItemClick ShowTaskCommand"
local:MvxItemTemplate="#layout/projectmytasksitem" />
but I can't do a equivalent bind in iOS.
That's my TableViewController:
[Register("ProjectMyTasksViewc")]
public class ProjectMyTasksViews : MvxTableViewController<ProjectMyTasksViewModel>
{
//other things
var source = new MvxSimpleTableViewSource(TableView, ProjectMyTasksItem.Key, ProjectMyTasksItem.Key);
TableView.Source = source;
this.CreateBinding(source).To<ProjectMyTasksViewModel>(viewModel => viewModel.Tasks).Apply();
this.CreateBinding(source).For(s => s.SelectedItem).To<ProjectMyTasksViewModel>(viewModel => viewModel.SelectedTask).Apply();
this.CreateBinding(source).For(tableSource => tableSource.SelectionChangedCommand).To<ProjectMyTasksViewModel>(viewModel => viewModel.ShowTaskCommand).Apply();
}
Here is my ViewModel:
public class ProjectMyTasksViewModel : MvxViewModel
{
public Action ShowTaskCommandAction { get; set; }
private IList<Task> _tasks;
public IList<Task> Tasks
{
get { return _tasks; }
set { _tasks = value; RaisePropertyChanged(() => Tasks); }
}
private Task _selectedTask;
public Task SelectedTask
{
get { return _selectedTask; }
set { _selectedTask = value; RaisePropertyChanged(() => SelectedTask); }
}
private MvxCommand _showTaskCommand;
public MvxCommand ShowTaskCommand
{
get
{
_showTaskCommand = _showTaskCommand ?? (_showTaskCommand = new MvxCommand(ExecuteShowTaskCommand));
return _showTaskCommand;
}
}
private void ExecuteShowTaskCommand()
{
if (!SelectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
}
I believe it has to do with the timing of your ShowTaskCommand getting executed vs the set of SelectedTask. So if you commented out the code inside ExecuteShowTaskCommand and place a breakpoint inside ExecuteShowTaskCommand as well as the set of SelectedTask you would find that the ExecuteShowTaskCommand is running first and then the set of the SelectedTask.
Alternative implementation
To avoid the timing issue you can instead pass the selected task into your command as a parameter.
MvxCommand<Task> _showTaskCommand;
public MvxCommand<Task> ShowTaskCommand =>
_showTaskCommand ?? (_showTaskCommand = new MvxCommand<Task>(ExecuteShowTaskCommand));
private void ExecuteShowTaskCommand(Task selectedTask)
{
if (!selectedTask.IsCompleted)
{
ShowTaskCommandAction?.Invoke();
}
}
I have mvvmcross project, a ToggleButton with bindings for Checked, TextOn, and TextOff properties. I set the texts for those programmatically, I see in the setter that RaisePropertyChanged() is called, but the button text in UI stays the same, unless I click on it or change value of the property bound to "Checked". Changing Checked seems like a workaround, but ugly, is there a proper way?
FirstView.axml contains
<ToggleButton
android:id="#+id/myBtn"
local:MvxBind="TextOff MyBtnOFFLabel; TextOn MyBtnONLabel; Checked MyBtnChecked"
android:textOff="OFF"
android:textOn="ON"
android:checked="true"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"
android:width="0dp" />
FirstViewModel.cs contains
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set { myBtnOFFLabel = value; RaisePropertyChanged(() => MyBtnOFFLabel); }
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set { myBtnONLabel = value; RaisePropertyChanged(() => MyBtnONLabel); }
}
public FirstViewModel()
{
Global.EventSomethingChanged += Handler_SomethingChanged;
}
void Handler_SomethingChanged(object sender, EventArgs e)
{
UpdateUI();
}
private void UpdateUI()
{
MyBtnOFFLabel = "new OFF";
MyBtnONLabel = "new ON";
}
SecondViewModel.cs contains
Global.FireEventSomethingChanged();
as expected, UpdateUI() is called in FirstViewModel.cs and it updates the label properties for myBtn (confirmed in debug mode), but when I close SecondViewModel in emulator I see the old label remaining in first view UI. If I click on that button, it switches to showing correct labels.
I believe you needed to RaisePropertyChanged on your MyBtnChecked. In your comment above you mention that adding it to UpdateUI() causes it to redraw properly. That's not a "work around" per se as you need to put that somewhere. You could also do this:
private string myBtnOFFLabel;
public string MyBtnOFFLabel
{
get { return myBtnOFFLabel; }
set {
myBtnOFFLabel = value;
RaisePropertyChanged(() => MyBtnOFFLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
private string myBtnONLabel;
public string MyBtnONLabel
{
get { return myBtnONLabel; }
set {
myBtnONLabel = value;
RaisePropertyChanged(() => MyBtnONLabel);
RaisePropertyChanged(() => MyBtnChecked);
}
}
As it sounds like your MyBtnChecked is dependent on changes to MyBtnOFFLabel and MyBtnONLabel. Otherwise you need to be firing this RaisePropertyChanged somewhere else.. For example I would have expected to see a property in your example code for MyBtnChecked which would do this. Then in your view model when MyBtnChecked is changed it would fire the RaisePropertyChanged event to mark it as checked or not when your ViewModel bool property is changed.