I created a custom control in Xamarin and it has a 2 way Bindable property.
public static BindableProperty SelectedItemsProperty =
BindableProperty.Create<MultiSelectList, IEnumerable>(o => o.SelectedItems, default(IEnumerable), BindingMode.TwoWay,
propertyChanged: OnSelectedItemsChanged);
public IEnumerable SelectedItems
{
get { return (IEnumerable)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
but the binding works only from ViewModel to the control. if the property value changes in the control it is not getting reflected in the viewmodel property that is bound to this. Just to be sure I assign new Ienumerable object whenever the property value changes inside the custom control.
I am using Xamarin.Forms version 2.0.1.6505
Any help on how to force the binding from control to viewmodel?
You should use an ObservableCollection instead of IEnumerable. The ObservableCollection will refresh the UI when it changed, not IEnumerable.
Related
I am using Prism and Autofac with Xamarin.Forms 4.0 with an MVVM architecture. Using the Navigation.NavigateAsync("MyPage") works unless I have a binding to the Date object with my ViewModel.
The page renders properly and I am navigated to it if my DatePicker has no binding.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" />
However the following will cause me to never navigate to the page.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" Date="{Binding SelectedStartDate, Mode=TwoWay}"
The property in the View Model, MyVM, looks like this.
private DateTime selectedStartDate;
public DateTime SelectedStartDate
{
get
{
return selectedStartDate;
}
set
{
SetProperty(ref selectedStartDate, value);
sample.ProcessStartDate = value;
}
}
Navigation with the following code fails with the Binding in XAML above:
INavigationResult status;
try
{
var parameters = new NavigationParameters();
parameters.Add("CurrentSample", SelectedSample);
status = await NavigationService.NavigateAsync("MyPage", parameters); //MyPage is registered with MyVM
}
catch (Exception ex)
{
string mess = ex.Message;
}
My work-around is to add an event handler to the code-behind.
<DatePicker x:Name="ProcessStartDate" Format="D" MinimumDate="01/01/2000" DateSelected="OnStartDateSelected"
So now my code-behind has a handler:
void OnStartDateSelected(object sender, DateChangedEventArgs args)
{
SampleDetailsViewModel vm = BindingContext as SampleDetailsViewModel;
vm.SelectedStartDate = args.NewDate;
}
I have a work-around for this page, But I don't want put code in the code-behind. This breaks the MVVM standard that I've managed to maintain on the other seven pages of the app. Am I Binding improperly with the DatePicker?
When Binding SelectedStartDate, you are not initializing it, making it binding to a null, because you have set the Binding Mode to "TwoWay".
Here you can find the various types of binding modes, quoting:
Causes changes to either the source property or the target property to
automatically update the other. This type of binding is appropriate
for editable forms or other fully-interactive UI scenarios.
a solution would be something like this (if you wanna keep the TwoWay mode, and dont mind starting with an default selected):
private DateTime selectedStartDate = DateTime.Now;
Or
Making the binding mode to "OneWayToSource", this makes updates to the binding source without, and not the target (remember that this way you can't change the selected date from the binding, only the datepicker can).
Updates the source property when the target property changes.
Or
If you wanna keep the TwoWay Mode and not having a default date selected, the way you did with code behind is a nice workaround.
I have a view. I have a bindable property there.
public partial class OrderCard : ContentView
{
public static readonly BindableProperty OrderProperty = BindableProperty.Create(nameof(Order), typeof(Order), typeof(OrderCard), null);
public Order Order
{
get { return (Order)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
public OrderCard()
{
InitializeComponent();
}
}
In the xaml of this view I'm binding to Order property like this:
Text="{Binding Order.Name, Source={x:Reference Root}}"
Root is a name in the xaml of a view OrderCard
When I use this view in the page everything works ok.
But I want to measure it's size even before adding it to the page.
var orderCard = new OrderCard { Order = order};
SizeRequest sizeRequest = orderCard.Measure(OrdersContainer.Width/5, OrdersContainer.Height);
But it gives me wrong numbers because bindings isn't applied.
How to force to apply bindings when view isn't attached to the page?
Bindings do not require being attached to a Page or anything else to be applied.
You might think they're not applied if your method for figuring out is to set a breakpoint on get_Order because that is never used, the Xaml loader uses GetValue directly. The usual way of figuring out if a Binding is correctly applied, is to add a PassthroughConverter (don't look for it, you have to write it yourself) to the Binding and put the breakpoint in the Convert method.
That being said, you can't Measure anything unless it's added to a page that is rendered on screen. If you try to Measure before that, you indeed get dummy values.
I was able to solve this problem by not doing a property Order but passing an order as BindingContext. Then I can measure the size of a view without attaching it to a page like this:
var orderCard = new OrderCard { BindingContext = order};
SizeRequest sizeRequest = orderCard.Measure(widthToTryToFitInTheView,heightToTryToFitInTheView);
I have a UserControl that has a CheckBox on it. When I consume the UserControl on my main XAML page, I'd like to TwoWay bind a property on the control to a property on my ViewModel e.g.
<myUserControl BtnIsBlacklisted="{Binding IsBlacklisted, Mode=TwoWay}" />
When IsBlacklisted changes, I'd like my checkbox to change too and vice-versa.
Here is what I have,
public static readonly DependencyProperty BtnIsBlacklistedProperty =
DependencyProperty.Register("BtnIsBlacklisted",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(false, new
PropertyChangedCallback(BtnIsBlacklistedPropertyChanged))
);
private static void BtnIsBlacklistedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// ... do something here ...
}
public bool BtnIsBlacklisted
{
get { return (bool)GetValue(BtnIsBlacklistedProperty); }
set { SetValue(BtnIsBlacklistedProperty, value); }
}
My UserControl has this for the CheckBox,
<CheckBox x:Name="myCheckBox"
...
IsChecked="{Binding Path=BtnIsBlacklisted,
ElementName=UserControl,
Converter={StaticResource BoolToNotBool},
Mode=TwoWay}" />
The property on my ViewModel object is as follows,
public bool IsBlacklisted
{
get
{
return App.VM.BlacklistedRetailers.Contains(this.Retailer);
}
set
{
if (value)
{
App.VM.BlacklistedRetailers.Add(this.Retailer);
}
else
{
while (App.VM.BlacklistedRetailers.Contains(this.Retailer))
{
App.VM.BlacklistedRetailers.Remove(this.Retailer);
}
}
this.NotifyPropertyChanged("IsBlacklisted");
}
}
The only way BlacklistedRetailers changes is through the set method above so there is no need to trigger a NotifyPropertyChanged from there ...
I have tried many of the suggestions in other questions i.e.
using a dependency property
including Mode=TwoWay
Binding on the UserControl using a self-referencing DataContext set on the containing grid (this does not work either).
however none of these have worked.
Some final notes:
This is for a Windows Phone 7.5 project
Edit: One way binding doe not work either, it seems it there is a problem binding to the UserControl's own properties
An ElementName Binding matches against x:Name values which are in the same name scope as the element on which the binding is being set. There's not enough of the code shown to tell but you're using "UserControl" which I'm guessing is not set as the name of the element, but is being used to try and match the type. The ElementName also might not be able to resolve if the CheckBox is declared inside a template.
I'm trying to bind CheckedChange from monodroid CheckBox to a command, but I get an error.
I want to unselect another item when a particular one is checked.
I think it is possible to do it with EventTrigger in wp7, but MvvmCross for android doesn't seem to support this feature.
Is MvvmCross limited to Button only ?
Thanks in advance for your help.
CheckedChanged is an EventHandler<CompoundButton.CheckedChangeEventArgs> so it isn't one of the delegate types that MvvmCross automatigically knows about.
However, there is a custom binding in place for this...
https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Binding.Droid/Target/MvxCompoundButtonCheckedTargetBinding.cs
And this custom binding should be registered using:
registry.RegisterFactory(new MvxSimplePropertyInfoTargetBindingFactory(typeof(MvxCompoundButtonCheckedTargetBinding), typeof(CompoundButton), "Checked"));
in https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.Binding.Droid/MvxAndroidBindingBuilder.cs
So if you have a ViewModel with a property IsSpecial
private bool _isSpecial;
public bool IsSpecial
{
get { return _isSpecial; }
set
{
_isSpecial = value;
RaisePropertyChanged(() => IsSpecial);
// your custom code here
}
}
then this binding should work:
'Checked':{'Path':'IsSpecial'}
And that should work for any CompoundButton - CheckBox, Switch, or your own compounds...
I've set up a viewmodel to bind a listcontrol to an ObservableCollection in my program. A UI control on the page adds and deletes objects to the collection, which works fine as the list is automatically updated.
After App-Switching and returning to the app, the buttons adds the objects, but the bindings seem to be lost. Any idea how I can maintain this even after returning? I don't really see the need to rebind the object (after defining it in XAML). Is there any way to foolproof this pattern, and ensure the bindings aren't lost upon returning to the app?
The XAML looks like this, but it's inside a UserControl - forgot to mention that
ItemsControl x:Name="PartyCollection" ItemTemplate="{StaticResource PartyCollectiontemplate}" ItemsSource="{Binding RoomParty, Source={StaticResource FormControlVM}}"
the codebehind looks like this
public class FormControlVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Party> RoomParty
{
get
{
return App.appData.currentChoices.roomParty;
}
set
{
App.appData.currentChoices.roomParty = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("RoomParty"));
}
}
}