Get ViewCell's model in constructor - xamarin

There are some questions without any real answer for this use case:
How can I retrieve the current ViewCell's model in its constructor?
What I'd like to have access, for instance, to the background of the cell, specified in the model.
I have tried many solutions, like binding string properties to fake Label objects inside the cell, but both in the constructor and in the OnAppearing method they are null.
For example, let's say I have a custom cell that needs to display a title, a description, and an optional icon. If the icon resource string is empty, it will display a default one:
public class ListItem
{
public string title { get; set; }
public string description { get; set; }
public string icon { get; set; }
}
public class CustomCell : ViewCell
{
public CustomCell()
{
StackLayout cell = new StackLayout();
StackLayout text = new StackLayout()
{
Orientation = StackOrientation.Vertical
};
Label titleLabel = new Label();
Label descriptionLabel = new Label();
titleLabel.SetBinding(Label.TextProperty, "title");
descriptionLabel.SetBinding(Label.TextProperty, "description");
Image image = new Image();
// Custom icon
if (titleLabel.Text.Length != 0)
{
image.Source = ImageSource.FromResource(???);
}
else
{
image.Source = ImageSource.FromResource("myproject.icons.default.png");
}
text.Children.Add(titleLabel);
text.Children.Add(descriptionLabel);
cell.Children.Add(text);
cell.Children.Add(image);
View = cell;
}
}
Is there a way?

You can write a custom view cell that takes arguments or model in its constructor like this
public class CustomViewCell : ViewCell
{
public Bar bar;
pulic CustomViewCell(Foo foo)
{
bar = foo;
}
public GetCurrentModel()
{
return bar;
}
}

Related

Xamarin Forms Collection view ItemsSource Binding not updating the UI

Xamarin.Forms 4.0 collection view ItemsSource binding is not working as expected if I set the binding in code behind. The items are displayed based on the initial value of the source collection but, the UI is not updating when the source collection is updated. Same is working if I set the binding in xaml.
Code behind:
public MainPage()
{
this.InitializeComponent();
this.BindingContext = this.mainViewModel = new MainViewModel();
CollectionView courseCollectionView = new CollectionView
{
ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem,
ItemTemplate = new DataTemplate(typeof(ItemView))
};
courseCollectionView.SetBinding(CollectionView.ItemsSourceProperty,
new Binding() { Source = this.mainViewModel.CountryNames, Mode =
BindingMode.TwoWay });
courseCollectionView.ItemsLayout = new GridItemsLayout(4,
ItemsLayoutOrientation.Vertical);
this.CoursesStack.Children.Clear();
this.CoursesStack.Children.Add(courseCollectionView);
}
View Model Property which is using for ItemsSource Binding:
ObservableCollection<Country> countryNames;
public ObservableCollection<Country> CountryNames
{
get => this.countryNames;
set
{
this.countryNames = value;
RaisePropertyChanged("CountryNames");
}
}
Expected: View should be updated as per the changes made to the ObsevableCollection (add/delete items from collection) which is bound to the ItemsSource Property.
Actual: View is not updated with changes to the ObservableCollection.
I believe your binding is wrong. Try:
courseCollectionView.SetBinding(CollectionView.ItemsSourceProperty, nameof(mainViewModel.CountryNames));
You need to specify the Path (mainViewModel.CountryNames) and not the Source
About updating UI when using CollectionView, I do one sample that you can take a look:
public partial class MainPage : ContentPage
{
public mainvidemodel viewmodel { get; set; }
public MainPage()
{
InitializeComponent();
viewmodel = new mainvidemodel();
this.BindingContext = viewmodel;
CollectionView collectionView = new CollectionView();
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "countries");
collectionView.ItemTemplate = new DataTemplate(() =>
{
StackLayout stacklayout = new StackLayout();
Label label1 = new Label();
label1.SetBinding(Label.TextProperty,"Id");
Label label2 = new Label();
label2.SetBinding(Label.TextProperty, "Name");
Label label3 = new Label();
label3.SetBinding(Label.TextProperty, "caption");
stacklayout.Children.Add(label1);
stacklayout.Children.Add(label2);
stacklayout.Children.Add(label3);
return stacklayout;
});
Button btn = new Button() { Text = "btn", WidthRequest = 200, HeightRequest = 50 };
btn.Clicked += Btn_Clicked;
stacklayout1.Children.Add(collectionView);
stacklayout1.Children.Add(btn);
}
private void Btn_Clicked(object sender, EventArgs e)
{
viewmodel.countries.Add(new Country() { Id = 8, Name = "country8", caption = "caption 8" });
}
}
public class mainvidemodel
{
public ObservableCollection<Country> countries { get; set; }
public mainvidemodel()
{
countries = new ObservableCollection<Country>()
{
new Country(){Id=1,Name="country1",caption="caption 1"},
new Country(){Id=2,Name="country2",caption="caption 2"},
new Country(){Id=3,Name="country3",caption="caption 3"},
new Country(){Id=4,Name="country4",caption="caption 4"},
new Country(){Id=5,Name="country5",caption="caption 5"},
new Country(){Id=6,Name="country6",caption="caption 6"},
new Country(){Id=7,Name="country7",caption="caption 7"},
};
}
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public string caption { get; set; }
}
In this case try to extend the MainPage from INotifyPropertyChanged and instead of RaisePropertyChanged("CountryNames") in set property, use OnPropertyChanged()

Getting carousel data back into object

I am creating a dynamic carousel view in my Xamarin app, and so far it works just fine, but...
My carousel contains students and each carousel page is a link to a student info page. I want to be able to set an object with the current selected student for me to grab on all the students subpages (hope this makes sense :-/).
My script is as follow.
StudentModel:
public class StudentData
{
public string id { get; set; }
public string name { get; set; }
public string course { get; set; }
public string schoolclass { get; set; }
public string profileImage { get; set; }
}
CarouselPart:
ObservableCollection<StudentData> collection = new ObservableCollection<StudentData>();
collection.Add(new StudentData { name = "Soren Hanson", schoolclass = "4. grade", course = "Math" });
collection.Add(new StudentData { name = "Michael Trane", schoolclass = "7. grade", course = "English" });
collection.Add(new StudentData { name = "Tammy Jump", schoolclass = "1. grade", course = "English" });
DataTemplate template = new DataTemplate(() =>
{
var imageBtn = new Button();
imageBtn.Image = "Images/default.png";
imageBtn.Clicked += delegate {
// ADDING THE CURRENT STUDENT TO MY CURRSTUDENT OBJECT //
//App.currStudent = collection.......
var menteeOptions = new MenteeOptions();
imageBtn.Navigation.PushAsync(menteeOptions);
}
}
carousel.ItemTemplate = template;
carousel.PositionSelected += pageChanged;
carousel.ItemsSource = collection;
Hoping for help with this and thanks in advance :-)
Well you question was quite confusing at first but I will answer it from what I understood you want to keep the selected student object with you on your subpages:
So you can either use SQLite for it which can be found here
Or you can just maintain it as a static system object on your app.xaml.cs and use it
Something like this
App.Xaml.cs
public static object YourDataHolder {get; set;}
In your clicked event:
imageBtn.Clicked += delegate {
// ADDING THE CURRENT STUDENT TO MY CURRSTUDENT OBJECT //
App.YourDataHolder = _yourCollection;
var menteeOptions = new MenteeOptions();
imageBtn.Navigation.PushAsync(menteeOptions);
}
The use it something like this :
fooObject=App.YourDataHolder as FooType;

MVVMCross with multiple types of CollectionViewCell and data binding issue

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();

Add resource/import image to custom UserControl attribute in VS Designer via Dialog(s)

My aim is to create a custom control displaying some images, which can be added/exchanged by the user of that control. So, if it is added to a Form, the GUI designer should be able to change some or all images provided by the control editing the appropriate attribute.
In my Test-Project I have a simple control with 4 Attributes:
public Image MyImage { get; set; } = null;
public List<int> MyListOfInt { get; set; } = new List<int>();
public List<Image> MyListOfImages { get; set; } = new List<Image>();
public ImageList MyImageList { get; set; } = new ImageList();
Using this control in a Windows Form Project, clicking on
MyImage brings up the 'Select resource' dialog. OK
MyListOfInt brings up the 'Int32 Collection Editor' dialog. OK
MyListOfImages brings up the 'Image Collection Editor' dialog, but using 'Add' button shows message:
'Cannot create an instance of System.Drawing.Image because it is an
abstract class.'
MyImageList shows an emtpy list, which cannot be edited.
My question is, if it's possible to tell VS Designer to use the 'Select resource' dialog when clicking 'Add' button and what needs to be done?
Starting from Marwie's comment, I was able to solve the problem.
There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:
The collection must implement the IList interface (inheriting from System.Collections.CollectionBase is in most of the cases the best option).
The collection must have an Indexer property.
The collection class must implement one or both of the following methods: Add and/or AddRange
So I created a class 'ImageItem' containing
an image
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
a name (optional)
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
a value (optional)
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
and a collection 'ImageCollection' holding instances of this class according to the conditions mentioned above:
public class ImageCollection : CollectionBase
public ImageItem this[int i]
public ImageItem Add(ImageItem item)
Then I created a control containing only this collection, initialized with one image:
public partial class MyControl: UserControl
{
public MyControl() {
InitializeComponent();
}
private ImageCollection m_MyImageCollection = new ImageCollection()
{ new ImageItem(0, "Failure", Properties.Resources.Cross), new ImageItem(1, "OK", Properties.Resources.Tickmark) };
[Browsable(true), Category("A Test"), DisplayName("Image Collection (ImageCollection)"), Description("Edit image collection")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ImageCollection MyImageCollection {
get { return m_MyImageCollection; }
}
}
After compiling this code the designer shows that property. Now it is possible to add images using the common designer GUI controls.
I tried to change the default images compiled into this control when using it on my form, but I recognized, that the designer cannot remove content. It only stores the 'Add' action. So I modified the code to search within the collection for another item with the same ID. If there is one available, that instance is removed and replaced with the new one. Therefore I had to implement the AddRange method too.
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
So my final classes are:
public class ImageItem {
private int m_Value = -1;
private string m_Name = "ImageItem";
private Image m_Picture = null;
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
public ImageItem() { }
public ImageItem(int value, string name, Image image) {
this.m_Value = value;
this.m_Name = name;
this.m_Picture = image;
}
}
And ImageCollection:
public class ImageCollection : CollectionBase {
public ImageCollection() {}
public ImageItem this[int i]
{
get { return (ImageItem)this.InnerList[i]; }
set { this.InnerList[i] = value; }
}
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
public void Remove(ImageItem item) {
this.InnerList.Remove(item);
}
public bool Contains(ImageItem item) {
return this.InnerList.Contains(item);
}
public ImageItem[] GetValues() {
ImageItem[] item= new ImageItem[this.InnerList.Count];
this.InnerList.CopyTo(0, item, 0, this.InnerList.Count);
return item;
}
protected override void OnInsert(int index, object value) {
base.OnInsert(index, value);
}
}
I've got another answer from MSDN:
How to edit UserControl attribute of type ImageList in Designer PropertyGrid (add/remove/exchange images)
I will describe the idea in short. First create a new control with an ImageList attribute.
public partial class NewControl : UserControl {
public NewControl() {
InitializeComponent();
}
public ImageList MyImageList { get; set; } = null;
}
Then drag this control on any form.
Additionally drag an ImageList control from Toolbox onto this
form - I called it 'MyImages'.
Edit MyImages → Images with designer.
Assign 'MyImages' to NewControl's instance attribute MyImageList in property grid
The only drawback I see here is, that if the control already has an initialized ImageList attribute, the designer cannot handle it. If you try to edit MyImageList before you assigned another list, the designer shows the controls default list, that comes with the control. But it's not possible to edit that list.
This solution is much easier to deal with and much shorter than the first solution above, so that I prefer it more.

Bind object data to Int xamarin forms

I have a MasterDetailPage that creates several Department objects. I want to grab the current department number so I can use it to sort a list later on in my program. How do I go about doing that? I have tried binding it to a label and then getting the data from that (very hacky, I know) but that's the only thing I could think of.
Department[] departments = {
new Department ("D", 1),
new Department ("De", 7),
new Department ("G", 4),
new Department ("M", 9),
new Department ("Pr", 167),
new Department ("Fr", 187),
new Department ("H", 169),
new Department ("B", 11),
new Department ("S", 399),
new Department ("N", 407),
new Department ("O", 201),
new Department ("U", 023)
};
ListView listView = new ListView {
ItemsSource = departments
};
this.Master = new ContentPage {
Title = "Departments", // Title required!
Content = new StackLayout {
Children = {
header,
listView
}
}
};
DetailPage2 detailPage = new DetailPage2 ();
this.Detail = detailPage; //detail page is where I want to use deptNum for sorting
listView.ItemSelected += (sender, args) => {
// Set the BindingContext of the detail page.
this.Detail.BindingContext = args.SelectedItem;
// Show the detail page.
this.IsPresented = false;
};
// Initialize the ListView selection.
listView.SelectedItem = departments [0];
}
}
}
Then in my detailpage I want to be able to pull the departmentNumber out and use it as an int
using System;
using Xamarin.Forms;
namespace irisxamarin
{
public class Department :BindableObject
{
public Department (string name, int deptNumber)
{
this.Name = name;
this.DeptNum = deptNumber;
}
public string Name { private set; get; }
public int DeptNum { private set; get; }
public override string ToString ()
{
return Name;
}
}
}
And here is some logic in the detailpage. This is where I would like to grab the current deptNum.
namespace irisxamarin
{
public class DetailPage2 : ContentPage
{
public DetailPage2 ()
{
Request request = new Request ();
Button settingsButton = new Button {
Text = "Settings",
TextColor = Color.Gray
};
//......................
//code above and below
ListView itemsList = new ListView {
ItemsSource = request.GetList (deptNum) //USE INT HERE
};
itemsList.ItemSelected += (sender, args) => {
this.BindingContext = args.SelectedItem;
};
itemLabel.SetBinding (Label.TextProperty, "DeptNum");
//DeptNum is the data I want but not in a label, just the int val
var listFrame = new Frame {
Content = itemsList,
OutlineColor = Color.Silver,
};
Each page is just a C# class. You can pass a value to it the way you would do with any class - generally the easiest way is to
pass values in the constructor
or if the page already exists, create public properties and set the value via the setter
If you want to set a value globally for use throughout your app, you can create a static class that is available everywhere and set state values in that class.

Resources