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
Related
My binding:
<local:MyContentView BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext.Entity.Recipe, Mode=OneWay}"/>
The BindingContext on the ContentView is being updated when Recipe is changed, but the controls inside MyContentView aren't populating with data. If Recipe is a valid value initially the controls inside MyContentView is populated with the data, but if Recipe starts off as null and is changed to a valid target the controls will not update despite the BindingContext changing.
according to your description, you want to bind contentview in contentpage, the data don't update when data source changed, I guess that you may don't implement INotifypropertychanged for Recipe, you can follow then following article to implement INotifyPropertyChanged.
https://xamarinhelp.com/xamarin-forms-binding/
Another way ti to use Bindableproperty, I do one sample for you, you can take a look:
Contentview:
<ContentView.Content>
<StackLayout>
<Label x:Name="label1" Text="{Binding Text}" />
</StackLayout>
public partial class mycontenview : ContentView
{
public static BindableProperty TextProperty = BindableProperty.Create(
propertyName: "Text",
returnType: typeof(string),
declaringType: typeof(mycontenview),
defaultValue: string.Empty,
defaultBindingMode: BindingMode.OneWay,
propertyChanged: HandlePropertyChanged);
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
private static void HandlePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
mycontenview contentview = bindable as mycontenview;
contentview.label1.Text = newValue.ToString();
}
public mycontenview()
{
InitializeComponent();
}
}
MainPage:
<StackLayout>
<Label Text="welcome to xamarin world!"/>
<Button x:Name="btn1" Text="btn1" Clicked="btn1_Clicked"/>
<local:mycontenview Text="{Binding str}"/>
</StackLayout>
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
private string _str;
public string str
{
get { return _str; }
set
{
_str = value;
OnPropertyChanged("str");
}
}
public MainPage()
{
InitializeComponent();
m = new model1() { str = "test 1", str1 = "test another 1" };
str = "cherry";
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((propertyName)));
}
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
private void btn1_Clicked(object sender, EventArgs e)
{
str = "this is test!";
}
}
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?
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));
}
i am trying to show the current sysdatetime in Top App bar and i was wondering anyway i can do that in XAML for win store apps.
public class MainViewModel : INotifyPropertyChanged
{
private readonly DispatcherTimer _timer = new DispatcherTimer();
private string _resDateTime;
public string ResDateTime
{
get
{
return _resDateTime;
}
set
{
_resDateTime = value;
NotifyPropertyChanged("ResDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public MainViewModel()
{
_timer.Tick += TimerOnTick;
_timer.Start();
}
private void TimerOnTick(object sender, object o)
{
ResDateTime = DateTime.Now.ToString();
}
}
add to the code behind
public MainPage()
{
this.InitializeComponent();
DataContext = new MainViewModel();
}
and put on xaml
<Page.TopAppBar>
<AppBar>
<TextBlock Text="{Binding ResDateTime}"></TextBlock>
</AppBar>
</Page.TopAppBar>
hope it will help
In your page you can set Page.TopAppBar, and Page.BottomAppBar like this:
<Page.TopAppBar>
<AppBar>
<TextBlock Text="Your text" />
</AppBar>
</Page.TopAppBar>
From there, you can whether bind the Text property, if you're using the MVVM pattern, or simply assign a value in the code behind of the page, by giving a name to the TextBlock element.
I have ObservableCollection and value that need to find the item in the collection. any ideas? (p.s converter not good idea, because i have many collections)
This functionality (applying a filter) belongs into the ViewModel. Here is an easy example for illustration.
You might also want to look at the CollectionViewSource for a more refined version of the same concept.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<ListBox ItemsSource="{Binding MyClasses}" DisplayMemberPath="Name" Margin="5" />
<ListBox ItemsSource="{Binding MyFilteredClasses}" DisplayMemberPath="Name" Margin="5" />
<TextBox Text="{Binding MySelectedClass.Name}" Margin="5" />
</StackPanel>
</Window>
ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Class1> _myClasses;
public ObservableCollection<Class1> MyClasses { get { return _myClasses; } set { _myClasses = value; OnPropertyChanged("MyClasses"); } }
private List<Class1> _myFilteredClasses;
public List<Class1> MyFilteredClasses { get { return _myFilteredClasses; } set { _myFilteredClasses = value; OnPropertyChanged("MyFilteredClasses"); } }
private Class1 _mySelectedClass;
public Class1 MySelectedClass { get { return _mySelectedClass; } set { _mySelectedClass = value; OnPropertyChanged("MySelectedClass"); } }
public ViewModel()
{
MyClasses = new ObservableCollection<Class1>()
{
new Class1() { Name = "Connelly" },
new Class1() { Name = "Donnelly" },
new Class1() { Name = "Fonnelly" },
new Class1() { Name = "McGregor" },
new Class1() { Name = "Griffiths" }
};
// filter your ObservableCollection by some criteria, and bind to the result (either another list, or just one item)
MyFilteredClasses = MyClasses.Where(c => c.Name.EndsWith("onnelly")).ToList();
MySelectedClass = MyClasses.FirstOrDefault(c => c.Name.StartsWith("Mc"));
}
}
public class Class1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _name;
public string Name { get { return _name; } set { _name = value; OnPropertyChanged("Name"); } }
}
}