I am attempting to upgrade an existing Xamarin project to the latest versions of various libraries. The Xamarin forms themselves live in a shared library. My page has a ListView on it & I am attempting to capture the event fired when an item is 'chosen'. However, the ICommand in my ViewModel is not executing.
NOTES:
I am using Xamarin.Forms 4.8...but had to downgrade Behaviors.Forms to 1.3 because the code is in a .NET Standard library.
If I upgrade to Behaviors.Forms 1.4 a design-time error occurs (see photo below)
SHARED LIBRARY INFORMATION:
.NET Standard 2.0
Acr.UserDialogs Version="7.1.0.454"
AzureMobileClient.Helpers Version="4.0.2.507-pre"
Behaviors.Forms Version="1.3.0"
Com.Airbnb.Xamarin.Forms.Lottie Version="3.1.3"
Prism.DryIoc.Forms Version="8.0.0.1850-pre"
Prism.Forms Version="8.0.0.1850-pre"
Refractored.MvvmHelpers Version="1.6.2"
Telerik.UI.for.Xamarin.Common Version="2020.2.624.1"
Telerik.UI.for.Xamarin.DataControls Version="2020.2.624.1"
Telerik.UI.for.Xamarin.DataGrid Version="2020.2.624.1"
Telerik.UI.for.Xamarin.Primitives Version="2020.2.624.1"
Telerik.UI.for.Xamarin.SkiaSharp Version="2020.2.624.1"
Xamarin.FFImageLoading.Forms Version="2.4.11.982"
Xamarin.FFImageLoading.Svg.Forms Version="2.4.11.982"
Xamarin.FFImageLoading.Transformations Version="2.4.11.982"
Xamarin.Forms Version="4.8.0.1364"
CONVERTER CLASS:
The original programmer returns 'ItemTapped' EventArgs...
public class SelectedItemEventArgsConverter : IValueConverter
{
#region <Methods>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as ItemTappedEventArgs;
return eventArgs != null ? eventArgs.Item : null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
VIEW MODEL:
The ListView binds-up & displays items (see photo's below)...
using CustomControls;
using Models;
using MvvmHelpers.Commands;
using Prism.Navigation;
using Prism.Services;
using System.Collections.Generic;
public class MainPageViewModel : ViewModelBase
{
#region Constructors
public MainPageViewModel(INavigationService navigationService, IPageDialogService pageDialogService, IDeviceService deviceService) : base(navigationService, pageDialogService, deviceService)
{
Initialize();
}
#endregion
#region <Properties>
public AccordionNode ShakeoutListItemsAccordion { get; private set; }
public Command<SimpleListItem> OnShakeoutListItemSelectedCommand { get; private set; }
public List<SimpleListItem> ShakeoutListItems { get; private set; } = new List<SimpleListItem>();
#endregion
#region <Events>
public async void OnShakeoutListItemSelected(SimpleListItem item)
{
if (item.Name == "Add Shakeout")
await NavigationService.NavigateAsync("ShakeoutDocumentGeneratorPage");
}
#endregion
private void Initialize()
{
// Commands
OnShakeoutListItemSelectedCommand = new Command<SimpleListItem>(OnShakeoutListItemSelected);
// Accordians
ShakeoutListItemsAccordion = new AccordionNode("OverwrittenInView", GlobalVariables.Accordion.Height, GlobalVariables.Accordion.HeaderBackgroundColor, GlobalVariables.Accordion.HeaderColorTextColor, GlobalVariables.Accordion.SeparatorColor);
// Data
ShakeoutListItems.Add(new SimpleListItem { Name = "Add Shakeout", Title = string.Empty, Type = string.Empty });
}
}
VIEW:
Because the converter-class 'ItemTapped' EventArgs, I am focusing-on that event...but I can chane this (if needed)
<?xml version="1.0" encoding="utf-8" ?>
<views:BaseContentPage 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"
mc:Ignorable="d"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
xmlns:forms="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
xmlns:converters="clr-namespace:ETC.Operations.Pulse.Mobile.Converters;assembly=ETC.Operations.Pulse.Mobile"
xmlns:customControls="clr-namespace:ETC.Operations.Pulse.Mobile.CustomControls;assembly=ETC.Operations.Pulse.Mobile"
xmlns:helpers="clr-namespace:ETC.Operations.Pulse.Mobile.Helpers;assembly=ETC.Operations.Pulse.Mobile"
xmlns:views="clr-namespace:ETC.Operations.Pulse.Mobile.Views;assembly=ETC.Operations.Pulse.Mobile"
x:Class="ETC.Operations.Pulse.Mobile.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<converters:SelectedItemEventArgsConverter x:Key="EventToCommand"/>
<forms:SvgImageSourceConverter x:Key="SvgImageSourceConverter"></forms:SvgImageSourceConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView VerticalOptions="FillAndExpand" Padding="10">
<StackLayout Spacing="0" Padding="0">
<!-- SHAKEOUTS -->
<StackLayout Spacing="0" Padding="0" Style="{DynamicResource AccordionTitleStyle}">
<StackLayout Style="{DynamicResource AccordionHeaderStackLayoutStyle}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShakeoutListItemsAccordion.ExpandContractAccordion}"/>
</StackLayout.GestureRecognizers>
<Label Text="Shakeouts" Style="{DynamicResource AccordionHeaderTextStyle}" VerticalTextAlignment="Center"/>
<Label TextColor="{Binding ShakeoutListItemsAccordion.HeaderTextColor}" HorizontalOptions="EndAndExpand" Text="{Binding ShakeoutListItemsAccordion.IconText}" VerticalTextAlignment="Center"/>
</StackLayout>
<BoxView HeightRequest="1" Color="{Binding ShakeoutListItemsAccordion.LineColor}" HorizontalOptions="FillAndExpand"></BoxView>
</StackLayout>
<!-- SHAKEOUTS: Controls -->
<StackLayout BackgroundColor="{DynamicResource BackgroundColor}" HeightRequest="200" IsVisible="{Binding ShakeoutListItemsAccordion.IsExpanded}" Padding="0" Spacing="0">
<ListView
x:Name="lvShakeoutListItems"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding ShakeoutListItems}"
RowHeight="40">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Style="{DynamicResource ListViewGridItemStyle}" RowSpacing="0" ColumnSpacing="0" Margin="13,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" Style="{DynamicResource ListViewLabelCenterRowLeft}" TextColor="{StaticResource LabelValueTextColor}" Margin="0,10,0,0"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemTapped">
<behaviors:InvokeCommandAction Command="{Binding OnShakeoutListItemSelectedCommand}" Converter="{StaticResource EventToCommand}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
</ListView>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</views:BaseContentPage>
PHONE IMAGES:
As you can see...the ViewModel is binding to data.
NOTES:
Upgrading to Behaviors.Forms 1.4 generates a design-time error.
UPDATES:
Here are my updates on the suggestions being made by contributors...
The 'Tap gesture on main cell object' option does not fire an event (at all)
The 'selected Item' event raises...but does not call the "MainPageViewModel OnShakeoutListItemSelected" event
I am currently looking at "Corcav Behavior" in NuGet
Option 1 : Use tap gesture on main cell object
<ListView
x:Name="lvShakeoutListItems"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding ShakeoutListItems}"
RowHeight="40">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Style="{DynamicResource ListViewGridItemStyle}" RowSpacing="0" ColumnSpacing="0" Margin="13,0,0,0">
**<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnShakeoutListItemSelectedCommand}" CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>**
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.8*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding Name}" Style="{DynamicResource ListViewLabelCenterRowLeft}" TextColor="{StaticResource LabelValueTextColor}" Margin="0,10,0,0"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Option 2: Use selected Item on main cell object
<ListView
x:Name="lvShakeoutListItems"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding ShakeoutListItems}"
SelectedItem="{Binding SelctedItem}"
RowHeight="40">
...
</>
Option 3 : Use corcav behavior nuget to get event to command behavior
Related
I am using ToolKits Expander and I am trying to bind a command, this is what I got so far:
public partial class AssignTaskPage : ContentPage
{
public AssignTaskPage()
{
InitializeComponent();
GetMathSubCatgories = new Command(() => MathSubCatgoriesCommand());
}
public ICommand GetMathSubCatgories { get; private set; }
void MathSubCatgoriesCommand()
{
Console.Write("Here");
}
}
And in my view
<xct:Expander Command="{Binding GetMathSubCatgories}">
<xct:Expander.Header>
<Frame Padding="10" Margin="10" HasShadow="False" BorderColor="LightGray" VerticalOptions="CenterAndExpand">
<StackLayout Orientation="Horizontal">
<Image Source="{Binding icon}" WidthRequest="25" HeightRequest="25"></Image>
<Label Text="{Binding name}" TextColor="{Binding textColor}" FontSize="Large" FontAttributes="Bold" HeightRequest="35" VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</Frame>
</xct:Expander.Header>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListView x:Name="SubCategories" ItemsSource="{Binding subCategories}" ItemSelected="SubCategories_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding name}" TextColor="#02cc9d" FontAttributes="Bold" HeightRequest="35" VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</xct:Expander>
This does not work at all, (I put a break point on Console.Write("Here"); and its not hitting it)
So I did some digging and found this tutorial:
https://www.syncfusion.com/kb/12154/how-to-bind-command-to-expander-in-itemtemplate-of-xamarin-forms-listview-sflistview
and here is the sample in git.
https://github.com/SyncfusionExamples/command-to-expander-in-itemtemplate-listview-xamarin
I understand what I have to do here, the problem I am facing is when this Command is called, I was looking to get a value and use it in my AssignTaskPage, but what the tutorial is saying to have a ViewModel which is in a separate file. So should I setup a MessagingCenter in my AssignTaskPage and call it in the ViewModel to get the value I want and pass it to AssignTaskPage?
Because your command isn't defined in the ViewModel that you're binding to.You could bind the command which is defined in your AssignTaskPage,and then bind the viewmodel for the parent element of the expander.
For example :
public partial class AssignTaskPage : ContentPage
{
public AssignTaskPage()
{
InitializeComponent();
GetMathSubCatgories = new Command(() => MathSubCatgoriesCommand());
BindingContext = this;
}
public ICommand GetMathSubCatgories { get; private set; }
void MathSubCatgoriesCommand(object obj)
{
DisplayAlert("Alert!", "" + (obj as Contact).ContactName + "expanded", "Ok");
}
}
the xaml (here use the xaml codes of the above sample),the grid bind the viewmodel,and your expander bind the command of the root (your page):
<?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:ExpanderXamarin"
x:Class="ExpanderXamarin.ExpandableListView"
x:Name="root"
xmlns:sflistview="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
xmlns:expander="clr-namespace:Syncfusion.XForms.Expander;assembly=Syncfusion.Expander.XForms">
<ContentPage.Content>
<Grid x:Name="mainGrid" BackgroundColor="#F0F0F0" Padding="4">
<Grid.BindingContext>
<local:ViewModel />
</Grid.BindingContext>
<sflistview:SfListView x:Name="listView" AutoFitMode="DynamicHeight" ItemsSource="{Binding ContactsInfo}">
<sflistview:SfListView.ItemTemplate>
<DataTemplate>
<Frame x:Name="frame" CornerRadius="2" Padding="{OnPlatform Android=1, iOS=1, UWP=0}" Margin="{OnPlatform Android=1, iOS=1, UWP=0}" OutlineColor="White" HasShadow="{OnPlatform Android=true, iOS=false, UWP=true}">
<Grid Padding="{OnPlatform Android=2, iOS=2, UWP=0}" Margin="{OnPlatform Android=1, iOS=1, UWP=0}" BackgroundColor="White" >
<expander:SfExpander x:Name="expander" HeaderIconPosition="None">
<expander:SfExpander.Behaviors>
<local:EventToCommandBehavior Command="{Binding Path=BindingContext.GetMathSubCatgories, Source={x:Reference root}}" EventName="Expanding" CommandParameter="{Binding .}"/>
</expander:SfExpander.Behaviors>
<expander:SfExpander.Header>
...
</expander:SfExpander.Header>
<expander:SfExpander.Content>
..
</expander:SfExpander.Content>
</expander:SfExpander>
</Grid>
</Frame>
</DataTemplate>
</sflistview:SfListView.ItemTemplate>
</sflistview:SfListView>
</Grid>
</ContentPage.Content>
</ContentPage>
if you want get the parameters you could bind the CommandParameter like above.
i'm trying to bind a command to a button inside a listView, but without success. I follow all other answers posted here, like this one:
Xamarin Forms Button Command binding inside a ListView
The actual result is that nothing happens. A thing that i notice is that visual studio, when i type after x:Reference suggests me just GridWebcam, like if it doesn't see other reference elements. What can i do?
Here my code:
<ContentPage
...
x:Name="WebcamList">
<ContentPage.Resources>
...
<ContentPage.Content>
<ListView ItemsSource="{Binding ListOfWebcam}"
SeparatorVisibility="None"
CachingStrategy="RecycleElement"
RowHeight="250"
VerticalOptions="FillAndExpand"
x:Name="ListWebcam">
<ListView.Header>
<StackLayout x:Name="HeaderStackLayout"
Padding="5,25,0,30"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand">
<Label x:Name="LabelHeader"
Text="Webcam:"
FontSize="Large"
FontAttributes="Bold"
TextColor="{x:Static statics:Palette.PrimaryColor}"
VerticalOptions="Center"
HorizontalOptions="Start" Margin="10,0,0,0"/>
</StackLayout>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<controls:ExtendedViewCell IsEnabled="False">
<controls:ExtendedViewCell.View>
<Grid x:Name="GridWebcam">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Frame Grid.Column="1"
Grid.RowSpan="2"
CornerRadius="20"
BackgroundColor="{x:Static statics:Palette.PrimaryColor}"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
HasShadow="True"
Margin="5,10">
<StackLayout>
<Label Text="{Binding t_str_vid,Converter={StaticResource WebcamNameConverter}}"
FontSize="Medium"
TextColor="White"
FontAttributes="Bold"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
</Label>
<Label TextColor="White"
FontSize="Medium"
Text="{Binding direzione,Converter={StaticResource DirectionToStringConverter}}"/>
<StackLayout Orientation="Horizontal">
<ffimageloading:CachedImage LoadingPlaceholder="Rolling.gif"
DownsampleToViewSize="False"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
Source="{Binding image1}"/>
<iconize:IconButton Text="fas-play-circle"
FontSize="50"
HorizontalOptions="EndAndExpand"
VerticalOptions="EndAndExpand"
TextColor="White"
Command="{Binding BindingContext.OpenVideoWebcamCommand, Source={x:Reference WebcamList}}"
CommandParameter="{Binding .}"
BackgroundColor="Transparent"/>
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</controls:ExtendedViewCell.View>
</controls:ExtendedViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
```
public class WebcamListViewModel : BaseViewModel
{
public ICommand OpenVideoWebcamCommand { set; get; }
private List<Webcam> _ListOfWebcam { get; set; }
public List<Webcam> ListOfWebcam
{
get { return _ListOfWebcam; }
set
{
_ListOfWebcam = value;
OnPropertyChanged();
}
}
private Task DownloadFramesTask;
CancellationTokenSource tokenSourceDownloadFrames = new CancellationTokenSource();
CancellationToken cancellationTokenDownloadFrames;
public WebcamListViewModel(INavigationService navigationService, IApiAutostradeManagerFactory apiAutostradeManagerFactory) : base(navigationService,apiAutostradeManagerFactory)
{
OpenVideoWebcamCommand = new Command<Webcam>(async (webcam) => {
await navigationService.NavigateAsync(Locator.WebcamVideoPopUpPage);
Messenger.Default.Send(new InfoWebcamVideoMessage(webcam.c_mpr, webcam.c_uuid, webcam.t_str_vid));
});
}
Well it could be related to this mysterious controls:ExtendedViewCell of yours :)
Also did you disable the ListView selection: <ListView ... SelectionMode="None" /> ?
As Roubachof said that I don't know if it is related to controls:ExtendedViewCell,please check if you have binding BindingContext, then you can take a look the following code:
<StackLayout>
<ListView x:Name="listview1" ItemsSource="{Binding persons}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Id}" />
<Label Text="{Binding name}" />
<Button
Command="{Binding BindingContext.command, Source={x:Reference listview1}}"
CommandParameter="{Binding Id}"
Text="Delete item" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page1 : ContentPage
{
public ObservableCollection<person> persons { get; set; }
public RelayCommand1 command { get; set; }
public Page1 ()
{
InitializeComponent ();
persons = new ObservableCollection<person>();
for(int i =0;i<20;i++)
{
person p = new person()
{
Id = i,
name = "cherry" + i
};
persons.Add(p);
command = new RelayCommand1(obj => method1((int)obj));
}
this.BindingContext = this;
}
public void method1(int Id)
{
persons.RemoveAt(Id);
//IEnumerable<person> list = persons.Where(x => x.Id == Id);
//foreach (person m in list)
//{
//}
}
}
public class person
{
public int Id { get; set; }
public string name { get; set; }
}
I have created a ViewCell that can be reused throughout my project:
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:C="using:****.Converters"
x:Class="****.Views.Settings.SettingCell">
<Grid BackgroundColor="{StaticResource BlueGray}">
<Grid.Resources>
<C:DebugConverter x:Key="debugConverter"/>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ImageSource}"/>
<Label Grid.Column="1"
Text="{Binding Path=Title,
Converter={StaticResource debugConverter},
FallbackValue=Title}"
HorizontalOptions="Start"
VerticalOptions="Center"/>
</Grid>
</ViewCell>
Here is ViewModel:
public partial class SettingCell : ViewCell
{
public SettingModel Model
{
get
{
return model;
}
set
{
model = value;
BindingContext = value;
}
}
public SettingModel model;
public static readonly BindableProperty SettingTypeProperty = BindableProperty.Create(nameof(SettingType), typeof(Setting), typeof(SettingModel));
public Setting SettingType
{
get
{
return (Setting)GetValue(SettingTypeProperty);
}
set
{
SetValue(SettingTypeProperty, value);
}
}
public SettingCell()
{
InitializeComponent();
switch (SettingType)
{
case Setting.Name:
Model = new NameSettingModel();
break;
default:
throw new NotImplementedException("Unknown Setting Type");
}
}
}
The Model is a subtype of an abstract class that is selected through a class discriminator. The class is very simple at this point since I'm just getting it wired up, but here is the base class along with on sub-type:
public class NameSettingModel : SettingModel
{
public NameSettingModel()
{
Title = "Name";
IconSource = "";
}
public override void ClickCommand()
{
Debug.WriteLine("Setting Command Run");
}
}
public abstract class SettingModel : BindableBase
{
public string Title
{
get
{
return GetProperty<string>();
}
set
{
SetProperty(value);
}
}
public string IconSource
{
get
{
return GetProperty<string>();
}
set
{
SetProperty(value);
}
}
public abstract void ClickCommand();
}
BindableBase is just a base binding class that implements INotifyPropertyChanged and stores property values in a dictionary so I don't have to make a field for each property.
When I run the project; the fallback value shows up each time. The thing that strikes me as odd is that when I put a break in my debugConverter which is as follows:
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
I see the value "Name" coming through the converter but the UI only ever shows the fallback value. Any ideas? Any reason that the binding would get the correct value but just not refresh to display it? I'm at a loss here. I'm happy to post any more code that could be helpful, just leave me a comment.
Thanks!
=========================== Edit =============================
The plot thickens. I now have a viewcell that is directly in my listview and the template I have inserted is just a Grid.
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Images="clr-namespace:MyApp.Images;assembly=MyApp"
xmlns:Converters="clr-namespace:MyApp.Views.Converters"
x:Class="MyApp.Views.Templates.SettingTemplate"
BackgroundColor="White" VerticalOptions="Center">
<Grid.Resources>
<Converters:SettingToIconConverter x:Key="SettingToIconConverter"/>
<Converters:InverseBoolConverter x:Key="InverseBoolConverter"/>
</Grid.Resources>
<StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center">
<Images:VectorImage ResourceId="{Binding Converter={StaticResource SettingToIconConverter}}"
WidthRequest="30" HeightRequest="30" Margin="2" VerticalOptions="Center"/>
<Label Text="{Binding Name, FallbackValue=Name}" FontAttributes="Bold" FontSize="Medium" Margin="2"
VerticalOptions="Center"/>
<ContentPresenter Content="{Binding BonusContent}" VerticalOptions="Center" Margin="2"/>
</StackLayout>
<Frame BackgroundColor="{StaticResource Slate}" Opacity="0.25" IsVisible="{Binding IsEnabled, UpdateSourceEventName=ValueChanged, Converter={StaticResource InverseBoolConverter}, FallbackValue=false}"/>
Here's the containing listpage that has two templates in it:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Templates:PeripheralDetailsTemplate/>
<Frame BackgroundColor="{StaticResource Slate}" Grid.Row="1"/>
<Frame BackgroundColor="White" Grid.Row="2">
<ListView x:Name="lvPeripheralSettings" ItemsSource="{Binding Settings}"
SelectionMode="None" ItemTapped="Setting_Tapped" RowHeight="40"
ios:ListView.SeparatorStyle="FullWidth">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="{Binding IsEnabled, UpdateSourceEventName=ValueChanged, Mode=TwoWay, Converter={StaticResource DebugConverter}}">
<Templates:SettingTemplate/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Frame>
</Grid>
Take note of the PeripheralDetailsTemplate:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="75"/>
</Grid.ColumnDefinitions>
<!--Icon-->
<Frame Padding="5">
<Images:VectorImage ResourceId="{Binding HardwareType, Converter={StaticResource hardwareTypeToSVGPathConverter}}"
WidthRequest="150" HeightRequest="150"
HorizontalOptions="Center" VerticalOptions="Center"/>
</Frame>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Frame>
<Label Text="{Binding Name, FallbackValue=Peripheral Name}" FontSize="Medium" FontAttributes="Bold" HorizontalOptions="Start" VerticalOptions="Center"/>
</Frame>
<Frame Grid.Row="1">
<Label Text="{Binding HardwareType, FallbackValue=Peripheral Type}" FontSize="Micro" HorizontalOptions="Start" VerticalOptions="Center"/>
</Frame>
<Frame Grid.Row="2">
<Label Text="{Binding MacAddress, StringFormat='Mac: {0}', FallbackValue=000111222333}" FontSize="Micro" HorizontalOptions="Start" VerticalOptions="Center"/>
</Frame>
<Frame Grid.Row="3">
<Label Text="{Binding FirmwareRevision, StringFormat='Firmware: {0}', FallbackValue=01AB}" FontSize="Micro" HorizontalOptions="Start" VerticalOptions="Center"/>
</Frame>
</Grid>
<Frame Grid.Column="2">
<Label Text="Conn" HorizontalOptions="Center" VerticalOptions="Center" TextColor="{Binding IsConnected, Converter={StaticResource BoolToColorConverter}, FallbackValue={StaticResource LynkdBlue}}"/>
</Frame>
</Grid>
It is bound to EXACTLY THE SAME PROPERTY AS THE ViewCell!!! and it works fine. I'm looking for the difference and haven't found it yet. I just know that I have properly set the bindingcontext because it databinds once and I know I'm properly firing NotifyPropertyChanged because the other template which is bound to the same property updates as I would expect. I have attached a debugconverter and it is never run. I think it's interesting that in both situations I was working with a ViewCell. These are two separate code bases with the same issue. :/
Simple answer; I was binding to a read-only property in my model that was based off of another property. When the other property changed I was not firing OnPropertyChanged for the read-only property.
I have read that the Editor control doesn't have a placeholder, so I have been trying to do a workaround without success. I know that the Entry control has a Placeholder, but I need multiline field as I am going to use it as a field where users can write a comment, and not just a single line.
This is my approach:
I have tried to put an Editor and a Label into a Grid control, with the Label being on top of the Editor. The Editor's InputTransparent flag is set to true. Then I just toggle the IsVisible property of the label depending on the whether the Editor has text or not. However, the problem is that I am using the MVVM pattern, so I don't know how to control the TextChanged event in the ViewModel. I have also tried to code behind, but the name of the Label can't be found.
This is my XAML code - Have only posted the relevant code:
<Grid Grid.Row="1">
<Editor Text="{Binding UserComment, Mode=TwoWay}" TextChanged="EditorTextChanged" HorizontalOptions="FillAndExpand"/>
<Label x:Name="PlaceholderLabel" Text="Write a comment" InputTransparent="True" HorizontalOptions="StartAndExpand"/>
</Grid>
For now, in the code-behind, I only have the EditorTextChanged event, which works fine, but it can't find PlaceholderLabel. I have binded the whole View to my ViewModel, is that the reason? How would approach it, if you had to follow the MVVM pattern?
It's worth mentioning that I have tried this approach. However, it didn't work as expected. The Placeholder would only appear when I clicked on the Editor and then unclicked it. It should appear in the beginning as on Facebook.
EDIT
This is the whole XAML code:
<ContentPage.Resources>
<ResourceDictionary>
<local:TeamAlignmentConverter x:Key="teamConverter"/>
<local:ImageAlignmentConverter x:Key="imageConverter"/>
<local:BooleanReverser x:Key="booleanReverser"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" Spacing="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="180"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.BindingContext>
<viewModel:MatchPageVM/>
</Grid.BindingContext>
<Grid BackgroundColor="White" RowSpacing="0" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*" />
<RowDefinition Height="2*" />
<RowDefinition Height="0.8*" />
</Grid.RowDefinitions>
<Label Text="{Binding Teams}" FontSize="26" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" VerticalOptions="CenterAndExpand" Grid.Row="0"/>
<Image Source="notificationsbell.png" Margin="25,10,0,0" HorizontalOptions="StartAndExpand" VerticalOptions="CenterAndExpand" Grid.Row="1"/>
<Label Text="{Binding Score}" FontSize="80" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" VerticalOptions="CenterAndExpand" VerticalTextAlignment="Center" Grid.Row="1"/>
<Label Text="Live" FontSize="24" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" VerticalOptions="CenterAndExpand" VerticalTextAlignment="Center" Grid.Row="2"/>
</Grid>
</Grid>
<Grid RowSpacing="0" VerticalOptions="FillAndExpand">
<Grid.BindingContext>
<viewModel:MatchPageVM/>
</Grid.BindingContext>
<cv:CarouselView ItemsSource="{Binding CollectionList}">
<cv:CarouselView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<flv:FlowListView x:Name="flowListView" FlowColumnCount="1" Grid.Row="0"
SeparatorVisibility="None" HasUnevenRows="True" IsVisible="{Binding ListSwitch}"
FlowItemsSource="{Binding CollectionList}" BackgroundColor="White" >
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Grid ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.005*"/>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding PlayerName}" Grid.Column="{Binding Team}" VerticalOptions="Center"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="{Binding Team, Converter={StaticResource teamConverter}}"/>
<Image Source="{Binding ImageURL}" HorizontalOptions="Center" Grid.Column="{Binding Team, Converter={StaticResource imageConverter}}" Aspect="AspectFill" VerticalOptions="Center"/>
<BoxView BackgroundColor="Black" Grid.Column="2" HeightRequest="20" VerticalOptions="Center"/>
</Grid>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<flv:FlowListView x:Name="flowListView2" FlowColumnCount="1" BackgroundColor="White"
HasUnevenRows="True" HeightRequest="180" IsVisible="{Binding ListSwitch, Converter={StaticResource booleanReverser}}" Grid.Row="0"
FlowItemsSource="{Binding CollectionList}" SeparatorVisibility="Default" SeparatorColor="Black">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Grid RowSpacing="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2.5*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding ImageURL}" HorizontalOptions="Start"
Grid.Row="0" Grid.Column="0" Aspect="AspectFit" Margin="0,10,0,0" VerticalOptions="Start"/>
<Label Text="{Binding UserName}" Grid.Row="0" Grid.Column="1" VerticalOptions="Start"
HorizontalOptions="Start" FontSize="Medium" FontAttributes="Bold" HorizontalTextAlignment="Start" Margin="0,10,0,5" />
<Label Text="{Binding UserComment}" Grid.Row="1" Grid.Column="0" VerticalOptions="Start"
HorizontalOptions="StartAndExpand" FontSize="Medium" HorizontalTextAlignment="Start" Grid.ColumnSpan="2" Margin="0,0,0,10" />
</Grid>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
<Grid Grid.Row="1" IsVisible="{Binding ListSwitch, Converter={StaticResource booleanReverser}}">
<Editor Text="{Binding UserComment, Mode=TwoWay}"
HorizontalOptions="FillAndExpand">
<Editor.BindingContext>
<viewModel:MatchPageVM/>
</Editor.BindingContext>
</Editor>
<Label Text="Skriv en kommentar"
HorizontalOptions="StartAndExpand" IsVisible="{Binding LabelIsVisible}" InputTransparent="True">
<Label.BindingContext>
<viewModel:MatchPageVM/>
</Label.BindingContext>
</Label>
</Grid>
</Grid>
</Grid>
</DataTemplate>
</cv:CarouselView.ItemTemplate>
</cv:CarouselView>
</Grid>
</StackLayout>
</ContentPage.Content>
Here is your own mentioned approach that does work:
<AbsoluteLayout
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<Editor
Text="{Binding Address, Mode=TwoWay}"
HorizontalOptions="FillAndExpand"
AbsoluteLayout.LayoutFlags="PositionProportional, WidthProportional"
AbsoluteLayout.LayoutBounds="0,0,1.01,100">
</Editor>
<Label Text="MyPlaceHolder" IsVisible="{Binding IsAddrerssPlaceHolderVisible}" HorizontalTextAlignment="Center"
AbsoluteLayout.LayoutBounds="0.5,0.5, 1, 0.5" VerticalTextAlignment="Center"
AbsoluteLayout.LayoutFlags="All" InputTransparent="True"/>
</AbsoluteLayout>
Here is Viewmodel part:
public bool IsAddrerssPlaceHolderVisible
{
get => _isAddrerssPlaceHolderVisible;
set
{
_isAddrerssPlaceHolderVisible= value;
RaisePropertyChanged();
}
}
public string Address
{
get => _address;
set
{
_address = value;
if (value.Length > 0)
{
IsAddrerssPlaceHolderVisible= false;
}
else
{
IsAddrerssPlaceHolderVisible= true;
}
RaisePropertyChanged();
}
}
It doesn't even need anything else which I previously mentioned by mistake! It works as simple as that :D
What you are looking for is a Custom Renderer, you see since Xamarin.Forms is just another level from the native elements, you can "access" the native elements using a CustomRenderer:
https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/
You can create the base Custom Editor:
using Xamarin.Forms;
namespace EditorWithPlaceholder
{
public class PlaceholderEditor : Editor
{
public static BindableProperty PlaceholderProperty
= BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(PlaceholderEditor));
public static BindableProperty PlaceholderColorProperty
= BindableProperty.Create(nameof(PlaceholderColor), typeof(Color), typeof(PlaceholderEditor), Color.Gray);
public string Placeholder
{
get { return (string) GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public Color PlaceholderColor
{
get { return (Color) GetValue(PlaceholderColorProperty); }
set { SetValue(PlaceholderColorProperty, value); }
}
}
}
And the Renderers on each platform:
Android:
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(PlaceholderEditor), typeof(PlaceholderEditorRenderer))]
namespace EditorWithPlaceholder.Droid.Renderers
{
public class PlacehoderEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
var element = (PlaceholderEditor) Element;
Control.Hint = element.Placeholder;
Control.SetHintTextColor(element.PlaceholderColor.ToAndroid());
}
}
}
iOS:
using System;
using Cirrious.FluentLayouts.Touch;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(PlaceholderEditor), typeof(PlaceholderEditorRenderer))]
namespace EditorWithPlaceholder.iOS.Renderers
{
public class PlaceholderEditorRenderer : EditorRenderer
{
private UILabel _placeholderLabel;
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
CreatePlaceholderLabel((PlaceholderEditor) Element, Control);
Control.Ended += OnEnded;
Control.TextChanged += OnChanged;
}
private void CreatePlaceholderLabel(PlaceholderEditor element, UITextView parent)
{
_placeholderLabel = new UILabel
{
Text = element.Placeholder,
TextColor = element.PlaceholderColor.ToUIColor(),
BackgroundColor = UIColor.Clear,
Font = UIFont.FromName(element.FontFamily, (nfloat)element.FontSize)
};
_placeholderLabel.SizeToFit();
parent.AddSubview(_placeholderLabel);
parent.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
parent.AddConstraints(
_placeholderLabel.AtLeftOf(parent, 7),
_placeholderLabel.WithSameCenterY(parent)
);
parent.LayoutIfNeeded();
_placeholderLabel.Hidden = parent.HasText;
}
private void OnEnded(object sender, EventArgs args)
{
if (!((UITextView) sender).HasText && _placeholderLabel != null)
_placeholderLabel.Hidden = false;
}
private void OnChanged(object sender, EventArgs args)
{
if (_placeholderLabel != null)
_placeholderLabel.Hidden = ((UITextView) sender).HasText;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Control.Ended -= OnEnded;
Control.Changed -= OnChanged;
_placeholderLabel?.Dispose();
_placeholderLabel = null;
}
base.Dispose(disposing);
}
}
}
I hope this information helps.
For reference:
https://solidbrain.com/2017/07/10/placeholder-text-in-xamarin-forms-editor/
I have a weird exception where the Compiler tells me that the Specified cast is not valid even though what im doing is very Simple.
I have a ListView binded to a ObservableCollection. And inside my Listview is a ViewCell with a Grid. Xamarin.Forms Version 2.3.2.127
<ListView ItemsSource="{Binding GiftCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding GiftName}"/>
<Label Grid.Row="1" Grid.Column="0" Text="{Binding GiftDescription}"/>
<Image Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Source="{Binding GiftImage}"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Model:
public class GiftModel {
public string GiftName { get; set; }
public string GiftDescription { get; set; }
public ImageSource GiftImage { get; set; }
}
ViewModel:
public class NextRoundViewModel : BaseViewModel {
public NextRoundViewModel(ApplicationModel applicationModel) {
ApplicationModel = applicationModel;
Initialize();
}
public ApplicationModel ApplicationModel { get; set; }
public ObservableCollection<GiftModel> GiftCollection { get; set; }
public string CurrentRound => "Runde 2";
private void Initialize() {
GiftCollection = new ObservableCollection<GiftModel> {
new GiftModel {
GiftName = "100 Punkte",
GiftDescription = "Test",
GiftImage = ImageSource.FromFile("Star.png"),
},
new GiftModel {
GiftName = "200 Punkte",
GiftDescription = "Test",
GiftImage = ImageSource.FromFile("Star.png"),
},
new GiftModel {
GiftName = "300 Punkte",
GiftDescription = "Test",
GiftImage = ImageSource.FromFile("Star.png"),
},
};
}
}
So ive tried everything but if i use for example a TextCell the Exception is gone.
System.InvalidCastException: Specified cast is not valid. It is just weird because i dont know where to look for the Bug.
I had this problem too, the issue was with the xaml. I had a <StackLayout> inside of my <DataTemplate>, you can remove your <Grid> and that should solve the problem.
Did you know that you could replace the <Grid> with an <ImageCell>:
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell
Text="{Binding GiftName}"
Detail="{Binding GiftDescription}"
ImageSource="{Binding GiftImage}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
You can place inside with .
Something like this
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
Remove the <ViewCell> from the DataTemplate. This should resolve the error. <Grid> will work in <CollectionView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
:
:
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
The following code gives the same exception, may help someone:
using ListView or CollectionView but in the ItemTemplate I use BindableLayout.ItemTemplate instead CollectionView.ItemTemplate
<CollectionView Grid.Row="0" ItemsSource="{Binding SummaryInfos}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" />
</CollectionView.ItemsLayout>
<!-- this code gives the same exception -->
<!-- must use CollectionView instead BindableLayout -->
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding SomeText}"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</CollectionView>