Consider the following screenshot to explain my simplified scenario. I have
Numpad
with two buttons Up
and Down
and a bindable property Text
. Pressing Up
and Down
will increase and decrease the value of Text
, respectively. The Down
button is disabled whenever Text
reaches 0
.Quantity
and UnitPrice
and a Numpad
.ItemViewModel
that has two properties Quantity
and UnitPrice
.Numpad
's Text
is bound to either Quantity
or UnitPrice
depending which radio button is being selected.As you can see, the Down
button should be disabled because Numpad
's Text
is 0
. But it is not the case. What is the culprit? I attempted to debug but the process is really confusing with a lot of branching and jumping to internal methods.
public partial class Numpad : ContentView
{
private int? _value;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: nameof(Text),
returnType: typeof(string),
defaultValue: "0",
defaultBindingMode: BindingMode.TwoWay,
declaringType: typeof(Numpad),
propertyChanged: (bindable, oldValue, newValue) =>
{
var @this = (Numpad)bindable;
((Command)@this.UpCommand).ChangeCanExecute();
((Command)@this.DownCommand).ChangeCanExecute();
Trace.WriteLine("property Text has been changed!");
});
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ICommand UpCommand { get; }
public ICommand DownCommand { get; }
public Numpad()
{
UpCommand = new Command(
execute: () =>
{
Text = $"{_value + 1}";
},
canExecute: () =>
{
Trace.WriteLine("UpCommand's CanExecute()");
if (int.TryParse(Text, out int x))
{
_value = x;
return true;
}
_value = null;
return false;
}
);
DownCommand = new Command(
execute: () =>
{
Text = $"{_value - 1}";
},
canExecute: () =>
{
Trace.WriteLine("DownCommand's CanExecute()");
if (int.TryParse(Text, out int x))
{
_value = x;
return _value > 0;
}
_value = null;
return false;
}
);
}
<ContentView
...
xmlns:loc="clr-namespace:MyProject.Views"
x:Class="MyProject.Views.Numpad"
x:Name="This">
<VerticalStackLayout BindingContext="{Reference This}">
<Button
Text="Up"
Command="{Binding UpCommand}" />
<Button
Text="Down"
Command="{Binding DownCommand}" />
</VerticalStackLayout>
</ContentView>
public class ItemViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _quantity;
private int _unitPrice;
public int Quantity
{
get { return _quantity; }
set
{
_quantity = value;
OnPropertyChanged();
}
}
public int UnitPrice
{
get { return _unitPrice; }
set
{
_unitPrice = value;
OnPropertyChanged();
}
}
}
public partial class ChangerPage : ContentPage
{
public ChangerPage()
{
BindingContext = new ItemViewModel { Quantity = 3, UnitPrice = 5 };
InitializeComponent();
RadioButtonQuantity.IsChecked = true;
}
private void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (e.Value)
{
var rb = sender as RadioButton;
if (rb == RadioButtonQuantity)
{
cv.SetBinding(Numpad.TextProperty, nameof(ItemViewModel.Quantity));
}
if (rb == RadioButtonUnitPrice)
{
cv.SetBinding(Numpad.TextProperty, nameof(ItemViewModel.UnitPrice));
}
}
}
}
<ContentPage
...
xmlns:loc="clr-namespace:MyProject.Views"
xmlns:vm="clr-namespace:MyProject.ViewModels"
x:Class="MyProject.ChangerPage"
x:DataType="vm:ItemViewModel"
Title="Changer Page">
<VerticalStackLayout RadioButtonGroup.GroupName="Numpad">
<RadioButton
x:Name="RadioButtonQuantity"
Content="{Binding Quantity,Mode=TwoWay}"
CheckedChanged="RadioButton_CheckedChanged"
/>
<RadioButton
x:Name="RadioButtonUnitPrice"
Content="{Binding UnitPrice,Mode=TwoWay}"
CheckedChanged="RadioButton_CheckedChanged" />
<loc:Numpad
x:Name="cv"
Text="{Binding Quantity,Mode=TwoWay}"
ReturnClicked="OnReturnClicked" />
</VerticalStackLayout>
</ContentPage>
I can reproduce this issue. The Binding does not work well for some reason after switching BindingContext
at runtime.
I managed to work it out by using the following code in code behind,
private void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (e.Value)
{
var rb = sender as RadioButton;
if (rb == RadioButtonQuantity)
{
cv.SetBinding(Numpad.TextProperty, nameof(ItemViewModel.Quantity),BindingMode.TwoWay);
this.cv.Text = itemViewModel.Quantity.ToString();
}
if (rb == RadioButtonUnitPrice)
{
cv.SetBinding(Numpad.TextProperty, nameof(ItemViewModel.UnitPrice), BindingMode.TwoWay);
this.cv.Text = itemViewModel.UnitPrice.ToString();
}
}
}
I set the BindingMode
as TwoWay for the Numpad.TextProperty
and explicitly set the value to it while switching the BindingContext
.
Hope it helps!