BindingMode=TwoWay in codebehind - xamarin.forms

Edit: apologies for the bad title, apparently something like "Setting bindingmode in code behind" didn't fit the highly nebulous requirements of SO. A title that is clearly more obvious than what the current one is.
Original: I am trying to set the binding of my listview in the code behind of its data selector template. The reason i am doing this, as I suspect (because doing something similar to it in a different template selector fixed it) that once you exit that page android seems to still contain a reference to it and then throws a amarin.forms.platform.android.viewcellrendererA disposed object exception.
my current xaml looks as follows:
<StackLayout Orientation="Vertical"
Padding="0, 20, 0, 0"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand">
<Label
x:Name="LabelName"
FontAttributes="Bold"
TextColor="Black"
FontSize="14"
VerticalOptions="Start"
HorizontalOptions="FillAndExpand"/>
<ListView x:Name="MultiselectList"
SeparatorVisibility="None"
RowHeight="30"
Margin="0, 10, 0, 0"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<customcontrols:NoHighlightCell>
<StackLayout Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
VerticalOptions="StartAndExpand">
<Image
x:Name="image"
Source="{Binding ImageUrl}"
VerticalOptions="StartAndExpand"
HorizontalOptions="Start"
Margin="0, 2, 0, 0"
WidthRequest="15"
HeightRequest="15" />
<Label
x:Name="name"
Text="{Binding Name}"
TextColor="Black"
VerticalOptions="StartAndExpand"
FontSize="14"
HorizontalOptions="Start"/>
</StackLayout>
</customcontrols:NoHighlightCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Text="Selection required"
TextColor="Red"
Margin="0, 10, 0, 0"
IsVisible="{Binding ValidationRequired}"
VerticalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
FontSize="14"/>
</StackLayout>
Here's my code behind:
private SoapNoteControlsViewModel viewModel;
public SelectListTemplate()
{
InitializeComponent();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
}
protected override void OnAppearing()
{
base.OnAppearing();
LabelName.Text = viewModel.Label;
MultiselectList.ItemsSource = viewModel.CheckboxItems;
MultiselectList.SelectedItem = viewModel.SelectedItem;
//how would i set the BindingMode here? Doing .SetBinding doesn't seem to do it.
}
protected override void OnBindingContextChanged()
{
try
{
base.OnBindingContextChanged();
if(BindingContext == null)
{
return;
}
viewModel = BindingContext as SoapNoteControlsViewModel;
} catch (Exception e)
{
Console.WriteLine(e);
}
}
}
I tried doing .SetBinding, but it keeps saying that BindingMode is a type (given that it is an enum). Looking at some of the examples on MSDN haven't really helped

You would do it like this:
MultiselectList.SetBinding(ListView.ItemsSourceProperty, nameof(viewModels.CheckboxItems), BindingMode.TwoWay);
The first parameter is the property that you want to bind to - e.g. the ItemsSource.
The second parameter is the name of the property to look for - e.g. your viewModel's CheckBoxItems. This collection will be bound/populated in the list.
The last parameter is what you would want to achieve - the BindingMode. You are correct in saying that it is an enum, so here we can set it to BindingMode.TwoWay.
For reference: BindingMode's Remarks page.

Related

Xamarin ViewCell subclass getting error in run time. "ViewCell Must Have a View"?

I am getting this runtime error saying that the CellView needs a View. I have not found anything on this so far, here on StackOverflow or on google, so I am in the total dark about what needs to be fixed.
I need to set up a list view with 2 sections. the first one is for the bikes that are in the database and the other section is for the ones that we detected but are not saved in the DB yet.
Both sections use the same UI (cell) and so I need to be able to have a sliding button (ContextAction MenuItem) in the first section and for the second section this action needs to be disabled/removed.
If it's not possible then I would need to have the action for the second section "Add" instead of "Forget".
This is why I created a subclass.
this is my code.
(edit: corections)
---- .xaml file ----
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="EGrid18.Components.BikeSelectionCell"
x:Name="ThisCell">
<ViewCell.View>
<StackLayout>
<Grid ColumnSpacing="10"
BindingContext="{x:Reference ThisCell}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding BikeName}"
StyleClass="bikecellname"
Grid.Row="0"
Grid.Column="0"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand"/>
<Image Source="{Binding Image}"
HeightRequest="16"
WidthRequest="16"
VerticalOptions="Center"
Aspect="AspectFit"
Grid.Column="1"
Grid.Row="0"/>
<Label Text="{Binding Distence, StringFormat='{0:F2} M'}"
Grid.Row="0"
Grid.Column="2"
VerticalOptions="Center"/>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
---- .cs file ----
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace EGrid18.Components
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BikeSelectionCell : ViewCell
{
public static BindableProperty BikeNameProperty =
BindableProperty.Create(nameof(BikeName), typeof(string), typeof(BikeSelectionCell), string.Empty);
public string BikeName
{
get => (string)GetValue(BikeNameProperty);
set => SetValue(BikeNameProperty, value);
}
public static BindableProperty ImageProperty =
BindableProperty.Create(nameof(Image), typeof(string), typeof(BikeSelectionCell), string.Empty);
public string Image
{
get => (string)GetValue(ImageProperty);
set => SetValue(ImageProperty, value);
}
public static BindableProperty DistenceProperty =
BindableProperty.Create(nameof(Distence), typeof(double), typeof(BikeSelectionCell), 0.0);
public double Distence
{
get => (double)GetValue(DistenceProperty);
set => SetValue(DistenceProperty, value);
}
public static readonly BindableProperty ForgetCommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(BikeSelectionCell), null);
public ICommand ForgetCommand
{
get { return (ICommand)GetValue(ForgetCommandProperty); }
set { SetValue(ForgetCommandProperty, value); }
}
public Command OnForget => new Command(() => Execute(ForgetCommand));
// Helper method for invoking commands safely
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if(propertyName == nameof(Image))
{
this.ContextActions.Clear();
if (Image != "unknown")
{
this.ContextActions.Add(new MenuItem()
{
Text = "Forget",
Command = ForgetCommand,
CommandParameter = this
});
}
}
}
}
}
--- the .xaml file that consumes the CellView subclass ---
<?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:vm="clr-namespace:EGrid18.ViewModels"
xmlns:iOS="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:components="clr-namespace:EGrid18.Components"
iOS:Page.UseSafeArea="True"
x:Class="EGrid18.Views.BikeSelectionPage"
Title="Bike Selection List">
<ContentPage.Resources>
<ResourceDictionary>
<vm:BikeListViewModel x:Key="vm"/>
<StyleSheet Source="/CSS/Styles.css"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem x:Name="ScanButton" Text="Scan"
Command="{Binding Source={StaticResource vm}, Path=OnScanCommand}"
CommandParameter="{Binding Source={StaticResource vm}, Path=IsScanning}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Margin="8,8,8,8">
<Label Text="Bike Selection Page"
StyleClass="titlelabel"/>
<ListView x:Name="ListView"
BindingContext="{StaticResource vm}"
StyleClass="listview"
ItemsSource="{Binding BikeList}"
IsGroupingEnabled="True"
SelectedItem="{Binding SelectedBike}"
Margin="8,8,8,8">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Heading}"
VerticalOptions="Center"
Margin="8,8,8,8"/>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<components:BikeSelectionCell
BikeName="{Binding Name}"
Image="{Binding Image}"
Distence="{Binding Distence}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
A ViewCell is not a View - it isn't part of View heirarchy. It is a "wrapper" that is needed by ListView, around each item.
Personally, I never create subclasses of ViewCell, though that can be done.
IMHO, Its more straightforward to make a custom View, then wrap that in <ViewCell> to use with ListView.
As a View, you can inherit from any view class. If you don't know what else to use, inherit from ContentView. (Or from StackLayout, if need to add multiple children views.) In your case, your View is a StackLayout.
public partial class BikeSelectionView : StackLayout
... // All the lines of code you have in BikeSelectionCell go here.
xaml:
<StackLayout ... x:Name="ThisCell">
<Grid ColumnSpacing="10" ...>
... All the remaining lines of code from your xaml ...
</StackLayout>
Usage inside ListView's ItemTemplate:
<DataTemplate>
<ViewCell>
<components:BikeSelectionView
BikeName="{Binding Name}"
Image="{Binding Image}"
Distence="{Binding Distence}"/>
</ViewCell>
</DataTemplate>
One advantage of this approach is if you switch from ListView to CollectionView (which does not use ViewCells), you can still use the custom class:
<CollectionView.ItemTemplate>
<DataTemplate>
<components:BikeSelectionView ... />
</DataTemplate>
</CollectionView.ItemTemplate>
The error was coming from a missing constructor that needed to initialize the Component.
public BikeSelectionCell()
{
InitializeComponent();
}
In the end I used the suggestion ToolmakerSteve. I also used a custom DataTemplateSelector to set specifics DataTemplate for the 2 different sections
I am setting this as the answer for any one looking for this problem as the problem was really the missing call to InitializeComponent();

Xamarin - How to call a function in Collection View without error?

I'm trying to call a function in a Xamarin project by using the SelectionChanged property.
Inside this property, I've called a function that I've declared in the cs file.
Here is the XAML code:
<CollectionView x:Name="PCCollection" SelectionMode="Single" SelectionChanged="Cell_Tapped" AutomationId="{Binding Tipologia_Alimento}">
Here is the CS function:
private async void Cell_Tapped(object sender, System.EventArgs e) {
Console.WriteLine("Tapped");
Console.WriteLine((sender as Cell).AutomationId.ToString());
}
When I click on the Collection View cell, it prints the value "Tapped" but it gives me also the Break Mode error: "The application is in break mode".
Could you help me with this error?
Thanks in advance.
Your syntax is not valid. The Collection View control does not have AutomationId property.
Sample
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionChanged="Cell_Tapped"
SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Label Grid.Column="1"
Text="{Binding Name}"
FontAttributes="Bold" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
void Cell_Tapped(object sender, SelectionChangedEventArgs e)
{
if (((CollectionView)sender).SelectedItem == null)
return;
string current = (e.CurrentSelection.FirstOrDefault() as Monkey)?.Name;
}
You can find more here
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/selection
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/populate-data

Xamarin CollectionView - Scroll Programatically

I have collection view . See below code
<CollectionView ItemsSource="{Binding photos}" HeightRequest="300"
ItemSizingStrategy="MeasureAllItems" Margin="10,0,10,0"
x:Name="photosView"
ItemsUpdatingScrollMode="KeepScrollOffset">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="3" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="2">
<Frame BackgroundColor="Red" HeightRequest="79" WidthRequest="53" Padding="0" Margin="0"
IsClippedToBounds="True" HasShadow="False" CornerRadius="10">
<Image Aspect="AspectFill" Source="{Binding imageUrl}"/>
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I am showing 3 rows and 3 columns of the images. If I have more than 9 images then I am showing Button which says Scroll for more photos. Now On click on the imageI have below code
void OnScrollMore(System.Object sender, System.EventArgs e)
{
//photosView.SendScrolled(new ItemsViewScrolledEventArgs() { FirstVisibleItemIndex = 0 });
photosView.ScrollTo(photosView.Y + 10, position: ScrollToPosition.MakeVisible, animate: true);
}
But nothing is happening . It is not getting scrolled to next row.
Any suggestions?
The reason your ScrollTo method is not working is because the photosView can't find the item 'photosView.Y + 10' in your photosView itemssource. The method you're invoking is trying to find an item in the ItemsSource. It is not scrolling to a y-position like you're trying to do. You can see it in the description of the method when going to the definition. It is waiting for an 'object item'.
public void ScrollTo(object item, object group = null, ScrollToPosition position = ScrollToPosition.MakeVisible, bool animate = true);
If what you're trying to do is scroll to the last added item of the collectionview, then try this working approach and build it up from there. Here everytime you press the button, an item (string) gets added. This item is set as the ScrollTo object at the end of the button click handler.
MainPage.xaml
<StackLayout Orientation="Vertical">
<CollectionView ItemsSource="{Binding photos}" HeightRequest="300"
ItemSizingStrategy="MeasureAllItems" Margin="10,0,10,0"
x:Name="photosView"
ItemsUpdatingScrollMode="KeepLastItemInView">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="3" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="2">
<Frame BackgroundColor="Red" HeightRequest="79" WidthRequest="53" Padding="0" Margin="0"
IsClippedToBounds="True" HasShadow="False" CornerRadius="10">
<Label Text="{Binding}" TextColor="White"
HorizontalOptions="Center" VerticalOptions="Center"
HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="scroll more" HorizontalOptions="Center" VerticalOptions="End" Clicked="OnScrollMore"/>
</StackLayout>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
ObservableCollection<string> ObservableItems { get; set; }
public MainPage()
{
InitializeComponent();
ObservableItems = new ObservableCollection<string>(new List<string>() { "een", "twee", "drie" });
photosView.ItemsSource = ObservableItems;
}
void OnScrollMore(System.Object sender, System.EventArgs e)
{
var item = (ObservableItems.Count + 1).ToString();
ObservableItems.Add(item);
photosView.ScrollTo(item, position: ScrollToPosition.MakeVisible, animate: true);
}
}
Resulting in:

How can I remove Margin from a Button in Xamarin Forms XAML?

I'm totally new to Xamarin, so please be patient!
Somehow Xamarin adds a mysterious Margin to all my Buttons and I don't know how to get rid of it.
Here is my Code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="RockStamp.CameraSearch_Scan">
<StackLayout Orientation="Vertical" Padding="0" Spacing="0">
<StackLayout Orientation="Horizontal" Padding="0" Spacing="0">
<Button Text="Test" HeightRequest="50" WidthRequest="60" TextColor="#333333" x:Name="btnBack" VerticalOptions="Center" HorizontalOptions="Start" ></Button>
<Label Text="Scan a..." FontSize="20" FontAttributes="Bold" BackgroundColor="{StaticResource BlueBackColor}" TextColor="White" VerticalOptions="Start" HorizontalOptions="FillAndExpand" />
</StackLayout>
<Label Text="Steady now, we try to detect your..." FontSize="16" VerticalOptions="Start" HorizontalOptions="Start" />
<!-- Camera Placeholder -->
<BoxView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="#eeeeee" ></BoxView>
<StackLayout Orientation="Horizontal" Padding="0" Spacing="0">
<Button Text="Cancel" TextColor="#333333" x:Name="btnCancel" VerticalOptions="Center" HorizontalOptions="FillAndExpand" ></Button>
<Button Text="Scan now!" TextColor="#333333" x:Name="btnScan" VerticalOptions="Center" HorizontalOptions="FillAndExpand" ></Button>
</StackLayout>
</StackLayout >
</ContentPage>
Here an image:
You can clearly see the space around the Button. Where does it come from - and more important: How can I remove it?
The problem is that the default button background contains this margin. You have to set the BackgroundColor to a color and set the BorderWidth and BorderRadius to zero manually.
<Button
BackgroundColor="Fuchsia"
BorderRadius="0"
BorderWidth="0"
Text="Test"
HeightRequest="50"
WidthRequest="60"
TextColor="#333333"
x:Name="btnBack"
Margin="0"
VerticalOptions="Start"
HorizontalOptions="Start" />
You may have fixed the problem :
The way I fixed this is to create a renderer on android to override the button on xamarin.
public class FixedMarginRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
// remove default background image
Control.Background = null;
Control.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "BackgroundColor")
{
// You have to set background color here again, because the background color can be changed later.
Control.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
}
}
}
Just to add on to Sven-Michael Stübe's answer.
If none of your buttons require the Margin then create a Style or a custom control in your PCL (eg: MarginLessButton).
Setting BackgroundColor, BorderWidth, and BorderRadius manually does not work anymore. But you can use platform configuration to remove the padding. It is also necessary to remove the shadow otherwise there will be some vertical margin.
Try something like this:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
namespace MyControls
{
public class ButtonNoMargin : Xamarin.Forms.Button
{
public ButtonNoMargin() : base()
{
this.On<Android>().SetUseDefaultPadding(false);
this.On<Android>().SetUseDefaultShadow(false);
}
}
}
For more information, see https://github.com/xamarin/Xamarin.Forms/pull/1935

WP7 Updating Pivot control title

I have a Pivot control which I am using as following within the XAML.
I have bound the Pivot Title to a method on my view model as its content will vary depending upon what is being displayed.
<controls:Pivot x:Name="MainPivot" ItemsSource="{Binding PivotItemHeaders}" Title="{Binding ApplicationTitle}" >
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<ListBox x:Name="EventsListbox"
ItemsSource="{Binding allEventItems}"
ItemTemplate="{StaticResource EventDisplay3}"
SelectionChanged="EventsListbox_SelectionChanged"/>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
The collection of items is being refreshed and the binding is working fine for these objects - however the Pivot title is not refreshing with the new value.
It seems stuck at the value when the page/pivot control was first shown.
Any ideas how I can get the pivot control to refresh? - Thanks
I just did a quick test, binding works just fine:
<controls:Pivot Title="MY APPLICATION" ItemsSource="{Binding Items}">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding LineOne}" />
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<Grid>
<Button Content="Update" Click="Button_Click" />
</Grid>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
And in the C#
private void Button_Click(object sender, RoutedEventArgs e)
{
App.ViewModel.Items.Clear();
App.ViewModel.Items.Add(new ItemViewModel() { LineOne = "foo" });
App.ViewModel.Items.Add(new ItemViewModel() { LineOne = "bar" });
App.ViewModel.Items.Add(new ItemViewModel() { LineOne = "baz" });
}
So clearly you're doing something very wrong. Post your code and we'll take a look.
Update
Title Binding also works
XAML
<controls:Pivot Title="{Binding Title}">
<controls:PivotItem Header="first">
<Grid>
<Button Click="Button_Click" Content="OK!" />
</Grid>
</controls:PivotItem>
</controls:Pivot>
C#:
private void Button_Click(object sender, RoutedEventArgs e)
{
Title = "foobar!";
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}

Resources