How to use set automatically row height on Xamarin Forms? - xamarin

I'm trying to make a ListView which has a DataTemplateSelector on ItemTemplate. Basically, it looks like an expandable cell, which shows more info when the item is selected.
My code on the DataTemplateSelector:
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return ((ItemAjuda)item).Selecionado ? ExpandedTemplate : SimpleTemplate;
}
and this is my code on the view:
<ListView
x:Name = "listView"
SeparatorVisibility = "None"
RowHeight = "-1"
HasUnevenRows="true"
ItemsSource="{Binding Itens}"
SelectedItem="{Binding ItemSelecionado, Mode=TwoWay}"
BackgroundColor = "{StaticResource DefaultBackground}">
<ListView.ItemTemplate>
<local:AjudaTemplateSelector/>
</ListView.ItemTemplate>
</ListView>
It works perfectly on Android, but on IOS the cell got a strange behavior. The row height changes but it doesn't looks like an uneven row :(
Ps: I've already tried to ForceUpdateSize() on the cell, but unfortunately it doesn't change anything (maybe because of the DataTemplateSelector?)

Related

Collectionview items font size

I am trying to make funcionality, to make larger text across whole application for user when he clicks a 'increase font size' button. Using MVVM, I have done it like this:
Increase font size button click
increase value of double field 'fontSize' which is binded to almost every text in layout
Update UI with new value after button click
However I don't know how to achieve this in Collectionview where I have got Binding in .xaml file, with some particular List (item is model class). The collectionview DataTemplate contains labels where I want to increase font size. Is there a way to do this without adding 'fontSize' field in my model class. If not how to update UI with 'new' List with increased font sizes.
I appreciate any help, tips and discussions.
Thank you.
You can create bindableproperty(fontsize) in your viewmodel and use Relative Binding so the label in Collectionview can change it's fontsize,code like:
ViewMode:
public class ColViewModel:BindableObject
{
public ObservableCollection<Student> students { set; get; }
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("fontsize", typeof(int), typeof(ColViewModel), null);
public int fontsize
{
get { return (int)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public ICommand IncreaseCommand { private set; get; }
public ColViewModel()
{students = new ObservableCollection<Student>();
getStudents();
fontsize = 24;
IncreaseCommand = new Command(() => {
fontsize++;
});
}
View:
<StackLayout>
<Label Text="This is a Title" FontSize="{Binding fontsize}"/>
<CollectionView ItemsSource="{Binding students}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" FontSize="{Binding Source={RelativeSource AncestorType={x:Type local:ColViewModel}}, Path=fontsize}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Button Text="Click to increase fontsize" Command="{Binding IncreaseCommand}"/>
</StackLayout>
Edit:
xmlns:local="clr-namespace:MyForms2.ViewModels"

How to make ActivityIndicator overlay full screen?

I have a StackLayout and a number of elements inside (buttons, texts etc).
I want the ActivityIndicator to overlay the entire screen and make it not able to do anything to those elements.
I have put ActivityIndicator inside the StackLayout but wrapped it with AbsoluteLayout thinking that AbsoluteLayout can easitly overlap everything:
<StackLayout>
<AbsoluteLayout>
<ActivityIndicator ... />
</AbsoluteLayout>
<...other elements...>
</StackLayout>
Instead activity indicator is displayed at the top of the StackLayout and other elements are available for affecting. I'm new in Xamarin and layouts, what am I doing wrong? All samples in the Internet have single ActivityIndicator per page...
It is better said that an AbsoluteLayout's children can easily overlap each other. Just as a StackLayout lets you stack controls inside , vertically or horizontally, an AbsoluteLayout lets you position controls inside using absolute or proportional values, thus if two controls have the same absolute positioning set, they will overlap 100%.
Therefore, you want to wrap your StackLayout and another StackLayout that has your ActivityIndicator inside an AbsoluteLayout using proportional sizing, e.g:
<AbsoluteLayout>
<StackLayout
x:Name="mainLayout"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All" >
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Button Text="Do Something"
Clicked="DoSomethingBtn_Clicked" />
</StackLayout>
<StackLayout
x:Name="aiLayout"
IsVisible="False"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Gray" Opacity="0.5">
<ActivityIndicator
x:Name="ai"
IsRunning="False"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Color="Black"/>
</StackLayout>
</AbsoluteLayout>
The above sets the two StackLayouts to both take up the full size of the parent container of the AbsoluteLayout, which is presumably a Page. The StackLayout that has the indicator is initially hidden. IN the page code behind for the above example, I show the second StackLayout and start the activity indicator and show it for 2 seconds, and then hide it again:
private async void DoSomethingBtn_Clicked(object sender, EventArgs e)
{
ai.IsRunning = true;
aiLayout.IsVisible = true;
await Task.Delay(2000);
aiLayout.IsVisible = false;
ai.IsRunning = false;
}
Here is what it looks like:
And since the second StackLayout completely covers the first, none of the controls in the first StackLayout are clickable.
Might be worth going over the docs for the AbsoluteLayout to understand the AbsoluteLayout.LayoutBounds and AbsoluteLayout.LayoutFlags:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/absolute-layout
If you want to "overlap", you need to be outside of the StackLayout. A Grid is the most common control for this:
<Grid>
<StackLayout>
<...other elements...>
</StackLayout>
<ActivityIndicator ... />
</Grid>
Here's a hacked-up control for making things full-screen via the horribly-named RelativeLayout (tested in Android only)
[ContentProperty("ContentInner")]
public class FullScreenLayout : ContentView
{
public View ContentInner
{
get => ((RelativeLayout) Content).Children[0];
set
{
var display = DeviceDisplay.MainDisplayInfo;
var screenWidth = display.Width / display.Density;
var screenHeight = display.Height / display.Density;
var wrapper = new RelativeLayout();
wrapper.Children.Add(value, () => new Rectangle(0, 0, screenWidth, screenHeight));
Content = wrapper;
}
}
}
It can be used like this:
<controls:FullScreenLayout>
<!-- Anything you want fullscreen here -->
</controls:FullScreenLayout>
Unfortunately, if you use NavigationPage, this won't overlap the navigation bar. Every other solution currently on this page has the same issue. According to this question, it's not possible to solve this without using platform-specific customer renderers. Ugh.
If you don't mind the page being dimmed, you can use Rg.Plugins.Popup which implements the custom renderers needed.
I ended up solving my similar problem (dimming most of the screen) by implementing a custom renderer for the navigation page itself.

Xamarin how to send picker value to a list

Im trying to send picker values that the user has chosen to a list. The list should keep on adding what the user chooses and not delete the previous. Any tips on how to do this?
private void MainPicker_SelectedIndexChanged(object sender, EventArgs e)
{
var product = MainPicker.Items[MainPicker.SelectedIndex];
DisplayAlert(product, "Layer added to calculation list", "OK");
}
private void ListView_ItemSelected(object sender,SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
// if selected add to list?
}
I believe your question is about adding a selected item from a picker to a list view which keeps on adding based on the picker selection.
You can do the below code as a public object in the class.
ObservableCollection<LayersClass> listProducts = new ObservableCollection<LayersClass>();
Then get the selected picker item. Assuming the output and collection object is of same type.
var product = MainPicker.Items[MainPicker.SelectedIndex];
if(null != product)
{
LayersClass layer = new LayersClass();
layer.Product = product;
listProducts.Add(layer);
}
XAML changes for the list view - You need to add the ViewCell tag below the DataTemplate which has your listview item child
<ListView
x:Name="productsListView"
HasUnevenRows="False"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="White" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Product}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have added the ViewCell inside the DataTemplate tag.

When using a text cell, is it possible to get the value of the text when it is tapped?

The text cell is inside of a listview. I'm populating the text of the text cell with data using Binding. I need to actually get the value of the text when the text cell is clicked on. Is there a way to do this?
XAML file:
<ListView ItemsSource="{Binding MyItems}" ItemTapped="{Binding OnItemTapped}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Key}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListVIew>
You bind the ItemTapped event to a custom event handler using Binding.
In codebehind, e.Item will contain the tapped item, from which you can extract the Key and Value.
Codebehind:
public class MyPage : ContentPage
{
...
private async void OnItemTapped(object sender, ItemTappedEventArgs e)
{
var tappedItem = e.Item;
// do something with tappedItem.Value...
}
}
Edit/Note: You will need to override the ToString() method in your model class in order to display whatever you want. You get the namespace displayed because that's the default behavior of calling ToString() on any object.
You could subscribe to ItemTapped or ItemSelected ListView events and pull your item from ItemTappedEventArgs.Item (in case of ItemTapped event) or SelectedItemChangedEventArgs.SelectedItem (in case of ItemSelected event) . More info in official Xamarin documentation
If you using MVVM approach look at EventToCommandBehavior and Converter.
Xamarin have good example here

Xaml Listview ItemTapped Binding MVVM

I have a Listview in Xaml and I need to bind ItemTapped event to my ModelView using MVVM.
My ListView looks like.
<ListView x:Name="list"
ItemsSource="{Binding employeeList}"
ItemTapped= {Binding selectedItem} >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ItemTapped is an event which, in MVVM, normally corresponds to an ICommand*. If you meant to bind to a normal data property, judging from the attempted binding statement in your XAML snippet, then it would make more sense to do bind ListView's SelectedItem property instead :
<ListView x:Name="list"
ItemsSource="{Binding EmployeeList}"
SelectedItem="{Binding SelectedItem}">
.....
</ListView>
If there is data related action that need to be taken upon ItemTapped event occur, it might instead be implemented on SelectedItem property changed which triggered via binding (as in the XAML binding above) :
private ItemModel _selectedItem;
public ItemModel SelectedItem
{
get { return _selectedItem; }
set
{
if(_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
//this can be placed before or after propertychanged notification raised,
//depending on the situation
DoSomeDataOperation();
}
}
}
*) Xamarin Blog : Turn Events into Commands with Behaviors

Resources