I have found a custom renderer online but there is an error. Does anyone know how can I make a date time control? Currently I am using separate date picker and time picker but I want it to be combined.
I will post the code below that I have found from another post. This is the link to the post Xamarin Forms date and time picker
using System;
using Foundation;
using Test;
using Test.Droid;
using UIKit;
using ObjCRuntime;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly:ExportRenderer(typeof(MyPicker), typeof(MyPickerRenderer))]
namespace Test.Droid
{
public class MyPickerRenderer : PickerRenderer
{
string SelectedValue;
[Obsolete]
public MyPickerRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
SetTimePicker();
}
}
void SetTimePicker()
{
UIDatePicker picker = new UIDatePicker
{
Mode = UIDatePickerMode.DateAndTime
};
picker.SetDate(NSDate.Now, true);
picker.AddTarget(this, new Selector("DateChange:"), UIControlEvent.ValueChanged);
Control.InputView = picker;
UIToolbar toolbar = (UIToolbar)Control.InputAccessoryView;
UIBarButtonItem done = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, (object sender, EventArgs click) =>
{
Control.Text = SelectedValue;
toolbar.RemoveFromSuperview();
picker.RemoveFromSuperview();
Control.ResignFirstResponder();
MessagingCenter.Send<Object, string>(this, "pickerSelected", SelectedValue);
});
UIBarButtonItem empty = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace, null);
toolbar.Items = new UIBarButtonItem[] { empty, done };
}
[Export("DateChange:")]
void DateChange(UIDatePicker picker)
{
NSDateFormatter formatter = new NSDateFormatter();
formatter.DateFormat = "MM-dd HH:mm aa"; //you can set the format as you want
Control.Text = formatter.ToString(picker.Date);
SelectedValue = formatter.ToString(picker.Date);
MessagingCenter.Send<Object, string>(this, "pickerSelected", SelectedValue);
}
}
}
Make the DateTimePicker inherit a ContentView instead of just an Entry, and then creates the Stacklayout which add the Entry and the date and time pickers to content.
See the DateTimePicker2.cs:
public class DateTimePicker2 : ContentView, INotifyPropertyChanged
{
public Entry _entry { get; private set; } = new Entry();
public DatePicker _datePicker { get; private set; } = new DatePicker() { MinimumDate = DateTime.Today, IsVisible = false };
public TimePicker _timePicker { get; private set; } = new TimePicker() { IsVisible = false };
string _stringFormat { get; set; }
public string StringFormat { get { return _stringFormat ?? "dd/MM/yyyy HH:mm"; } set { _stringFormat = value; } }
public DateTime DateTime
{
get { return (DateTime)GetValue(DateTimeProperty); }
set { SetValue(DateTimeProperty, value); OnPropertyChanged("DateTime"); }
}
private TimeSpan _time
{
get
{
return TimeSpan.FromTicks(DateTime.Ticks);
}
set
{
DateTime = new DateTime(DateTime.Date.Ticks).AddTicks(value.Ticks);
}
}
private DateTime _date
{
get
{
return DateTime.Date;
}
set
{
DateTime = new DateTime(DateTime.TimeOfDay.Ticks).AddTicks(value.Ticks);
}
}
BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker2), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged);
public DateTimePicker2()
{
BindingContext = this;
Content = new StackLayout()
{
Children =
{
_datePicker,
_timePicker,
_entry
}
};
_datePicker.SetBinding<DateTimePicker2>(DatePicker.DateProperty, p => p._date);
_timePicker.SetBinding<DateTimePicker2>(TimePicker.TimeProperty, p => p._time);
_timePicker.Unfocused += (sender, args) => _time = _timePicker.Time;
_datePicker.Focused += (s, a) => UpdateEntryText();
GestureRecognizers.Add(new TapGestureRecognizer()
{
Command = new Command(() => _datePicker.Focus())
});
_entry.Focused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() => _datePicker.Focus());
};
_datePicker.Unfocused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() =>
{
_timePicker.Focus();
_date = _datePicker.Date;
UpdateEntryText();
});
};
}
private void UpdateEntryText()
{
_entry.Text = DateTime.ToString(StringFormat);
}
static void DTPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var timePicker = (bindable as DateTimePicker2);
timePicker.UpdateEntryText();
}
}
Usage in Xaml:
<StackLayout>
<local:DateTimePicker2></local:DateTimePicker2>
</StackLayout>
To make it easier for anyone wanting to use the DateTimePicker with XAML integration (with a Binding), I merged answers from the original post of Wendy Zhang, the comments below it and the question (Custom control not being set to content width) from csharpdude77.
Code:
public class DateTimePicker : ContentView, INotifyPropertyChanged
{
private Entry _entry { get; set; } = new Entry() { WidthRequest = 300 };
private DatePicker _datePicker { get; set; } = new DatePicker() { MinimumDate = DateTime.Today, IsVisible = false };
private TimePicker _timePicker { get; set; } = new TimePicker() { IsVisible = false };
private string _stringFormat { get; set; }
private TimeSpan _time
{
get { return TimeSpan.FromTicks(DateTime.Ticks); }
set { DateTime = new DateTime(DateTime.Date.Ticks).AddTicks(value.Ticks); }
}
private DateTime _date
{
get { return DateTime.Date; }
set { DateTime = new DateTime(DateTime.TimeOfDay.Ticks).AddTicks(value.Ticks); }
}
public string StringFormat { get { return _stringFormat ?? "dd/MM/yyyy HH:mm"; } set { _stringFormat = value; } }
public DateTime DateTime
{
get { return (DateTime)GetValue(DateTimeProperty); }
set { SetValue(DateTimeProperty, value); OnPropertyChanged(nameof(DateTime)); }
}
public static BindableProperty DateTimeProperty = BindableProperty.Create(nameof(DateTime), typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged);
public DateTimePicker()
{
Content = new StackLayout()
{
Children =
{
_datePicker,
_timePicker,
_entry
}
};
_datePicker.SetBinding(DatePicker.DateProperty, nameof(_date));
_timePicker.SetBinding(TimePicker.TimeProperty, nameof(_time));
_timePicker.Unfocused += (sender, args) => _time = _timePicker.Time;
_datePicker.Focused += (s, a) => UpdateEntryText();
GestureRecognizers.Add(new TapGestureRecognizer()
{
Command = new Command(() => _datePicker.Focus())
});
_entry.Focused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() => _datePicker.Focus());
};
_datePicker.Unfocused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() =>
{
_timePicker.Focus();
_date = _datePicker.Date;
UpdateEntryText();
});
};
}
private void UpdateEntryText()
{
_entry.Text = DateTime.ToString(StringFormat);
}
private static void DTPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var timePicker = bindable as DateTimePicker;
timePicker.UpdateEntryText();
}
}
Sample XAML usage:
<StackLayout>
<local:DateTimePicker DateTime="{Binding Source={RelativeSource AncestorType={x:Type class:NewRitViewModel}}, Path=DateTime}" StringFormat="dd-MM-yyyy HH:mm"></local:DateTimePicker>
</StackLayout>
On Xamarin.forms Android, you could try the code below.
Create a DateTimePicker.cs class.
public class DateTimePicker : Entry, INotifyPropertyChanged
{
public DatePicker _datePicker { get; private set; } = new DatePicker() { MinimumDate = DateTime.Today, IsVisible = false };
public TimePicker _timePicker { get; private set; } = new TimePicker() { IsVisible = false };
string _stringFormat { get; set; }
public string StringFormat { get { return _stringFormat ?? "dd/MM/yyyy HH:mm"; } set { _stringFormat = value; } }
public DateTime DateTime
{
get { return (DateTime)GetValue(DateTimeProperty); }
set { SetValue(DateTimeProperty, value); OnPropertyChanged("DateTime"); }
}
private TimeSpan _time
{
get
{
return TimeSpan.FromTicks(DateTime.Ticks);
}
set
{
DateTime = new DateTime(DateTime.Date.Ticks).AddTicks(value.Ticks);
}
}
private DateTime _date
{
get
{
return DateTime.Date;
}
set
{
DateTime = new DateTime(DateTime.TimeOfDay.Ticks).AddTicks(value.Ticks);
}
}
BindableProperty DateTimeProperty = BindableProperty.Create("DateTime", typeof(DateTime), typeof(DateTimePicker), DateTime.Now, BindingMode.TwoWay, propertyChanged: DTPropertyChanged);
public DateTimePicker()
{
BindingContext = this;
_datePicker.SetBinding<DateTimePicker>(DatePicker.DateProperty, p => p._date);
_timePicker.SetBinding<DateTimePicker>(TimePicker.TimeProperty, p => p._time);
_timePicker.Unfocused += (sender, args) => _time = _timePicker.Time;
_datePicker.Focused += (s, a) => UpdateEntryText();
GestureRecognizers.Add(new TapGestureRecognizer()
{
Command = new Command(() => _datePicker.Focus())
});
Focused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() => _datePicker.Focus());
};
_datePicker.Unfocused += (sender, args) =>
{
Device.BeginInvokeOnMainThread(() =>
{
_timePicker.Focus();
_date = _datePicker.Date;
UpdateEntryText();
});
};
}
private void UpdateEntryText()
{
Text = DateTime.ToString(StringFormat);
}
static void DTPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var timePicker = (bindable as DateTimePicker);
timePicker.UpdateEntryText();
}
}
Uasge in App.xaml.cs
var dtPicker = new DateTimePicker()
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
StringFormat = "HH:mm dd/MM/yyyy"
};
MainPage = new ContentPage
{
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Children = {
dtPicker._datePicker,
dtPicker._timePicker,
dtPicker
},
BackgroundColor = Color.Aqua
}
};
This is my Code Behind
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
void OnDateSelected(object sender, DateChangedEventArgs args)
{
Recalculate();
}
void OnSwitchToggled(object sender, ToggledEventArgs args)
{
Recalculate();
}
void Recalculate()
{
TimeSpan timeSpan = endDatePicker.Date - startDatePicker.Date +
(includeSwitch.IsToggled ? TimeSpan.FromDays(1) : TimeSpan.Zero);
resultLabel.Text = String.Format("{0} day{1} between dates",
timeSpan.Days, timeSpan.Days == 1 ? "" : "s");
}
}
I have attached code for date time picker
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DaysBetweenDates"
x:Class="DaysBetweenDates.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
<StackLayout Margin="10">
<Label Text="Days Between Dates"
Style="{DynamicResource TitleStyle}"
Margin="0, 20"
HorizontalTextAlignment="Center" />
<Label Text="Start Date:" />
<DatePicker x:Name="startDatePicker"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />
<Label Text="End Date:" />
<DatePicker x:Name="endDatePicker"
MinimumDate="{Binding Source={x:Reference startDatePicker},
Path=Date}"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />
<StackLayout Orientation="Horizontal"
Margin="0, 0, 0, 30">
<Label Text="Include both days in total: "
VerticalOptions="Center" />
<Switch x:Name="includeSwitch"
Toggled="OnSwitchToggled" />
</StackLayout>
<Label x:Name="resultLabel"
FontAttributes="Bold"
HorizontalTextAlignment="Center" />
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DaysBetweenDates"
x:Class="DaysBetweenDates.MainPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 20, 0, 0" />
</OnPlatform>
</ContentPage.Padding>
<StackLayout Margin="10">
<Label Text="Days Between Dates"
Style="{DynamicResource TitleStyle}"
Margin="0, 20"
HorizontalTextAlignment="Center" />
<Label Text="Start Date:" />
<DatePicker x:Name="startDatePicker"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />
<Label Text="End Date:" />
<DatePicker x:Name="endDatePicker"
MinimumDate="{Binding Source={x:Reference startDatePicker},
Path=Date}"
Format="D"
Margin="30, 0, 0, 30"
DateSelected="OnDateSelected" />
<StackLayout Orientation="Horizontal"
Margin="0, 0, 0, 30">
<Label Text="Include both days in total: "
VerticalOptions="Center" />
<Switch x:Name="includeSwitch"
Toggled="OnSwitchToggled" />
</StackLayout>
<Label x:Name="resultLabel"
FontAttributes="Bold"
HorizontalTextAlignment="Center" />
</StackLayout>
i am trying to render list of information inside a frame the information which comes up from database is in two parts ie "Description" : "Value"
so the row inside list make i group of description and value and some may not have
so can anyone help me how can i add the label to grid or stack as per the data which i get from my services because it could be 3 sometimes and it could be 5 sometime and creating label and binding them would be a static job
There is a very clean answer if you can use xamarin forms3.5 or greater, BindableLayout.
Xaml file
<ListView
x:Name="listView"
HasUnevenRows="True"
ItemsSource="{Binding Category}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" TextColor="Blue" />
<StackLayout BindableLayout.ItemsSource="{Binding users}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my ViewModel
public class DynamicSwitchViewModel:BaseViewModel
{
public DynamicSwitchViewModel(ContentPage view):base(view)
{
ObservableCollection<User> users1 = new ObservableCollection<User>();
users1.Add(new User() { Name = "karan3" });
users1.Add(new User() { Name = "karan4" });
users1.Add(new User() { Name = "karan5" });
ObservableCollection<User> users2 = new ObservableCollection<User>();
users2.Add(new User() { Name = "karan1" });
users2.Add(new User() { Name = "karan2" });
users2.Add(new User() { Name = "karan3" });
users2.Add(new User() { Name = "karan4" });
users2.Add(new User() { Name = "karan5" });
ObservableCollection<User> users3 = new ObservableCollection<User>();
users3.Add(new User() { Name = "karan1" });
users3.Add(new User() { Name = "karan2" });
users3.Add(new User() { Name = "karan3" });
Category = new ObservableCollection<Category>();
Category.Add(new Category() { Name = "1",users=users1 });
Category.Add(new Category() { Name = "2",users=users2 });
Category.Add(new Category() { Name = "3",users=users3 });
}
private ObservableCollection<Category> category;
public ObservableCollection<Category> Category
{
get { return category; }
set { SetProperty(ref category, value); }
}
}
public class Category
{
public ObservableCollection<User> users { get; set; }
public string Name { get; set; }
}
public class User
{
public string Name { get; set; }
}
I am unable to Bind ListView: and I am successfully bind list view when I have used code behind designing but I want to change designing in XAML but Now I am unable to Bind my list view, Data successfully send to server but unable to bind listbiew.
XAML: <ListView Grid.Row="0" ItemsSource="{Binding TextContainer}" x:Name="ListView"
SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<Label Text="{Binding Text}" YAlign="Center" Font="Large" TextColor="Red" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CS:
private ObservableCollection<MessageText> TextContainer = new ObservableCollection<MessageText>();
public ListView listView = new ListView();
public event OnMessageSent MessageSent;
public delegate void OnMessageSent(string message);
public Chat()
{
MessageSent += (message) =>
{
var text= "Me: " + message;
TextContainer.Add(new MessageText { Text = text });
SignalRClient.SendMessage(UsernameTextbox.Text, message);
};
SignalRClient.OnMessageReceived += (username, message) => {
if (username == UsernameTextbox.Text)
{
}
else
{
var cc = username + ": " + message;
TextContainer.Add(new MessageText { Text = cc });
}
};
}
public void AddText(string text)
{
TextContainer.Add(new MessageText { Text = text });
}
private void EnterButton_Clicked(object sender, EventArgs e)
{
try
{
var messageSent = MessageSent;
if (messageSent != null)
messageSent(Messagebox.Text);
Messagebox.Text = string.Empty;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
}
public class MessageText
{
public string Text { get; set; }
}
}
I am using chat application. and unable to bind local message as well
Your TextContainer field should be public and a property.
Change this:
private ObservableCollection<MessageText> TextContainer = new ObservableCollection<MessageText>();
To:
public ObservableCollection<MessageText> TextContainer { get; set; } = new ObservableCollection<MessageText>();
Note how I changed private to public and added { get; set; } to indicate this is a property with automatic getters and setters.
More information on data-binding can be found on the new Microsoft docs pages for this.
Please assist:
I have implemented the MVVM design on a simple app using Xamarin.
I have one Model (User) and one ViewModel (UserViewModel).
Please note that this app is my first Xamarin/MVVM app and that I am new to this.
The issue that i have is that adding or removing a User, the View does NOT update.
When I add or remove a user I can confirm that the Database is updated, but not my view.
Please see my code below, what am i missing?
User Model:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
public List<Role> RolesList { get; set; }
}
UserViewModel Code:
public class UsersViewModel : INotifyPropertyChanged
{
private UserServices UserServ { get; set; }
public User UserSelected { get; set; }
private ObservableCollection<User> userList;
public ObservableCollection<User> UserList
{
get
{
return userList;
}
set
{
if (userList != value)
{
userList = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public UsersViewModel()
{
UserServ = new UserServices();
UsersLoadAsync();
}
public async void UsersLoadAsync()
{
UserList = await UserServ.UsersGetAsync();
}
}
User Helper Service code (Added for completeness)
public class UserServices
{
public async Task<ObservableCollection<User>> UsersGetAsync()
{
ObservableCollection<User> UserList = await App.UserService.GetAsync();
return UserList;
}
public async Task<bool> UsersAddAsync(User user)
{
bool success = await App.UserService.PostAsync(user);
return success;
}
public async Task<bool> UsersRemoveAsync(User user)
{
bool success = await App.UserService.DeleteAsync(user.Id, user);
return success;
}
}
View Xaml Code:
<?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:local="clr-namespace:PB_Logbook"
x:Class="PB_Logbook.MainPage"
xmlns:ViewModels="clr-namespace:PB_Logbook.ViewModels;assembly:PB_Logbook">
<ContentPage.BindingContext>
<ViewModels:UsersViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<ListView ItemsSource="{Binding UserList, Mode=TwoWay}" HasUnevenRows="True" ItemSelected="Item_SelectedAsync" IsPullToRefreshEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Username}" FontSize="24"/>
<Label Text="{Binding FirstName}" FontSize="18" Opacity="0.6"/>
<Label Text="{Binding LastName}" FontSize="18" Opacity="0.6"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Add" Clicked="AddButton_ClickedAsync"></Button>
<Button Text="Remove" Clicked="RemoveButton_ClickedAsync"></Button>
</StackLayout>
</ContentPage>
View code behind:
public partial class MainPage : ContentPage
{
private UserServices UserServices { get; set; }
private UsersViewModel UsersVM { get; set; }
public MainPage()
{
InitializeComponent();
UserServices = new UserServices();
UsersVM = new UsersViewModel();
}
private async void AddButton_ClickedAsync(object sender, EventArgs e)
{
await AddUserAsync();
}
private async void RemoveButton_ClickedAsync(object sender, EventArgs e)
{
await RemoveUserAsync();
}
private async void Item_SelectedAsync(object sender, EventArgs e)
{
UsersVM.UserSelected = ((User)((ListView)sender).SelectedItem);
}
private async void Pull_RefreshAsync(object sender, EventArgs e)
{
//UsersVM.UsersLoadAsync();
}
private async Task AddUserAsync()
{
Random rnd = new Random();
int rndNumber = rnd.Next(1, 100);
User user = new User()
{
Username = "User " + rndNumber,
FirstName = "Firstname " + rndNumber,
LastName = "Surname " + rndNumber,
IsActive = true
};
bool success = await UserServices.UsersAddAsync(user);
if (success)
{
if (!UsersVM.UserList.Contains(user))
UsersVM.UserList.Add(user);
}
}
private async Task RemoveUserAsync()
{
bool success = await UserServices.UsersRemoveAsync(UsersVM.UserSelected);
if (success)
{
if (UsersVM.UserList.Contains(UsersVM.UserSelected))
UsersVM.UserList.Remove(UsersVM.UserSelected);
}
}
}
The issue is with adding/removing users that does not update in my view.
Thank you.
If you're new to Xamarin MVVM, this link will help you understand the basics of MVVM in Xamarin Forms
https://deanilvincent.github.io/2017/06/03/basic-understanding-of-mvvm-and-databinding-in-xamarin-forms/
I would suggest as well, please lessen your behind the codes and just implement everything including the commands in your ViewModel.
You've written that your codes are working when saving and updating but not reflecting the view right? You should put your method in fetching the list right after your save command.
Like this in your xaml
<Button Text="Save" Command="{Binding SaveCommand}"/>
In your ViewModel, you should use Command from Xamarin
public Command SaveCommand{
get{
return new Command(async()=>{
// your command save here
// then put your method for fetching the updated list: your UsersLoadAsync();
});
}
}
If you're new to MVVM, you can also check this link. It uses Xamarin MVVM. When you finish, you'll have simple weather app with simple mvvm implementations
I hope it helps you
I have Page with an Activity Indicator with the following code:
<?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="MyApp.ClientSearch" Title="Search">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<StackLayout.Children>
<SearchBar x:Name="txtSearchClient" TextChanged="OnTextChanged"></SearchBar>
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" x:Name="indicadorCargando" />
<ListView x:Name="lstClients"></ListView>
</StackLayout.Children>
</StackLayout>
</ContentPage.Content>
</ContentPage>
In the partial class associated to this xaml, I have:
namespace MyApp
{
public partial class ClientSearch: ContentPage
{
public BusquedaClientes()
{
InitializeComponent();
}
async void OnTextChanged(object sender, TextChangedEventArgs e)
{
if (this.txtSearchClient.Text.Length >= 3)
{
var list_clients = App.ClientsManager.GetTasksAsync(txtSearchClient.Text);
this.IsBusy = true;
var template = new DataTemplate(typeof(TextCell));
template.SetBinding(TextCell.DetailProperty, "name_ct");
template.SetBinding(TextCell.TextProperty, "cod_ct");
lstClients.ItemTemplate = template;
lstClients.ItemsSource = await list_clients;
this.IsBusy = false;
}
}
}
}
As you can see, this.IsBusy is setting the Page property, so tried to bind to that property in the XAML. Unfortunetly it doesn't work:
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" x:Name="indicadorCargando" />
How can I bind the values of the ActivityIndicator to the IsBusy page property?
I already know that setting the values like this:
this.IsBusy = true;
indicadorCargando.IsVisible=true;
indicadorCargando.IsRunning=true;
But I don't want to do that, I want to set one value instead of three.
You certainly could go the route of a separate view model, which is not a bad idea. However for the specific question, it doesn't look like you're setting the BindingContext anywhere, so it isn't going to get the IsBusy property that you want.
I haven't tried setting the BindingContext for a control to itself, but something like this ought to work:
<ActivityIndicator
IsVisible="{Binding IsBusy}"
IsRunning="{Binding IsBusy}"
x:Name="indicadorCargando"
BindingContext="{x:Reference indicadorCargando}" />
You need to give your page a name (x:Name="myPage") and then declare the binding using a reference to it using {Binding Source={x:Reference myPage}, Path=IsBusy} for the ActivityIndicator's IsVisible and IsRunning values. More info on this answer.
Here is how I would rewrite it:
public class ClientSearch : ContentPage
{
public ClientSearch()
{
BindingContext = new ClientSearchViewModel();
var stack = new StackLayout();
var searchBar = new SearchBar();
searchBar.SetBinding<ClientSearchViewModel>(SearchBar.TextProperty, x => x.Text);
var actInd = new ActivityIndicator();
actInd.SetBinding<ClientSearchViewModel>(ActivityIndicator.IsVisibleProperty, x => x.IsBusy);
actInd.SetBinding<ClientSearchViewModel>(ActivityIndicator.IsRunningProperty, x => x.IsBusy);
var lv = new ListView
{
ItemTemplate = new DataTemplate(() =>
{
var txtCell = new TextCell();
txtCell.SetBinding<MyItemModel>(TextCell.TextProperty, x => x.Name_Ct);
txtCell.SetBinding<MyItemModel>(TextCell.TextProperty, x => x.Cod_Ct);
return txtCell;
})
};
lv.SetBinding<ClientSearchViewModel>(ListView.ItemsSourceProperty, x => x.items);
stack.Children.Add(searchBar);
stack.Children.Add(actInd);
stack.Children.Add(lv);
Content = stack;
}
}
public class ClientSearchViewModel : BaseViewModel
{
public string Text { get; set; }
public bool IsBusy { get; set; }
public IEnumerable<MyItemModel> items { get; set; }
protected override async void OnPropertyChanged(string propertyName = null)
{
if (propertyName != nameof(Text) || Text.Length < 3) return;
IsBusy = true;
items = await App.ClientsManager.GetTasksAsync(Text);
IsBusy = false;
}
}
[ImplementPropertyChanged]
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}