How a user select to display 1 out of two (or more) fields on a single Label in Xamarin - data-binding

I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Displaying the binding field (Contact_Name), while second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail' .
Question posted before and Another user tried to help but it didn't work!
Model Class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
}
XAML Page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code Behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View Model Class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}

You could use IsVisible property to achieve that, not need to bind only one lable.
Therefore, binding Contact_Address and Contact_eMail with two lables in StackLayout as follows:
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_Address}" IsVisible="{Binding AddressVisible}" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_eMail}" IsVisible="{Binding EMailVisible}" LineBreakMode="WordWrap" />
</StackLayout>
Then in Contacts add two visiable proerty:
public class Contacts: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
private bool addressVisible;
public bool AddressVisible
{
set
{
if (addressVisible != value)
{
addressVisible = value;
OnPropertyChanged("AddressVisible");
}
}
get
{
return addressVisible;
}
}
private bool eMailVisible;
public bool EMailVisible
{
set
{
if (eMailVisible != value)
{
eMailVisible = value;
OnPropertyChanged("EMailVisible");
}
}
get
{
return eMailVisible;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in Contentpage, you could modify the visiable propery when button be clicked:
private void Display_Address(object sender, EventArgs e)
{
foreach(var item in CList )
{
item.AddressVisible = true;
item.EMailVisible = false;
}
}
private void Display_eMail(object sender, EventArgs e)
{
foreach (var item in CList )
{
item.AddressVisible = false;
item.EMailVisible = true;
}
}
Here is the effect:

Related

How to make a checkbox stay checked when i navigate through pages? Xamarin

I have this listview and in the listview I have an ItemTemplate with a DataTemplate and a ViewCell in which I have a checkbox named "box1". I want to make it stay checked when i switch pages, but i can't acess it via name because it is in a DataTemplate and in a ViewCell. I have tried to name all the controls down to the checkbox and get access to it like that, but it does not seem to work...
This is my xaml:
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True"
>
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal"
x:Name="mydt"
>
<ViewCell
x:Name="myvc"
>
<Grid BackgroundColor="Transparent"
x:Name="mygrid"
>
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe"
>
<StackLayout Orientation="Horizontal"
>
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal"
FontSize="15"
TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox
x:Name="box1"
IsChecked="{Binding Checked}"
Color="Green"
Margin="60, 0, 0, 0"
CheckedChanged="box1_CheckedChanged"
BindingContext="{Binding ., Mode=TwoWay}"
/>
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my event handler from the Content Page in cs:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
label_cal.Text = calories_consumed.ToString();
}
This is my updated Meal class using INotifyPropertyChanged:
public class Meal : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And this is my code behind for the ContentPage:
protected override async void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(await App.Database.GetMealAsync());
}
public static bool ch;
Event handler for the checkbox in the code behind:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
meal.IsChecked = ch;
label_cal.Text = calories_consumed.ToString();
}
To be short you can just save the value in Preferences or make a variable in a model that is static and bind that values OnAppearing
There are mainly two ways that you could store the checkbox's state.
1.You could use Json to Serialize and deserialize the model that has a IsChecked property that binding with the checkbox.For more details, you could refer to this thread.
Code in checkbox_CheckedChanged event:
private void checkbox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = (CheckBox)sender;
var selectMeal = checkbox.BindingContext as Meal;
selectMeal.Checked = e.Value;
//save the data and checkbox state,you could save the data as a json string
string json = JsonConvert.SerializeObject(blistView);
Preferences.Set("listmeals", json);
}
2.You could store the state of the checkbox using sqlite-net-pcl.Please refer to below MS official docs for more details.
And then retrieve the check state of the checkbox via OnAppearing Method.
protected override void OnAppearing()
{
// retrieve the check state of the checkbox in your sqlite database.
base.OnAppearing();
TodoItemDatabase database = await TodoItemDatabase.Instance;
listView.ItemsSource = await database.GetItemsAsync();
}
MS official docs:https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
The way you are connecting your class your view/xaml is incorrect. If you want to do it without MVVM you can go about it by creating a model that implements INotifyPropertyChanged and an ObservableCollection as listView itemssource.
Model class Meal could look like this:
public class Meal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
#region INotify property changed
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
The MainPage.xaml like this:
<StackLayout>
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal" x:Name="mydt" >
<ViewCell x:Name="myvc" >
<Grid BackgroundColor="Transparent"
x:Name="mygrid" >
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe">
<StackLayout Orientation="Horizontal">
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal" FontSize="15" TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox x:Name="box1" IsChecked="{Binding IsChecked}" Color="Green" Margin="60, 0, 0, 0" />
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Clicked="Button_Clicked" Text="Check status items"/>
</StackLayout>
And your MainPage.xaml.cs code behind like this:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(new List<Meal>
{
new Meal { Name = "Meal 01", Ingredients = "Ingredients 01", Calories = "250" },
new Meal { Name = "Meal 02", Ingredients = "Ingredients 02", Calories = "350" },
new Meal { Name = "Meal 03", Ingredients = "Ingredients 03", Calories = "450" }
});
}
/// <summary>
/// check items ischecked status
/// </summary>
private void Button_Clicked(object sender, EventArgs e)
{
foreach (var item in listView.ItemsSource)
{
if (item is Meal meal && meal.IsChecked)
System.Diagnostics.Debug.WriteLine($"{meal.Name} is checked");
}
}
}
Look at the other answer for saving the model and/or list. When saved the list can be retrieved in the OnAppearing and set instead of recreated as shown in this example. I've added a button on the bottom that prints out which Meals are selected in the output window. See screenshot:

How call OnPropertyChanged in double embedded data in xamarin?

I have a Content page, which contain CollectionView inside CarouselView.
First time when the page is loading the double embedded binding is work fine. Show everithing correctly.
But when i try to change embedded property value nothing happen. How update these properties?
formChooseElement.formViewerElements[0].formViewerElementAnswares[0].color=Color.Green;
Like:
<CarouselView ItemsSource="{Binding formViewerElements}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding text}" />
<CollectionView ItemsSource="{Binding formViewerElements}" >
<CollectionView.ItemTemplate>
<DataTemplate >
<StackLayout BackgroundColor="{Binding color}">
<Label Text="{Binding text}" >
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
BindingContext in code behind:
this.BindingContext = new FormViewerViewModell();
ViewModell:
public class FormViewerViewModell : INotifyPropertyChanged
{
public FormViewerViewModell()
{
GenerateData
}
private FormChooseElement FormChooseElement;
public FormChooseElement formChooseElement
{
get => FormChooseElement;
set
{
FormChooseElement = value;
OnPropertyChanged(nameof(FormChooseElement));
}
}
public ObservableCollection<FormViewerElement> formViewerElements
{
get => formChooseElement.formViewerElements;
set
{ formChooseElement.formViewerElements = value;
OnPropertyChanged(nameof(formChooseElement.formViewerElements));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
FormChooseElement
public class FormChooseElement
{
public ...
public ObservableCollection<FormViewerElement> formViewerElements { get; set; }
}
FormViewerElement
public class FormViewerElement
{
public ...
public ObservableCollection<FormViewerElementAnsware> formViewerElementAnswares { get; set; }
}
FormViewerElementAnsware
public class FormViewerElementAnsware
{
public ...
public Color color { get; set; };

A Single Label can display 2 Data fields alternately select by user

I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Display the binding field and second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail'
Model class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
public string DisplayField { get; set; }
}
XAML page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View model class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
Screen Shot
Screen Shot 2
If you want to display different value in ListView by user selected, I suggest you can use Picker to choose, I do one sample that you can take a look.
<ContentPage.Content>
<StackLayout>
<Picker x:Name="choose" SelectedIndexChanged="choose_SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Contact_Address</x:String>
<x:String>Contact_eMail</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<ListView
x:Name="listview1"
HasUnevenRows="True"
ItemsSource="{Binding items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label
FontSize="Medium"
LineBreakMode="WordWrap"
Text="{Binding Contact_Name}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selectedm}"
LineBreakMode="WordWrap"
Text="{Binding Contact_eMail}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selecteda}"
LineBreakMode="WordWrap"
Text="{Binding Contact_Address}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
public partial class Page31 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<Contacts> items { get; set; }
private Boolean _selecteda;
public Boolean selecteda
{
get { return _selecteda; }
set
{
_selecteda = value;
RaisePropertyChanged("selecteda");
}
}
private Boolean _selectedm;
public Boolean selectedm
{
get { return _selectedm; }
set
{
_selectedm = value;
RaisePropertyChanged("selectedm");
}
}
public Page31()
{
InitializeComponent();
items = new ObservableCollection<Contacts>();
for(int i=0;i<20;i++)
{
Contacts contact = new Contacts()
{
Contact_ID = i, Contact_Name = "cherry " + i, Contact_Address = "the street " + i, Contact_eMail = "cherry#outlook.com "+i
};
items.Add(contact);
}
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void choose_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex ==0)
{
selecteda = true;
selectedm = false;
}
else
{
selectedm = true;
selecteda = false;
}
}
}

Focus on Next Entry after max length of 1

I have created dynamic 20 entries and want to focus on next entry after user enter a digit and max length of entry is 1. The focus should be automatically move on next entry.I am sharing my code.Thanks in advance for help.
//model
public class CrossingUIModel
{
public int Id { get; set; }
public string FieldValue { get; set; }
}
//on change property
private ObservableCollection<CrossingUIModel> bindCrossingUIModel;
public ObservableCollection<CrossingUIModel> BindCrossingUIModel
{
get { return bindCrossingUIModel; }
set
{
bindCrossingUIModel = value;
OnPropertyChanged(nameof(BindCrossingUIModel));
}
}
//creating ui
public void CreateUI()
{
UserDialogs.Instance.ShowLoading();
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
UserDialogs.Instance.HideLoading();
}
//xml file
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand">
<Entry x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
ReturnType="Next" MaxLength="1" Keyboard="Numeric"
TextChanged="Fields_TextChanged" ></Entry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Since you had used Data-Binding , it would be better to handle all the logic in ViewModel .
in code behind
Define a custom Entry
public class MyEntry:Entry
{
public static readonly BindableProperty IsFocusProperty =BindableProperty.Create("IsFocus", typeof(bool), typeof(MyEntry), false,propertyChanged: OnChanged);
static void OnChanged(BindableObject bindable, object oldValue, object newValue)
{
var entry = bindable as MyEntry;
var focus = (bool)newValue;
if(focus)
{
entry.Focus();
}
else
{
entry.Unfocus();
}
}
public bool IsFocus
{
get { return (bool)GetValue(IsFocusProperty); }
set {
SetValue(IsFocusProperty, value);
}
}
public MyEntry()
{
this.Focused += MyEntry_Focused;
this.Unfocused += MyEntry_Unfocused;
}
private void MyEntry_Unfocused(object sender, FocusEventArgs e)
{
this.IsFocus = false;
}
private void MyEntry_Focused(object sender, FocusEventArgs e)
{
this.IsFocus = true;
}
}
in Model
public class CrossingUIModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string fieldValue;
public string FieldValue
{
get
{
return fieldValue;
}
set
{
if (fieldValue != value)
{
fieldValue = value;
OnPropertyChanged("FieldValue");
}
}
}
bool isFocus = false;
public bool IsFocus
{
get
{
return isFocus;
}
set
{
if (isFocus != value)
{
isFocus = value;
OnPropertyChanged("IsFocus");
}
}
}
}
in ViewModel
public class MyViewModel
{
public ObservableCollection<CrossingUIModel> BindCrossingUIModel { get; set; }
public MyViewModel()
{
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
foreach (CrossingUIModel model in BindCrossingUIModel)
{
model.PropertyChanged += Model_PropertyChanged;
}
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName== "FieldValue")
{
var model = sender as CrossingUIModel;
if(model.FieldValue.Length==1)
{
model.FieldValue = model.FieldValue.Substring(0, 1);
model.IsFocus = false;
int id = model.Id;
BindCrossingUIModel[id].IsFocus = true;
}
}
}
}
in xaml
Now we don't need to set MaxLength and TextChanged any more .
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout WidthRequest="100" HeightRequest="30" HorizontalOptions="FillAndExpand">
<local:MyEntry WidthRequest="80" BackgroundColor="LightBlue" HeightRequest="30" x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
IsFocus="{Binding IsFocus, Mode=TwoWay}"
ReturnType="Next" Keyboard="Numeric"
></local:MyEntry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
By the way , you could use Grid instead of StackLayout as the Parent Layout of the Entry.

Xamarin forms tabbed page not retrieving data from in OnAppearing

I retrieve data from the Azure database to show one of the tabbed pages. when calling the method from ViewModel in OnAppearing not retrieve data, but when click the button it retrieves and shows on the page.
Please advice If I have constructed ViewModel and view correctly? if so why it doesn't work. ?
Connection manager:
public partial class DatabaseManager
{
static DatabaseManager defaultInstance = new DatabaseManager();
MobileServiceClient client;
IMobileServiceTable<Person> personTable;
private DatabaseManager()
{
this.client = new MobileServiceClient(Constants.AzureMobileAppURL);
this.personTable = client.GetTable<Person>();
}
public static DatabaseManager DefaultManager
{
get
{
return defaultInstance;
}
private set
{
defaultInstance = value;
}
}
public MobileServiceClient CurrentClient
{
get { return client; }
}
}
Model:
public class Person
{
[JsonProperty(PropertyName = "FirstName")]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[JsonProperty(PropertyName = "DisplayName")]
public string DisplayName
{
get { return displayName; }
set { displayName = value; }
}
[JsonProperty(PropertyName = "LastName")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
ViewModel:
public class ProfilePageViewModel : ViewModelBase
{
DatabaseManager manager;
string firstName = "";
string lastName = "";
string displayName = "";;
IMobileServiceTable<Person> personTable;
public ProfilePageViewModel()
{
manager = DatabaseManager.DefaultManager;
this.personTable = manager.CurrentClient.GetTable<Person>();
RefreshCommand = new Command(
execute: async () =>
{
try
{
await GetProfileAsync();
}
catch
{
}
});
}
public async Task GetProfileAsync()
{
try
{
IEnumerable<Person> items = await personTable
.Where(pserson => pserson.Active)
.ToEnumerableAsync();
foreach (var item in items)
{
FirstName = item.FirstName;
LastName = item.LastName;
DisplayName = item.DisplayName;
}
}
catch (Exception e)
{
}
}
public string FirstName
{
private set { SetProperty(ref firstName, value); }
get { return firstName; }
}
public string LastName
{
private set { SetProperty(ref lastName, value); }
get { return lastName; }
}
public string DisplayName
{
private set { SetProperty(ref displayName, value); }
get { return displayName; }
}
public ICommand RefreshCommand { private set; get; }
}
View:
ProfilePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SLSNZ.Views.ProfilePage"
xmlns:controls="clr-
namespace:ImageCircle.Forms.Plugin.Abstractions;
assembly=ImageCircle.Forms.Plugin"
xmlns:local="clr-namespace:SLSNZ.ViewModels"
Title="Profile">
<ContentPage.Resources>
<ResourceDictionary>
<local:ProfilePageViewModel x:Key="viewModel">
</local:ProfilePageViewModel>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource">
<On Platform="iOS" Value="icon-profile.png" />
</OnPlatform>
</ContentPage.Icon>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<StackLayout BindingContext="{StaticResource viewModel}">
<Label Text="Display Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding DisplayName}"
VerticalOptions="Center"
HorizontalOptions="Start"
VerticalOptions="Start/>
<Label Text="First Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding FirstName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Label Text="Last Name"
TextColor="Gray"
FontSize="Small"
HorizontalOptions="Start" />
<Label Text="{Binding LastName}"
FontSize="Large"
HorizontalOptions="Start"
VerticalOptions="Start" />
<Button Text="Refresh"
Command="{Binding RefreshCommand}"
Grid.Row="0" Grid.Column="1"/>
</StackLayout>
</ContentPage>
View:
ProfilePage.cs
public partial class ProfilePage : ContentPage
{
ProfilePageViewModel viewModel;
public ProfilePage()
{
InitializeComponent();
viewModel = new ProfilePageViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
await viewModel.GetProfileAsync();
}
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName =
null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName
= null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
In your view by the time you await viewModel.GetProfileAsync(); The view will already render.
Your GetProfileAsync in the View Model does an await so will get the data then update it.
I suggest changing the IMobileServiceTable personTable to a property and implement a on Property change to notify the view that the data has changes.
So your viewmodel should implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Then when the Data Changes you can notify it in the view model like:
OnPropertyChanged("personTable");
Also in your view change your code to:
pre-initialize the viewmodel:
public ProfilePage()
{
InitializeComponent();
SetViewModel();
}
protected async void SetViewModel()
{
viewmodel = await viewModel.GetProfileAsync();
}
This way you wont block the UI thread and when you call the OnPropertyChnage it will notify your view to update.
UPDATE:
I have created a small sample Xamarin project for you to demonstrate how you can bind and notify the view of changes.
You had a few issues in your view as well where your DisplayName label was not closed properly and you had duplicate properties for HorizontalOptions in some labels.
Download this Xamarin sample. It had hard coded data but will show you the flow of setting the data and the Binding Context of the View without locking the UI thread.
https://github.com/loanburger/54430503

Resources