Xamarin CollectionView - Scroll Programatically - xamarin.forms

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:

Related

xamarin radio buttons not displaying

I have a content page with the following data template for RadioButtons:
<DataTemplate x:Key="RadioTemplate">
<StackLayout Padding="0,2,0,0">
<StackLayout
Orientation="Horizontal" >
<Label
Text="{Binding Title}"
Style="{StaticResource LabelStyle}">
</Label>
<ImageButton HorizontalOptions="Center"
CornerRadius="6"
VerticalOptions="Center"
Clicked="HelpButton_Clicked"
HeightRequest="12"
WidthRequest="12"
BorderColor="Black"
BackgroundColor="Black"
BorderWidth="1"
IsVisible="{Binding ShowHelpButton}">
<ImageButton.Source>
<FontImageSource FontFamily="FAFreeSolid"
Color="White"
Glyph="{x:Static fa:FontAwesomeIcons.Question}"/>
</ImageButton.Source>
</ImageButton>
</StackLayout>
<Label
Style="{StaticResource HelpStyle}"
Text="{Binding HelpTopic.Text}"
IsVisible="{Binding HelpTopic.IsVisible, Mode=TwoWay}">
</Label>
<StackLayout
RadioButtonGroup.GroupName="{Binding ChoiceList.Code}"
RadioButtonGroup.SelectedValue="{Binding Value}"
BindableLayout.ItemsSource="{Binding ChoiceList.Choices}"
BindableLayout.EmptyView="No choices available"
Orientation="Horizontal"
Margin="10,0,0,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<RadioButton Value="{Binding Value}"
Content="{Binding Label}"
IsChecked="{Binding IsSelected}"
CheckedChanged="OnRadioChanged"
FlowDirection="LeftToRight">
</RadioButton>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</DataTemplate>
It is called from a collection view on the content page:
<StackLayout>
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label
Text="Work Order Request"
HorizontalTextAlignment="Start"
HorizontalOptions="StartAndExpand"
VerticalTextAlignment="Center"
TextColor="White"
FontSize="Large"/>
<Button Text="Back" Clicked="BackButton_Clicked" HorizontalOptions="End" />
</StackLayout>
<Label
Padding="0,2,0,0"
Text="Verify your data before submitting"
HorizontalTextAlignment="Start"
TextColor="White"
FontSize="Micro" />
</StackLayout>
</Frame>
<Label
Margin="10,0,0,0"
Grid.Column="0"
Text="SOME TEXT HERE"
HorizontalOptions="Start"
FontSize="Micro"/>
<CollectionView
ItemsSource="{Binding MainPageView.Fields}"
EmptyView="No fields for this screen."
ItemTemplate="{StaticResource templateSelector}"
SelectionChanged="CollectionView_SelectionChanged">
</CollectionView>
<StackLayout Margin="10,0,10,5" Orientation="Vertical">
<Button Command="{Binding MainPageView.SubmitCommand}" Text="Submit" />
<validator:ErrorSummaryView
IsVisible="{Binding MainPageView.ShowValidationSummary, Mode=OneWay}"
ErrorStateManager="{Binding MainPageView.ErrorStateManager, Mode=OneWay}"
Margin="0,0,0,5"/>
</StackLayout>
</StackLayout>
If I have the App.xaml.cs instantiate the view directly, it displays fine:
public App()
{
InitializeComponent();
MainPage = new MainPage(false);
}
However, if I change and add a landing page that has a button to redirect to that page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.Ui.Dashboard">
<ContentPage.Content>
<StackLayout>
<Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
<StackLayout>
<Label
Text="TEST DASHBOARD"
HorizontalTextAlignment="Start"
VerticalTextAlignment="Center"
TextColor="White"
FontSize="Large"/>
<Label
Padding="0,2,0,0"
Text="Sample UI/UX Pages"
HorizontalTextAlignment="Start"
TextColor="White"
FontSize="Micro" />
</StackLayout>
</Frame>
<StackLayout Orientation="Horizontal">
<Button Text="Work Order" Clicked="WorkOrder_Clicked" HorizontalOptions="FillAndExpand" BackgroundColor="#2196F3" TextColor="White" FontAttributes="Bold" FontSize="Large" CornerRadius="10"/>
<Button Text="Randomized Field Order" Clicked="Randomized_Clicked" HorizontalOptions="FillAndExpand" BackgroundColor="#2196F3" TextColor="White" FontAttributes="Bold" FontSize="Large" CornerRadius="10" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Test.Ui
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Dashboard : ContentPage
{
public Dashboard()
{
InitializeComponent();
}
private void WorkOrder_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new MainPage(false));
}
private void Randomized_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new MainPage(true));
}
}
}
and change App.xaml.cs to
using Xamarin.Forms;
namespace Test.Ui
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage( new Dashboard());
}
protected override void OnStart()
{
// load up the style xaml files here
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
}
Now, when I click the Work Order button, the radio options aren't displayed. If I change the stacklayout for the radio options to veritical orientation, they do. So the data is there but the view isn't able to render when I'm in a NavigationPage. Thoughts? I'd really like to have my radio buttons be horizontal when there aren't many choices.
Thanks
In my data template, I had to add an explicit WidthRequest = 150
<StackLayout
RadioButtonGroup.GroupName="{Binding ChoiceList.Code}"
RadioButtonGroup.SelectedValue="{Binding Value}"
BindableLayout.ItemsSource="{Binding ChoiceList.Choices}"
BindableLayout.EmptyView="No choices available"
Orientation="Horizontal"
Margin="10,0,0,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<RadioButton Value="{Binding Value}"
Content="{Binding Label}"
IsChecked="{Binding IsSelected}"
VerticalOptions="Center"
IsClippedToBounds="False"
HeightRequest="27"
WidthRequest="150"
CheckedChanged="OnRadioChanged">
</RadioButton>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
I guess the StackLayout wasn't able to calculate how wide to make each one. Will see if I can figure out a different way to do it so it adjusts for the width of the content.

BindingMode=TwoWay in codebehind

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.

Hide View cell in ListView

I have a listview grouped, and everytime i touch the group header, all the items in the group hide, but for some reason the cells remain
I have tried to set a binded IsVisible property to all elements in the ViewCell, but still cant make it work
Heres my ListView xaml code:
<ListView x:Name="GroupsList"
ItemsSource="{Binding Dishes}"
IsGroupingEnabled="True"
SeparatorColor="Black"
SeparatorVisibility="Default"
HasUnevenRows="True"
GroupShortNameBinding="{Binding Key}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell Height="50">
<StackLayout
VerticalOptions="FillAndExpand"
BackgroundColor="LightSlateGray">
<Button BackgroundColor="Transparent"
BorderColor="Transparent"
BorderWidth="0"
Text="{Binding Key}"
TextColor="White"
HorizontalOptions="StartAndExpand"
Command="{Binding BindingContext.SelectGroupHeader, Source={x:Reference DishesPage}}"
CommandParameter="{Binding Key}"
ContentLayout="Right, 300"
ImageSource="next_disclosure"></Button>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="2, 5, 5, 0"
IsVisible="{Binding IsVisible}">
<Frame Padding="2"
HasShadow="False"
BackgroundColor="White">
<StackLayout Orientation="Horizontal">
<Label Margin="10"
Text="{Binding Name}"
TextColor="Black"
FontSize="Medium"
HorizontalOptions="StartAndExpand"></Label>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is my ViewModel code:
private async Task SelectedGroupHeader(string item)
{
foreach(var group in Dishes)
{
if(item == group.Key)
{
foreach(var dish in group)
{
if (dish.IsVisible)
{
dish.IsVisible = false;
}
else
{
dish.IsVisible = true;
}
}
}
}
}
before hiding:
after hiding
The problem is that you are setting ContentView.IsVisible to false when you want to hide the items, but when you do that you hide the ContentView and the items remain in the list, so you still see the items, but without a ContentView (blanks)!
One way i found out you can achieve your purpose is to store somewhere the items of the tapped group and clear its content when you tap on it. When you do that the group becomes empty, and thus the items in the ListView are not visible anymore. When you tap again in the group header you add again the items to the group so that the ListView displays again your items.
The code i got working is more or less as follows:
In XAML i only removed the binding to ContentView.IsVisible:
<ListView x:Name="GroupsList"
ItemsSource="{Binding Dishes}"
IsGroupingEnabled="True"
SeparatorColor="Black"
SeparatorVisibility="Default"
HasUnevenRows="True"
GroupShortNameBinding="{Binding Key}">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell Height="50">
<StackLayout
VerticalOptions="FillAndExpand"
BackgroundColor="LightSlateGray">
<Button BackgroundColor="Transparent"
BorderColor="Transparent"
BorderWidth="0"
Text="{Binding Key}"
TextColor="White"
HorizontalOptions="StartAndExpand"
Command="{Binding BindingContext.SelectGroupHeader, Source={x:Reference DishesPage}}"
CommandParameter="{Binding Key}"
ContentLayout="Right, 300"
ImageSource="next_disclosure"></Button>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="2, 5, 5, 0">
<Frame Padding="2"
HasShadow="False"
BackgroundColor="White">
<StackLayout Orientation="Horizontal">
<Label Margin="10"
Text="{Binding Name}"
TextColor="Black"
FontSize="Medium"
HorizontalOptions="StartAndExpand"></Label>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In your ViewModel you would have to do something like:
private async Task SelectedGroupHeader(string item)
{
foreach(var group in Dishes)
{
if(item == group.Key)
{
if(!group.IsHidden)
{
group.Clear();
group.IsHidden = true; // This is simply a flag you can set in your group class to keep track of if the group is collapsed or not
}
else
{
AddItemsToGroup(group)// Add this group all the elements that belongs to it.
group.IsHidden = false;
}
}
}
}
I hope this helps!
Note: this only works if your collection of items is an ObservableCollection, so that the ListView gets notified when the collection changes!

How to get the itemlistview details when a button is clicked

When a Add to cart button is clicked i want clicked row to add to local database.
i tried to implement clicked option, but is shows System.InvalidCastException: Specified cast is not valid.
My Xaml is :
<controls:FlowListView x:Name="gallery"
FlowColumnCount="2"
SeparatorVisibility="Default"
HasUnevenRows="True"
FlowItemsSource="{Binding ItemsGallery}"
FlowUseAbsoluteLayoutInternally="True"
FlowItemTapped="OnFlowItemTapped"
FlowColumnExpand="Proportional"
BackgroundColor="White">
<controls:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout>
<Frame BorderColor="#DCDCDC" HasShadow="True" Margin="2,2,2,2" CornerRadius="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="35"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<ffimageloading:CachedImage Source="{Binding image}" Grid.Row="0" LoadingPlaceholder="ItemsGallery" HeightRequest="120" WidthRequest="120" />
<Label Grid.Row="1"
VerticalOptions="End"
FontAttributes="Bold"
FontSize="Medium"
HorizontalTextAlignment="Start"
TextColor="Black"
Text="{Binding name}" />
<Label Grid.Row="2" HorizontalOptions="Start"
VerticalOptions="End"
FontAttributes="Bold"
FontSize="Medium"
Text="{Binding weight}" />
<Label HorizontalOptions="Start" Grid.Row="3"
VerticalOptions="End"
FontAttributes="Bold"
FontSize="Medium"
Margin="2"
Text="{Binding price}" />
<Button Text="Add TO Cart" Grid.Row="4" BackgroundColor="Coral" TextColor="WhiteSmoke" Clicked="Button_Clicked_1" />
</Grid>
</Frame>
</StackLayout>
</DataTemplate>
</controls:FlowListView.FlowColumnTemplate>
</controls:FlowListView>
and my logic is :
private async void Button_Clicked_1(object sender, EventArgs e)
{
if (((ListView)sender).SelectedItem == null)
return;
var myList = (ListView)sender;
var myProuct= (myList.SelectedItem as ProductDetail);
}
the sender of a Button Click event will be a BUTTON, not the ListView containing the button
private async void Button_Clicked_1(object sender, EventArgs e) {
// the sender of a button click event is a BUTTON
var btn = (Button)sender;
var myProduct = (ProductDetail)btn.BindingContext;
}
You could go with regular data binding way with viewmodel, or you can try this
Button Text="Add TO Cart" Grid.Row="4" BackgroundColor="Coral" TextColor="WhiteSmoke" Command="{Binding AddCommand}" CommandParameter = "{Binding .}"
But then you would need to edit the model building up ItemsGallery list.
public ICommand AddCommand { get; set; }
And when you are creating your objects
new Gallery
{
//your stuff
AddCommand = new Command<Gallery>(async (item) => await
AddItem(item))
})
private async Task AddItem(Gallery item)
{
//your implementation
}

Xamarin Switch Toggled event : get bound item of list

Latest Xamarin on Mac as of writing:
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ItemsListView" ItemsSource="{Binding Items}" VerticalOptions="FillAndExpand" HasUnevenRows="true" RefreshCommand="{Binding LoadItemsCommand}" IsPullToRefreshEnabled="true" IsRefreshing="{Binding IsBusy, Mode=OneWay}" CachingStrategy="RecycleElement" ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<StackLayout Padding="10">
<Label Text="{Binding Summary}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemTextStyle}" FontSize="Medium" />
<Label Text="{Binding Reporter}" LineBreakMode="NoWrap" Style="{DynamicResource ListItemDetailTextStyle}" FontSize="Micro" />
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="EndAndExpand">
<Label Text="{Binding Start}" FontSize="Medium" VerticalOptions="Center"/>
<Switch IsToggled="{Binding Result}" Toggled="Handle_Toggled" VerticalOptions="Center"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
How do I get the bound item to a Switch control in a ListView. The item is in the list of items bound to the ListView, provided by the ViewModel. You can 'switch' the item's toggle without selecting the row.
void Handle_Toggled(object sender, Xamarin.Forms.ToggledEventArgs e)
{
// In this event handler, how do I get the bound item from the ListView??
}
Normally, what you'd need to do, is to react to the changes of property "Result" in your item class, and keep your logic out of the Page class.
If you need to handle in the event, you can do it this way:
void Handle_Toggled(object sender, Xamarin.Forms.ToggledEventArgs e)
{
var switch = sender as Switch;
var item = switch.Parent.BindingContext as ItemViewModel;
}
Replace ItemViewModel by your item's type.

Resources