Binding issue in custom Xamarin.Forms control - binding

I have a strange problem with bindings on a custom control. I created a custom toolbar:
public partial class TopToolbar
{
public static readonly BindableProperty BackCommandProperty =
BindableProperty.Create(nameof(BackCommand), typeof(ICommand), typeof(TopToolbar), propertyChanged: BackCommandChanged);
public ICommand BackCommand
{
get => (ICommand) GetValue(BackCommandProperty);
set => SetValue(BackCommandProperty, value);
}
public TopToolbar()
{
InitializeComponent();
}
// for debug purposes only
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
Debug.WriteLine(BindingContext);
}
// for debug purposes only
private static void BackCommandChanged(BindableObject bindable, object oldvalue, object newvalue)
{
Debug.WriteLine($"old: {oldvalue}, new: {newvalue}");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Core.Controls.TopToolbar"
x:Name="TopToolbarView"
BindingContext="{x:Reference TopToolbarView}"
Orientation="Vertical">
<StackLayout Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
<Image Source="{StaticResource Image.Toolbar.LeftArrow}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BackCommand}" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
I use it on a page in this way:
<pages:ContentPage.Content>
<StackLayout BackgroundColor="{StaticResource LightGrayColor}"
Spacing="0"
Padding="0">
<controls:TopToolbar Title="Master Data" BackCommand="{Binding MyBackCommand}" />
BindingContext of the page is a view model:
public class MyCustomersPageModel
{
public RelayCommand MyBackCommand { get; set; }
public MyCustomersPageModel()
{
MyBackCommand = // command creation;
}
}
From the debugging I know that BindingContext of a control is set (OnBindingContextChanged called) properly to itself (TopToolbar object) twice - first time when there's no child views and second time after they are added. I've checked that BindingContext is correctly propagated in all child controls.
Unfortunately the BackCommand is not bind at all. The setter of the TopToolbar.BackCommand is not called even once.
Interestingly when I replace setting the BindingContext on a control to setting the Souce directly in bindings everything works fine:
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Core.Controls.TopToolbar"
x:Name="TopToolbarView"
Orientation="Vertical">
<StackLayout Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
<Image Source="{StaticResource Image.Toolbar.LeftArrow}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference TopToolbarView}, Path=BackCommand}" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
Any clue what I do wrong?

It is working as expected. I would recommend using Source.
In first case, when you set BindingContext on TopToolbar to itself, then I would imagine this would be the sequence of events:
Custom control is constructed, BindingContext is assigned to self through reference.
Page instance is created, control is added to it.
When page's BindingContext is set, through power of property inheritance, all it's child controls' BindingContext is updated.
At this point your custom control's BindingContext is still referencing itself as value-propagation doesn't override manually set context.
Therefore, binding <controls:TopToolbar BackCommand="{Binding MyBackCommand}" fails, as this binding will try to look for MyBackCommand on it's binding-context which is TopToolbar.
But, in second case, when you specify binding Source as TopToolbar on Tapped command, then this should be the sequence of events:
Custom control is constructed, BindingContext is null.
Page instance is created, control is added to it.
When page's BindingContext is set, through power of property inheritance, all it's child controls' BindingContext is updated, including your custom control.
At this point your custom control's BindingContext is now referencing MyCustomersPageModel. So binding in <controls:TopToolbar BackCommand="{Binding MyBackCommand}" is appropriately set.
Now the Tapped binding doesn't care about BindingContext as it's source is explicitly specified, which is parent control TopToolbar - whose BackCommand in turn is bound to the view model's command. Hence, the view-model command is now bound to gesture-recognizer's Tapped command. And it works!

Related

Binding with BasePage in Xamarin Forms

I am implementing a Xamarin app with FreshMVVM framework and I want to use a BasePage to share some code among Pages.
The problem is that when I need to bind some Properties in the MainPage.xaml I have to specify the Source in this way to make it working: Text="{Binding Title, Source={x:Reference mainPage}}". Otherwise without Source Binding doesn't work.
Ok, I get it but is this the right way? Is there another way to achieve the same result? What about when I have plenty of bindings in a page? For instance, is it possible "setting" the Source at un upper level, because in my opinion setting the same Source for each Binding is very annoying.
BasePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestXamarin.BasePage"
x:Name="basePage">
<ContentView>
<StackLayout Orientation="Vertical">
<Label Text="HEADER" FontSize="Large"/>
<Label Text="{Binding Text, Source={x:Reference basePage}}" FontSize="Large"/>
<ContentPresenter BindingContext="{Binding Parent.BindingContext}"
Content="{Binding PageContent, Source={x:Reference basePage}}" />
</StackLayout>
</ContentView>
</ContentPage>
BasePage.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TestXamarin
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BasePage : ContentPage
{
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(BasePage));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly BindableProperty PageContentProperty = BindableProperty.Create(
nameof(PageContent),
typeof(object),
typeof(BasePage));
public object PageContent
{
get { return GetValue(PageContentProperty); }
set { SetValue(PageContentProperty, value); }
}
public BasePage()
{
InitializeComponent();
}
}
}
MainPage.xaml
<local:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestXamarin"
x:Class="TestXamarin.MainPage"
Text="FROM MAIN PAGE"
x:Name="mainPage">
<local:BasePage.PageContent>
<StackLayout>
<Label Text="Body" FontSize="Large"/>
<Label Text="{Binding Title, Source={x:Reference mainPage}}" FontSize="Large"/>
</StackLayout>
</local:BasePage.PageContent>
</local:BasePage>
MainPage.xaml.cs
public partial class MainPage : BasePage
{
public MainPage()
{
Title = "MAIN PAGE";
InitializeComponent();
}
}
Another way to achieve what you are trying to do would be with control templates.
Here I have defined a template in App.xaml
<ControlTemplate x:Key="ActivityIndicatorTemplate">
<Grid>
<ContentPresenter />
<StackLayout Style="{StaticResource BlockingPanel}"
IsVisible="{TemplateBinding BindingContext.IsBusy}">
<ActivityIndicator Style="{StaticResource ActivityIndicatorStyle}"
IsVisible="{TemplateBinding BindingContext.IsBusy}"
IsRunning="{TemplateBinding BindingContext.IsBusy}" />
</StackLayout>
</Grid>
</ControlTemplate>
Note the content presenter and the TemplateBinding.
I use it on a page like this.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.MyTestPage"
ControlTemplate="{StaticResource ActivityIndicatorTemplate}"
Title="{Binding Title}">
<Grid>
...
</Grid>
</ContentPage>
The page content replaces the content presenter in the template. Looks simpler than a base page.

Bind a ListBox or ItemsControl inside a Custom Button Control

Hi I am new to XAML and I am trying to build a custom toggle button control. My control has the following code which does not give any compile or run time error but does not display the data. I kept getting error when I tried to do regular templatebinding, after research I found that I need to use Relative Source. But cant get the list to populate. Can someone guide on what I am doing wrong? Or if there is a better way to do this?
Objective: Build a checkable control which has a list control.
XAML Generic.xml: (this code is inside a togglebutton controltemplate)
<ListBox BorderBrush="Red" BorderThickness="2"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MyRoutes}">
<!--<ItemsControl.ItemsSource>
<Binding Mode="TwoWay" Path="MyRoutes">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent"/>
</Binding.RelativeSource>
</Binding>
</ItemsControl.ItemsSource>-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!--<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"></TextBlock>-->
<TextBlock Text="{Binding Path=Value, RelativeSource={RelativeSource Self}}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
Code Behind PlainToggleButton.cs:
public string[] MyRoutes
{
get { return (string[])GetValue(MyRoutesProperty); }
set { SetValue(MyRoutesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyRoutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyRoutesProperty =
DependencyProperty.Register("MyRoutes", typeof(string[]), typeof(PlainToggleButton), new PropertyMetadata(OnMyRoutesChanged));
private static void OnMyRoutesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
return;
}
Code MainWindow.xaml:
<local:PlainToggleButton Grid.Row="1" MyRoutes="{Binding MyRoutes}">
</local:PlainToggleButton>

Silverlight 4 Tooltip Visibility Binding

I'm trying to bind tooltip visibility in XAML and I'm running into a confusing problem where my visibility binding works fine on other controls but not on the tooltip.
I have a form with a submit button that is disabled when the required fields haven't been entered. When the button is disabled I want a tooltip on it with a relevant message. When it's enabled there is no need for the tooltip. To accomplish this I place the button in a transparent border and set the tooltip on the border since a tooltip on the button itself will never show when it's disabled. However, visibility binding to the tooltip fails and it seems I can only change the visibility in code-behind. I can use the exact same binding on visibility for various controls (in the example below I use it on a TextBlock as well). If I apply the exact same binding in code-behind, it works fine. Why doesn't this work in XAML?
XAML
<UserControl.Resources>
<local:VisibilityConverter x:Key="visibilityConverter"/>
<local:VisibilityConverter x:Key="reversedVisibilityConverter" IsReversed="True"/>
</UserControl.Resources>
<StackPanel Background="White"
Width="310">
<TextBlock Text="Using XAML binding for tooltip visibility..."
FontWeight="Bold"/>
<CheckBox x:Name="cbEnable"
Content="Enable Submit Button"/>
<Border Background="Transparent"
Margin="0,10,0,0">
<ToolTipService.ToolTip>
<!-- This has the same binding as the 2nd TextBlock below, it should be visible when cbEnable is NOT checked and collapsed when it is checked -->
<ToolTip Content="Submit Button Is Disabled"
Visibility="{Binding IsChecked, ElementName=cbEnable, Converter={StaticResource reversedVisibilityConverter}}"/>
</ToolTipService.ToolTip>
<Button Content="Submit"
IsEnabled="{Binding IsChecked, ElementName=cbEnable}"/>
</Border>
<!-- This TextBlock is visibile when cbEnable is checked -->
<TextBlock Text="Submit Button is enabled"
Visibility="{Binding IsChecked, ElementName=cbEnable, Converter={StaticResource visibilityConverter}}"/>
<!-- This TextBlock is visibile when cbEnable is NOT checked (same as ToolTip binding above -->
<TextBlock Text="Submit Button is disabled"
Visibility="{Binding IsChecked, ElementName=cbEnable, Converter={StaticResource reversedVisibilityConverter}}"/>
<TextBlock Text="Using code-behind binding for tooltip visibility..."
FontWeight="Bold"
Margin="0,20,0,0"/>
<CheckBox x:Name="cbEnable2"
Content="Enable Submit Button"/>
<Border Background="Transparent"
Margin="0,10,0,0">
<ToolTipService.ToolTip>
<ToolTip x:Name="toolTip2"
Content="Submit Button 2 Is Disabled"/>
</ToolTipService.ToolTip>
<Button Content="Submit 2"
IsEnabled="{Binding IsChecked, ElementName=cbEnable2}"/>
</Border>
<TextBlock Text="Submit Button 2 is enabled"
Visibility="{Binding IsChecked, ElementName=cbEnable2, Converter={StaticResource visibilityConverter}}"/>
<TextBlock Text="Submit Button 2 is disabled"
Visibility="{Binding IsChecked, ElementName=cbEnable2, Converter={StaticResource reversedVisibilityConverter}}"/>
</StackPanel>
Code-behind:
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
toolTip2.SetBinding(ToolTip.VisibilityProperty, new System.Windows.Data.Binding("IsChecked") {
Source = cbEnable2,
Converter = new VisibilityConverter() { IsReversed = true }
});
}
}
VisibilityConverter:
public class VisibilityConverter : IValueConverter {
public bool IsReversed { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
bool isVisible = System.Convert.ToBoolean(value, CultureInfo.InvariantCulture);
if (IsReversed) {
isVisible = !isVisible;
}
return (isVisible ? Visibility.Visible : Visibility.Collapsed);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
bool isVisible = ((Visibility)value == Visibility.Visible);
if (IsReversed) {
isVisible = !isVisible;
}
return isVisible;
}
}
Well, I don't know why this fixed it but here is what I did: I renamed the property in my ViewModel. Yep, I know. Seems ridiculous. I changed the property from IsWaitingVisible to WaitingVisibility. I got the idea because I changed the ViewModel property from a bool to text and then bound it to a temporary, visible TextBlock. It would not show the value the same way a different property in the same ViewModel would. That was just crazy so I renamed the property to something else and voila! The text started appearing in the UI. Then, I reconnected the visibility property of the grid (and changed my VisibilityConverter to work with strings instead of bool) and everything worked.
I guess for the purposes of science, I should change the property name back to IsWaitingVisible and see if it breaks. If so, I will have to conclude that this is a hard bug in SL 5.
This kind of flakiness just scares me when I think about building reliable apps in Silverlight.

Binding textbox with list of items

I am trying to create my first app for Windows Phone 7.
I have a Detail View and the .cs class associated with the view.
In my view, I have :
<StackPanel Grid.Row="0" Grid.Column="0">
<TextBlock Name="textBlock1" Text="..."/>
</StackPanel>
In the .cs:
...
listAgences = new List<Agence>();
Agence agence1 = new Agence();
agence1.Name = "test";
agence1.Adresse = "test1";
...
listAgences.Add(agence1);
...
How do I get to have the Text in the textbox to be "test"?
I tried stuff like:
Text="{Binding Path=listAgences[0].Name}";
I know how to do this in ASP.NET, but here I'm quite lost.
Presuming that your datacontext class has a public property List<Agence> ListAgences { get; set; } then binding like that should work, though.

binding a combobox to different DataContext

The combobox items are taken from one table, one field on which binding is made. After I saved in the database the selected item in another table, I want that the selected item to be the one which was saved. But the selected item is lost. So, my question is: can I bind the combobox to two DataContexts or maybe another solution ?.
To give an example to be more clear: the combobox items are predefined values taken from a datasource and the value selected must be saved and shown on the interface. So, from what I can see must be a binding to the predefined values and also a binding to the value saved to make a connection to the selected item.
Any suggestion?
Ioana, I don't seem to get what you're aiming at..
if you take this xaml:
<Window x:Class="WpfApplication4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<StackPanel>
<TextBox Text="{Binding Path=SelectedText, Mode=TwoWay}"
Width="200"/>
<ComboBox Width="200"
VerticalAlignment="Center"
HorizontalAlignment="Center"
SelectedItem="{Binding Path=SelectedText, Mode=TwoWay}"
ItemsSource="{Binding Path=Texts, Mode=OneWay}">
</ComboBox>
</StackPanel>
</Window>
and this codebehind:
public partial class Window1 : INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
this.Texts = new List<string>(new[] {"foo","bar"});
this.DataContext = this;
}
private ObservableCollection<string> texts;
public ObservableCollection<string> Texts
{
get
{
return texts;
}
set
{
texts = value;
if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("Texts"));
}
}
private string selectedText;
public string SelectedText
{
get
{
return selectedText;
}
set
{
selectedText = value;
if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("SelectedText"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You do have the Items and the selectedValue databound.
Notice the INotifyPropertyChanged.
Is this what you're trying to achieve ?

Resources