MVVMCross iOS table view cell binding - xamarin

I'm new with MVVMCross model for iOS. I want to handle tableview cell tap, and get the tapped cell index. But I don't know how to access the index.
Here is my View code.
var menuSource = new MenuTableViewSource(menuTableView, MenuCell.Key, MenuCell.Key);
this.menuTableView.Source = menuSource;
var set = this.CreateBindingSet<BooksView, BooksViewModel>();
set.Bind(menuSource).To(vm => vm.MenuCellTexts);
set.Bind(menuSource).For(s => s.SelectionChangedCommand).To(vm => vm.ItemSelectedCommand);
set.Apply();
Here is my ViewModel code.
private MvxCommand _itemSelectedCommand;
public MvxCommand ItemSelectedCommand
{
get
{
_itemSelectedCommand = _itemSelectedCommand ?? new MvxCommand(DoSelectedItem);
return _itemSelectedCommand;
}
}
private void DoSelectedItem()
{
// How to get the tapped cell index here??
}

You can try finding the index by passing the selected row object to your Command like this:
private MvxCommand<YourClassName> _itemSelectedCommand;
public MvxCommand<YourClassName> ItemSelectedCommand
{
get
{
_itemSelectedCommand = _itemSelectedCommand ?? new MvxCommand(DoSelectedItem);
return _itemSelectedCommand;
}
}
private void DoSelectedItem(YourClassName item)
{
// How to get the tapped cell index here??
var index = MenuCellTexts.IndexOf(item);
}

Related

How can I use a Xamarin DataTemplate with an ObservableCollection instead of a List?

I have a page that looks like this. I tested out the code using a List but I need to use an ObservableCollection as the contents of the data will change after it's initially populated. My problem is that when I change from a List to ObservableCollection I no longer see any data appearing.
public YourPage()
{
var viewModel = _vm = new YourPageViewModel()
var dataTemplate = new DataTemplate(()=>
{
var mygrid = new MyGrid ();
mygrid.SetBinding(MyGrid.TextProperty, "Text");
return mygrid;
});
StackLayout stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, _vm.ListOfText);
BindableLayout.SetItemTemplate(stackLayout, dataTemplate);
Content = stackLayout;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.OnAppearing();
}
And a ViewModel
public partial class YourPageViewModel : BaseViewModel
{
private ObservableCollection<TestModel> _listOfText;
public ObservableCollection<TestModel> ListOfText{
get => _listOfText;
set => SetProperty(ref _listOfText, value);
}
public void OnAppearing()
{
var tempList = ...;
ListOfText = new ObservableCollection<TestModel>(tempList);
}
}
Given this situation with an ObservableCollection then the data template doesn't seem to show any data.
Does anyone have an idea what might be wrong?
You will want to create the ObservableCollection only once. If you use data-binding on it, it will subscribe to certain events that will emit the changes in the collection.
By doing ListOfText = new ObservableCollection<TestModel>(tempList); it will cause those events to be disconnected and your data won't show up. Instead, change it to be more like this
public partial class YourPageViewModel : BaseViewModel
{
// This changed
private ObservableCollection<TestModel> _listOfText = new ObservableCollection<TestModel>(tempList);
public ObservableCollection<TestModel> ListOfText{
get => _listOfText;
set => SetProperty(ref _listOfText, value);
}
public void OnAppearing()
{
var tempList = ...;
// This changed
ListOfText.Clear();
foreach (var text in tempList)
ListOfText.Add(text);
}
}
Notice how I only create the ObservableCollection once and repopulate each time instead of creating a new one.
Becasue when you set the data-binding in the constructor of your page,the ListOfText is not populated the data until you call _vm.OnAppearing(); in OnAppearing() method.
You could try to pipulate the data in the constructor of your viewmodel:
public partial class YourPageViewModel : BaseViewModel
{
private ObservableCollection<TestModel> _listOfText;
public ObservableCollection<TestModel> ListOfText{
get => _listOfText;
set => SetProperty(ref _listOfText, value);
}
public YourPageViewModel ()
{
var tempList = ...;
_listOfText = new ObservableCollection<TestModel>(tempList);
}
}
and then you don't need to call _vm.OnAppearing(); in OnAppearing() method.
You need to make sure that you have populated your data when you bind it.
The solutions given work for when the data is populate one time after the binding is set up but then after that the layout will not be refreshed.
To solve this problem what is needed is to place the DataTemplate in a CollectionView
CollectionView collectionView = new CollectionView();
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "ListOfText");
collectionView.ItemTemplate = new DataTemplate(() =>
{
var mygrid = new MyGrid ();
mygrid.SetBinding(MyGrid.TextProperty, "Text");
return mygrid;
});
Content = collectionView;
When this is done the ObservableCollection can be refreshed at any time and the screen layout will change when it's refreshed.

Initialize an xamarin view after an async method

Good evening everyone.
For some time now I have been to Xamarin. My first tests are rather conclusive. I decided to try to make a small application that retrieves information in a database via an API and then update this data via a ListView.
When I launch the application on my emulator everything works but as soon as I install the application on my phone it crashes. I thought this was because the API but I have an API that I use to check the Login / password that works correctly.
The API that returns the data reviews a lot of line about 3500/4000, can this be the reason?
So I passed the loading of the data in my viewModel in an async method but the problem now is that the view loads before the data is loaded correctly. Is there a way to get the view initialized after the data is loaded?
Below my code.
Initializing my viewModel
class ManageInventViewModel
{
public ObservableCollection<InventViewModel> InventProduct { get; set; }
public Command<InventViewModel> UpdateCommand
{
get
{
return new Command<InventViewModel>(invent =>
{
var index = invent.IndexLigneInventaire;
InventProduct.Remove(invent);
InventProduct.Insert(index, invent);
});
}
}
public Command<InventViewModel> ResetStock
{
get
{
return new Command<InventViewModel>(invent =>
{
var index = InventProduct.IndexOf(invent);
InventProduct.Remove(invent);
invent.RealStockProduct = 0;
InventProduct.Insert(index, invent);
});
}
}
public ManageInventViewModel()
{
LoadInventaire();
}
private async void LoadInventaire()
{
var listMvt = await Utils.Utils.GetListMouvementUntilDate();
var listStock = Utils.Utils.GetStockByProduct(listMvt).Take(20);
InventProduct = new ObservableCollection<InventViewModel>();
var indexLine = 0;
foreach (var stock in listStock)
{
var inventViewModel = new InventViewModel
{
LibelleProduit = stock.LibelleProduit,
PrCodeProduit = stock.PrCodeProduit,
UpCodeProduit = stock.UpCodeProduit,
RealStockProduct = stock.StockTheoProdct,
StockTheoProdct = stock.StockTheoProdct,
IndexLigneInventaire = indexLine
};
++indexLine;
InventProduct.Add(inventViewModel);
}
}
}
Initializinz my view
public partial class InventPage : ContentPage
{
public InventPage()
{
InitializeComponent();
TableInvent.ItemSelected += (sender, e) =>
{
if (TableInvent.SelectedItem != null)
{
if (TableInvent.SelectedItem is InventViewModel item)
{
PopupNavigation.Instance.PushAsync(new ChangeStockModal(item, this));
}
TableInvent.SelectedItem = null;
}
};
}
private void Reset_Stock(object sender, EventArgs e)
{
var input = sender as Button;
var inventViewModel = input?.BindingContext as InventViewModel;
var listViewModel = BindingContext as ManageInventViewModel;
listViewModel?.ResetStock.Execute(inventViewModel);
}
public void Update_Stock_List(InventViewModel dataStockUpdate)
{
var listViewModel = BindingContext as ManageInventViewModel;
listViewModel?.UpdateCommand.Execute(dataStockUpdate);
PopupNavigation.Instance.PopAsync();
}
}
Thanks
I managed to create the ActivityIndicator but I can not get my data loaded while I'm displaying the wait screen.
Regarding this issue, I don't see you useActivityIndicator from your code,maybe you didn't update your code, I think if you use useActivityIndicator , You can bind one property to ActivityIndicator IsRunning and IsVisible, then you can solve your issue.
Related use ActivityIndicator step, you can take a look:
ActivityIndicator

MvvmCross binding with NSTableView

After searching multiple blogs and videos I find that to implement the UITableView one can use MvxTableViewController, but what to use for NSTableView?
I do not find any tutorial, example that covers OSX binding TableView using MvvmCross. Any leads will be appreciated.
We don't have MvxTableViewController for macOS.
However, if you abstract from that, binding to a NSTableView is very similar to a UITableView on iOS.
private NSTableView _tableView;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_tableView = new NSTableView();
// add constraints or size otherwise
var source = new MvxTableViewSource(_tableView);
_tableView.Source = source;
var set = this.CreateBindingSet<MyViewController, MyViewModel>();
set.Bind(source).For(v => v.ItemsSource).To(vm => vm.Items);
set.Apply();
}
This will bind the ViewModel Items to the ItemsSource. However, you will still need to specify what to bind in the cell. The simplest way to do this is to provide a TableColumn.
var column = new MvxTableColumn();
column.Identifier = "First";
column.BindingText = "Text Name";
column.HeaderCell = new NSCell("Example");
_tableView.AddColumn(column);
This will bind the Text property of the TableColumn to Name in the items provided in Items in the ViewModel.
If you need more than this you will need to subclass MvxTableViewSource and override GetOrCreateViewFor and in there provide your own subclass of MvxTableCellView where you do more. This could look something as follows.
public class MyCustomCell : MvxTableCellView
{
public MyCustomCell(IntPtr handle) : base(handle)
{
}
public MyCustomCell(string bindingText) : base(bindingText)
{
this.Frame = new CGRect(0, 0, 100, 50);
TextField = new NSTextField(new CGRect(50, 0, 100, 50))
{
Editable = false,
Bordered = false
};
ImageView = new NSImageView(new CGRect(0, 0, 50, 50));
AddSubview(TextField);
AddSubview(ImageView);
this.Initialize(bindingText);
}
private string _imageUrl;
public string ImageUrl
{
get => _imageUrl;
set
{
_imageUrl = value;
ImageService.Instance.LoadUrl(_imageUrl).Into(ImageView);
}
}
}
And the table source:
public class MyTableSource : MvxTableViewSource
{
private string _bindingText;
public MyTableSource(NSTableView tableView, string bindingText) : base(tableView)
{
_bindingText = bindingText;
}
public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row)
{
if (ItemsSource == null)
return null;
var item = ItemsSource.ElementAt((int)row);
var view = new MyCustomCell(_bindingText);
if (view is IMvxDataConsumer bindable)
bindable.DataContext = item;
return view;
}
}
Then instead of using MvxTableViewSource in the first example, use your own MyTableSource instead:
var source = new MyTableViewSource(_tableView, "Text Name; ImageUrl Url");
_tableView.Source = source;
Where Name and Url are in the Item in the Items bound to the ItemsSource.

Using Messaging center xamarin forms PCL to set current objects value

I have a scenario where i create Entry Controls programmatically.
foreach (var control in FormInfo.FormElementsInfo)
{
case "textbox":
//Some code
break;
case "dropdown":
Entry objDropdown = new Entry();
objDropdown.HeightRequest = 40;
objDropdown.StyleId = Convert.ToString(control.ElementId);
objDropdown.SetBinding(Entry.TextProperty, "ElementValue",BindingMode.TwoWay);
objDropdown.BindingContext = control;
layout.Children.Add(objDropdown);
MessagingCenter.Subscribe<Picklists, string>(objDropdown, "PicklistSelected", (sender, arg) =>
{
objDropdown.Text = arg;
// I tried this too as this is two way binding. It didn't show the value.
//control.ElementValue = arg;
} );
break;
}
If i click on any entry it will open me a list view. Once i select the option in the list view it will populate that data in the Entry.
But this should show the selected value only in the current entry but it is changing the value in all the entry's.
How to avoid this situation. I want the selected value to be populated only in the current entry.
Any suggestion would be appreciated. Thank you.
=== More clear question=====
If we create n number of Entry controls programmatically with 2 way binding . Is it possible to change the single entry value on selecting something in other page? If yes how to achieve this?
FormInfo
public class FormInfo
{
public List<FormsElementInfo> FormElementsInfo { get; set; }
}
FormsElementInfo
public class FormsElementInfo : INotifyPropertyChanged
{
private string _elementValue;
public string ElementValue {
get => _elementValue;
set {
if(_elementValue != value)
{
_elementValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ElementValue"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Content Page
public class ListStackOverflow : ContentPage
{
private FormInfo _info = new FormInfo
{
FormElementsInfo = new List<FormsElementInfo>()
{
new FormsElementInfo { ElementValue = "test 1"},
new FormsElementInfo { ElementValue = "test 2"},
new FormsElementInfo { ElementValue = "test 3"},
new FormsElementInfo { ElementValue = "test 4"},
}
};
private StackLayout _stack = new StackLayout();
private List<string> _source = new List<string>
{
"output 1","output 2","output 3","output 4",
};
public ListStackOverflow()
{
//BindingContext = _info;
foreach(var c in _info.FormElementsInfo)
{
Entry tempEntry = new Entry
{
HeightRequest = 40,
Placeholder = c.ElementValue,
BindingContext = c
};
tempEntry.SetBinding(Entry.TextProperty, "ElementValue");
_stack.Children.Add(tempEntry);
}
ListView _lv = new ListView { ItemsSource = _source };
_lv.ItemSelected += Lv_ItemSelected;
_stack.Children.Add(_lv);
Content = _stack;
}
private void Lv_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var selectedElement = e.SelectedItem.ToString();
var index = _source.IndexOf(selectedElement);
var entry = _info.FormElementsInfo[index];
entry.ElementValue = selectedElement;
}
}
Output
Selecting the corresponding index in the listview will update "ElementValue" for the same index.
First of all Thank you #Joshua Poling for taking time to help me.
I think MessagingCenter is not suitable for this approach.
I am assigning a unique styleId to each element that i create.That basically stores the position in the stack layout.
I have written a delegate which returns the selected value and also the position of the element. As the element is always an Entry that fires this event. I used the below code to achieve this.
Entry myentry = (Xamarin.Forms.Entry)layout.Children[src.ElementId];

Refreshing ListView in Xamarin.Forms for UWP

Details of my System is
Operating System : Windows 10 Pro N
Visual Studio Enterprise 2015
Xamarin.Forms 2.3.1..114
I have created a Tabbed view in which I am navigating to new page using Navigation.PushModalAsync method. In the view, I have a listview with custom Data Template. The Data Template is of ViewCell which contains two Images and one label.
What I am trying to do is when ever a cell is selected, I am showing the Image for checked row and when other row is selected then hiding the other row images and showing the currently selected image.
When first time view loads, I am setting the first row as selected and everything working good, but when I am selecting any other row then ListView is not refreshing. The Image IsVisible property is set correctly but it is not reflecting on the List.
See below code for reference
Code for the ListView
var listView = new ListView();
listView.ItemsSource = StaticData.ListData;
listView.ItemTemplate = new DataTemplate(typeof(CustomDataCell));
listView.VerticalOptions = LayoutOptions.FillAndExpand;
listView.BackgroundColor = Color.White;
listView.SeparatorVisibility = SeparatorVisibility.Default;
listView.RowHeight = 30;
listView.SeparatorColor = Color.White;
listView.ItemTapped += (sender, e) =>
{
if (e == null) return;
selectedValue = (e.Item as ValiditySchema).Value;
SelectValidityItem(listView,selectedValue); // In this method I am setting the IsSelected property to true and other rows IsSelected property to false.
};
Code for CustomDataCell
public class CustomDataCell : ViewCell
{
public Label CellText { get; set; }
public BoxView ImageDetail { get; set; }
public Image CheckedImage { get; set; }
public CustomDataCell()
{
CellText = new Label();
CellText.FontAttributes = FontAttributes.Bold;
CellText.SetBinding(Label.TextProperty, "Text");
CellText.VerticalOptions = LayoutOptions.Center;
CellText.HorizontalOptions = LayoutOptions.Start;
CellText.TextColor = Color.Black;
ImageDetail = new BoxView();
ImageDetail.WidthRequest = 20;
ImageDetail.HeightRequest = 10;
ImageDetail.SetBinding(BoxView.BackgroundColorProperty, "ColorName");
//declaring image to show the row is selected
CheckedImage = new Image();
CheckedImage.Source = "Images/checked.png";
CheckedImage.HorizontalOptions = LayoutOptions.CenterAndExpand;
CheckedImage.VerticalOptions = LayoutOptions.Center;
CheckedImage.SetBinding(Image.IsVisibleProperty, "IsSelected");
var ContentCell = new StackLayout();
ContentCell.Children.Add(ImageDetail);
ContentCell.Children.Add(CellText);
ContentCell.Children.Add(CheckedImage);
ContentCell.Spacing = 5;
ContentCell.Orientation = StackOrientation.Horizontal;
var maiCell = new StackLayout();
maiCell.Orientation = StackOrientation.Vertical;
maiCell.Children.Add(ContentCell);
View = maiCell;
}
}
In order for the ListView to know that items in your ItemsSource have changed you need to raise a INotifyPropertyChanged event on that specific item.
Usually instead of binding the data directly to the ListView, you would rather have a ViewModel representation for each item, following the MVVM pattern:
View <-> ViewModel <-> Model
So what you need to do is to create a ViewModel for your items in StaticData.ListData:
public class ListItemViewModel : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set {
_isSelected = value;
OnPropertyChanged();
}
}
// more properties here...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then you can bind the IsSelected property to your image's Visibility property.
This way when you change IsSelected in your ViewModel, the correct event gets fired and the ListView now knows that something changed and that it needs to refresh the view.

Resources