How to make 3 column Xamarin.Forms FlexLayout with expanding center column and variable width left/right columns? - xaml

I'm trying to create a FlexLayout in Xamarin.Forms that will allow me to have the left and right columns be a variable width, and have the center column (and its contents) fill the remaining space and be centered on the screen.
Here is my current code, and here is what it's producing. Notice that "CENTER TEXT" in blue is centered within its StackLayout, but the StackLayout is not centered on the screen since the left and right columns have different widths.
Is FlexLayout a good choice for this, or should I use Grid or something else? Ideally, each column will expand to fit its content, with the center column's content being centered on the screen.
Note that the contents of each column is dynamic, so the widths of the left and right columns is also dynamic.
Thank you!
Code:
<FlexLayout x:Name="titleBar"
MinimumHeightRequest="40"
Padding="10"
JustifyContent="SpaceBetween"
AlignItems="Center"
AlignContent="Center">
<StackLayout x:Name="leftActionButton"
VerticalOptions="Center"
BackgroundColor="Red"
Orientation="Horizontal">
<Image x:Name="leftActionImg"
Margin="0, 0, 5, 0"
HeightRequest="40"
VerticalOptions="Center" />
<Label x:Name="leftActionLabel"
VerticalOptions="Center" />
</StackLayout>
<StackLayout VerticalOptions="Center"
BackgroundColor="Blue"
FlexLayout.Grow="1"
FlexLayout.Shrink="0">
<Label x:Name="title"
HorizontalTextAlignment="Center"/>
</StackLayout>
<StackLayout x:Name="rightActionButton"
BackgroundColor="Yellow"
VerticalOptions="Center"
Orientation="Horizontal">
<Label x:Name="rightActionLabel"
VerticalOptions="Center"
HorizontalOptions="End"
HorizontalTextAlignment="End" />
<Image x:Name="rightActionImg"
HeightRequest="40"
VerticalOptions="Center"
HorizontalOptions="End" />
</StackLayout>
</FlexLayout>
Results:

I was struggling with the same problem recently. So I was investing a day to find a solution. The result is disillusioning and I wouldn't call it a proper solution. I'm posting my thoughts here, because I couldn't find anything similar on the net.
I created a component which is responsible for balancing all three columns: It subscribes to width changes of the left and right column and propagates the relative difference to the center column.
The center column takes the relative difference as correction by applying a padding.
MainPage: In the Resources part you see that I'm declaring an instance of ColumnBalancer. Later I subscribe Width changes to ColumnBalancer, so ColumnBalancer gets to known when the very left and very right column (content, actually) changes. The PaddingOffset binding is retrieved from the same ColumnBalancer instance whenever the Width values have changed.
<ContentPage
x:Class="App7863.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cb="clr-namespace:ColumnBalancing"
mc:Ignorable="d">
<ContentPage.Resources>
<ResourceDictionary>
<cb:ColumnBalancer x:Key="ColumnBalancer" x:Name="ColumnBalancer" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<Grid
Grid.Row="0"
Padding="10"
BackgroundColor="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="39" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Navigate Back Button -->
<Label
Grid.Column="0"
Width="{Binding Source={x:Reference ColumnBalancer}, Path=ReferenceWidthLeft}"
Text="<"
FontSize="20"
BackgroundColor="Magenta"
WidthRequest="39"
HeightRequest="39"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<!-- Page Title -->
<StackLayout
Grid.Column="1"
Margin="0"
Padding="{Binding Source={x:Reference ColumnBalancer}, Path=PaddingOffset}"
BackgroundColor="LightYellow"
Spacing="0">
<Label
Text="Center Title"
FontSize="20"
BackgroundColor="Magenta"
HeightRequest="39"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
LineBreakMode="TailTruncation" />
</StackLayout>
<!-- Toolbar Items -->
<StackLayout
Grid.Column="2"
Width="{Binding Source={x:Reference ColumnBalancer}, Path=ReferenceWidthRight}"
Margin="0"
Padding="0"
BackgroundColor="LightGreen"
Orientation="Horizontal"
Spacing="6">
<Label
x:Name="ToolbarT1"
Text="T1"
FontSize="20"
BackgroundColor="Green"
WidthRequest="39"
HeightRequest="39"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
IsVisible="False" />
<Label
x:Name="ToolbarT2"
Text="T2"
FontSize="20"
BackgroundColor="Green"
WidthRequest="39"
HeightRequest="39"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
IsVisible="False" />
</StackLayout>
<Label
Grid.Row="2"
Grid.ColumnSpan="3"
Text="Subtitle with more info"
FontSize="20"
BackgroundColor="LightBlue"
HeightRequest="39"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
HorizontalOptions="StartAndExpand"
HorizontalTextAlignment="Start" />
</Grid>
<!-- Content -->
<Grid
Grid.Row="1"
Padding="40"
BackgroundColor="LightCoral">
<StackLayout BackgroundColor="LightBlue">
<Button Text="Toogle T1" Clicked="Button_ToogleT1" />
<Button Text="Toogle T2" Clicked="Button_ToogleT2" />
</StackLayout>
</Grid>
</Grid>
</ContentPage>
ColumnBalancer: Exposes a ReferenceWidthLeft and ReferenceWidthRight which take the Width values from the left resp. right column content. Whenever the ReferenceWidth* properties change, a new PaddingOffset is set.
public class ColumnBalancer : BindableObject
{
public static readonly BindableProperty PaddingOffsetProperty = BindableProperty.Create(
nameof(PaddingOffset),
typeof(Thickness),
typeof(ColumnBalancer),
default(Thickness),
BindingMode.OneWay);
public Thickness PaddingOffset
{
get => (Thickness)this.GetValue(PaddingOffsetProperty);
set => this.SetValue(PaddingOffsetProperty, value);
}
public static readonly BindableProperty ReferenceWidthRightProperty = BindableProperty.Create(
nameof(ReferenceWidthRight),
typeof(double),
typeof(ColumnBalancer),
default(double),
BindingMode.OneWayToSource,
null,
OnReferenceWidthRightPropertyChanged);
public double ReferenceWidthRight
{
get => (double)this.GetValue(ReferenceWidthRightProperty);
set => this.SetValue(ReferenceWidthRightProperty, value);
}
public static readonly BindableProperty ReferenceWidthLeftProperty = BindableProperty.Create(
nameof(ReferenceWidthLeft),
typeof(double),
typeof(ColumnBalancer),
default(double),
BindingMode.OneWayToSource,
null,
OnReferenceWidthLeftPropertyChanged);
public double ReferenceWidthLeft
{
get => (double)this.GetValue(ReferenceWidthLeftProperty);
set => this.SetValue(ReferenceWidthLeftProperty, value);
}
private static void OnReferenceWidthLeftPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
if (!(bindable is ColumnBalancer columnBalancer) || !(newvalue is double newLeftValue && newLeftValue >= 0))
{
return;
}
UpdatePaddingOffset(columnBalancer, newLeftValue, columnBalancer.ReferenceWidthRight);
}
private static void OnReferenceWidthRightPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
if (!(bindable is ColumnBalancer columnBalancer) || !(newvalue is double newRightValue && newRightValue >= 0))
{
return;
}
UpdatePaddingOffset(columnBalancer, columnBalancer.ReferenceWidthLeft, newRightValue);
}
private static void UpdatePaddingOffset(ColumnBalancer columnBalancer, double left, double right)
{
if (left < 0)
{
left = 0;
}
if (right < 0)
{
right = 0;
}
var relativePadding = Math.Abs(left - right);
if (right > left)
{
columnBalancer.PaddingOffset = new Thickness(relativePadding, 0, 0, 0);
}
else
{
columnBalancer.PaddingOffset = new Thickness(0, 0, relativePadding, 0);
}
}
}

I was able to get this to work by using a 2-column grid and overlaying the "center" column on top by using Grid.ColumnSpan="2".
I realize this has the potential for the content in the center to overlap the content on the left and right, but I'm OK with working around this limitation if that's the best I can do. Still welcome other, more robust suggestions, though!
<Grid x:Name="titleBar"
MinimumHeightRequest="40"
Padding="10"
HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout x:Name="leftActionButton"
VerticalOptions="Center"
Orientation="Horizontal"
HorizontalOptions="Start"
Grid.Column="0">
<Image x:Name="leftActionImg"
Margin="0, 0, 5, 0"
HeightRequest="40"
VerticalOptions="Center" />
<Label x:Name="leftActionLabel"
VerticalOptions="Center" />
</StackLayout>
<StackLayout x:Name="rightActionButton"
VerticalOptions="Center"
HorizontalOptions="End"
Orientation="Horizontal"
Grid.Column="1">
<Label x:Name="rightActionLabel"
VerticalOptions="Center"
HorizontalOptions="End"
HorizontalTextAlignment="End" />
<Image x:Name="rightActionImg"
HeightRequest="40"
VerticalOptions="Center"
HorizontalOptions="End" />
</StackLayout>
<StackLayout VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
Grid.Column="0"
Grid.ColumnSpan="2">
<Label x:Name="title"
HorizontalTextAlignment="Center"/>
</StackLayout>
</Grid>

Related

Binding Grid Column Width on Xamarin.Forms

I'm attempting to build a 3 column status bar with the Grid control in Xamarin.Forms. I receive 3 values: open, pending and filled. I would like the column width to be bound to those values. However, the column widths remain equal.
Current Look:
Desired Look:
This is achieved by replacing the bindings in the Column Width with 3*, 0*, and 2* respectively.
XAML
<Frame HorizontalOptions="FillAndExpand" CornerRadius="10" HasShadow="False" Padding="0" IsClippedToBounds="True">
<Grid ColumnSpacing="0" HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Filled, StringFormat='{0:N}*'}" />
<ColumnDefinition Width="{Binding Pending, StringFormat='{0:N}*'}" />
<ColumnDefinition Width="{Binding Open, StringFormat='{0:N}*'}" />
</Grid.ColumnDefinitions>
<BoxView Grid.Column="0" Color="#5cb85c" />
<Label Grid.Column="0" Text="{Binding Filled}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
<BoxView Grid.Column="1" Color="#f0ad4e" />
<Label Grid.Column="1" Text="{Binding Pending}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
<BoxView Grid.Column="2" Color="#d43f3a" />
<Label Grid.Column="2" Text="{Binding Open}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</Frame>
UPDATE:
I updated my code with your suggestion. The issue is that the converter is not fired. I do not get an error and the breakpoint in the converter is never reached. I added "Source={x:Reference listView}" as I've seen some SO answers suggest it. However, it doesn't help. I also tried to add a basic double to string converter to the label and it doesn't execute either.
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:conv="clr-namespace:MyApp.Converters" x:Class="MyApp.Views.OrgDash.PositionsPage" Title="Positions">
<ContentPage.Resources>
<ResourceDictionary>
<conv:NumberToGridLengthConverter x:Key="numberToGridLengthConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<AbsoluteLayout x:Name="absLayout" VerticalOptions="FillAndExpand">
<ListView x:Name="listView" HasUnevenRows="true" ItemTapped="OnItemTapped" HeightRequest="{Binding Path=Height, Source={x:Reference absLayout}}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10,10,10,10" Orientation="Vertical">
<StackLayout HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<Label Text="{Binding Name}" VerticalTextAlignment="Center" FontAttributes="Bold" />
</StackLayout>
<Frame HorizontalOptions="FillAndExpand" CornerRadius="10" HasShadow="False" Padding="0" IsClippedToBounds="True">
<Grid ColumnSpacing="0" HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Filled, Source={x:Reference listView}, Converter={StaticResource numberToGridLengthConverter}}" />
<ColumnDefinition Width="{Binding Pending, Source={x:Reference listView}, Converter={StaticResource numberToGridLengthConverter}}" />
<ColumnDefinition Width="{Binding Open, Source={x:Reference listView}, Converter={StaticResource numberToGridLengthConverter}}" />
</Grid.ColumnDefinitions>
<BoxView Grid.Column="0" Color="#5cb85c" />
<Label Grid.Column="0" Text="{Binding Filled" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
<BoxView Grid.Column="1" Color="#f0ad4e" />
<Label Grid.Column="1" Text="{Binding Pending}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
<BoxView Grid.Column="2" Color="#d43f3a" />
<Label Grid.Column="2" Text="{Binding Open}" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentView x:Name="overlay" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All" IsVisible="True" BackgroundColor="#C0808080" Padding="10, 0">
<ActivityIndicator WidthRequest="110" HeightRequest="70" IsRunning="True" IsVisible="True" Color="Black" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
</ContentView>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
Converter Class:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace MyApp.Converters
{
public class NumberToGridLengthConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var numberValue = (double)value;
return new GridLength(numberValue, GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var gridLength = (GridLength)value;
return gridLength.Value;
}
}
}
I guess that your binding will cause binding error and you will see in Debug window something like this:
[0:] Binding: '3.00*' can not be converted to type 'Xamarin.Forms.GridLength'.
[0:] Binding: '0.00*' can not be converted to type 'Xamarin.Forms.GridLength'.
[0:] Binding: '2.00*' can not be converted to type 'Xamarin.Forms.GridLength'.
I would use a custom converter to convert number value to GridLength:
public class NumberToGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var numberValue = (double)value;
return new GridLength(numberValue, GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var gridLength = (GridLength)value;
return gridLength.Value;
}
}
Xaml:
<ContentPage.Resources>
<ResourceDictionary>
<conv:NumberToGridLengthConverter x:Key="numberToGridLengthConverter" />
</ResourceDictionary>
</ContentPage.Resources>
...
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Filled, Converter={StaticResource numberToGridLengthConverter}}" />
<ColumnDefinition Width="{Binding Pending, Converter={StaticResource numberToGridLengthConverter}}" />
<ColumnDefinition Width="{Binding Open, Converter={StaticResource numberToGridLengthConverter}}" />
</Grid.ColumnDefinitions>

First-time user onboarding with Xamarin.Forms

I am implementing a first-time user onboarding in a Xamarin.Forms application like the one on the image below:
Please, how can I quickly do that?
Thank you in advance.
Do you want to achieve the result like following GIF?
You can use CarouselView and IndicatorView to achieve it.
Here is layout code.I dont adjust the layout like yours, you can adjust it by yourself, If you want to make Next and Skip Button over float the CarouselView, you can use AbsoluteLayout
<StackLayout Margin="10">
<CarouselView x:Name="myCarouselView" ItemsSource="{Binding Monkeys}"
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="50"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
<Label Text="{Binding Location}"
HorizontalOptions="Center" />
<Label Text="{Binding Details}"
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Skip" Grid.Column="0" BackgroundColor="Transparent" Clicked="Button_Clicked"></Button>
<Button Text="Next" Grid.Column="1" BackgroundColor="Transparent" Clicked="Button_Clicked_1"></Button>
</Grid>
<IndicatorView x:Name="indicatorView"
MaximumVisible="6"
Margin="0,0,0,40"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" />
</StackLayout>
Here is layout bakground code.
public partial class BasicIndicatorViewPage : ContentPage
{
int monkeyCount;
public BasicIndicatorViewPage()
{
InitializeComponent();
MonkeysViewModel monkeysViewModel= new MonkeysViewModel();
monkeyCount = monkeysViewModel.Monkeys.Count;
BindingContext = monkeysViewModel;
}
private void Button_Clicked(object sender, System.EventArgs e)
{
Navigation.PushAsync(new Page1());
Navigation.RemovePage(this);
}
private void Button_Clicked_1(object sender, System.EventArgs e)
{
if (myCarouselView.Position < monkeyCount-1)
{
myCarouselView.Position += 1;
}
else
{
Navigation.PushAsync(new Page1());
Navigation.RemovePage(this);
}
}
}
Here is my demo, you can download it.
https://github.com/851265601/XFormsCarouselView-IndicatorViewLoginPage
If you want to put the indicatorView and Button in the same line like this screenshot.
You can use this layout(indicatorView to the Grid).
<StackLayout Margin="10">
<CarouselView x:Name="myCarouselView" ItemsSource="{Binding Monkeys}"
IndicatorView="indicatorView">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="50"
HeightRequest="300"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand">
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="150"
WidthRequest="150"
HorizontalOptions="Center" />
<Label Text="{Binding Location}"
HorizontalOptions="Center" />
<Label Text="{Binding Details}"
FontAttributes="Italic"
HorizontalOptions="Center"
MaxLines="5"
LineBreakMode="TailTruncation" />
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Skip" Grid.Column="0" BackgroundColor="Transparent" Clicked="Button_Clicked"></Button>
<IndicatorView x:Name="indicatorView"
MaximumVisible="6"
IndicatorColor="LightGray"
SelectedIndicatorColor="DarkGray"
HorizontalOptions="Center" Grid.Column="1" />
<Button Text="Next" Grid.Column="2" BackgroundColor="Transparent" Clicked="Button_Clicked_1"></Button>
</Grid>
</StackLayout>
You could use the offical Xamarin.Forms CarouselView but that's still in preview. You could also potentially look at the Sharpnado's solution with HorizontalListView. Your third option would be to use Alex Rainman's CarouselView.

Xamarin Forms Carousel view issue with binding

Hello I’m having an issue with binding a list of string to my Carousel View
First I have a list of an object I get from my server
public class PostObject
{
public string PostOwner { get; set; }
public string Id { get; set; }
public string Post { get; set; }
public string ProfileImage { get; set; }
public List<string> PostImages { get; set; }
}
List<PostObject> posts = new List<PostObject>();
This works as I expected.
Next I have a card view I created and within the card view I want to have a Carousel View.
So I have setup my Xaml like this (omitting the parts that works for clarity)
<?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:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
NavigationPage.HasNavigationBar="True"
NavigationPage.HasBackButton="False"
NavigationPage.BackButtonTitle="Back"
Title="amici"
x:Class="amici.Posts">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" VerticalOptions="Center" Spacing="10" >
<Label x:Name="GroupTitle" TextColor="White" FontSize="Medium"/>
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.ToolbarItems>
<ToolbarItem Name="iconexample" Icon="settings.png" Priority="0" Order="Primary" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ItemsListView"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
IsPullToRefreshEnabled="true"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
CachingStrategy="RecycleElement">
<!--ItemSelected="OnItemSelected"-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Padding="10">
<Frame x:Name="myframe" HasShadow="True" >
<Grid HorizontalOptions="FillAndExpand" RowSpacing="0" ColumnSpacing="0" >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Text="{Binding PostOwner}" LineBreakMode="WordWrap" Font="Bold,16" />
<controls:CircleImage Grid.Row="0" Margin="10" BorderColor="white" BorderThickness="1" VerticalOptions="Start" HorizontalOptions="Start" Source="{Binding ProfileImage}" Aspect="AspectFit">
<controls:CircleImage.WidthRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android, iOS">65</On>
</OnPlatform>
</controls:CircleImage.WidthRequest>
<controls:CircleImage.HeightRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android, iOS">65</On>
</OnPlatform>
</controls:CircleImage.HeightRequest>
</controls:CircleImage>
<Label Grid.Row="1" Text="{Binding Post}" LineBreakMode="WordWrap" Font="Bold,16" />
<CarouselView x:Name="PostImages" Grid.Row="2" ItemsSource="{Binding PostImages}">
<CarouselView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding .}" Aspect="AspectFill" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<!--<Image Grid.Row="2" Source="{Binding ImageURL}" Aspect="AspectFill" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />-->
<BoxView Grid.Row="3" BackgroundColor="black" HeightRequest="1" HorizontalOptions="FillAndExpand"/>
<StackLayout Grid.Row="4" Orientation="Horizontal" >
<Label Text="Likes: " LineBreakMode="NoWrap" Font="Bold,14" />
<Label Text="0" LineBreakMode="NoWrap" FontSize="14" />
</StackLayout>
<StackLayout Grid.Row="5" Orientation="Horizontal" >
<Label Text="Comments: " LineBreakMode="NoWrap" Font="Bold,14" HorizontalOptions="End" />
<Label Text="0" HorizontalOptions="End" LineBreakMode="NoWrap" FontSize="14" />
</StackLayout>
<!--<Label Grid.Row="2" Text="{Binding OwnerFullName}" LineBreakMode="NoWrap" FontSize="16" />-->
</Grid>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
In my code behind I have this
public Posts (GroupInfo ginfo)
{
InitializeComponent ();
GroupTitle.Text = ginfo.Title;
CurrentGroupInfo = ginfo;
GetDataPosts();
ItemsListView.RefreshCommand = new Command(() => {
GetDataPosts();
ItemsListView.IsRefreshing = false;
});
}
public void GetDataPosts()
{
try
{
string apikey = Application.Current.Properties["api"].ToString();
ItemsListView.ItemsSource = null;
posts.Clear();
if (RestController.GetMyPostData(ref posts, CurrentGroupInfo.Id.ToString(), apikey))
{
ItemsListView.ItemsSource = posts;
}
}
catch(Exception e)
{
Debug.WriteLine(e.Message);
}
}
now up to this point everything works and no errors but when the app goes to render the page I get a error
System.TypeInitializationException: The type initializer for 'Xamarin.Forms.ItemsView' threw an exception.
which I trace back to the Carousel View. When I comment out the Carousel View the it works. so I'm thinking I can't bind OR use the Carousel View in the way I thought?
The CarouselView used to be a plugin that has now become part of Xamarin.Forms effective with version 4. You can use an earlier XF version but you will need to get this plugin, add assembly references to your XAML and put initialization code in your platform specific projects.
Alternatively, you could upgrade to Xamarin.Forms 4
Even though you can see the Carousel View on Xamarin.Forms 3.6.0.344457, it only defines an interface there. There're no implementations and properties in the Carousel View Class.
You can only utilize it under Xamarin.Forms 4.0. As it is still a preview version now, there're some limitations. See my post here: https://stackoverflow.com/a/56235795/8354952 for more information.

Ui is not updating on realtime

I have a listview for displaying all the items that i getting from api.i have a live event which will modify one field inside the item of type observablecollection
<?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="Dyocense.Views.ManageJobs"
Title="All jobs">
<ContentPage.ToolbarItems>
<ToolbarItem Text="ADD" Clicked="AddJob"></ToolbarItem>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<SearchBar x:Name="Search" SearchButtonPressed="SearchBar_SearchButtonPressed"></SearchBar>
<ListView x:Name="JobsListView"
ItemsSource="{Binding Items}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemAppearing="BrowseJobList_ItemAppearing"
IsPullToRefreshEnabled="true"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10">
<Frame HasShadow="True" >
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
FontSize="Medium"
FontAttributes="Bold"/>
<Label Text="Status"
Grid.Row="1" Grid.Column="0"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding Status}"
Grid.Row="1" Grid.Column="1"
FontSize="16"/>
<Label Text="Goal"
Grid.Row="2" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding Goal}"
Grid.Row="2" Grid.Column="2"
FontSize="16" />
<Label Text="Part"
Grid.Row="3" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding PartName}"
Grid.Row="3" Grid.Column="2"
FontSize="16" />
<Label Text="Assembly"
Grid.Row="4" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding NodeName}"
Grid.Row="4" Grid.Column="2"
FontSize="16" />
<Label Text="GoodCount"
Grid.Row="5" Grid.Column="0"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding GoodCount}"
Grid.Row="6" Grid.Column="0"
FontSize="16"
HorizontalTextAlignment="Center"/>
<Label Text="RejectCount"
Grid.Row="5" Grid.Column="1"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding RejectCount}"
Grid.Row="6" Grid.Column="1"
FontSize="16"
HorizontalTextAlignment="Center"/>
<Label Text="DownTimeCount"
Grid.Row="5" Grid.Column="2"
FontSize="16"
FontAttributes="Bold"/>
<Label Text="{Binding DownTimeCount}"
Grid.Row="6" Grid.Column="2"
FontSize="16"
HorizontalTextAlignment="Center"/>
</Grid>
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Footer>
<Grid Padding="6" IsVisible="{Binding IsBusy}">
<!--set the footer to have a zero height when invisible-->
<Grid.Triggers>
<Trigger TargetType="Grid" Property="IsVisible" Value="False">
<Setter Property="HeightRequest" Value="0" />
</Trigger>
</Grid.Triggers>
<!--the loading content-->
<Label Text="Loading..." VerticalOptions="Center" HorizontalOptions="Center" />
</Grid>
</ListView.Footer>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
i want to update the good count on real time. i debugged the code its getting updated in my list but only updating the UI on a user scroll. Here is my viewmodel
public class JobViewModel: INotifyPropertyChanged
{
private ObservableCollection<Job> items;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Job> Items
{
get { return items; }
set
{
items = value;
if (items != value)
{
items = value;
OnPropertyChanged(nameof(Items));
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private void updateJoblistOnEvent(NotificationEntity message)
{
var jobId = message.JobId;
Job job = Items.FirstOrDefault(a => a.JobId.Equals(jobId));
switch (message.EventCode)
{
case MessageTypeEnum.Pulse: //Pulse
job.GoodCount = job.GoodCount + 1;
//job.Status = 'Running';
break;
default:
break;
}
}
i am suspecting two things
CachingStrategy="RecycleElement" i removed this one that its not updating with scroll also
any problem with INotifyPropertyChanged, i tried to remove one item from list its working and updating the ui.i want to update the field inside the item
can any one help me
As deduced from the comments, you need to also implement the INotifyPropertyChanged interface on the Job object. Using the ObservableCollection only helps for changes in that collection, so when you remove or add a Job object, not if something changes inside the Job object.
So, in your Job object do this (code reverse engineered from what you posted):
public class Job : INotifyPropertyChanged
{
private int goodCount;
public int GoodCount
{
get { return goodCount; }
set
{
if (goodCount != value)
{
goodCount = value;
OnPropertyChanged(nameof(GoodCount));
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You could remove the INotifyPropertyChanged from your JobViewModel, but you probably need it there at some point as well

Xamarin Forms ScrollView displays blank screen

The following XAML shows my page just fine, until I added the ScrollView , now it just displays a blank screen.
Can anyone spot anything obvious with this? I suspect it my be because I'm setting the height of the grid within the ScrollView but I'm not sure
<ContentPage.Content>
<ScrollView>
<Grid BackgroundColor="#ede8db" x:Name="articleGrid" >
<Grid.RowDefinitions>
<RowDefinition Height="47.5*" />
<RowDefinition Height="5*" />
<RowDefinition Height="47.5*" />
</Grid.RowDefinitions>
<ratio:ContentRatioContainer Grid.Row="0" x:Name="imgContainer">
<Image Aspect="AspectFill" Source="KevingroveCarouselImg.png" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="All" x:Name="bigImg"
HeightRequest="{artina:OnOrientationDouble PortraitPhone=880, LandscapePhone=125, PortraitTablet=600, LandscapeTablet=400 }" Grid.Row="0"/>
</ratio:ContentRatioContainer>
<artina:Button Margin="10,10,10,10" x:Name="ImgZoom" Clicked="EnlargeImage" HorizontalOptions="End" VerticalOptions="Start" Image="IncreaseImageIcon.png" BackgroundColor="Transparent" HeightRequest="30" WidthRequest="30"/>
<StackLayout Grid.Row="1" Orientation="Horizontal" BackgroundColor="#ede8db" Margin="0" Padding="30,0,30,0" x:Name="iconStack" HorizontalOptions="FillAndExpand" >
<Label HorizontalTextAlignment="Center" Text="{x:Static ratio:FontAwesome.WIFI}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="{artina:OnOrientationDouble PortraitPhone=25, LandscapePhone=18, PortraitTablet=30, LandscapeTablet=20 }" />
<Label HorizontalTextAlignment="Center" Text="{x:Static ratio:FontAwesome.CAMERA}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="{artina:OnOrientationDouble PortraitPhone=25, LandscapePhone=18, PortraitTablet=30, LandscapeTablet=20 }" />
<Label HorizontalTextAlignment="Center" Text="{x:Static ratio:FontAwesome.MAP}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="{artina:OnOrientationDouble PortraitPhone=25, LandscapePhone=18, PortraitTablet=30, LandscapeTablet=20 }" />
<Label x:Name="expand" HorizontalTextAlignment="Center" HorizontalOptions="EndAndExpand" Text="{x:Static ratio:FontAwesome.ARROW_DOWN}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="{artina:OnOrientationDouble PortraitPhone=25, LandscapePhone=18, PortraitTablet=30, LandscapeTablet=20 }" />
</StackLayout>
<StackLayout Grid.Row="2" x:Name="articleInfo" Padding="30,0,30,0" >
<StackLayout x:Name="TopLayout" Opacity="0.0">
<StackLayout Orientation="Horizontal">
<Label HorizontalTextAlignment="Center" Text="{x:Static ratio:FontAwesome.MAP_MARKER}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="30" />
<Label Text="Address" VerticalOptions="Center" HorizontalOptions="StartAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label HorizontalTextAlignment="Center" Text="{x:Static ratio:FontAwesome.CLOCK_O}" Style="{StaticResource FontIcon}" TextColor="Black" FontSize="30" />
<Label Text="Openening Times" VerticalOptions="Center" HorizontalOptions="StartAndExpand" />
</StackLayout>
<StackLayout x:Name="EmptySpaceStack" HeightRequest="80" />
<StackLayout x:Name="txt" >
<Label x:Name="header" Text="HEADER" Style="{ StaticResource HeaderStyle }" />
<Label x:Name="description" Text="TEST" FontSize="Large" />
<Label x:Name="articleTxt" Text="BLAH..." FontSize="Medium"/>
</StackLayout>
</StackLayout>
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
UPDATE
looks like it's the ContentRationContainer that causes the problem, just to figure out why...
public class ContentRatioContainer : ContentView
{
public double ratio;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
var width = Application.Current.MainPage.Width;
var height = Application.Current.MainPage.Height;
if (Device.Idiom == TargetIdiom.Phone)
{
ratio = Convert.ToDouble(Resx.AppResources.ImageRatioContentPhone);
}
if (Device.Idiom == TargetIdiom.Tablet)
{
if (width > height)
{
ratio = Convert.ToDouble(Resx.AppResources.ImageRatioContentTabL);
}
else
{
ratio = Convert.ToDouble(Resx.AppResources.ImageRatioContentTabP);
}
}
return new SizeRequest(new Size(widthConstraint, widthConstraint * ratio));
}
public static BindableProperty ContentRatioProperty = BindableProperty.Create(nameof(ContentRatio), typeof(double), typeof(ContentRatioContainer), (double)1);
public double ContentRatio
{
get { return (double)this.GetValue(ContentRatioProperty); }
set { this.SetValue(ContentRatioProperty, value); }
}
}