How to invoke Binding.Path programmatically in Xamarin.Forms? - xamarin.forms

I have a DateTimeViewModel with DateTime property of type DateTime.
I can do actually in XAML as follows. And it works.
<StackLayout.BindingContext>
<Binding Path="DateTime">
<Binding.Source>
<toolkit:DateTimeViewModel/>
</Binding.Source>
</Binding>
</StackLayout.BindingContext>
<Label Text="{Binding Hour,StringFormat='The hours are {0}'}" />
<Label Text="{Binding Minute,StringFormat='The minutes are {0}'}" />
<Label Text="{Binding Second,StringFormat='The seconds are {0}'}" />
<Label Text="{Binding Millisecond,StringFormat='The milliseconds are {0}'}" />
However I am interested in learning whether I can remove
<StackLayout.BindingContext>
<Binding Path="DateTime">
<Binding.Source>
<toolkit:DateTimeViewModel/>
</Binding.Source>
</Binding>
</StackLayout.BindingContext>
and replace it with something like as follows (not works anyway)
BindingContext = new DateTimeViewModel();
this.SetBinding(Label.TextProperty, "DateTime");
Could you tell me how to set the binding path programmatically?
Edit
To make less confusing, let me add the details here.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class DateTimeViewModel : ViewModelBase
{
public DateTimeViewModel()
{
Device.StartTimer(TimeSpan.FromMilliseconds(15),
() =>
{
DateTime = DateTime.Now;
Counter++;
return true;
}
);
}
private DateTime dateTime = DateTime.Now;
public DateTime DateTime
{
get => dateTime;
private set => SetProperty(ref dateTime, value);
}
private long counter = 0;
public long Counter { get => counter; private set => SetProperty(ref counter, value); }
}

To set the binding path programmatically you have to use the following code:
BindableObject.SetBinding(BindableProperty targetProperty, new Binding() {Path = "path"})
In your case it would be something like the following:
this.SetBinding(Label.TextProperty, new Binding() {Path = "DateTime"});
You should bind properties of the same type, in your code you are trying to bind a Label.TextProperty with a property of the type DateTime. make the DateTime property in your ViewModel one of the type "string", and then do the conversion in the ViewModel to "DateTime" or vice versa.
You should not also set parameters, variables, or functions names equal to their type.

Related

Execute ReactiveUI Command from Event in Xamarin Forms

I have entry fields in a Xamarin Forms page that I want to trigger a ReactiveUI command when the user is finished entering text into them. I am using ReactiveUI.Events.XamForms and am trying to trigger a command based off of the Unfocused event, but I am not sure how to set up the command to get that to work.
Here is my XAML:
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage
x:Class="XamarinReactiveUISwipeView.MainPage"
x:TypeArguments="vm:MainPageViewModel"
xmlns:vm="clr-namespace:XamarinReactiveUITest.ViewModel;assembly=XamarinReactiveUITest"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ios="clr- namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns="http://xamarin.com/schemas/2014/forms"
ios:Page.UseSafeArea="true">
<StackLayout>
<StackLayout Margin="10,0,0,0" Orientation="Horizontal">
<Label Text="Task ID: " />
<Entry x:Name="EntryTaskID" />
</StackLayout>
<StackLayout Margin="10,0,0,0" Orientation="Horizontal">
<Label Text="Task Name: " />
<Entry x:Name="EntryTaskName" />
</StackLayout>
</StackLayout>
</rxui:ReactiveContentPage>
Here is my code behind:
public partial class MainPage : ReactiveContentPage<MainPageViewModel>
{
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.WhenActivated(disposable =>
{
this.Bind(ViewModel, vm => vm.TheTaskItem.TaskID, page => page.EntryTaskID.Text)
.DisposeWith(disposable);
this.Bind(ViewModel, vm => vm.TheTaskItem.TaskName, page => page.EntryTaskName.Text)
.DisposeWith(disposable);
EntryTaskName.Events().Unfocused.InvokeCommand(ViewModel, vm => vm.TheCommand);
});
}
}
here is my model:
public class TaskItem
{
public TaskItem() { }
public string TaskID { get; set; }
public string TaskName { get; set; }
}
and here is my view model:
public class MainPageViewModel : ReactiveObject
{
public MainPageViewModel()
{
TheTaskItem = new TaskItem { TaskID = "1", TaskName = "TheTaskName" };
TheCommand = ReactiveCommand.Create<FocusEventArgs, Unit>(ExecuteTheCommand);
}
public ReactiveCommand<FocusEventArgs, Unit> TheCommand { get; }
private void ExecuteTheCommand(FocusEventArgs args)
{
//do something
}
private TaskItem _theTaskItem;
public TaskItem TheTaskItem
{
get => _theTaskItem;
set => this.RaiseAndSetIfChanged(ref _theTaskItem, value);
}
}
In the view model above, it won't compile, but I can't figure out how to set up the ExecuteTheCommand method. The error is:
'void MainPageViewModel.ExecuteTheCommand(FocusEventArgs)' has the wrong return type
But in looking at examples, it looked like methods with void returns use the Unit type.
What do I need to do here to set up the command properly to get this to work?
In comment above, OP says that this works if change:
TheCommand = ReactiveCommand.Create<FocusEventArgs, Unit>(ExecuteTheCommand);
to:
TheCommand = ReactiveCommand.Create<FocusEventArgs>(ExecuteTheCommand);
Unexpectedly, the type of the variable that holds the result of Create, does not use that same generic type signature. It needs to be:
public ReactiveCommand<FocusEventArgs, Unit> TheCommand { get; }
According to the doc in link by Rodney Littles in comment, Unit is used to represent "void return type". (Generic type "TOutput" would be "void"; but "void" is not a valid Generic type.)

How to bind map/pin in view model properly in Xamarin.Forms?

I followed the doc, trying to bind the pin, but failed. The map is always showing the default position Rome. Here is the source code:
In DetailPage.xmal:
<Frame Margin="10,5"
CornerRadius="10"
Padding="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="300" />
</Grid.RowDefinitions>
<maps:Map MapType="Street" Grid.Row="0" ItemsSource="{Binding WorkPlace}">
<maps:Map.ItemTemplate>
<DataTemplate>
<maps:Pin Position="{Binding Position}"
Address="{Binding Address}"
Label="{Binding Description}" />
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
</Grid>
</Frame>
In DetailPageModel.cs:
public class DetailPageModel : PageModelBase
{
private Timesheet _detailedTimesheet;
private ObservableCollection<Location> _workPlace;
public ObservableCollection<Location> WorkPlace
{
get => _workPlace;
set => SetProperty(ref _workPlace, value);
}
public ReportDetailPageModel()
{
}
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is Timesheet selectedTimesheet)
{
_detailedTimesheet = selectedTimesheet;
WorkPlace = new ObservableCollection<Location>()
{
new Location(
_detailedTimesheet.ProjectAddress,
"Test Location",
new Position(_detailedTimesheet.ProjectLatitude, _detailedTimesheet.ProjectLongitude))
};
}
await base.InitializeAsync(navigationData);
}
}
In Location.cs:
public class Location : ExtendedBindableObject
{
Position _position;
public string Address { get; }
public string Description { get; }
public Position Position
{
get => _position;
set => SetProperty(ref _position, value);
}
public Location(string address, string description, Position position)
{
Address = address;
Description = description;
Position = position;
}
}
In ExtendedBindableObject.cs:
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;
}
Since the navigationData is correctly received in view model and the page's binding context is also working, I just don't know what could be missing. Any hint would be appreciated!
And actually I have one more confusion, why does the official doc use a custom Location class instead of the Pin class, as Pin inherits from Element/BindableObject/Object?

Xamarin Forms DatePicker Not binding with viewmodel property in UWP?

My DatePicker is not binding with my viewmodel property in UWP whereas its working in android and ios.
My view:
<DatePicker x:Name="StartDate" Date="{Binding StartDate, Mode=TwoWay}" Opacity="0.45" HeightRequest="50" Focused="OnScreenTapped" />
ViewModel:
private DateTime? _startDate;
public DateTime? StartDate
{
get
{
return _startDate;
}
set
{
_startDate = value;
OnPropertyChanged("StartDate");
}
}
I initailaised my date with StartDate = new DateTime(2000, 01, 01); but it shows current date only......
Also when I change the date in date picker it does not change in viewmodel....
Any help is appreciated...Thanks
I have tried to reproduce your issue, But DatePicker binding works in my side. You could refer to the following code to check if you have missed some key procedure.
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinDatePickerTest"
x:Class="XamarinDatePickerTest.MainPage"
xmlns:ViewMode ="clr-namespace:XamarinDatePickerTest.ViewModel"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
>
<ContentPage.BindingContext>
<ViewMode:MainPageViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<DatePicker VerticalOptions="CenterAndExpand" Date="{Binding Datetime}">
<DatePicker.Format>yyyy-MM-dd</DatePicker.Format>
<DatePicker.MinimumDate>
<sys:DateTime x:FactoryMethod="Parse">
<x:Arguments>
<x:String>Jan 1 2000</x:String>
</x:Arguments>
</sys:DateTime>
</DatePicker.MinimumDate>
</DatePicker>
</StackLayout>
</ContentPage>
MainPageViewModel.cs
public class MainPageViewModel : INotifyPropertyChanged
{
private DateTime? _datetime;
public DateTime? Datetime
{
get => _datetime;
set
{
_datetime = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
this.Datetime = new DateTime(2011, 12, 18);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I have uploaded the code sample to github, please check.
Just experienced the same issue. My problem was the nullable date. By default, the DatePicker doesn't allow nullable dates (defaults to todays date), but you can add a custom control to get it to work.

Create a tag as a bindable property

Is there an alternative for a Tag Bindable Property?
A tag property is available in WPF. However, it does not exist in Xamarin.Forms.
I would like to create a bindable context between two elements.
Specifically, I would like to bind a label's Tag property to an entry's Text property.
I have attempted the following:
Tag Property
public class TagBehavior
{
public static readonly BindableProperty TagProperty = BindableProperty.CreateAttached<TagBehavior, BindableObject>(
bindableObject => TagBehavior.GetTag(bindableObject),
null, /* default value */
BindingMode.OneWay,
null,
(bo, old, #new) => TagBehavior.OnCompletedChanged(bo, old, #new),
null,
null);
public static BindableObject GetTag(BindableObject bindableObject)
{
return (BindableObject)bindableObject.GetValue(TagBehavior.TagProperty);
}
public static void SetTag(BindableObject bindingObject, BindableObject value)
{
bindingObject.SetValue(TagBehavior.TagProperty, value);
}
public static void OnCompletedChanged(BindableObject bindableObject, BindableObject oldValue, BindableObject newValue)
{
//var tag = TagBehavior.GetTag(entry);
//if (tag != null)
//{
// Debug.WriteLine("TODO - Handle tag's value change event");
//}
}
}
XAML
xmlns:Behaviors="clr-namespace:ViewMenu.Behaviors;assembly=ViewMenu"
. . .
<Entry x:Name="textBox1" BindingContext="{StaticResource ViewModel}" Text="{Binding Path=Content1}" Grid.Row="0" Grid.Column="0" >
<Entry.Behaviors>
<Behaviors:TextBoxFocusBehavior />
</Entry.Behaviors>
</Entry>
<Label x:Name="label1" Grid.Row="0" Grid.Column="0"
Behaviors:TagBehavior.Tag="{Binding Source={x:Reference textBox1}, Path=Text}">
<Label.Behaviors>
<Behaviors:LabelDisplayBehavior />
</Label.Behaviors>
</Label>
However, I get an error in the output window saying:
SetValue: Can not convert to type
'Xamarin.Forms.BindableObject'
Any suggestions?
You're creating an attached property of type BindableObject which you try to bind to the property Text of the TextBox which is of type string. Obviously, string cannot be casted to BindableObject hence the error.
Instead, declare your attached property of type string, or if you want to stay more general, of type object:
public class TagBehavior
{
public static readonly BindableProperty TagProperty = BindableProperty.CreateAttached<TagBehavior, string>(
bindableObject => TagBehavior.GetTag(bindableObject),
null, /* default value */
BindingMode.OneWay,
null,
(bo, old, #new) => TagBehavior.OnCompletedChanged(bo, old, #new),
null,
null);
public static string GetTag(BindableObject bindableObject)
{
return (string)bindableObject.GetValue(TagBehavior.TagProperty);
}
public static void SetTag(BindableObject bindingObject, string value)
{
bindingObject.SetValue(TagBehavior.TagProperty, value);
}
public static void OnCompletedChanged(BindableObject bindableObject, string oldValue, string newValue)
{
//var tag = TagBehavior.GetTag(entry);
//if (tag != null)
//{
// Debug.WriteLine("TODO - Handle tag's value change event");
//}
}
}

Windows store 8.1 dynamic binding not updating in UI

I have made a simple test for updating binded values in the UI but nothing seems to update, only intial values are set but never updated, what would i be missing?
code:
//the model class
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged= delegate { };
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
public DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
Then simply in my main page i do this:
public ObservableCollection<DemoCustomer> progcollection = new ObservableCollection<DemoCustomer>();
public MainPage()
{
this.InitializeComponent();
progcollection = new ObservableCollection<DemoCustomer>();
this.progcollection.Add(new DemoCustomer());
this.txtblk.DataContext = progcollection[0].CustomerName;
}
Then in a click listener for example i do this:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
progcollection[0].CustomerName = "we changed the name!";
}
But nothing updates in the UI!!!
And here is my XAML:
<Page
x:Class="downloadprogressbinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:simpledownload"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Name="txtblk" HorizontalAlignment="Left" Margin="994,421,0,0" TextWrapping="Wrap" Text="{Binding Mode=TwoWay}" VerticalAlignment="Top" Height="89" Width="226" FontSize="36"/>
<Button Content="Button" HorizontalAlignment="Left" Height="51" Margin="116,24,0,0" VerticalAlignment="Top" Width="407" Click="Button_Click_1"/>
</Grid>
Using path keyword in binding and specifying the field solved it,like this:
{Binding Path=thetext, Mode=TwoWay}

Resources