I have FlexLayout that I use to display the contents of the collection. I want to realize the possibility that when you click on an element of the collection, the command is executed. I want to make sure that when you click on an item, another view is loaded that loads data that corresponds to the selected day. How do I get collection items to react to clicks?
<ContentPage.Resources>
<DataTemplate x:Key="ColorItemTemplate">
<Grid Margin="5"
HeightRequest="120"
WidthRequest="105">
<BoxView Color="Azure" />
<Label Text="{Binding Day}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</DataTemplate>
</ContentPage.Resources>
<StackLayout Margin="10">
<Label Text="CollectionView"
FontSize="30"
FontAttributes="Bold"
HorizontalOptions="Center" />
<Label Text="{Binding MyCollection.Count, StringFormat='Count: {0}'}" />
<ScrollView>
<FlexLayout Direction="Row"
Wrap="Wrap"
AlignItems="Center"
AlignContent="Center"
BindableLayout.ItemsSource="{Binding MyCollection}"
BindableLayout.ItemTemplate="{StaticResource ColorItemTemplate}"/>
</ScrollView>
</StackLayout>
but when clicking on an item, where do you want to send data to?
In the screenshot above, you can see 20 objects in the collection. They are created in the class constructor and they display the time at which they were created.
PowerTrainings = new ObservableCollection<PowerTraining>();
for (int i = 0; i < 20; i++)
{
PowerTrainings.Add(new PowerTraining() { DayOfTraining = dateTime });
}
According to my idea, every time a user (in particular, I, because I do it for myself) will choose a particular day from the history, another view will be loaded, which will display the exercises that he did on that day. But how to correctly implement in the XAML code a code that will determine that this particular element of the collection was clicked (in which dates are displayed) and pass this date to another view, which, based on the property DayOfTraining , will make a query to the database with classes on this day and form another collection?
If I represent it by analogy with WPF, then I would create a
MouseDoubleClick event and pass the object to another view through the
SelectedIndex property. But in the case of Xamarin, I don't understand
(yet) how everything works.
I could deploy your app to my emulator, but when clicking on an item, where do you want to send data to?
Depending on the scenario, there are several ways to pass data between two Views.
A simple method is to use Xamarin.Forms MessagingCenter.
The Xamarin.Forms MessagingCenter class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between them.
Publish a message
Publishers notify subscribers of a message with one of the MessagingCenter.Send overloads. The following code example publishes a Hi message:
MessagingCenter.Send<MainPage>(this, "Hi");
Subscribe to a message
Subscribers can register to receive a message using one of the MessagingCenter.Subscribe overloads.
MessagingCenter.Subscribe<MainPage> (this, "Hi", (sender) =>
{
// Do something whenever the "Hi" message is received
});
Unsubscribe from a message
Subscribers can unsubscribe from messages they no longer want to receive. This is achieved with one of the MessagingCenter.Unsubscribe overloads:
MessagingCenter.Unsubscribe<MainPage>(this, "Hi");
Update:
And I don't understand how you can find out which element the user
clicked on
You can add TapGestureRecognizer for your DataTemplate.
I modified your DataTemplate and add TapGestureRecognizer for it. You can refer the following code:
<ContentPage.Resources>
<DataTemplate x:Key="ColorItemTemplate">
<Frame Margin="5"
HeightRequest="120"
BackgroundColor="Azure"
WidthRequest="105">
<Label Text="{Binding DayOfTraining}"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.TypeListSelectedCommand, Source={x:Reference flexLayout}}" CommandParameter="{Binding}"></TapGestureRecognizer>
</Frame.GestureRecognizers>
</Frame>
</DataTemplate>
</ContentPage.Resources>
In class TrainingViewModel,add command TypeListSelectedCommand as follows:
public ICommand TypeListSelectedCommand => new Command<PowerTraining>(selectedItem);
void selectedItem(PowerTraining obj)
{
PowerTraining item = obj as PowerTraining;
System.Diagnostics.Debug.WriteLine("clicked item = " + item.DayOfTraining);
}
Note:
1.flexLayout is the x:Name of your FlexLayout:
<FlexLayout x:Name="flexLayout" />
In addition, you can add TapGestureRecognizer like this:
<ContentPage.Resources>
<DataTemplate x:Key="ColorItemTemplate">
<Frame Margin="5"
HeightRequest="120"
BackgroundColor="Azure"
WidthRequest="105">
<Label Text="{Binding DayOfTraining}"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
</Frame.GestureRecognizers>
</Frame>
</DataTemplate>
</ContentPage.Resources>
And in CalendarOfTraining.xaml.cs,add function TapGestureRecognizer_Tapped:
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
Frame boxView = sender as Frame;
PowerTraining SelectedItem = (PowerTraining)boxView.BindingContext;
System.Diagnostics.Debug.WriteLine(" clicked item = " + SelectedItem.DayOfTraining);
}
Related
I am using bindable stack layout with ItemTemplateSelector. My DataTemplates are in another file which is ResourceDictionary included in MainView as MergedResourceDictionay. In one of my DataTempplates I have Label with TapGestureRecognizer that is supposed to trigger command in MainViewViewModel, and that I can't seem to get working....
I tried having Source={x:Reference MainPage} in my Command binding, but can't reference it since it's not in same file
(Xamarin.Forms.Xaml.XamlParseException: 'Position 28:51. Can not find
the object referenced by MainPage')
<--! this is snippet from MainPage -->
<ScrollView Orientation="Vertical" Grid.Row="1">
<local:BindableStackLayout BindableLayout.ItemsSource="{Binding Day.TodayEntry}"
x:Name="BindableStack" Spacing="10" Margin="10"
BindableLayout.ItemTemplateSelector="{StaticResource CardDetailTemplateSelector}"/>
</ScrollView>
<--! this is problematic snippet from data template -->
<Label Text="REMOVE" FontSize="Medium" TextColor="White" HorizontalOptions="End" Margin="3,0,0,0">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RemoveEntryCommand, Source={x:Reference MainPage}}"
CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
The reason that does not work is that XAML compilation look-ups are page-specific which means if you have two different pages and you search using names it will not work in most cases.
What I usually do in such scenarios is I use the parent keyword for my BindingContext!
So assuming your Label (which is in the ViewCell) has a parent which is a Grid you can do something like
Command="{Binding Source={x:Reference parentLayoutGrid}, Path=Parent.Parent.BindingContext.RemoveEntryCommand}"
Where the number of parents properties required depends on your ViewHeirarchy and which View would have the correct context.
Good luck.
Feel free to get back in case of queries
I've got a list of objects that I'm binding to a ListView and using a DataTemplate to show in a Xamarin app. So far, pretty simple. But the kicker is that I want one of the controls (a label specifically) to update continually.
So far this is what I have...
<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 Padding="10">
<Label Text="{Binding Title}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<mycountdown:TimerLabel Text="{Binding TimeRemainingString}"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the code behind for my TimerLabel class...
public class TimerLabel : Label
{
private bool beating;
public TimerLabel()
{
StartHeartbeat();
}
public void StartHeartbeat()
{
//only start beating again if not currently beating.
if (!beating)
{
beating = true;
Heartbeat();
}
}
public void StopHeartbeat()
{
beating = false;
}
async void Heartbeat()
{
while (beating)
{
this.Text = DateTime.UtcNow.ToLongTimeString();
}
}
}
This works, but the problem is that the heartbeat for each item in the list keeps running even when I navigate away from the page.
Ideally, the heartbeat would only run for items that are visible on screen, but I would settle for letting them all run and just disabling them when I leave the page.
The problem is I can't figure out how to access the StopHeartbeat() method from the page's code behind or the view model.
It's not going to be limited to only Labels either. I will end up having a few other controls that need to update in the UI thread continuously, but this is the simplest one to start with.
If there's another way I should be doing this, please say so.
Thanks!
if you are Bind List in ListItem Source then please change it from List to ObservableCollection it will work let me know if any question or can you upload viewModel code as well
Answered by the top comment in my post.
You want to hide a StackLayout depending on the value of a property that is evaluated in the constructor, but when hiding it xamarin leaves the blank and does not upload the lower elements upwards, so I will remove the element through code behind (I'm using MVVM)
But I do not know how to structure my code so that I can evaluate whether or not I eliminate the StackLayout when loading the Vista
In my view I occupy X: Name to identify the element that I want to eliminate or show, after evaluating the condition.
MyView.XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
x:Class="AppValora.Views.Sisquim.VerSisquimView">
<StackLayout
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Start">
<Image
HorizontalOptions="Center"
Source="{Binding ImageRombo}"
HeightRequest="160"
WidthRequest="160"
IsVisible="{Binding IsVisibleImagenRombo}">
</Image>
<Image
HorizontalOptions="Center"
Source="cuadro_nch"
HeightRequest="160"
WidthRequest="160">
</Image>
</StackLayout>
// STACKLAYOUT YOU WANT TO ELIMINATE
<StackLayout
x:Name="StackIsVisible">
<Image
Source="{Binding ImageRomboDos}"
HeightRequest="160"
WidthRequest="160"
IsVisible="{Binding IsVisibleImagenRombo}">
</Image>
</StackLayout>
</ContentPage>
In my ViewModel, I receive the parameter that I then evaluate in a conditional
MyViewModel.CS:
public VerSisquimViewModel(SqsHelper sqsHelper)
{
if (sqsHelper.RSEC == null)
{
IsVisibleLabelRomboImagenDos = false;
}
}
What magic can I do in my Code Behind so that when the condition is met, eliminate the StackLayout and disappear that blank space?
currently I have....
MyView.cs (CODE BEHIND):
public partial class MyView : ContentPage
{
StackLayout hiddenStackLayout;
public VerSisquimView ()
{
NavigationPage.SetBackButtonTitle(this, "");
StackIsVisible.Children.RemoveAt(1);
InitializeComponent();
}
}
how can I from the code behind change the value of the property when I start this page?
Is it a bad practice to use code behind with the MVVM pattern?
How can i fix this? any help for me?
Is it a bad practice to use code behind with the MVVM pattern?
If the code is view related, than it's necessary. Sometimes you just can't avoid code in the code-behind. A Rule to keep in mind, typically when you start working with the view, managing things can get to be quite cumbersome. If you find a way to leverage the platform, in this case Xaml, let it do the work for you.
the thing about visibility, the element is rendered on the screen. The user just can't see it. This is why the space is being taken up. Sometimes visibility will work, other times not so much.
In this situation, you can avoid code in code-behind and stick to your VM.
I would suggest using a grid and use a converter to hide a grid row. When I say hide the row, I mean by setting a row's height to 0. So it's rendered but we are going to collapse it. I only added two rows, you can add more to accommodate more content as you see fit.
Edit: added sample namespace. If you are using Visual Studio, there should be intellisense. You have to add the local namespace, so the xaml engine can find the converter class.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converter="clr-namespace:DataBindingDemos" <-- set the appropriate value
BindingContext="{Binding Main, Source={StaticResource Locator}}"
x:Class="AppValora.Views.Sisquim.VerSisquimView">
<ContentPage.Resources>
<converter:BoolToGridRowVisibilityConverter key="BoolToGridRowVisibilityConverter">
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding IsStackVisible, Converter={StaticResource BoolToGridRowVisibilityConverter}}" />
</Grid.RowDefinitions>
<StackLayout
Grid.Row="0"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Start">
<Image
HorizontalOptions="Center"
Source="{Binding ImageRombo}"
HeightRequest="160"
WidthRequest="160"
IsVisible="{Binding IsVisibleImagenRombo}">
</Image>
<Image
HorizontalOptions="Center"
Source="cuadro_nch"
HeightRequest="160"
WidthRequest="160">
</Image>
</StackLayout>
<!-- STACKLAYOUT YOU WANT TO ELIMINATE -->
<StackLayout Grid.Row="1"
x:Name="StackIsVisible">
<Image
Source="{Binding ImageRomboDos}"
HeightRequest="160"
WidthRequest="160"
IsVisible="{Binding IsVisibleImagenRombo}">
</Image>
</StackLayout>
</Grid>
</ContentPage>
Add a converter
public class BoolToGridRowVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? new GridLength(1, GridUnitType.Auto) : new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Only one way bindings are supported with this converter");
}}
}
For more information, see a post on Xamarin forums
You can put the IsVisible="{Binding IsVisibleImagenRombo}" to the StackIsVisible stacklayout, so that the entire space will be erased.
I have following list view (the item source is set outside and a list of strings):
<?xml version="1.0" encoding="utf-8" ?>
<ListView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XXX.EditItemsList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
With clicking the button, I want to delete the current item (string) in the list. How is this possible?
Thanks for your help :)
In principle i think the answer given by #Krzysztof Skowronek is right, I will simply try to elaborate on it, and avoid the use of ViewModel since it seems you are not using it (although the use of it is a Best Practice on Xamarin Forms).
Following your own code, i wrote the following code in XAML:
<?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:local="clr-namespace:DeleteButton"
x:Class="DeleteButton.MainPage">
<ListView x:Name="listView"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete" Clicked="Delete"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
About this part of the solution i would give the following comments:
Note the use of ConntentPage instead of ListView at the top of the XAML, is that intentional?
Then, note the addition of x:Name on ListView. It will be used to communicate with the ListView from the code behind.
Additionally notice the use of HasUnevenRows set to True. This causes the ListView to adjust automatically the height of the rows.
Finally see that in Button i have set the event Clicked to "Delete", which is the name of the event handler in the code behind as you will see.
On the code behind i wrote:
using System;
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace DeleteButton
{
public partial class MainPage : ContentPage
{
ObservableCollection<String> list;
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
list = new ObservableCollection<string>()
{
"Task 1", "Task 2", "Task 3", "Task 4", "Task 5",
"Task 6", "Task 7", "Task 8", "Task 9", "Task 10"
};
listView.ItemsSource = list;
}
public void Delete(Object Sender, EventArgs args)
{
Button button = (Button)Sender;
StackLayout listViewItem = (StackLayout)button.Parent;
Label label = (Label)listViewItem.Children[0];
String text = label.Text;
list.Remove(text);
}
}
}
There i define the list of strings as an ObservableCollection (ObservableCollection causes the ListView to get a notification each time it changes, so that the ListView updates its contents, see the docu for more details).
Then i set the ItemSource property of the ListView to the collection of Strings, as you have already done.
Finally comes the EventHandler Delete, called by the Click event on Button, in XAML. The algorithm here is quite simple:
First the sender is cast to a Button (we know that the object firing the event is a Button).
Then we walk up the hierarchy tree up to the StackLayout containing the Button and the Label and retrieve the first child of it, which we know is the Label.
Once we have the Label we retrieve its Text property and call the Remove method of the collection to get ride of that item.
And that's it.
Note: If i would implement this functionality myself i would rather define a collection of objects which would containt a Text property as well as an Id property in order to remove exactly the element tapped. In the code above, if the collection contains two identical strings, the EventHandler would go simply for the first occurrence.
I hope this helps you to find the right way to solve your problem.
If you don't want to use Commands, you can use the Button's Clicked event. IE,
<Button Text="Delete" Clicked="HandleDeleteButtonClicked" />
Then in your code behind file,
private void HandleDeleteButtonClicked(object sender, EventArgs e)
{
// Assuming your list ItemsSource is a list of strings
// (If its a list of some other type of object, just change the type in the (cast)):
var stringInThisCell = (string)((Button)sender).BindingContext;
// Now you can delete stringInThisCell from your list.
myList.Remove(stringInThisCell);
}
If we are willing to keep MVVM approach, then in your View, name the ContentPage (or whichever root element is there) and use it as Source to bind command:
<?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="canaraydash.View.InviteListPage"
x:Name="InvitesView">
<ListView x:Class="XXX.EditItemsList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}" />
<Button Text="Delete"
Command="{Binding Path=BindingContext.AcceptRequestCommand, Source={x:Reference InvitesView}}"
CommandParameter="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and in your ViewModel, define 'AcceptRequestCommand' command!
Create a DeleteItem command with a parameter in your control (or it's ViewModel preferably) and then in xaml:
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding .}"/>
<Button Text="Delete" Command="{Binding Source={this should be your VM for the whole thing}, Path=DeleteItem}" CommandParameter="{Binding}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
In command:
list.Remove(parameter);
If the the list is obeservable, it will disappear.
Here's my solution on the problem
<DataTemplate>
<Button Text="{Binding DisplayName}" Image="tab_about.png" ContentLayout="Top,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:AnimationViewModel}}, Path=AnimationCommand}"
CommandParameter="{Binding .}" />
</DataTemplate>
And the model view file is a normal command :
public ObservableCollection<LedAnimation> AnimationList { get; }
public Command<LedAnimation> AnimationCommand { get; }
public AnimationViewModel()
{
Title = "Animation";
AnimationList = new ObservableCollection<LedAnimation>();
AnimationCommand = new Command<LedAnimation>(OnLedAnimationTap);
_serverService.AnimationCapabilities.ForEach(x => AnimationList.Add(x));
}
private void OnLedAnimationTap(LedAnimation animation)
{
if (animation == null)
return;
Console.WriteLine($"VM:{animation.Name}");
}
<ListView x:Name="myList" ItemTapped="OnMyItemTapped" ItemsSource="{Binding myList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout> CONTENT HERE </StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
where myList is an ObservableCollection
Desired: Displays all list items immediately at once without animation
Actual: Displays list items one at a time (similar to the add item animation)
Any ideas?
(This sequential displaying of items is significantly more noticeable when the list of items within the list view is larger)
The only way I've found to do it is to use a custom renderer.
These are the lines I'm using to disable the insert, delete and reload rows animations:
if (e.OldElement != null)
{
InsertRowsAnimation = UITableViewRowAnimation.None;
DeleteRowsAnimation = UITableViewRowAnimation.None;
ReloadRowsAnimation = UITableViewRowAnimation.None;
}
This needs to be placed in your CustomListViewRenderer that is inheriting from Xamarin forms ListViewRenderer.
I put mine in the OnElementChanged event.
You can use a platform specific property to disable animations:
<ContentPage ...
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core">
<StackLayout Margin="20">
<ListView ios:ListView.RowAnimationsEnabled="false">
</ListView>
</StackLayout>
</ContentPage>
Read more here:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/platform/ios/listview-row-animations