Handling both tap and scroll in CollectionView - xaml

I'm trying to handle a tap gesture so that when I tap on the CollectionView, it will display a toast. However when I add TapGestureRecognizer, it blocks me from scrolling the collection. I want that it can both scroll and tap to open something. Here's my code so far:
View:
<CollectionView
x:Name="eventsBox"
ItemsSource="{Binding EventList}"
SelectionMode="None"
Header="Events"
ItemsUpdatingScrollMode="KeepLastItemInView">
<CollectionView.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding EventBoxTapCommand}"/>
<SwipeGestureRecognizer
Direction="Up"
Swiped="EventBoxSwiped"/>
<SwipeGestureRecognizer
Direction="Down"
Swiped="EventBoxSwiped"/>
</CollectionView.GestureRecognizers>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Event">
<HorizontalStackLayout>
<Label Text="{Binding EventDescription}"/>
<Label Text="{Binding EventTime, StringFormat=' at {0}'}"/>
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Code behind for Swipe gesture recognizer
private void EventBoxSwiped(object sender, SwipedEventArgs e)
{
switch (e.Direction)
{
case SwipeDirection.Up:
Console.WriteLine("Swiped Up");
//Custon scroll here?
break;
case SwipeDirection.Down:
Console.WriteLine("Swiped Down");
//Custom scroll here?
break;
}
}
I'm thinking of programmatically scroll by recognizing swipe gesture but I haven't found a way to do it. Moreover, the recognition of both tap and swipe is sometime really not responsive.

This is a known issue tracked in Gesture Recognizer Inhibits CollectionView Scrolling.You can follow up there or create a new one on Github.
To fix the issue, you can try to add a ScrollView outside of the CollectionView like below. Also, it is recommended to add Left& Right direction of the swipe gesture in a CollectionView.
<ScrollView>
<CollectionView>
<CollectionView.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding EventBoxTapCommand}"/>
<SwipeGestureRecognizer
Direction="Left"
Swiped="EventBoxSwiped"/>
<SwipeGestureRecognizer
Direction="Right"
Swiped="EventBoxSwiped"/>
</CollectionView.GestureRecognizers>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Event">
<VerticalStackLayout>
<Label Text="{Binding EventDescription}"/>
<Label Text="{Binding EventTime, StringFormat=' at {0}'}"/>
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>

After Jason's comment, I came up with a work-around to achieve the effect I want.
I added SelectionMode=Single to the CollectionView and changed the background color of selected item matching the theme color of the app with VisualStateManager. Therefore it appears that no item is selected but still get the tap and scroll event.
<CollectionView
x:Name="eventsBox"
ItemsSource="{Binding EventList}"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectItemCommand}"
Header="Events"
ItemsUpdatingScrollMode="KeepLastItemInView">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Event">
<HorizontalStackLayout>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState Name="Normal" />
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Dark=Black, Light=White}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label Text="{Binding EventDescription}"/>
<Label Text="{Binding EventTime, StringFormat=' at {0}'}"/>
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Thanks everyone for the help and information xD

Related

Xamarin.Forms. Collectionview adds a lot of extra white space after its content

I would like there to be no empty space after the content of the collection view.
I tried to put my button in footer but then i ran into problem that property isenabled = false is not being applied to it in my code behind.
Here is my xaml file:
<ContentPage.Content>
<RefreshView x:DataType="local:QuestionViewModel" Command="{Binding LoadCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<ScrollView>
<StackLayout>
<views:MyTextView LaTeX="{Binding Formulation}" HorizontalOptions="Center" Margin="15, 10, 15, 0"/>
<!--<RefreshView x:DataType="local:QuestionViewModel" Command="{Binding LoadCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">-->
<CollectionView
ItemsSource="{Binding Answers, Mode=TwoWay}"
SelectedItems="{Binding SelectedAnswers, Mode=TwoWay}"
SelectionMode="Multiple"
x:Name="collectionView"
SelectionChanged="collectionView_SelectionChanged"
>
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Answer">
<StackLayout Padding="10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter TargetName="frame" Property="Frame.BackgroundColor" Value="{Binding AnswerColor}"/>
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter TargetName="frame" Property="Frame.BackgroundColor" Value="{DynamicResource MainColor}"/>
<Setter TargetName="label" Property="views:MyTextView.TextColor" Value="White"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Frame CornerRadius="10" HasShadow="True" x:Name="frame" BackgroundColor="{Binding AnswerColor}">
<views:MyTextView x:Name="label" LaTeX="{Binding Content}" />
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!--</RefreshView>-->
<StackLayout>
<Label x:Name="conditionLabel" TextColor="{DynamicResource AnswerColor}"/>
<Button x:Name="checkButton" Text="Проверить ответы" CornerRadius="10" Margin="10"
Command="{Binding CheckAnswersCommand}"/>
</StackLayout>
</StackLayout>
</ScrollView>
</RefreshView>
</ContentPage.Content>
Result:
1 part of screen
2 part of screen
You shouldn't use Listview/Collection View with in Scrollview as described in official documentation.
There is an alternative to listview within Scrollview. Look at following code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ScrollViewDemos"
x:Class="ScrollViewDemos.Views.ColorListPage"
Title="ScrollView demo">
<ScrollView>
<StackLayout BindableLayout.ItemsSource="{x:Static local:NamedColor.All}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<BoxView Color="{Binding Color}"
HeightRequest="32"
WidthRequest="32"
VerticalOptions="Center" />
<Label Text="{Binding FriendlyName}"
FontSize="24"
VerticalOptions="Center" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage>
Follow Documentation for more support:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/scrollview
Unfortunately, this is a known problem, and an open issue.
See that thread for possible work-arounds. And to track any progress on fixing it.
Bottom line: you have to manually specify/calculate height. There are multiple ways to do so, of various difficulty. Thread mentions some. You would need to Google for more details.
One quick-and-dirty solution I like, when acceptable, is to wrap CollectionView in Grid, and then use RowDefinitions to force how much height is given to row containing CollectionView, how much to everything else. Something like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<CollectionView Grid.Row="0" ...>
...
</CollectionView>
<StackLayout Grid.Row = "1" ...>
... everything that is below the CV ...
</StackLayout>
</Grid>
This would restrict row 0 (which contains CollectionView) to top 1/3 of screen.

CollectionView GridItemsLayout Change BackgroundColor of nested Item (Button)

I have a problem concerning a CollectionView with a GridItemsLayout. In my application, I have a StackLayout with a horizontal CollectionView(GridItemsLayout), which contains a Button bound to a certain category.
Whenever a button is clicked/tapped, it filters the ListView (of type Surgery) below based on the category. All of that is working fine, however, I would like to highlight the Category/Button by changing it BackgroundColor to see which category is currently used for filtering.
<CollectionView HeightRequest="70"
x:Name="categoryCollection"
ItemsSource="{Binding Categories}"
Margin="20,0,20,0"
SelectionMode="Single">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal" Span="1" HorizontalItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,5,0,5">
<Button x:Name="categoryButton" Text="{Binding Name}"
Command="{Binding Path=BindingContext.FilterCommand, Source={x:Reference Name=SurgeryListView}}"
CommandParameter="{Binding .}"
CornerRadius="15"
FontFamily="OpenSans" Margin="0,5,10,5"
Opacity="0.6" FontSize="16" TextTransform="None">
</Button>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ListView x:Name="listView"
ItemsSource="{Binding Surgeries}"
RefreshCommand="{Binding LoadSurgeriesCommand}"
IsRefreshing="{Binding IsRefreshing}"
IsPullToRefreshEnabled="True"
RowHeight="70"
BackgroundColor="{StaticResource darkThemeBackground}"
ItemTemplate="{StaticResource surgeryDataTemplateSelector}">
<ListView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding SurgerySelectedCommand}"
EventArgsConverter="{StaticResource ItemTappedConverter}">
</behaviors:EventToCommandBehavior>
</ListView.Behaviors>
</ListView>
I tried to use VisualStates but that was not working, because tapping the button does not actually change the SelectedItem (one would need to click the surrounding/parent grid element for that). Moreover, the altered VisualState was only applied to the Grid's BackgroundColor, not to that of the actual button.
Question: How can I highlight the current Category/Button by changing its Background Color?
Since the selection is working good, remains only the ui/xaml part, if you look at the documentation about VisualStateManager, you can add the following style:
<CollectionView.Resources>
<Style TargetType="Grid">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="LightSkyBlue" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
</CollectionView.Resources>

Windows Phone - ToggleSwitch in LongListSelector (not changing when scrolling)

This is my LongListSelector in my page.xaml:
<phone:LongListSelector Name="ModuleList"
Margin="0,0,0,0"
Padding="0,0,0,0"
Style="{StaticResource LLSFloatingScrollbarStyle}"
ItemTemplate="{StaticResource ModuleListTemplate}"
ItemsSource="{Binding Modules}"/>
My template:
<DataTemplate x:Key="ModuleListTemplate">
<StackPanel Margin="{Binding StackModuleMargin}">
...
<toolkit:ToggleSwitch x:Name="LockSwitch"
Grid.Row="2" Margin="0,4,0,-26" Padding="0"
Content="" SwitchForeground="{StaticResource TrackitoOrange}"
IsChecked="{Binding IsToggleCheck, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Command:EventToCommand Command="{Binding DataContext.LockSwitchTapCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</toolkit:ToggleSwitch>
...
The problem is that click is raised when I just scroll with list. Yes I have my finger in place where is toggle but I want to fix it when I scroll then toggle shoudn't be changed. Thanks
There's a probably a design issue you should explore if you have two competing tap events in your UI.
That said, you could override OnScroll and mark the event as handled at the original level so it doesn't bubble up to your toggle button.

How to make a Stack Panel Visible based on the Tapped Event of a ListView in Windows Phone 8.1?

In my application, I am having a ListView. ListView lists a set of images.
So when the application is running, and when that page is loaded, a list of images are shown.
<ListView ItemsSource="{Binding imageLists}" Background="Red" Tapped="ListView_Tapped">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Background="Green">
<Image Source="{Binding imagePath}" CacheMode="BitmapCache" Stretch="UniformToFill"/>
<StackPanel Name="imageTitle" Visibility="Collapsed" Background="Blue"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" Text="Dumy Image Title" FontSize="10"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As you can see from the code, I have one image & stackpanel inside the listview. And the stackpanel's visibility has been set to collapsed for convenience.
The StackPanel imageTitle resides inside the ListView. Stackpanel contains a TextBlock housing the images name. For now it's dumy text.
On Tapping any image in the list, I am trying to make the stackPanel visible.
The Code Behind:
private void ListView_Tapped(object sender, TappedRoutedEventArgs e)
{
imageTitle.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
Since the stackpanel is inside the listview & I am trying to make it visible on the tap event of the listview, I am not able to acheive the needed result. I know my code is wrong.
If I specify the stackpanel outside the listview, I can make it visible using the code I gave inside the ListView_Tapped function. But even in that case, I need to show the stackpanel (name of the image I clicked) inside the listview item (image I clicked).
Any help??
Can this be achieved using only XAML?
Here's a pure xaml way.
Rather than changing the Visibility of the imageTitle (not a great UX), let's change its Opacity to make its appearing more interesting.
First we need to create a storyboard inside this data template. This storyboard will fade in the imageTitle in 400ms.
And then we drag a ControlStoryboard behavior from Expression Blend's Asset panel onto the top level Grid. Basically we want the storyboard to fire off when this Grid is tapped.
Please see below code for reference.
<DataTemplate x:Key="GroupTemplate">
<Grid Background="Green">
<Grid.Resources>
<Storyboard x:Name="ShowImageTitleStoryboard">
<DoubleAnimation Duration="0:0:0.4" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="imageTitle"/>
</Storyboard>
</Grid.Resources>
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Tapped">
<Media:ControlStoryboardAction Storyboard="{StaticResource ShowImageTitleStoryboard}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Image Source="{Binding Image}" CacheMode="BitmapCache" Stretch="UniformToFill"/>
<StackPanel x:Name="imageTitle" Background="Blue"
HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
<TextBlock Foreground="White" Text="Dumy Image Title" FontSize="10"/>
</StackPanel>
</Grid>
</DataTemplate>
Apart from JustinXL's answer it can also be done by using ChangePropertyAction. Also pure XAML:
<DataTemplate>
<Grid Background="Green">
<Image Source="{Binding imagePath}" CacheMode="BitmapCache" Stretch="UniformToFill">
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Tapped">
<ic:ChangePropertyAction TargetObject="{Binding ElementName=imageTitle}" PropertyName="Visibility" Value="Visible"/>
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Image>
<StackPanel Name="imageTitle" Visibility="Collapsed" Background="Blue"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Foreground="White" Text="Dumy Image Title" FontSize="10"/>
</StackPanel>
</Grid>
</DataTemplate>
It's just another way to change a property - JustinXL's answer will provide nice animation with opacity, which will look much better.
I dunno why you specifically want to handle the scenario using XAML. For the Microsoft's recommended MVVM model you should bind a property to your element field and then you can write a converter for the same to return back "Visible" or "Collapse".

How to change the item template of list box depending upon the Current Orientation in metro app?

Hi I am developing an metro app , my app works perfectly fine in landscape mode but now i want to make it compatible to Portrait mode also.
This is how i have defined students listbox :-
<ListBox x:Name="lstbxbStudents" Background="Transparent" Height="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectionChanged="lstbxbStudents_SelectionChanged_1" HorizontalAlignment="Left" Width="Auto" Margin="4,50,0,122" Style="{StaticResource ListBoxStyle1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Width="100" Orientation="Horizontal">
<TextBlock Text="{Binding stunum}" VerticalAlignment="Center" HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Width="450">
<TextBlock Text="{Binding studsc}" HorizontalAlignment="Left" />
</StackPanel>
<StackPanel Orientation="Horizontal" Width="180">
<StackPanel>
<TextBlock Width="50" Text="{Binding stu_cod}" x:Name="txtblkstucode" HorizontalAlignment="Right" />
</StackPanel>
</StackPanel>
<StackPanel Width="150">
<TextBlock Text="{Binding stuby_prc}" VerticalAlignment="Center" TextAlignment="Right" HorizontalAlignment="Center" />
</StackPanel>
<StackPanel Width="100">
<TextBlock Text="{Binding stuqty, Mode=TwoWay}" TextAlignment="Center" x:Name="txtbxbqty" Tag="{Binding stunum}" VerticalAlignment="Center" HorizontalAlignment="Right" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now my doubt is when i rotate it to Portrait mode from landscape mode , since the width of the textblocks present inside the itemtemplate of listbox is already defined , when i rotate the it to Portrait, i am not able to see all the data present (it cuts off) since the width of the Portrait mode is less when compared to landscape mode.
1)Can i have two item templates for same listbox and switch between those two templates depending upon current orientation ??
2)How can i decrease/increase the width of the textblock present inside a item template of a listbox in runtime codebehind when the orentation changed event is fired.??
3)will visual states be useful at this point , if so then how ??
4)is there any other way of which i can solve the problem , am i missing any alternatives ??
Please help me out , Thanks in advance.
You can change the ItemTemplate using visual states. First put both of your item templates in resources:
<Page.Resources>
<DataTemplate x.Key="Landscape">
<!-- your landscape template -->
</DataTemplate>
<DataTemplate x.Key="Portrait">
<!-- your portrait template -->
</DataTemplate>
</Page.Resources>
Initially assign the landscape template to ListBox:
<ListBox x:Name="listbox" ItemTemplate="{StaticResource Landscape}" />
In VisualStateManager change the template for FullScreenPortrait visual state:
<VisualStateManager.VisualStateGroups>
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="listbox" Storyboard.TargetProperty="ItemTemplate">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Portrait}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateManager.VisualStateGroups>
Make sure you are using LayoutAwarePage as the base class for your page to make this work.