I'm trying to display some text -- or an image -- if there's no data for my ListView to display in my Xamarin Forms 5 app but I can't get it to work.
Here's what my ListView looks like:
<ListView
x:Name="CustomersList"
ItemsSource="{Binding Customers, TargetNullValue='No customers!'}"
BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell
Text="{Binding FullName}"
TextColor="{StaticResource PrimaryDark}"
Command="{Binding Source={x:Reference Name=CustomersList}, Path=BindingContext.CustomerSelectedCommand}"
CommandParameter="{Binding .}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The source for the ListView is set as follows in my ViewModel
ObservableRangeCollection<CustomerModel> customers;
public ObservableRangeCollection<CustomerModel> Customers
{
get => customers;
set
{
if (customers == value)
return;
customers = value;
OnPropertyChanged();
}
}
If my API call returns nothing, I do a Customers = null;
When I use TargetNullValue as shown above, I acutally get errors where I bind the TextCell to FullName. It shows:
'FullName' property not found on 'N', target property:
'Xamarin.Forms.TextCell.Text'
It then repeats this error message for every character in No Customers!.
I then tried using FallbackValue. This time I don't get an error but it simply doesn't show the "No Customers!" message.
How do I handle a ListView having no data to show? At the very least, I'd like to show some simple text message but it would be even better to show something nicer, maybe an image.
If you are ok to switch from ListView to CollectionView, you can use the built-in CollectionView.EmptyView:
<CollectionView ItemsSource="{Binding Customers}"
EmptyView="No customers!" ... />
With the same EmptyView you can define whatever view:
<CollectionView>
<CollectionView.EmptyView>
<ContentView>
<image ... />
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
Docs: CollectionView EmptyView
Related
Is it possible to display a list of ImageSources in XAML with Xamarin like you do with an List of Strings? In string you would do:
<ListView x:Name="newsList" ItemsSource="{Binding newsList}" ItemTapped="listView_ItemTapped" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshNewsCommand}" IsRefreshing="{Binding IsCurrentlyRefreshing}" />
However if you do this with an list of ImageSources it will not work.
Does anyone know an appropriate way to load this list of images into my few? Preferably so that i can load them next to the items of my NewsList?
Yes, you can define an "ItemTemplate" on the ListView (xaml / c#). There are 2 predefined ViewCells that you can assign to the ItemTemplate. One of them is "ImageCell". But ImageCell displays an Image + Text.
Also you might be interested in this:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/customizing-cell-appearance/
You will have to create a custom ViewCell.
Something like this (pseudo code):
ViewCell:
<ViewCell>
<ViewCell.View>
<Image Source="{Binding CoolImageSource}" />
</ViewCell.View>
</ViewCell>
//Your ListView, bring the namespace in, to reference your viewcell
//You can also define the item directly inside of the DataTemplate
//But keep your code clean and hold the viewcells separately
<ListView xmlns:vc=YourNameSpace.ViewCellsFolder>
<ListView.ItemTemplate>
<DataTemplate>
<vc:MyCoolViewCell />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
//And finally assign a List of such model:
public class ImageItem
{
public ImageSource CoolImageSource { get; set; }
}
I have the following XAML code, where I have a TextBox for user input (phone number) and GridView for user contacts.
<GridView x:Name="ContactsCollection" Grid.Row="1" Style="{StaticResource DefaultGridView}" IsItemClickEnabled="True" ItemsSource="{Binding UserContacts}" ItemTemplate="{StaticResource HorizontalGridViewDataTemplate}" ItemContainerStyle="{StaticResource DefaultGridViewItemContainer}">
<i:Interaction.Behaviors>
<c:EventTriggerBehavior EventName="SelectionChanged">
<c:InvokeCommandAction Command="{Binding SelectContactCommand}" CommandParameter="{Binding SelectedItem, ElementName=ContactsCollection}"/>
</c:EventTriggerBehavior>
</i:Interaction.Behaviors>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
And my TextBox that is binded to the Recipient in the ViewModel.
<TextBox x:Uid="Recipient" Text="{Binding Recipient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" PreventKeyboardDisplayOnProgrammaticFocus="True" toolkitControls:TextBoxMask.Mask="7(999)999-99-99" toolkitControls:TextBoxMask.PlaceHolder="X" InputScope="NumericPin" HeaderTemplate="{StaticResource UiControlHeaderDataTemplate}" Style="{StaticResource DefaultTextBoxStyle}"/>
Here goes code from the ViewModel.
public string Recipient
{
get { return _recipient; }
set
{
if (value != _recipient)
{
_recipient = value;
RaisePropertyChanged(nameof(Recipient));
ContinueTransferCommand.RaiseCanExecuteChanged();
}
}
}
Now, when I click any contact from the GridView the following functions is called, that assign contact's phone number to the Recipient property.
private void SelectContactCommandAction(object contact)
{
Contact c = contact as Contact;
if (c.Phones?.Count > 0)
Recipient = FormatDataHelper.FormatPhoneNumber(c.Phones.FirstOrDefault().Number);
}
When I click any contact for the FIRST time, the Recipient property is updated, and the UI is updated too. But, if I select other contact, in the debug mode I can see, that Recipient property is updated and saved new value, but the UI (TextBox) is not updated at all. The only way it works, I have to delete any character from the TextBox and select a contact again, in that case UI value will be updated again.
Can somebody please explain this?
I work an a Windows 8 application which shows a GridView on one page. When ever the user selects an item of this grid and clicks on a button, the next page is loaded with detail information of the selected item.
I am using MVVM for this and have a DelegateCommand from Prims:
public DelegateCommand<Route> ShowRouteDetailsCommand { get; private set; }
This command is initialized inside the constructor:
this.ShowRouteDetailsCommand = new DelegateCommand<Route>(this.ShowRouteDetails);
The navigation is done by Prisms navigation service:
private void ShowRouteDetails(Route route)
{
this.NavigationService.Navigate(PageNames.RouteDetails, route.Id);
}
The routes are shown inside a GridView:
<GridView x:Name="RouteGrid"
ItemsSource="{Binding Routes}"
SelectionMode="Single">
<GridView.ItemTemplate>
<DataTemplate> ...
The command is currently added inside the app bar (just for testing):
<AppBarButton Command="{Binding ShowRouteDetailsCommand}"
CommandParameter="{Binding SelectedValue,
ElementName=RouteGrid, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Icon="Forward" />
My problem is, that the parameter of ShowRouteDetails is allways empty. It doesn't even matter if I try GridViews SelectedValue or SelectedItem property.
I know that I could easily add a SelectedRoute property, bind the SelectedItem to it and use it in ShowRouteDetails but this seems dirty to me.
Why don't you just create a var in your viewModel and bind it to the SelectedItem of the gridView? In this way, when you run the command, you have only to read the value of that var.
<GridView x:Name="RouteGrid" ItemsSource="{Binding Routes}"
SelectionMode="Single" SelectedItem="{Binding myVar}">
<GridView.ItemTemplate>
<DataTemplate>
I'm trying to figure out the RepeaterView in Xamarin Forms Labs.
THE BACKGROUND
I have a simple entity called Entry that has property called Notes
class Entry
{
public string Notes { get; set; }
}
In the common PCL, I've inherited from the RepeaterView to make it non-generic.
class EntryRepeater : Xamarin.Forms.Labs.Controls.RepeaterView<MyKids.Core.Entities.Entry>
{
}
And the XAML of the page where I'm trying to use the repeater looks like this
<rep:EntryRepeater ItemsSource="{Binding Entries}">
<rep:EntryRepeater.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout>
<Label Text="{Binding Notes}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</rep:EntryRepeater.ItemTemplate>
</rep:EntryRepeater >
where Entries is an ObservableCollection.
THE ISSUE
The problem is that the binding in the DataTemplate seems to point to the main model and not to one item in the collection. BUT it is repeated the correct number of times.
[0:] Binding: 'Notes' property not found on 'MyApp.ViewModels.EntryListViewModel', target property: 'Xamarin.Forms.Label.Text'
To circle back to this question, this was actually an error in earlier versions of Xlabs. It's now fixed and the code above does work.
I have a form with some validations set in entity metadata class. and then binding entity instance to UI by VM. Something as below:
Xaml like:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
</StackPanel>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,Mode=TwoWay,
DisplayMemberPath="MyName"
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True,UpdateSourceTrigger=Explicit}" />
</Grid>
Code-behind like:
public MyForm()
{
InitializeComponent();
this.xTest.BindingValidationError +=new EventHandler<ValidationErrorEventArgs>((s,e)=>{
BindingExpression be = this.xTest.GetBindingExpression(ComboBox.SelectedItemProperty);
be.UpdateSource();
if (e.Action == ValidationErrorEventAction.Added)
((ComboBox)s).Foreground = new SolidColorBrush(Colors.Red);
});
}
Metadata like:
[Required]
public string Name { get; set; }
[RequiredAttribute]
public int MyItemID { get; set; }
But when running the app, I got nothing display in valudationSummary.
For CombBox, even there is error, looks like BindingValidationError event is never fired.
How to resolve it?
Why are you using an Explicit UpdateSourceTrigger?
Silverlight validation happens inside the binding framework, when the binding is updating the source object. The way you have this, there won't be a binding validation error because you never tell the binding to update the source object. Well, actually you do, but it happens inside the validation error event handler. You've written chicken-and-egg code.
Remove your UpdateSourceTrigger on your binding or set it to Default.
Remove the explicit call to BindingExpression.UpdateSource.
Remove setting the ComboBox foreground to red - you are using NotifyOnValidationError=True, which eliminates any need to manually color the control.
Remove the DisplayMemberPath from the binding
So your XAML:
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top">
<input:ValidationSummary />
<ComboBox x:Name="xTest" ItemsSource="{Binding MyList}"
SelectedItem="{Binding MyItem,
Mode=TwoWay,
ValidatesOnDataErrors=True,
ValidatesOnNotifyDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}" />
</StackPanel>
</Grid>
And your code:
public MyForm()
{
InitializeComponent();
// you don't need anything here to have the validations work
}