Bind object data to Int xamarin forms - xamarin

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.

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

Get ViewCell's model in constructor

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

Xamarin Prism cannot navigate with parameters

for my app i create my own buttons using a frame and adding a tapgesture to it. here i use the navigation of prism to go to a specific page with a parameter. however. the viewmodel i'm going to does not trigger the Navigated to method. here is some code.
during debugging it seems that the adding of the parameters is no problem. however the constructor for the viewmodel is called instead.
button
public class FolderButton : Frame
{
public FolderButton(Folder folder, INavigationService navigationService)
{
var navParams = new NavigationParameters();
navParams.Add("folder", folder);
GestureRecognizers.Add(new TapGestureRecognizer()
{
Command = new Command(async () => { await navigationService.NavigateAsync("FolderInventory", navParams); }),
});
BackgroundColor = Color.CornflowerBlue;
var thickness = new Thickness();
thickness.Bottom = 10;
thickness.Left = 10;
thickness.Right = 10;
thickness.Top = 10;
Margin = thickness;
CornerRadius = 5;
var completeStack = new StackLayout();
var imgStack = new StackLayout();
imgStack.Padding = thickness;
imgStack.Children.Add(new Image { Source = "folder.png" });
completeStack.Children.Add(imgStack);
var lblStack = new StackLayout();
lblStack.Padding = thickness;
lblStack.Children.Add(new Label
{
Text = folder.Name,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Start
});
completeStack.Children.Add(lblStack);
Content = completeStack;
}
}
called viewmodel
public class FolderInventoryViewModel : BindableBase, INavigatedAware
{
public Folder Folder => _folder;
private readonly INavigationService _navigationService;
private Folder _folder;
private readonly ISQLiteService _sqlService;
private List<Frame> _buttons;
public List<Frame> Buttons
{
get => _buttons;
set => _buttons = value;
}
public FolderInventoryViewModel(Folder folder, INavigationService navigationService, ISQLiteService sqlService)
{
_folder = folder;
_sqlService = sqlService;
_navigationService = navigationService;
GetItemsForFolder();
}
private void GetItemsForFolder()
{
var itemList = _sqlService.GetAllFolderItems(Folder.Name);
foreach (var item in itemList)
{
var itemButton = new ItemButton(_navigationService, item);
_buttons.Add(itemButton);
}
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
if (parameters["folder"] is Folder folder)
{
_folder = folder;
}
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters["folder"] is Folder folder)
{
_folder = folder;
}
}
}
This is not the essence of using the framework. To properly use the Prism with its NavigationParameters you first properly maintain the MVVM idea behind it.
E.g.
<Button Command="{Binding testCommand}" text="TestButton"/>
Your ViewModel (Pardon about this, you need to inject NavigationService to your ViewModel's constructor)
private DelegateCommand _testCommand;
public DelegateCommand testCommand =>
_testCommand?? (_testCommand= new DelegateCommand(ExecuteTest));
private void ExecuteTest()
{
NavigationParameters navigationParameters = new NavigationParameters();
navigationParameters.Add("yourParameterId", value);
NavigationService.NavigateAsync("YourPage", navigationParameters);
}
And then onto your next page
Inherit INavigationAware to your NextPage : YourNextPage: BaseViewModel, INavigationAware
INavigationAware has 3 methods NavigatingTo, NavigatedTo, NavigatedFrom
Inside OnNavigatedTo you can call the parameters you have passed
public void OnNavigatedTo(NavigationParameters parameters)
{
//You can check if parameters has value before passing it inside
if(parameters.ContainsKey("YourParameterId")
{
yourItem = (dataType)parameters[YourParameterId];
}
}
Also note: The constructor will always be called first before the Navigating methods

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