ObservableCollection don't Binding
Hello guys,
I need help in one question of ObservableCollection using in Xamarin Forms.
I need build one "Custom ListView" for TabIndex works correctly in my screen.
For this i create in code behind:
- ScrollView
- Label
- Entry
Screen rendering is works ok, only my problem is on binding in one ObservableCollection.
I my tests one property single string using OnPropertyChanged works correctly, but using ObservableCollection dont works. I tried using CollectionChanged but withour success.
Someone having same problem? Exist solution for this?
ViewModel:
private ObservableCollection<RawData> rawDataList;
public ObservableCollection<RawData> RawDataList
{
get { return rawDataList; }
set { rawDataList = value; OnPropertyChanged("RawDataList"); }
}
Code Behind
private void BuildRawDataTemplate()
{
var grid = BuildGrid();
#region Templates
grid.Children.Add(BuildScheduleHeaderTitleTemplate(), 0, 0); // Posição: Coluna: 0 | Linha: 0
grid.Children.Add(BuildScheduleDetailsSubtitleTemplate("Dados Brutos"), 0, 1); // Posição: Coluna: 0 | Linha: 1
#endregion
#region RawData
var scrollView = BuildScrollView();
var stackLayout = BuildStackLayout("DefaultLight", 10, StackOrientation.Vertical, null, LayoutOptions.Center);
rawDataCount = scheduleViewModel.RawDataList.Count();
foreach (var item in scheduleViewModel.RawDataList.Select((rawData, i) => new { i, rawData }))
{
stackLayout.Children.Add(GenerateLabel(item.rawData.Identification));
if (item.rawData.InfoTypeId == (int)InfoType.Equipment)
stackLayout.Children.Add(GenerateEquipmentsPicker(item.rawData, item.i, item.i == 0, rawDataCount == (item.i + 1)));
else
stackLayout.Children.Add(GenerateEntry(item.rawData, item.i, item.i == 0, rawDataCount == (item.i + 1)));
}
scrollView.Content = stackLayout;
grid.Children.Add(scrollView, 0, 2); // Posição: Coluna: 0 | Linha: 2
#endregion
#region Buttons
var stackLayoutFooterButtons = BuildStackLayout(string.Empty, null, StackOrientation.Horizontal, LayoutOptions.End, null);
stackLayoutFooterButtons.Children.Add(GeneratePrimaryButton("RESULTADOS", scheduleViewModel.VisibleResultCommand, rawDataCount++, scheduleViewModel.SelectedSampleBase.Finished));
stackLayoutFooterButtons.Children.Add(GeneratePrimaryButton("CALCULAR", scheduleViewModel.CalculateRawDataCommand, rawDataCount++, !scheduleViewModel.SelectedSampleBase.Synced));
stackLayoutFooterButtons.Children.Add(GeneratePrimaryButton("SALVAR", scheduleViewModel.SaveRawDataCommand, rawDataCount++, !scheduleViewModel.SelectedSampleBase.Synced));
grid.Children.Add(stackLayoutFooterButtons, 0, 3); // Posição: Coluna: 0 | Linha: 3
#endregion
Content = grid;
SetFocusOnFirstEntry();
}
private Entry GenerateEntry(RawData rawData, int tabIndex, bool firstElement = false, bool hasNextTabIndex = false)
{
var entry = new Entry();
entry.TabIndex = tabIndex;
entry.Text = rawData.DisplayValue;
entry.SetBinding(Entry.TextProperty, nameof(rawData.DisplayValue), BindingMode.TwoWay);
entry.StyleId = $"RawDataDynamicEntry_{tabIndex}";
entry.IsTabStop = true;
entry.IsEnabled = rawData.NotSynced;
if (rawData.InfoTypeId == (int)InfoType.Integer)
entry.Behaviors.Add(new NumericValidationBehavior());
if (firstElement)
entry.Focus();
// Temp
var focusOnReturnBehavior = new FocusOnReturnBehavior();
focusOnReturnBehavior.HasNextTabIndex = hasNextTabIndex;
entry.Behaviors.Add(focusOnReturnBehavior);
return entry;
}
I am little confused by the question. But I believe you have problem adding the entries to listview. Your Observable collection data is not getting populated in Listview.
I wrote a post on creating multiselect listview. You just see how I'm creating a listview and binding the contents of observable collection to it.
Please see here: https://androidwithashray.blogspot.com/2018/03/multiselect-list-view-using-xamarin.html
Please note for listview, you don't need scrollview as listview auto scrolls if the contents are more to fit inside the screen.
Hope this helps!!
For resolve my problem i using:
Code-Behind
#region Events Changed
private void EntryTextChanged(object sender, TextChangedEventArgs e)
{
var entryChanged = (Entry)sender;
selectedRawData.DisplayValue = entryChanged.Text;
}
private void PickerSelectedIndexChanged(object sender, System.EventArgs e)
{
var pickerChanged = (Picker)sender;
selectedRawData.DisplayValue = selectedItem.Identification;
}
#endregion
Related
public DataTemplate CreateQuestionAnswerRadioButtonTemplate(string question, List<string> answers){
DataTemplate template = new DataTemplate(() =>
{
StackLayout parentLayout = new StackLayout()
{
Padding = new Thickness(20, 20, 20, 20),
HeightRequest = 500,
};
ScrollView surveyScrollView = new ScrollView()
{
Orientation = ScrollOrientation.Vertical,
};
StackLayout questionLayout = new StackLayout()
{
Padding = new Thickness(5, 5, 5, 5),
HeightRequest = 500,
};
Label questLabel = new Label();
questLabel.Text = question;
questLabel.TextColor = Color.FromHex("#EF4D80");
questLabel.FontAttributes = FontAttributes.Bold;
questLabel.FontSize = 18;
BindableRadioGroup radioGroup = new BindableRadioGroup(false);
radioGroup.ItemsSource = answers;
questionLayout.Children.Add(questLabel);
questionLayout.Children.Add(radioGroup);
surveyScrollView.Content = questionLayout;
parentLayout.Children.Add(surveyScrollView);
return parentLayout;
});
return template;
}
Adding these Data Templates to a List.
new CarouselView
{
Margin = new Thickness(0, 20, 0, 0),
ItemsSource = dataTemplates,
ItemTemplate = dataTemplates[0],
};
Now when I swipe the Carousel, How do I load dataTemplates[1 or 2 or 3] ??
I have a Next Button in which in am setting the item source of the Carousel View to dataTemplates[1] but the template does not get updated
Pls Suggest the right approach ?
dataTemplates = new List<DataTemplate>();
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_1, SurveyQuestion_1_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_3, SurveyQuestion_3_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_4, SurveyQuestion_4_Answers));
dataTemplates.Add(CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_5, SurveyQuestion_5_Answers));
You need a DataTemplateSelector for your CarouselView.
in your code behind of the page:
new CarouselView
{
Margin = new Thickness(0, 20, 0, 0),
ItemsSource = dataTemplates,
ItemTemplate = new SurveyDataTemplateSelector()
};
SurveyDataTemplateSelector
DataTemplate survey1Template;
DataTemplate survey3Template;
DataTemplate survey4Template;
DataTemplate survey5Template;
public SurveyDataTemplateSelector()
{
survey1Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_1, SurveyQuestion_1_Answers);
survey3Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_3, SurveyQuestion_3_Answers);
survey4Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_4, SurveyQuestion_4_Answers);
survey5Template = CreateQuestionAnswerRadioButtonTemplate(Constants.SurveyQuestion_5, SurveyQuestion_5_Answers);
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// Here you define which DataTemplate is selected, for example:
if (item == null)
{
return null;
}
SurveyAnswers answers = item as SurveyAnswers;
if (answers.question == 1)
{
return survey1Template;
}
else if (answers.question == 3)
{
return survey3Template;
}
else if (answers.question == 4)
{
return survey4Template;
}
else if (answers.question == 5)
{
return survey5Template;
}
return null;
}
Check this link for a good documentation by Xamarin: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/templates/data-templates/selector/
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];
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.
I'm trying to implement a solution to increase the size of a ListView Cell when tapped using Xamarin Forms (and custom renderers if required).
I'm still pretty new to C#, and the idea of data binding is still a little unclear to me, however, it seems like that is the way to go to solve this problem (perhaps something along the lines of binding the Height / HeightRequest properties of the cell?).
My attempts thus far have been unsuccessful.
If anyone could give me a push in the right direction it would be much appreciated.
Thank you!
ViewCell does not expose Height as a BindableProperty in Xamarin.Forms 1.4.2x
However if you create your own BindableProperty in your Model you can achieve changing the height still as shown below:-
Model:-
public class MenuItem2 : BindableObject
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<MenuItem2, string>(p => p.Text, default(string));
public static readonly BindableProperty CellHeightProperty = BindableProperty.Create<MenuItem2, int>(p => p.CellHeight, default(int));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public int CellHeight
{
get { return (int)GetValue(CellHeightProperty); }
set { SetValue(CellHeightProperty, value); }
}
}
XAML:-
<StackLayout>
<Button x:Name="cmdButton1" Text="Change Cell Heights" Clicked="cmdButton1_Clicked"/>
<ListView x:Name="lstItems" />
</StackLayout>
XAML Code-Behind:-
lstItems.HasUnevenRows = true;
lstItems.ItemTemplate = new DataTemplate(typeof(Classes.MenuCell2));
//
lstItems.ItemsSource = new List<MenuItem2>
{
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
};
If you don't set .HasUnevenRows you will not be able to change the cell height.
void cmdButton1_Clicked(object sender, EventArgs e)
{
Random objRandom = new Random();
//
var objItems = lstItems.ItemsSource;
//
foreach (MenuItem2 objMenuItem in objItems)
{
int intNewCellHeight = objRandom.Next(80, 160);
objMenuItem.CellHeight = intNewCellHeight;
objMenuItem.Text = "Cell Height = " + intNewCellHeight.ToString();
}
}
Custom ViewCell:-
public class MenuCell2 : ViewCell
{
public MenuCell2()
{
Label objLabel = new Label
{
YAlign = TextAlignment.Center,
TextColor = Color.Yellow,
};
objLabel.SetBinding(Label.TextProperty, new Binding("Text"));
StackLayout objLayout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.StartAndExpand,
Children = { objLabel }
};
Frame objFrame_Inner = new Frame
{
Padding = new Thickness(15, 15, 15, 15),
HeightRequest = 36,
OutlineColor = Color.Accent,
BackgroundColor = Color.Blue,
Content = objLayout,
};
Frame objFrame_Outer = new Frame
{
Padding = new Thickness(0, 0, 0, 10),
Content = objFrame_Inner
};
View = objFrame_Outer;
this.BindingContextChanged += MenuCell2_BindingContextChanged;
}
void MenuCell2_BindingContextChanged(object sender, EventArgs e)
{
MenuItem2 objMenuItem = (MenuItem2)this.BindingContext;
objMenuItem.PropertyChanged += objMenuItem_PropertyChanged;
}
void objMenuItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CellHeight":
this.Height = (this.BindingContext as MenuItem2).CellHeight;
(this.View as Frame).ForceLayout();
break;
}
}
Remember to call ForceLayout on the root element of the ViewCell's View property, so it can redraw correctly.
This will give you a result something similar to the following (tested only on WindowsPhone at present):-
In order to do it on a ViewCell being tapped, on the XAML Page add:-
lstItems.ItemTapped += lstItems_ItemTapped;
and then change the model for the item to something like this:-
void lstItems_ItemTapped(object sender, ItemTappedEventArgs e)
{
(e.Item as MenuItem2).CellHeight = 200;
}
Xamarin now has an official example of doing this right within xaml and xaml code behind:
Overview:
https://developer.xamarin.com/samples/xamarin-forms/UserInterface/ListView/DynamicUnevenListCells/
Code:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/DynamicUnevenListCells
I have a list picker which is displayed in my phone application page.I have created list picker in starting of class,and i am adding the list picker in the phoneApplicationPage_loaded() method.When the page is launched the first time, ,the scenario works perfectly and its navigates further to second page.When i navigate back to previous page(containing list picker),it shows Invalid Operation Exception occured stating "Element is already the child of another element."
I want to know how to handle these scenarios?
Code is below
namespace My.Design
{
public partial class myclass : PhoneApplicationPage
{
String[] values = null;
ListPicker picker = new ListPicker();
StackPanel sp;
StackPanel mainFrame;
String statementInfo = "";
public myclass()
{
InitializeComponent();
}
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Phone Application Page Loaded_>>>>>>");
List<String> source = new List<String>();
displayUI();
}
public void displayUI()
{
Debug.WriteLine("About to display UI in miniStatement");
Debug.WriteLine("<-------------Data--------->");
Debug.WriteLine(statementInfo);
Debug.WriteLine("<-------------Data--------->");
int count = VisualTreeHelper.GetChildrenCount(this);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(this, i);
string childTypeName = child.GetType().ToString();
Debug.WriteLine("Elements in this Child" + childTypeName);
}
}
List<String> source = new List<String>();
String[] allParams = ItemString.Split('#');
source.Add("PleaseSelect");
for (int i = 0; i < allParams.Length; i++)
{
Debug.WriteLine("All Params Length" + allParams[i]);
if (!(allParams[i].Equals("") && (!allParams[i].Equals(null))))
{
if (values != null)
{
Debug.WriteLine("Values length" + values.Length);
values[values.Length] = allParams[i];
}
else
{
Debug.WriteLine("Allparams Length" + allParams[i]);
source.Add(allParams[i]);
}
}
}
//picker = new ListPicker();
this.picker.ItemsSource = source;
mainFrame = new StackPanel();
TextBlock box = new TextBlock();
box.Text = "> DEmoClass";
box.FontSize = 40;
mainFrame.Children.Add(box);
Canvas canvas = new Canvas();
StackPanel sp = new StackPanel();
TextBlock box1 = new TextBlock();
box1.Text = "Number";
box1.HorizontalAlignment = HorizontalAlignment.Center;
box1.FontSize = 40;
SolidColorBrush scb1 = new SolidColorBrush(Colors.Black);
box1.Foreground = scb1;
sp.Children.Add(box1);
picker.Width = 400;
picker.Height = 150;
sp.Children.Add(picker);
Canvas.SetTop(sp, 150);
canvas.Children.Add(sp);
mainFrame.Children.Add(canvas);
this.ContentPanel1.Children.Add(mainFrame);
}
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
/*
Debug.WriteLine("OnNavigatingFrom>>>.>>MainPage");
if (sp != null)
{
sp.Children.Remove(picker);
}*/
base.OnNavigatingFrom(e);
}
}
}
If you are not intending to update the listpicker after navigating back from the second page add the following line in your Loaded event handler
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
this.Loaded -= PhoneApplicationPage_Loaded;
Debug.WriteLine("Phone Application Page Loaded_>>>>>>");
List<String> source = new List<String>();
displayUI();
}
i don't know why you can not use that case when app resume from tombstoned.
error happened because when you back to your page , loaded event runs again.
by the way,
Application_Activated 's argument can tell you app resumes from tombstoned or not--.
if (e.IsApplicationInstancePreserved)
{
IsTombstoning = false;
}
else
{
IsTombstoning = true;
}
I'm curious why you're creating it in code and not leaving it in XAML? Also the error is coming from the fact that you're attempting to add it twice into a location that can probably only have a single content element. What's the higher level problem you're trying to solve?