Can't bind IsBusy property - xaml

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

Related

How to get a value from custom control in a broader control via binding in VM

MAUI.NET
I have a special picker for my own that I want to reuse in many places
<ContentView ...
x:Class="XYZ.Views.ABCPicker"
x:DataType="views:ABCPicker">
<Grid>
<Picker ItemsSource="{Binding ...}" SelectedItem="{Binding ChoosenDeviceTypeProperty}"/>
</Grid>
</ContentView>
and its code behind with my AttachedProperty:
public partial class ABCPicker : ContentView
{
public static readonly BindableProperty ChoosenDeviceTypeProperty = BindableProperty.CreateAttached("ChoosenDeviceType", typeof(string), typeof(ABCPicker), "Detect by connect", BindingMode.TwoWay);
public static string GetChoosenDeviceType(BindableObject view)
{
return (string)view.GetValue(ChoosenDeviceTypeProperty);
}
public static void SetChoosenDeviceType(BindableObject view, string value)
{
view.SetValue(ChoosenDeviceTypeProperty, value);
}
public ABCPicker()
{
this.BindingContext = this;
InitializeComponent();
}
}
I want to consume it in my broader control ViewModel
public class BroaderControlViewModel : BaseViewModel
{
...
private string myResult;
public string MyResult
{
get { return myResult; }
set { SetPropertyAndNotify(ref myResult, value); }
}
public ICommand MyCommand { private set; get; }
public BroaderControlViewModel()
{
MyCommand = new Command(() =>
{
// here I want the to get a choosen value to proceed with it after the user have choosen value in my special picker
});
}
}
with a view like below:
<ContentView ...
x:Class="XYZ.Views.BroaderControlView"
x:DataType="viewModels:BroaderControlViewModel">
<VerticalStackLayout ...>
<views:ABCPicker ChoosenDeviceType="{Binding MyResult, Mode=TwoWay}" />
<Button Text="Connect" Command="{Binding MyCommand}"/>
</VerticalStackLayout>
</ContentView>
I tried different binding types, also with x:Reference this and also with normal binding properties - not attached ones.
How it should be matched?

Binding to a property of the object

I have a Label and I want to bind Text to a property of an object
public class MainCar: INotifyPropertyChanged
{
string typeCar;
public string TypeCar
{
set
{
if (typeCar != value)
{
typeCar = value;
OnPropertyChanged("TypeCar");
}
}
get
{
return typeCar;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In my Xaml code I have a label and I do not understand how to bind Text of my Label to object`s property TypeCar
XAML CODE
<Label x:Name="label" FontSize="Large" Text="" />
BEHIND CODE
public Car_add()
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
this.BindingContext = new TypesCar();
}
VIEWMODEL CLASS
public class TypesCar
{
public TypesCar()
{
var vm = new MainCar() { TypeCar = "Ford" };
}
this is very well documented
<Label x:Name="label" FontSize="Large" Text="{Binding TypeCar}" />
then in the code behind
var vm = new MainCar() { TypeCar = "Ford" };
this.BindingContext = vm;
OR, if you are binding to a property on the SAME PAGE and NOT A VM
this.BindingContext = this;
note that if you want your UI to update as your VM changes, the VM must implement INotifyPropertyChanged

Xamarin.Forms Exception System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation

I'm very new at Xamarin.Forms and I have one issue that I can't solve.
When I click on a button, it is supposed to navigate to page RegistracijaKorisnika.xaml (It is on Bosnian language .. sorry for that), but it does not because of the exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation
The page RegistracijaKorisnika.xaml has ViewModel page RegistracijaKorisnikaViewModel.cs and when i put in RegistracijaKorisnika.xaml.cs line :
BindingContext = new RegistracijaKorisnikaViewModel()
The exception occurs.
When I delete line
BindingContext = new RegistracijaKorisnikaViewModel()
I can navigate to the RegistracijaKorisnika.xaml.
Here is the code and I am hoping that you can help me.
This is RegistracijaKorisnika.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="ITJobFinder.View.RegistracijaKorisnika">
<ContentPage.Content>
<StackLayout>
<Entry Placeholder="Ime" x:Name="entryImeKorisnika" Text="{Binding ImeKorisnika}"/>
<Entry Placeholder="Prezime" x:Name="entryPrezimeKorisnika" Text="{Binding PrezimeKorisnika}"/>
<Entry Placeholder="Email" x:Name="entryEmailKorisnika" Text="{Binding EmailKorisnika}"/>
<Entry Placeholder="Password" x:Name="entryPasswordKorisnika" Text="{Binding PasswordKorisnika}"/>
<Entry Placeholder="Naziv zavrsene obrazovne institucije" x:Name="entryNazivFakulteta" Text="{Binding NazivObrazovanjaKOrisnika}"/>
<Entry Placeholder="Godina zavrsetka obrazovanja" x:Name="entryGodinaZavrsetkaObrazovanja" Text="{Binding GodinaZavrsetkaObrazovanja}"/>
<Button Text="Registruj se!" Command="{Binding SaveNewUser}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is RegistracijaKorisnika.xaml.cs
namespace ITJobFinder.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegistracijaKorisnika : ContentPage
{
public RegistracijaKorisnika ()
{
InitializeComponent ();
BindingContext = new RegistracijaKorisnikaViewModel();
}
}
}
This is RegistracijaKorisnikaViewModel.cs
namespace ITJobFinder.ViewModel
{
public class RegistracijaKorisnikaViewModel : BaseViewModel
{
private string imeKorisnika;
private string prezimeKorisnika;
private string emailKorisnika;
private string passwordKorisnika;
private string nazivObrazovanjaKOrisnika;
private string godinaZavrsetkaObrazovanja;
public string ImeKorisnika
{
get => imeKorisnika;
set
{
imeKorisnika = value;
OnPropertyChanged();
}
}
public string PrezimeKorisnika
{
get => PrezimeKorisnika;
set
{
prezimeKorisnika = value;
OnPropertyChanged();
}
}
public string EmailKorisnika
{
get => emailKorisnika;
set
{
emailKorisnika = value;
OnPropertyChanged();
}
}
public string PasswordKorisnika
{
get => passwordKorisnika;
set
{
passwordKorisnika = value;
OnPropertyChanged();
}
}
public string NazivObrazovanjaKOrisnika
{
get => nazivObrazovanjaKOrisnika;
set
{
nazivObrazovanjaKOrisnika = value;
OnPropertyChanged();
}
}
public string GodinaZavrsetkaObrazovanja
{
get => godinaZavrsetkaObrazovanja;
set
{
godinaZavrsetkaObrazovanja = value;
OnPropertyChanged();
}
}
public ICommand SaveNewUser { get; private set; }
private DataBaseContext _context;
public RegistracijaKorisnikaViewModel()
{
_context = new DataBaseContext();
SaveNewUser = new Command(SaveUser);
}
void SaveUser()
{
Korisnik k = new Korisnik
{
ImeKorisnika = ImeKorisnika,
PrezimeKorisnika = PrezimeKorisnika,
EmailKorisnika = EmailKorisnika,
PasswordKorisnika = PasswordKorisnika,
GodinaZavrsetkaObrazovanja = GodinaZavrsetkaObrazovanja,
NazivObrazovanjaKOrisnika = NazivObrazovanjaKOrisnika
};
_context.Korisnici.Add(k);
_context.SaveChanges();
}
}
}
this get will cause an self-referential loop - your get should be using the lower-case internal variable, not the upper case Property name
public string PrezimeKorisnika
{
// should be "prezimeKorisnika"
get => PrezimeKorisnika;
set
{
prezimeKorisnika = value;
OnPropertyChanged();
}
}

Xamarin.Forms binding to a binding property or something

I have a page
<?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:controls="clr-namespace:PassSystem.Controls;assembly=PassSystem"
x:Class="PassSystem.Views.CreatePassPage"
Title="Оформление пропуска">
<ScrollView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout>
<FlexLayout Direction="Column" BackgroundColor="White">
<controls:ActionOption Title="Название" Value="1" LeftMargin="18">
</controls:ActionOption>
</FlexLayout>
</StackLayout>
</ScrollView>
</ContentPage>
and i have contentview
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:PassSystem.Controls;assembly=PassSystem"
xmlns:effects="clr-namespace:PassSystem.Effects;assembly=PassSystem"
xmlns:local="clr-namespace:PassSystem"
x:Class="PassSystem.Controls.ActionOption">
<FlexLayout x:Name="MainLayout" Direction="Row" JustifyContent="Center" AlignContent="Center" AlignItems="Center" HeightRequest="52"
effects:ClickableEffect.ClickCommand="!!"
effects:ClickableEffect.CancelCommand="{Binding UpCommand}"
effects:ClickableEffect.UpCommand="{Binding UpCommand}"
effects:ClickableEffect.DownCommand="{Binding DownCommand}">
<Label Text="{Binding Title}"
TextColor="{StaticResource PrimaryTextColor}"
FontSize="15"
VerticalTextAlignment="Center"
VerticalOptions="FillAndExpand"
FlexLayout.Grow="1"
Margin="{Binding TitleMargin, Mode=TwoWay}">
</Label>
<StackLayout Orientation="Horizontal" HeightRequest="16" VerticalOptions="CenterAndExpand" Margin="0,0,8,0">
<Label Text="{Binding Value}" FontSize="12" TextColor="{StaticResource SecondaryTextColor}" VerticalTextAlignment="Center"/>
<controls:IconView Source="forward" HeightRequest="14" WidthRequest="14" ForegroundColor="{StaticResource AdditionalTextColor}"/>
</StackLayout>
<FlexLayout.Effects>
<effects:ClickableEffect />
</FlexLayout.Effects>
</FlexLayout>
</ContentView>
.cs file
public partial class ActionOption : ContentView
{
public ActionOption()
{
InitializeComponent();
BindingContext = this;
}
private string _title;
public string Title
{
get => _title;
set
{
if(value == _title) return;
_title = value;
OnPropertyChanged();
}
}
private string _value;
public string Value
{
get => _value;
set
{
if (value == _value) return;
_value = value;
OnPropertyChanged();
}
}
public Thickness TitleMargin => new Thickness(_leftMargin, 0, 6, 0);
private double _leftMargin;
public double LeftMargin
{
get => _leftMargin;
set
{
if (Math.Abs(value - _leftMargin) < 0.01d) return;
_leftMargin = value;
OnPropertyChanged();
OnPropertyChanged(nameof(TitleMargin));
}
}
private ICommand _clicked;
public ICommand Clicked
{
get => _clicked;
set
{
if (value == _clicked) return;
_clicked = value;
OnPropertyChanged();
}
}
public ICommand UpCommand => new Command(() => MainLayout.BackgroundColor = Color.FromHex("#fff"));
public ICommand DownCommand => new Command(() => MainLayout.BackgroundColor = (Color)((App)Application.Current).Resources["HighlightingColor"]);
}
And I need to bind the ClickableEffect.ClickCommand from the page. I.e.
<controls:ActionOption Title="TitleHere" Value="1" LeftMargin="18" Clicked="{Binding ClickedCommand}">
</controls:ActionOption>
And in control
`effects:ClickableEffect.ClickCommand="{Binding ClickCommand (FromPage)}"`
Additional information. ClickableEffect
{
public ClickableEffect() : base("PassSystem.ClickableEffect")
{
}
#region Click
public static readonly BindableProperty ClickCommandProperty = BindableProperty
.CreateAttached("ClickCommand", typeof(ICommand), typeof(ClickableEffect), (object)null);
public static ICommand GetClickCommand(BindableObject view)
{
return (ICommand)view.GetValue(ClickCommandProperty);
}
public static void SetClickCommand(BindableObject view, ICommand value)
{
view.SetValue(ClickCommandProperty, value);
}
public static readonly BindableProperty ClickCommandParameterProperty = BindableProperty
.CreateAttached("ClickCommandParameter", typeof(object), typeof(ClickableEffect), (object)null);
public static object GetClickCommandParameter(BindableObject view)
{
return view.GetValue(ClickCommandParameterProperty);
}
public static void SetClickCommandParameter(BindableObject view, object value)
{
view.SetValue(ClickCommandParameterProperty, value);
}
#endregion
}
Implementation of Clickable Effect on Android:
public class ClickableListener : Java.Lang.Object, View.IOnTouchListener, View.IOnClickListener, View.IOnLongClickListener
{
private Element Element { get; }
private View View { get; }
public ClickableListener(Element element, View view)
{
Element = element;
View = view;
}
...
public void OnClick(View v)
{
Tap();
}
private void Tap()
{
var command = ClickableEffect.GetClickCommand(Element);
var parameter = ClickableEffect.GetClickCommandParameter(Element);
command?.Execute(parameter);
}
}
[assembly: ResolutionGroupName("PassSystem")]
[assembly: ExportEffect(typeof(AndroidClickableEffect), "ClickableEffect")]
namespace PassSystem.Droid.Native.Effects
{
public class AndroidClickableEffect : PlatformEffect
{
private bool _attached;
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
if(!_attached)
{
var control = Control ?? Container;
var listener = new ClickableListener(Element, control);
control.SetOnClickListener(listener);
...
_attached = true;
}
}
}
How to implement this? Thank you.
P.S. It's hard for me to explain and I hope you understand. :)
In order to achieve the desired behaviour:
ClickCommand have to be an attached property of a ClickableEffect. More information can be found in the official doc: Passing Effect Parameters as Attached Properties.
Since you have a ContentView in between the effect and the page, you have to make sure that BindindContext of the ContentView is set correctly. I am afraid that you will have to define a ClickCommand bindable property on the ContentView level and bind it to the effect's command. So: Page.BindingContext.ClickCommand => ContentView.ClickCommand => ClickableEffect.ClickCommand (where => is binding).
Which basically raise a question - why do you need an effect?

Label Text Not Updating From Bindable Property

I have a very simple form that I'm using to experiment with BindableProperty. Here's the XAML for the form
<?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:MyBindableProperty"
x:Class="MyBindableProperty.MainPage">
<StackLayout>
<local:MyLabel x:Name="BindingLabel" Text="{Binding Text}" MyText="{Binding Text}"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Entry x:Name="BindingEntry" Text="{Binding Text, Mode=TwoWay}"/>
<Entry x:Name="BindingEntry2" Text="{Binding Text, Mode=TwoWay}"/>
<Button x:Name="BindingButton" Text="Reset"/>
</StackLayout>
</ContentPage>
And here is the code behind
public partial class MainPage : ContentPage
{
public DataSourceClass DataSourceObject { get; set; }
public MainPage()
{
DataSourceObject = new DataSourceClass { Text = "Test1" };
BindingContext = DataSourceObject;
InitializeComponent();
BindingButton.Clicked += BindingButton_Clicked;
}
private void BindingButton_Clicked(object sender, EventArgs e)
{
var boundText = this.BindingLabel.Text;
var boundMyText = this.BindingLabel.MyText;
DataSourceObject.Text = "Test2";
}
}
Finally, here is the custom label class used in the XAML -
public class MyLabel : Label
{
public string MyText
{
get
{
return (string)GetValue(MyTextProperty);
}
set
{
SetValue(MyTextProperty, value);
}
}
public static readonly BindableProperty MyTextProperty = BindableProperty.Create(nameof(MyText), typeof(string), typeof(MyLabel), "Test", BindingMode.TwoWay, propertyChanged: MyTextChanged);
public static void MyTextChanged(BindableObject bindable, object oldValue, object newValue)
{
((MyLabel)bindable).TextChanged(newValue.ToString());
}
public void TextChanged(string newText)
{
Device.BeginInvokeOnMainThread(() => this.Text = newText);
}
}
The issues I'm having are
when the page initialises the MyTextChanged handler fires, but not after any subsequent changes
when the Reset button is clicked the value in DataSourceObject.Text is correctly updated with the value from the Entry element
no matter how I try to set the values of BindingLabel and BindingEntry2 they never reflect the values of DataSourceObject.Text after the page has loaded.
Any help would be greatly appreciated.
I stumbled across this so I updated the DataSourceClass from this
public class DataSourceClass
{
public string Text { get; set; }
}
to this
public class DataSourceClass : INotifyPropertyChanged
{
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and now everything works.
I thought BindableProperty was meant to supersede INotifyPropertyChanged?