CollectionView does not automatically Srcoll in Xamarin - xamarin.forms

I am using CollectionView to display data. I want CollectionView autoplay. However it doesn't work, even though I have it set to run automatically from time to time.. Don't know what I did wrong.
.xaml
<StackLayout HeightRequest="100">
<CollectionView x:Name="_data" HeightRequest="150" HorizontalScrollBarVisibility="Never" VerticalScrollBarVisibility="Never">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" SnapPointsType="Mandatory" SnapPointsAlignment="Start" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Image HorizontalOptions="Fill" Aspect="AspectFill" Source="{Binding Image}" WidthRequest="200"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
.xaml.cs
public Test2()
{
InitializeComponent();
LoadData();
}
protected async void LoadData()
{
var monkeyJson = await httpClient.GetStringAsync(monkeyUrl);
var monkeys = JsonConvert.DeserializeObject<Monkey[]>(monkeyJson);
foreach (var monkey in monkeys)
{
Monkeys.Add(monkey);
}
_listProd = Monkeys.ToList();
_data.ItemsSource = _listProd;
Device.StartTimer(TimeSpan.FromSeconds(5), (Func<bool>)(() =>
{
_data.ScrollTo(_listProd.Count + 2, -1, ScrollToPosition.Start, true);
return true;
}));
}
Note that I used CarouselView. However this time I want to use CollectionView
I searched for keywords related to CollectionView. However, there were no positive results. Looking forward to everyone's help. Thank you.
Update
...
MainThread.BeginInvokeOnMainThread(() =>
{
Device.StartTimer(TimeSpan.FromSeconds(5), (Func<bool>)(() =>
{
_data.ScrollTo(_listProd.Count + 2, -1, ScrollToPosition.Start, true);
return true;
}));
});

Related

Command RelativeSource not working in Xamarin

I have Binding Data problem in subpages. I have a Page that shows a list of products. When clicking on the product it will go to the Product Details subpage. Everything works fine:
Product.xaml
<RefreshView x:DataType="locals:DashboardViewModel" Padding="0" Command="{Binding LoadDashboardCommand}" IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout BindableLayout.ItemsSource="{Binding ProductNew}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame x:DataType="model:Product">
<StackLayout>
<Label FontSize="14" MaxLines="2" LineBreakMode="TailTruncation" HeightRequest="40" Text="{Binding Name}"/>
</StackLayout>
<Frame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type locals:DashboardViewModel}}, Path=ProductTappedView}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Product.xaml.cs
DashboardViewModel dashboardViewModel;
public Product()
{
BindingContext = dashboardViewModel = new DashboardViewModel(Navigation);
dashboardViewModel.OnAppearing();
}
DashboardViewModel.cs
public class DashboardViewModel : BaseDashboardViewModel
{
public Command LoadDashboardCommand { get; }
public Command ProductTappedView { get; }
public ObservableCollection<Product> ProductNew { get; }
public DashboardViewModel(INavigation _navigation)
{
LoadDashboardCommand = new Command(async () => await ExecuteLoadDashboardCommand());
ProductNew = new ObservableCollection<Product>();
ProductTappedView = new Command<Product>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(Product detailProduct)
{
await Navigation.PushAsync(new ProductDetail(detailProduct));
}
............
}
Next in my Productdetail page show product details. I have Read more. When clicked it will redirect to another subpage: ContentDetailProd.xaml
ProductDetail.xaml
<ContentPage.BindingContext>
<locals:ViewDetailProductViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="15">
<Label Text="{Binding ProductNews.Name}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label Text="Read more"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Source={RelativeSource AncestorType={x:Type locals:ViewDetailProductViewModel}}, Path=ContentProductTappedView}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
</StackLayout>
</ContentPage.Content>
ViewDetailProductViewModel.cs
public class ViewDetailProductViewModel : BaseDashboardViewModel
{
public Command ContentProductTappedView { get;}
public ViewDetailProductViewModel()
{
ProductNews = new Product();
ContentProductTappedView = new Command<Product>(OnViewContentDetailProduct);
}
private async void OnViewContentDetailProduct(Product detailProduct)
{
await Navigation.PushAsync(new ContentDetailProd(detailProduct));
}
}
ContentDetailProd.xaml.cs
public ContentDetailProd(Product detailProduct)
{
InitializeComponent();
//Load Readmore
}
However when I Debug it actually doesn't run on the event: ContentProductTappedView and as a result it doesn't redirect to the ContentDetailProd.xaml page. I've tried everything but still can't solve the problem.
Looking forward to everyone's help. Thanks
If you want to pass the data through a Page Constructor, you could refer to the link below. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical#passing-data-when-navigating
I changed like this based on the documentation #Wendy Zang - MSFT provided. Everything looks good.
ProductDetail.xaml
<StackLayout x:Name="_readmore" Padding="15">
<Label Text="{Binding ProductNews.Name}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label x:Name="_content" MaxLines="2" LineBreakMode="TailTruncation" Text="{Binding ProductNews.Contents}" FontFamily="RobotoMedium" FontSize="18" TextTransform="Uppercase"/>
<Label Text="Read more"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="_readmore_Tapped" />
</StackLayout.GestureRecognizers>
</StackLayout>
ProductDetail.xaml.cs
private async void _readmore_Tapped(object sender, EventArgs e)
{
var contentget = _content.Text;
var detailProduct = new Product
{
Contents = contentget,
};
var detailContentPage = new ContentDetailProd(detailProduct);
detailContentPage.BindingContext = detailProduct;
await Navigation.PushAsync(detailContentPage);
}
ContentDetailProd.xaml.cs
public ContentDetailProd(Product detailProduct)
{
InitializeComponent();
//Load Readmore
if (detailProduct != null)
{
_contentmore.Text = detailProduct.Contents;
}
}

Xamarin Forms CollectionView with SwipeView on items - tapping ImageButton selects cell

I have an ImageButton in every cell in my CollectionView. When I tap on the ImageButton I expect it to capture the touch event and handle it, however it also passes the touch event up to the cell and selects that cell in the CollectionView.
Tapping the call changes the SelectedItem and opens the detail page for that contact. Tapping the ImageButton starts a call, but immediately switches to the detail page.
Here is a screenshot of the page:
The CollectionView is defined as:
<CollectionView
x:Name="contactsList"
ItemsSource="{Binding Contacts}"
SelectionMode="Single"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
ItemSizingStrategy="MeasureAllItems"
IsGrouped="True"
EmptyView="No Contacts">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.GroupHeaderTemplate>
...
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView
x:DataType="models:Contact">
...
<StackLayout
BackgroundColor="{StaticResource BackgroundColor}">
<Grid
Padding="0,15,0,10"
ColumnDefinitions="80,*,80"
RowDefinitions="*,*"
BackgroundColor="{StaticResource BackgroundColor}">
<Ellipse
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Fill="{Binding Colour, Converter={StaticResource intToBrushColor}}"
.../>
<Label
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Text="{Binding Initials}"
.../>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding FullName}"
.../>
<StackLayout
Grid.Column="1"
Grid.Row="1"
Orientation="Horizontal">
<Image
HeightRequest="15"
Source="{Binding WasOutgoing, Converter={StaticResource callDirectionToIcon}}"/>
<Label
Grid.Column="1"
Grid.Row="1"
Text="{Binding TimeStamp}"
.../>
</StackLayout>
<ImageButton
Grid.Column="2"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,0,15,0"
Padding="10"
BackgroundColor="Transparent"
Source="{StaticResource IconCalls}"
Command="{Binding BindingContext.CallCommand, Source={x:Reference contactsPage}}"
CommandParameter="{Binding .}"/>
</Grid>
<BoxView
Style="{StaticResource Seperator}"/>
</StackLayout>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
How do I make the ImageButton keep the touch event and stop the cell from being selected when the ImageButton is tapped?
Here are a few dirty workarounds I considered but these are not ideal:
Split the cell into two Grids and have two TapGestureRecognizers.
Track if the ImageButton was tapped and ignore the next selection change.
These are not ideal, will cost more and break MVVM pattern. The root cause of this issue is the ImageButton not keeping the touch event or marking it as handled.
Does anyone know a cleaner solution to this problem?
I've narrowed your problem down to use of SwipeView, in ItemTemplate. This seems to force the item to be selected.
Without it, works as intended.
I infer that SwipeView alters touch events, to force row selection, in order to perform its action.
See WORKAROUND below, for a hack fix.
xaml:
<ContentPage.Content>
<StackLayout>
<CollectionView
x:Name="contactsList"
ItemsSource="{Binding Contacts}"
SelectionMode="Single"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
ItemSizingStrategy="MeasureAllItems" >
<CollectionView.ItemTemplate>
<DataTemplate>
<!--<SwipeView>-->
<StackLayout>
<Grid
Padding="0,15,0,10"
ColumnDefinitions="*,80">
<Label Grid.Column="0" Text="abcdef" />
<Button
Grid.Column="1"
Padding="4"
Text="Press Me"
Clicked="Button_Clicked"
/>
</Grid>
</StackLayout>
<!--</SwipeView>-->
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
xaml.cs:
public partial class CollectionViewWithCellButtonPage : ContentPage
{
private Model selectedContact;
public CollectionViewWithCellButtonPage()
{
InitializeComponent();
BindingContext = this;
}
private void Button_Clicked(object sender, EventArgs e)
{
}
public ObservableCollection<Model> Contacts { get; set; } = new ObservableCollection<Model> {
new Model(),
new Model(),
new Model(),
};
public Model SelectedContact {
get => selectedContact;
set => selectedContact = value;
}
}
With breakpoints on SelectedContact setter, and on Button_Clicked, a click on button does not affect SelectedContact. Click elsewhere on row does. This is the desired behavior.
Then uncomment <SwipeView> and </SwipeView>.
Now, SelectedContact setter is called. BEFORE Button_Clicked.
Because the call is BEFORE, I don't see any easy fix.
Fixing this "right" probably requires custom renderer (per platform) for SwipeView.
WORKAROUND
Got it to work. But this is a hack.
Delay action taken when SelectContact. This gives us time to find out if Button was pushed. (Step 2 will show _suppressSelection getting set.)
private Model _selectedContact;
private bool _suppressSelection;
public Model SelectedContact
{
get => _selectedContact;
set
{
Device.BeginInvokeOnMainThread(async () =>
{
await DelayedSetSelectedContact(value);
});
}
}
private async Task DelayedSetSelectedContact(Model value)
{
await Task.Delay(100);
if (_suppressSelection)
{
// Button was pressed. DO NOTHING - DON'T select the item.
// Clear state for next time.
_suppressSelection = false;
}
else
{
_selectedContact = value;
// ... Do your other work here ...
}
}
Button click sets _suppressSelection. Make sure _suppressSelection can't get "stuck on".
private System.Timers.Timer _buttonTimer;
protected override void OnAppearing()
{
base.OnAppearing();
// Make sure _suppressSelection can't get "stuck on".
_buttonTimer = new System.Timers.Timer { Interval = 500, AutoReset = false };
_buttonTimer.Elapsed += Timer_Elapsed;
}
private void Button_Clicked(object sender, EventArgs e)
{
// FIRST LINE in method - do this as early as possible.
_suppressSelection = true;
//... your main logic here ...
// Make sure _suppressSelection can't get "stuck on".
_buttonTimer.Start();
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// "if" line can be commented out. I just have it so breakpoint on following line is only hit if
// timer is needed to do its job. Some sequences of item selection and button presses do hit that breakpoint.
if (_suppressSelection)
_suppressSelection = false;
}
Clean up when leave page.
protected override void OnDisappearing()
{
base.OnDisappearing();
// Stop timer. Release reference.
if (_buttonTimer != null)
{
_buttonTimer.Stop();
_buttonTimer = null;
}
// Clean up state, in case navigate back to page.
_suppressSelection = false;
}
Full code in CollectionViewWithCellButtonPage in ToolmakerSteve - repo XFormsSOAnswers.

Xamarin Forms Switch inside Stacklayout BindableLayout get toggled event using MVVM (Prism.Forms)

I have the following list:
<StackLayout BindableLayout.ItemsSource="{Binding NotificationList}" Margin="0,10,0,10" >
<BindableLayout.ItemTemplate>
<DataTemplate> <StackLayout Style="{StaticResource StacklayoutAStyle}">
<Label Text="{Binding notificationLabel}" VerticalOptions="Center" VerticalTextAlignment="Center"/>
<Switch IsToggled="{Binding isNotificationToggled, Mode=TwoWay}" VerticalOptions="Center" HorizontalOptions="End" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
I'd like to get the notificationLabel text when I toggle the corresponding switch in the list. My ViewModel looks like this:
private ObservableCollection<NotificationModel> _notificationList = new ObservableCollection<NotificationModel>
{
new NotificationModel { notificationLabel = "Notification1", isNotificationToggled = true },
new NotificationModel { notificationLabel = "Notification2", isNotificationToggled = false },
new NotificationModel { notificationLabel = "Notification3", isNotificationToggled = false },
new NotificationModel { notificationLabel = "Notification4", isNotificationToggled = true },
};
public ObservableCollection<NotificationModel> NotificationList
{
get { return _notificationList; }
set { SetProperty(ref _notificationList, value); }
}
When I toggled Notification 3 for example, it switches to on (true), but how can I capture that event with "Notification 3"?
You can use Switch.Toggled to listen for toggled event,then get the BindingContext (NotificationModel),then determine which Switch is turned on according to NotificationModel.notificationLabel.
<StackLayout BindableLayout.ItemsSource="{Binding NotificationList}" Margin="0,10,0,10" >
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Style="{StaticResource StacklayoutAStyle}">
<Label Text="{Binding notificationLabel}" VerticalOptions="Center" VerticalTextAlignment="Center"/>
<Switch IsToggled="{Binding isNotificationToggled, Mode=TwoWay}" VerticalOptions="Center" HorizontalOptions="End" Toggled="Switch_Toggled" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
in your page.xaml.cs:
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
Switch sw = sender as Switch;
NotificationModel notificationModel = (NotificationModel)sw.BindingContext;
if (e.Value)
{
switch (notificationModel.notificationLabel)
{
case "Notification1":
//do something
break;
case "Notification2":
//do something
break;
case "Notification3":
//do something
break;
case "Notification4":
//do something
break;
}
}
}

How to implement a listView "quick filtering" like week calendar view?

I've created a customer specific task management app with tasks placed on specific dates (and sometime hours), but here the date is important.
I'm using a listView and have a DatePicker setting for selected other dates than today. So far so good.
I would like to implement a week quick-filter option so that e.g., the dates of the current week is displayed at the top of the list view and a click on a certain date would filter the listView accordingly. Kind of a standard outlook-like week view.
How would I do this in the best way?
CustomControl that I put above the listView?
ViewPager control?
Any ideas or suggestions much appreciated.
P.S. I need to be able to target both Android and iOS.
Set two Properties in the ViewModel one for containing all the Items EntireCollection and another to store the Filtered Items FilteredCollection. On button click derive the Filtered item from entire list using Where.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListItem> filteredCollection;
public ObservableCollection<ListItem> FilteredCollection
{
get
{
return filteredCollection;
}
set
{
filteredCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<ListItem> entireCollection;
public ObservableCollection<ListItem> EntireCollection
{
get
{
return entireCollection;
}
set
{
entireCollection = value;
OnPropertyChanged();
}
}
public ViewModel()
{ ...
this.FilterCollection = this.EntireCollection;
...
}
}
Button clicked
void Button_Clicked(System.Object sender, System.EventArgs e)
{
DateTime selectedDate = ((DateTime)((sender as VisualElement).BindingContext)).Date;
viewModel.FilteredCollection = new ObservableCollection<ListItem>(viewModel.EntireCollection.Where(x =>
{
if (DateTime.Equals(x.DateAdded, selectedDate))
{
var asd = x.DateAdded.Day;
return true;
}
return false;
}));
}
XAML
<StackLayout>
<ScrollView
x:Name="calender"
Orientation="Horizontal">
<StackLayout
BackgroundColor="Blue"
BindableLayout.ItemsSource="{Binding Dates}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button
TextColor="White"
BackgroundColor="Blue"
Clicked="Button_Clicked"
Text="{Binding Day}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<ListView
ItemsSource="{Binding FilteredCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding DateAdded}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope it helps!!

Xamarin Forms: Dynamically creating a Listview item -> Problem with BindingContext

as I was already describing in another post here on Stackoverflow, I was trying to get a different layout (one frame spanning multiple listview items). Now I decided to try the following approach: My ViewModel is a List of Lists (just like for a grouped listview). However instead of using a grouped listview, I have a normal ListView in which the single Items of the child list will be created in Code-behind as soon as the bindingContext of the ParentViewCell is available:
private void CommentViewCell_BindingContextChanged(object sender, EventArgs e)
{
if (this.BindingContext == null) return;
var model = this.BindingContext as CommentViewModel;
DateCommentViewCell dateCell = new DateCommentViewCell
{
BindingContext = model
};
ParentCommentViewCell parentCell = new ParentCommentViewCell
{
BindingContext = model
};
ContentStackView.Children.Add(dateCell.View);
ContentStackView.Children.Add(parentCell.View);
foreach (CommentBaseViewModel cbvm in model)
{
if (cbvm is CommentViewModel)
{
ChildCommentViewCell childCell = new ChildCommentViewCell
{
BindingContext = cbvm
};
ContentStackView.Children.Add(childCell.View);
}
}
}
When I run this, the visuals are actually ok and look how I intended them to.
However the BindingContext is wrong: The ChildCommentViewCell BindingContext does not reference the CommentViewModel of the child, but that of the parent when being displayed. I checked the BindingContext of the ChildCommentViewCell like this
public ChildCommentViewCell ()
{
InitializeComponent ();
BindingContextChanged += ChildCommentViewCell_BindingContextChanged;
}
private void ChildCommentViewCell_BindingContextChanged(object sender, EventArgs e)
{
Debug.WriteLine("### ChildCommentViewCell BindingContext Changed");
test();
}
public void test()
{
var context = this.BindingContext as CommentViewModel;
Debug.WriteLine("### Instance: " + this.GetHashCode());
Debug.WriteLine("### \tBinding Context: " + context.CommentModel.Text);
Debug.WriteLine("### \tLabel: " + ChildCommentText.Text);
}
and the output on the console is just fine. However when running on my phone, the actual content is (as written above) that of the ParentCommentViewModel. Any ideas?
The XAML code of the ChildCommentViewCell element is the following:
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App.View.ViewCell.ChildCommentViewCell">
<StackLayout Padding="10,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<StackLayout Grid.Column="0" VerticalOptions="FillAndExpand" Orientation="Vertical" Spacing="0">
<Label Text="{Binding CommentModel.AuthorName}" Style="{StaticResource CommentAuthor}"/>
</StackLayout>
<Frame IsClippedToBounds="True" HasShadow="False" Margin="5" Padding="3" BackgroundColor="LightGray" CornerRadius="3.0">
<StackLayout Grid.Column="1" VerticalOptions="FillAndExpand" Orientation="Vertical" Spacing="0">
<Label x:Name="ChildCommentText" Text="{Binding Path=CommentModel.Text, StringFormat=' {0}'}" Style="{StaticResource CommentContent}"/>
<Label Text="{Binding CommentTimeAgo}" Style="{StaticResource CommentTime}" HorizontalOptions="Start"/>
</StackLayout>
</Frame>
</StackLayout>
</StackLayout>
</StackLayout>
</ViewCell>
One additional thing: I tried to debug the "Appearing"-Event, however this does not even get called once...?!
Thank you very much in advance!
Found my problem in the BindingContextChanged method: I had to explicitly bind the BindingContext to the view, not only to the ViewCell:
foreach (CommentBaseViewModel cbvm in model)
{
if (cbvm is CommentViewModel)
{
ChildCommentViewCell childCell = new ChildCommentViewCell
{
BindingContext = cbvm
};
childCell.View.BindingContext = cbvm;
ContentStackView.Children.Add(childCell.View);
}
}

Resources