New at Xamarin and Xamarin.Forms.
I can't find any documentation on EntryCell.ClearValue(BindableProperty p)
I want to clear the second EntryCell when a user goes into the first EntryCell
firstcellEntryCell.Tapped += (sender, e) => secondEntryCell.ClearValue(EntryCell.TextProperty);
Does this make sense, or is this not the intended usage for EntryCell.ClearValue?
I have used the tableview example in the Xamarin Forms documentation for simplicity:
namespace EntryCellTest
{
public class EntryCellTestView : ContentPage
{
public EntryCellTestView ()
{
this.BindingContext = new EntryCellTestViewModel ();
Label header = new Label {
Text = "EntryCell",
Font = Font.BoldSystemFontOfSize (50),
HorizontalOptions = LayoutOptions.Center
};
var entryCell1 = new EntryCell {
Label = "EntryCell1:",
Placeholder = "Type Text Here"
};
entryCell1.SetBinding(EntryCell.TextProperty, new Binding("EntryText1", BindingMode.TwoWay));
var entryCell2 = new EntryCell {
Placeholder = "Type Text Here"
};
// entryCell2.SetBinding (EntryCell.TextProperty, "EntryText2",BindingMode.TwoWay);
entryCell2.SetBinding(EntryCell.TextProperty, new Binding("EntryText2", BindingMode.TwoWay));
entryCell2.SetBinding (EntryCell.LabelProperty, "EntryTextLabelProperty");
TableView tableView = new TableView {
Intent = TableIntent.Form,
Root = new TableRoot {
new TableSection {
entryCell1,
entryCell2
}
}
};
// Accomodate iPhone status bar.
this.Padding = new Thickness (10, Device.OnPlatform (20, 0, 0), 10, 5);
// Build the page.
this.Content = new StackLayout {
Children = {
header,
tableView
}
};
}
}
public class EntryCellTestViewModel : INotifyPropertyChanged
{
public EntryCellTestViewModel ()
{
this.EntryTextLabelProperty = "Just a test";
}
private string entryTextLabelProperty;
public string EntryTextLabelProperty {
get {
return entryTextLabelProperty;
}
set {
entryTextLabelProperty = value;
OnPropertyChanged ("EntryTextLabelProperty");
}
}
private string entryText1;
public string EntryText1 {
get {
return entryText1;
}
set {
entryText1 = value;
OnPropertyChanged ("EntryText1");
}
}
private string entryText2;
public string EntryText2 {
get {
return entryText2;
}
set {
entryText2 = value;
this.EntryText1 = "";
OnPropertyChanged ("EntryText2");
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged ([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
#endregion
}
}
Obviously you change around the bindings and setter logic to achieve the desired result.
Related
I have a Tabbed page in my Xamarin Forms app. There I have two children content pages. There are two pickers and a button in the toolbar of the Tabbed page. What I want is to populate data from the server according to the values, which is changing in the pickers. I am using the MVVM design pattern. How can I pass picker values to the children pages? I tried to use the MessagingCenter, It works sometimes but not all the time.
Tabbedpage
public partial class LifekpiRankingTabbedRM : ZeroTabbarHeight
{
public LifekpiRankingTabbedRM(string uRegion)
{
InitializeComponent();
BindingContext = new LifekpiRankingRmTabbedViewModel(uRegion);
Children.Add(new LifeKpiRankingViewRM(uRegion));
Children.Add(new LifeKpiRankingViewRMsBM(uRegion));
}
}
Tabbedpage VM
public class LifekpiRankingRmTabbedViewModel : BaseViewModelHelper
{
public MonthData2 SelectedMonth
{
get => _selectedMonth;
set
{
_selectedMonth = value;
GenerateData(_selectedMonth.mIndex, SelectedKpi).Await(CompletedTask, ErrorHandle);
OnPropertyChanged();
}
}
public string SelectedKpi
{
get => _selectedKpi;
set
{
_selectedKpi = value;
GenerateData(SelectedMonth.mIndex, _selectedKpi).Await(CompletedTask, ErrorHandle);
OnPropertyChanged();
}
}
public LifekpiRankingRmTabbedViewModel(string uRegion)
{
this.uRegion = uRegion;
SelectedMonth = GetCurrentMonth();
SelectedKpi = KpiList.First();
reportType = "Monthly";
ReportTypeTapCommand = new Command(ChangeTypeCommand);
}
private async Task GenerateData(int selectedMonth, string selectedKpi)
{
AcrDialogClass.ShowLoadingDialog("Loading...");
if (runCount > 2)
{
var resRmData = await _apiServices.GetLifeRegionalKpiWiseRanking(selectedMonth, selectedKpi, isCumulative);
var resBmForRmData = await _apiServices.GetLifeBranchKpiWiseRankingForRM(selectedMonth, selectedKpi, uRegion, isCumulative);
if (resRmData != null)
{
MessagingCenter.Send<App, string>(App.Current as App, "KPI_REGINAL_RANKING", resRmData);
}
if (resBmForRmData != null)
{
MessagingCenter.Send<App, string>(App.Current as App, "KPI_BM_RANKING_FOR_RM", resBmForRmData);
}
}
else
{
runCount++;
}
}
private MonthData2 GetCurrentMonth()
{
var months = Enumerable.Range(1, 12).Select(i => new { I = i, M = DateTimeFormatInfo.InvariantInfo.GetMonthName(i) });
foreach (var ss in months)
{
MonthList.Add(new MonthData2() { mIndex = ss.I, mName = ss.M });
}
return MonthList.Find(r => r.mIndex == dt.Month);
}
private void ChangeTypeCommand()
{
if (isCumulative)
{
reportType = "Monthly";
isCumulative = false;
}
else
{
reportType = "Cumulative";
isCumulative = true;
}
GenerateData(SelectedMonth.mIndex, SelectedKpi).Await(CompletedTask, ErrorHandle);
}
private void CompletedTask()
{
AcrDialogClass.HideLoadingDialog();
}
private void ErrorHandle(Exception ex)
{
AcrDialogClass.HideLoadingDialog();
}
}
Tab 1
public partial class LifeKpiRankingViewRM : ContentPage
{
private LifeKpiRankingViewModelRM vm;
public LifeKpiRankingViewRM(string uRegion)
{
InitializeComponent();
BindingContext = new LifeKpiRankingViewModelRM(uRegion);
vm = BindingContext as LifeKpiRankingViewModelRM;
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<App, string>(App.Current, "KPI_REGINAL_RANKING", (snd, arg) =>
{
vm.SetValues(arg);
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<App, string>(App.Current, "KPI_REGINAL_RANKING");
}
}
Tab 2
public partial class LifeKpiRankingViewRMsBM : ContentPage
{
private LifeKpiRankingViewModelBMForRM vm;
public LifeKpiRankingViewRMsBM(string uRegion)
{
InitializeComponent();
BindingContext = new LifeKpiRankingViewModelBMForRM(uRegion);
vm = BindingContext as LifeKpiRankingViewModelBMForRM;
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<App, string>(App.Current, "KPI_BM_RANKING_FOR_RM", (snd, arg) =>
{
vm.SetValues(arg);
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<App, string>(App.Current, "KPI_BM_RANKING_FOR_RM");
}
}
At first, you can try to get the child page's instance and then set its value.
If you set the tabbedpage in the app.cs with MainPage = new LifekpiRankingTabbedRM():
var tabbedpage = App.Current.MainPage as LifekpiRankingTabbedRM;
var childpage1 = tabbedpage.Children[0];
var vm1 = childpage1.BindingContext as LifeKpiRankingViewModelRM;
vm1.SetValues(...);
Generally speaking, when you have the instance of the TabbedPage, you can get its child page and update the child page's value.
In addition, you can show more details about the MessageCenter not work and we can help you go on using the MessageCenter.
I'm having trouble with the coding to properly listen for Firebase add or update events. My attempt below has the data loading into the Syncfusion Datagrid, but there is a weird glitch where when I click the mouse on the Datagrid and pull-down, the first record in my 4 record set gets added to the bottom of the Datagrid, showing a 5th record... if I update an element in the Datagrid, the change is not reflected in Firebase. If I add or change a value in firebase, it does not update in Datagrid. Any help to steer me in the right direction to get this to work would be appreciated. Here's the code:
the VisualStudio 2019
CookPage.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Chart_sample"
xmlns:gauge="clr-namespace:Syncfusion.SfGauge.XForms;assembly=Syncfusion.SfGauge.XForms"
xmlns:Syncfusion="clr-namespace:Syncfusion.SfDataGrid.XForms;assembly=Syncfusion.SfDataGrid.XForms"
mc:Ignorable="d"
x:Class="Chart_sample.Views.CookPage">
<StackLayout>
<Syncfusion:SfDataGrid x:Name="sfGrid">
</Syncfusion:SfDataGrid>
</StackLayout>
</ContentPage>
CookPage.xaml.cs
using Chart_sample.Services;
using Syncfusion.SfDataGrid.XForms;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Chart_sample.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CookPage : ContentPage
{
FirebaseHelper firebaseHelper = new FirebaseHelper();
public CookPage()
{
InitializeComponent();
// for Syncfusion DataGrid
firebaseHelper.listenForEvents();
sfGrid.ItemsSource = ViewProgramModel._returnedEvents;
sfGrid.ColumnSizer = ColumnSizer.Star;
sfGrid.AllowEditing = true;
sfGrid.NavigationMode = NavigationMode.Cell;
sfGrid.SelectionMode = Syncfusion.SfDataGrid.XForms.SelectionMode.Single;
}
}
}
FirebaseHelper.cs
using Firebase.Database;
using Firebase.Database.Query;
using System;
using System.Linq;
namespace Chart_sample.Services
{
public class FirebaseHelper
{
internal ViewProgramModel ViewProgramModel { get; set; }
FirebaseClient firebase = new FirebaseClient("https://pelletpirate.firebaseio.com/");
private readonly string ChildProgram = "ControllerData/Pellet_Pirate_1/Program";
public static IDisposable returnedEvents;
public async void listenForEvents()
{
ViewProgramModel._returnedEvents.Clear();
var programs = await firebase.Child(ChildProgram).OnceAsync<ViewProgramModel>();
for (int i = 0; i < programs.Count; i++)
{
ViewProgramModel._returnedEvents.Add(programs.ElementAt(i).Object);
}
returnedEvents = firebase.Child(ChildProgram).OrderByKey().AsObservable<ViewProgramModel>()
.Subscribe(eventReceived =>
{
if (eventReceived.EventType == Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate)
{
var found = ViewProgramModel._returnedEvents.FirstOrDefault(i => i._KEY == eventReceived.Key);
if (found == null)
{
// not in observable collection, add it
ViewProgramModel._returnedEvents.Add(eventReceived.Object);
}
else
{
// event was updated
int tempIndex = ViewProgramModel._returnedEvents.IndexOf(found);
ViewProgramModel._returnedEvents[tempIndex] = eventReceived.Object;
}
}
});
}
}
}
ViewProgrammodel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace Chart_sample
{
public class ViewProgramModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _KEy;
private string MOde;
private int TArget;
private string TRigger;
private int TRiggerVAlue;
public string _KEY
{
get { return _KEy; }
set
{
this._KEy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("_KEY"));
}
}
public string MODE
{
get { return MOde; }
set
{
this.MOde = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MODE"));
}
}
public int TARGET
{
get { return TArget; }
set
{
this.TArget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TARGET"));
}
}
public string TRIGGER
{
get { return TRigger; }
set
{
this.TRigger = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TRIGGER"));
}
}
public int TRIGGERVALUE
{
get { return TRiggerVAlue; }
set
{
this.TRiggerVAlue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TRIGGERVALUE"));
}
}
public static ObservableCollection<ViewProgramModel> _returnedEvents = new ObservableCollection<ViewProgramModel>();
}
}
I edit your demo, I achieve the update, Add, delete function.
Here is running GIF.
I change your ViewProgramModel like following code. Just move the _returnedEvents to the FirebaseHelper.cs
namespace Chart_sample
{
public class ViewProgramModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _KEy;
private string MOde;
private int TArget;
private string TRigger;
private int TRiggerVAlue;
public string _KEY
{
get { return _KEy; }
set
{
this._KEy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("_KEY"));
}
}
public string MODE
{
get { return MOde; }
set
{
this.MOde = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MODE"));
}
}
public int TARGET
{
get { return TArget; }
set
{
this.TArget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TARGET"));
}
}
public string TRIGGER
{
get { return TRigger; }
set
{
this.TRigger = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TRIGGER"));
}
}
public int TRIGGERVALUE
{
get { return TRiggerVAlue; }
set
{
this.TRiggerVAlue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TRIGGERVALUE"));
}
}
}
Here is FirebaseHelper.cs, Note: I achieve the update function just for the TARGET Column, I suggest your to add a Primary-key(Auto-increase) for every record in your database to achieve your search function.
public class FirebaseHelper
{
public ObservableCollection<ViewProgramModel> _returnedEvents { get; set; }
public FirebaseHelper()
{
_returnedEvents = new ObservableCollection<ViewProgramModel>();
}
// internal ViewProgramModel MyViewProgramModel { get; set; }
FirebaseClient firebase = new FirebaseClient("https://xxxxxxxxxx.firebaseio.com/");
private readonly string ChildProgram = "ControllerData/xxxxxx_Pirate_1/Program";
public static IDisposable returnedEvents;
public async Task AddViewProgramModel()
{
//new ViewProgramModel() { MODE="test", TARGET=122, TRIGGER="122", TRIGGERVALUE=333, }
await firebase
.Child(ChildProgram)
.PostAsync( new ViewProgramModel() { MODE = "test", TARGET = 122, TRIGGER = "122", TRIGGERVALUE = 333, });
GetAllData();
}
public async Task UpdateViewProgramModel(ViewProgramModel viewProgramModel , string oldValue)
{
var toUpdatePerson = (await firebase
.Child(ChildProgram)
.OnceAsync<ViewProgramModel>()).FirstOrDefault(a => a.Object.TARGET == Convert.ToInt32( oldValue));
await firebase
.Child(ChildProgram)
.Child(toUpdatePerson.Key)
.PutAsync(viewProgramModel);
GetAllData();
}
public async Task DeleteViewProgramModel(string mode)
{
var toDeletePerson = (await firebase
.Child(ChildProgram)
.OnceAsync<ViewProgramModel>()).FirstOrDefault(a => a.Object.MODE == mode);
await firebase.Child(ChildProgram).Child(toDeletePerson.Key).DeleteAsync();
GetAllData();
}
public async void GetAllData()
{
_returnedEvents.Clear();
var programs = await firebase.Child(ChildProgram).OnceAsync<ViewProgramModel>();
for (int i = 0; i < programs.Count; i++)
{
_returnedEvents.Add(programs.ElementAt(i).Object);
}
}
public async void listenForEvents()
{
_returnedEvents.Clear();
var programs = await firebase.Child(ChildProgram).OnceAsync<ViewProgramModel>();
for (int i = 0; i < programs.Count; i++)
{
_returnedEvents.Add(programs.ElementAt(i).Object);
}
//returnedEvents = firebase.Child(ChildProgram).OrderByKey().AsObservable<ViewProgramModel>()
// .Subscribe(eventReceived =>
// {
// if (eventReceived.EventType == Firebase.Database.Streaming.FirebaseEventType.InsertOrUpdate)
// {
// var found = _returnedEvents.FirstOrDefault(i => i._KEY == eventReceived.Key);
// if (found == null)
// {
// // not in observable collection, add it
// _returnedEvents.Add(eventReceived.Object);
// }
// else
// {
// // event was updated
// int tempIndex = _returnedEvents.IndexOf(found);
// _returnedEvents[tempIndex] = eventReceived.Object;
// }
// }
//});
}
}
}
Here is CookPage.xaml
<StackLayout>
<Button Text="add" Clicked="Button_Clicked"></Button>
<Button Text="delete" Clicked="Button_Clicked_1"></Button>
<Syncfusion:SfDataGrid x:Name="sfGrid" ItemsSource="{Binding _returnedEvents, Mode=TwoWay} " >
</Syncfusion:SfDataGrid>
</StackLayout>
Here is code about CookPage.cs.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CookPage : ContentPage
{
FirebaseHelper firebaseHelper = new FirebaseHelper();
public CookPage()
{
InitializeComponent();
// for Syncfusion DataGrid
firebaseHelper.listenForEvents();
//sfGrid.ItemsSource = ViewProgramModel._returnedEvents;
BindingContext= firebaseHelper;
sfGrid.ColumnSizer = ColumnSizer.Star;
sfGrid.AllowEditing = true;
sfGrid.NavigationMode = NavigationMode.Cell;
sfGrid.AllowLoadMore = true;
sfGrid.AutoGenerateColumns = true;
//sfGrid.AutoGenerateColumnsMode= AutoGenerateColumnsMode.
sfGrid.SelectionMode = Syncfusion.SfDataGrid.XForms.SelectionMode.Single;
sfGrid.AllowPullToRefresh = true;
sfGrid.CurrentCellEndEdit += SfGrid_CurrentCellEndEdit; ;
}
private async void SfGrid_CurrentCellEndEdit(object sender, GridCurrentCellEndEditEventArgs e)
{
//throw new System.NotImplementedException();
var selectObj = sender as SfDataGrid;
RowColumnIndex index = e.RowColumnIndex;
int selectColumnIndex = index.ColumnIndex; //2
int selectRowIndex = index.RowIndex; //3
var ob=firebaseHelper._returnedEvents;
ViewProgramModel selectObject =ob[selectRowIndex-1];
var newVale = e.NewValue.ToString();
var oldeValue = e.OldValue.ToString();
//Here just judge TARGET Column, you should judge all Columns
if (selectColumnIndex == 2)
{
selectObject.TARGET = Convert.ToInt32(newVale);
}
//If you want to achieve the all Grid change function, you should judge the selectRowIndex for every change
//if (selectRowIndex==1)
//{
// selectObject.MODE = newVale;
//}else if (selectRowIndex==2)
//{
// selectObject.TARGET = Convert.ToInt32( newVale);
//}else if (selectRowIndex == 3)
//{
// selectObject.TRIGGER = newVale;
//}else if (selectRowIndex == 4)
//{
// selectObject.TRIGGERVALUE = Convert.ToInt32(newVale);
//}
await firebaseHelper.UpdateViewProgramModel(selectObject, oldeValue);
}
private async void Button_Clicked(object sender, System.EventArgs e)
{
await firebaseHelper.AddViewProgramModel();
}
private async void Button_Clicked_1(object sender, System.EventArgs e)
{
await firebaseHelper.DeleteViewProgramModel("test");
}
}
I'm having 2 content pages inside the TabbedPage. One content page contains ListView. I want to switch to other tab on selecting the item in the ListView. How can I achieve this ?
Listen for the ListView.ItemSelected event and forward it to your TabbedPage.
You can change the current active page using the TabbedPage.CurrentPage property.
public class MasterPage : TabbedPage
{
public MasterPage()
{
var page1 = new Page1();
var page2 = new Page2();
page1.ItemSelected += (sender, args) =>
{
if (args.SelectedItem is string answer && answer.Equals("Yes"))
{
CurrentPage = page2;
}
};
Children.Add(page1);
Children.Add(page2);
}
}
public class Page1 : ContentPage
{
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
public Page1()
{
Title = "Page1";
var labelQuestion = new Label()
{
Text = "Do you want to switch to Page2?"
};
var listViewAnswers = new ListView()
{
ItemsSource = new string[] { "Yes", "No" },
ItemTemplate = new DataTemplate(() =>
{
var cell = new TextCell();
cell.SetBinding(TextCell.TextProperty, ".");
return cell;
})
};
listViewAnswers.ItemSelected += (sender, args) => ItemSelected?.Invoke(this, args);
Content = new StackLayout()
{
Children =
{
labelQuestion,
listViewAnswers
}
};
}
}
public class Page2 : ContentPage
{
public Page2()
{
Title = "Page2";
Content = new StackLayout()
{
Children =
{
new Label()
{
Text = "Page2"
}
}
};
}
}
A simple example that I can give you is this:
public class MainTabbedPage : TabbedPage {
readonly Page tab1Page;
readonly Page tab2Page;
public MainTabbedPage() {
tab1Page = new Page1() { Title = "Tab 1 title", Icon="circle.png" };
tab2Page =new Page2() { Title = "Tab 2 title", Icon="square.png" };
// To change tab order, just shuffle these Add calls around.
Children.Add(tab1Page);
Children.Add(tab2Page);
}
public void SwitchToTab1() {
CurrentPage = tab1Page;
}
public void SwitchToTab2() {
CurrentPage = tab2Page;
}
}
public class Page1 : ContentPage {
public Page1() {
var button = new Button() {
Text = "Switch to Tab 2",
};
button.Clicked += async (sender, e) => {
var tabbedPage = this.Parent as TabbedPage;
tabbedPage.SwitchToTab2();
};
Content = new StackLayout {
Children = {
button,
}
};
}
}
If I have an Entry in my Xamarin Forms project and I keep typing on it until it goes off the end of the viewable page the text does not horizontally scroll in iOS as it does on Android - is there a way to make it do this?
I've tried replacing it with an Editor, but that doesn't have a placeholder, which I need.
An Entry is rendered as a UITextField on iOS which does not support scrolling. An Editor is rendered as a UITextView which does support scrolling.
One option would be to create a custom editor that includes a placeholder.
I have code for this. It's not pretty but it works.
Here is the CustomEditor code including new bindable properties for placeholder text and color.
public class CustomEditor : Editor
{
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(CustomEditor), default(string));
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public static readonly BindableProperty PlaceholderColorProperty =
BindableProperty.Create(nameof(PlaceholderColor), typeof(Color), typeof(CustomEditor), default(Color));
public Color PlaceholderColor
{
get { return (Color)GetValue(PlaceholderColorProperty); }
set { SetValue(PlaceholderColorProperty, value); }
}
public static readonly BindableProperty DisabledColorProperty =
BindableProperty.Create(nameof(DisabledColor), typeof(Color), typeof(CustomEditor), default(Color));
public Color DisabledColor
{
get { return (Color)GetValue(DisabledColorProperty); }
set { SetValue(DisabledColorProperty, value); }
}
}
Here is the iOS custom renderer
[assembly: ExportRenderer(typeof(CustomEditor), typeof(CustomEditorRenderer))]
namespace Test.iOS.CustomRenderers
{
public class CustomEditorRenderer : EditorRenderer
{
private UILabel PlaceholderLabel { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Control == null) return;
Control.Layer.BorderWidth = 0.5f;
Control.Layer.CornerRadius = 8;
Control.Layer.BorderColor = Color.FromHex("CDCDCD").ToCGColor();
if (PlaceholderLabel != null) return;
var element = Element as CustomEditor;
PlaceholderLabel = new UILabel
{
Text = element?.Placeholder,
TextColor = element?.PlaceholderColor.ToUIColor(),
BackgroundColor = UIColor.Clear
};
var edgeInsets = Control.TextContainerInset;
var lineFragmentPadding = Control.TextContainer.LineFragmentPadding;
Control.AddSubview(PlaceholderLabel);
var vConstraints = NSLayoutConstraint.FromVisualFormat(
"V:|-" + edgeInsets.Top + "-[PlaceholderLabel]-" + edgeInsets.Bottom + "-|", 0, new NSDictionary(),
NSDictionary.FromObjectsAndKeys(
new NSObject[] { PlaceholderLabel }, new NSObject[] { new NSString("PlaceholderLabel") })
);
var hConstraints = NSLayoutConstraint.FromVisualFormat(
"H:|-" + lineFragmentPadding + "-[PlaceholderLabel]-" + lineFragmentPadding + "-|",
0, new NSDictionary(),
NSDictionary.FromObjectsAndKeys(
new NSObject[] { PlaceholderLabel }, new NSObject[] { new NSString("PlaceholderLabel") })
);
PlaceholderLabel.TranslatesAutoresizingMaskIntoConstraints = false;
Control.AddConstraints(hConstraints);
Control.AddConstraints(vConstraints);
PlaceholderLabel.Hidden = Element.Text.NotEmpty();
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Text")
{
PlaceholderLabel.Hidden = Control.Text.NotEmpty();
}
if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
{
var element = Element as CustomEditor;
SetTextColor(element);
}
}
private void SetTextColor(CustomEditor element)
{
Control.TextColor = element.IsEnabled ? element.TextColor.ToUIColor() : element.DisabledColor.ToUIColor();
}
}
}
I took from a GitHub TkCustomMap project TK.CustomMap, and I want to call my custom service, to fill my custom list, and on ItemSelected event, from autocomplete searchBar to MapCenter it with Coordinates from my list model.
I've tried and I stopped at binding and on ItemSelect event.
Here is my MapPage:
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace TK.CustomMap.Sample
{
public partial class SamplePage : ContentPage
{
public SamplePage()
{
//InitializeComponent();
this.CreateView();
this.BindingContext = new SampleViewModel();
}
private async void CreateView()
{
var autoComplete = new PlacesAutoComplete { ApiToUse = PlacesAutoComplete.PlacesApi.CustomList };
autoComplete.SetBinding(PlacesAutoComplete.MapSelectedCommandProperty, "MapCenter");
var newYork = new Position(40.7142700, -74.0059700);
var mapView = new TKCustomMap(MapSpan.FromCenterAndRadius(newYork, Distance.FromKilometers(2)));
mapView.IsShowingUser = true;
mapView.SetBinding(TKCustomMap.CustomPinsProperty, "Pins");
mapView.SetBinding(TKCustomMap.MapClickedCommandProperty, "MapClickedCommand");
mapView.SetBinding(TKCustomMap.MapLongPressCommandProperty, "MapLongPressCommand");
mapView.SetBinding(TKCustomMap.MapCenterProperty, "MapCenter");
mapView.SetBinding(TKCustomMap.PinSelectedCommandProperty, "PinSelectedCommand");
mapView.SetBinding(TKCustomMap.SelectedPinProperty, "SelectedPin");
mapView.SetBinding(TKCustomMap.RoutesProperty, "Routes");
mapView.SetBinding(TKCustomMap.PinDragEndCommandProperty, "DragEndCommand");
mapView.SetBinding(TKCustomMap.CirclesProperty, "Circles");
mapView.SetBinding(TKCustomMap.CalloutClickedCommandProperty, "CalloutClickedCommand");
mapView.SetBinding(TKCustomMap.PolylinesProperty, "Lines");
mapView.SetBinding(TKCustomMap.PolygonsProperty, "Polygons");
mapView.SetBinding(TKCustomMap.MapRegionProperty, "MapRegion");
mapView.SetBinding(TKCustomMap.RouteClickedCommandProperty, "RouteClickedCommand");
mapView.SetBinding(TKCustomMap.RouteCalculationFinishedCommandProperty, "RouteCalculationFinishedCommand");
mapView.SetBinding(TKCustomMap.TilesUrlOptionsProperty, "TilesUrlOptions");
mapView.SetBinding(TKCustomMap.MapFunctionsProperty, "MapFunctions");
mapView.IsRegionChangeAnimated = true;
autoComplete.SetBinding(PlacesAutoComplete.BoundsProperty, "MapRegion");
RelativeLayout _baseLayout = new RelativeLayout();
_baseLayout.Children.Add(
mapView,
Constraint.RelativeToView(autoComplete, (r, v) => v.X),
Constraint.RelativeToView(autoComplete, (r, v) => autoComplete.HeightOfSearchBar),
heightConstraint: Constraint.RelativeToParent((r) => r.Height - autoComplete.HeightOfSearchBar),
widthConstraint: Constraint.RelativeToView(autoComplete, (r, v) => v.Width));
_baseLayout.Children.Add(
autoComplete,
Constraint.Constant(0),
Constraint.Constant(0));
Content = _baseLayout;
}
}
}
Here is my PlacesAutoComplete class:
using System;
using System.Collections.Generic;
using System.Linq;
using TK.CustomMap.Api;
using TK.CustomMap.Api.Google;
using TK.CustomMap.Api.OSM;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using static TK.CustomMap.Sample.SearchBarModel;
namespace TK.CustomMap.Sample
{
public class SearchBarModel : IPlaceResult
{
public string Subtitle { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class PlacesAutoComplete : RelativeLayout
{
public static BindableProperty BoundsProperty = BindableProperty.Create<PlacesAutoComplete, MapSpan>(
p => p.Bounds,
default(MapSpan));
// TODO: SUMMARIES
public enum PlacesApi
{
//Google,
//Osm,
//Native,
//THIS IS MY LIST!
CustomList
}
private readonly bool _useSearchBar;
private bool _textChangeItemSelected;
private SearchBar _searchBar;
private Entry _entry;
private ListView _autoCompleteListView;
private IEnumerable<IPlaceResult> _predictions;
public PlacesApi ApiToUse { get; set; }
public static readonly BindableProperty MapSelectedCommandProperty =
BindableProperty.Create<PlacesAutoComplete, Command<Position>>(
p => p.SetMap, null);
public List<SearchBarModel> myList = new List<SearchBarModel>();
public Command<Position> SetMap
{
get { return (Command<Position>)this.GetValue(MapSelectedCommandProperty); }
set { this.SetValue(MapSelectedCommandProperty, value); }
}
public double HeightOfSearchBar
{
get
{
return this._useSearchBar ? this._searchBar.Height : this._entry.Height;
}
}
private string SearchText
{
get
{
return this._useSearchBar ? this._searchBar.Text : this._entry.Text;
}
set
{
if (this._useSearchBar)
this._searchBar.Text = value;
else
this._entry.Text = value;
}
}
public new MapSpan Bounds
{
get { return (MapSpan)this.GetValue(BoundsProperty); }
set { this.SetValue(BoundsProperty, value); }
}
public PlacesAutoComplete(bool useSearchBar)
{
this._useSearchBar = useSearchBar;
this.Init();
}
public string Placeholder
{
get { return this._useSearchBar ? this._searchBar.Placeholder : this._entry.Placeholder; }
set
{
if (this._useSearchBar)
this._searchBar.Placeholder = value;
else
this._entry.Placeholder = value;
}
}
public PlacesAutoComplete()
{
this._useSearchBar = true;
this.Init();
}
private void Init()
{
OsmNominatim.Instance.CountryCodes.Add("de");
this._autoCompleteListView = new ListView
{
IsVisible = false,
RowHeight = 40,
HeightRequest = 0,
BackgroundColor = Color.White
};
this._autoCompleteListView.ItemTemplate = new DataTemplate(typeof(MapSearchCell));
View searchView;
if (this._useSearchBar)
{
this._searchBar = new SearchBar
{
Placeholder = "Search for address..."
};
this._searchBar.TextChanged += SearchTextChanged;
this._searchBar.SearchButtonPressed += SearchButtonPressed;
searchView = this._searchBar;
}
else
{
this._entry = new Entry
{
Placeholder = "Sarch for address"
};
this._entry.TextChanged += SearchTextChanged;
searchView = this._entry;
}
this.Children.Add(searchView,
Constraint.Constant(0),
Constraint.Constant(0),
widthConstraint: Constraint.RelativeToParent(l => l.Width));
this.Children.Add(
this._autoCompleteListView,
Constraint.Constant(0),
Constraint.RelativeToView(searchView, (r, v) => v.Y + v.Height));
this._autoCompleteListView.ItemSelected += ItemSelected;
this._textChangeItemSelected = false;
}
private void SearchButtonPressed(object sender, EventArgs e)
{
if (this._predictions != null && this._predictions.Any())
this.HandleItemSelected(this._predictions.First());
else
this.Reset();
}
private void SearchTextChanged(object sender, TextChangedEventArgs e)
{
if (this._textChangeItemSelected)
{
this._textChangeItemSelected = false;
return;
}
this.SearchPlaces();
}
private async void SearchPlaces()
{
try
{
if (string.IsNullOrEmpty(this.SearchText))
{
this._autoCompleteListView.ItemsSource = null;
this._autoCompleteListView.IsVisible = false;
this._autoCompleteListView.HeightRequest = 0;
return;
}
IEnumerable<IPlaceResult> result = null;
if (this.ApiToUse == PlacesApi.CustomList)
{
myList.Add(new SearchBarModel
{
Name = "Test1",
Description = "On item select, show me on map!",
Longitude = 20.4680701,
Latitude = 44.8152658
});
myList.Add(new SearchBarModel
{
Name = "Test2",
Description = "On item select, show me on map!",
Longitude = 20.4233035,
Latitude = 44.805651,
});
myList.Add(new SearchBarModel
{
Name = "Test3",
Description = "On item select, show me on map!",
Longitude = 20.456054,
Latitude = 44.8839925
});
myList.Add(new SearchBarModel
{
Name = "Test4",
Description = "On item select, show me on map!",
Longitude = 20.4328035,
Latitude = 44.8071928,
});
result = myList.OfType<IPlaceResult>().ToList<IPlaceResult>();
}
else
{
result = await OsmNominatim.Instance.GetPredictions(this.SearchText);
}
if (result != null && result.Any())
{
this._predictions = result;
this._autoCompleteListView.HeightRequest = result.Count() * 40;
this._autoCompleteListView.IsVisible = true;
this._autoCompleteListView.ItemsSource = this._predictions;
}
else
{
this._autoCompleteListView.HeightRequest = 0;
this._autoCompleteListView.IsVisible = false;
}
}
catch (Exception x)
{
// TODO
}
}
//From here my code partially works
private void ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null) return;
var prediction = (IPlaceResult)e.SelectedItem;
this.HandleItemSelected(prediction);
}
private void HandleItemSelected(IPlaceResult prediction)
{
if (this.SetMap != null && this.SetMap.CanExecute(this))
{
this.SetMap.Execute(prediction);
}
this._textChangeItemSelected = true;
this.SearchText = prediction.Description;
this._autoCompleteListView.SelectedItem = null;
this.Reset();
}
private void Reset()
{
this._autoCompleteListView.ItemsSource = null;
this._autoCompleteListView.IsVisible = false;
this._autoCompleteListView.HeightRequest = 0;
if (this._useSearchBar)
this._searchBar.Unfocus();
else
this._entry.Unfocus();
}
}
}