How to display a list of class objects in a Collection View? - xaml

I want to display a list (observable collection) of BleDevice type in my view. I'm using mvvm pattern in .net maui (.net 7).
Model:
public class BleDevice
{
public BleDevice(){}
public BleDevice(string name, string mac)
{
Name = name;
MacAddress = mac;
}
public string Name { get; set; }
public string MacAddress { get; set; }
}
ViewModel:
public partial class MainViewModel: ObservableObject
{
public MainViewModel()
{
devices = new ObservableCollection<BleDevice>();
devices.Add(new BleDevice("Mystronics Winder", "00:00:00:00:00"));
devices.Add(new BleDevice("Living Room TV", "25:e7:aa:05:84"));
}
[ObservableProperty]
ObservableCollection<BleDevice> devices;
}
View(xaml): (Edited)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp2.MainPage"
xmlns:viewmodel="clr-namespace:MauiApp2.ViewModel"
xmlns:model="clr-namespace:MauiApp2.Model"
x:DataType="viewmodel:MainViewModel">
<VerticalStackLayout>
<CollectionView ItemsSource="{Binding Devices}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{model:BleDevice}">
<Grid>
<Label Text="{Binding Name}"/>
<Label Text="{Binding MacAddress}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
Error:
XFC0045 Binding: Property "Name" not found on "MauiApp2.ViewModel.MainViewModel". MauiApp2 \source\repos\MauiApp2\MauiApp2\View\MainPage.xaml
Why it does recognize the "{Binding Devices}" but not "{Binding Name}" and "{Binding MacAddress}"?

ViewModel Remove the [ObservableProperty] attribute and change to this:
public partial class MainViewModel : ObservableObject
{
public MainViewModel()
{
Devices = new ObservableCollection<BleDevice>();
Devices.Add(new BleDevice("Mystronics Winder", "00:00:00:00:00"));
Devices.Add(new BleDevice("Living Room TV", "25:e7:aa:05:84"));
}
public ObservableCollection<BleDevice> Devices { get; set; }
}
View(xaml) Remove the x:DataType="xxx":
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp2.MainPage"
xmlns:viewmodel="clr-namespace:MauiApp2.ViewModel"
xmlns:model="clr-namespace:MauiApp2.Model"
>
<VerticalStackLayout>
<CollectionView ItemsSource="{Binding Devices}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding MacAddress}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>

Related

Can't create Bindable Property for custom ContentView

I have created a ContentView with a single Label (I plan to add more later).
PageHeadingView.xaml
<ContentView.Content>
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
</ContentView.Content>
I defined a BindableProperty in my code behind. I also set the BindingContext of my view to be itself.
PageHeadingView.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PageHeadingView : ContentView
{
public PageHeadingView()
{
InitializeComponent();
this.BindingContext = this;
}
public static readonly BindableProperty HeadingTextProperty = BindableProperty.Create(nameof(HeadingText), typeof(string), typeof(PageHeadingView), default(string));
public string HeadingText { get => (string)GetValue( HeadingTextProperty); set => SetValue(HeadingTextProperty, value); }
}
I then added the View to my ContentPage. I also added a test Label inside a StackLayout to ensure my bindings were working correctly.
HomePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MyProject.Views"
x:Class="MyProject.HomePage">
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<views:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And set my BindingContext in code.
HomePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
//ViewModel contains a string property named: Name;
this.BindingContext = new ViewModel();
}
}
When I run my code, my PageHeadingView does not display any text. I can see the red background color, so I know the control has been added to the Page correctly. The test Label I placed in StackLayout also works correctly, and I am able to see the bound value.
What do I need to do to make my CustomView display Bindable content?
From your code, you may have some problems when using bindableproperty bidning, I create simple sample that you can take a look:
PageHeadingView.xaml:
<ContentView.Content>
<StackLayout BackgroundColor="Red" Orientation="Vertical">
<Label x:Name="HeadingLabel" />
</StackLayout>
</ContentView.Content>
PageHeadingView.cs, you can update HeadingText value by HeadingTextPropertyChanged, then display HeadingLabel.
public partial class PageHeadingView : ContentView
{
public static BindableProperty HeadingTextProperty= BindableProperty.Create(
propertyName: "HeadingText",
returnType: typeof(string),
declaringType: typeof(PageHeadingView),
defaultValue: "",
defaultBindingMode: BindingMode.OneWay,
propertyChanged: HeadingTextPropertyChanged);
public string HeadingText
{
get { return base.GetValue(HeadingTextProperty).ToString(); }
set { base.SetValue(HeadingTextProperty, value); }
}
private static void HeadingTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (PageHeadingView)bindable;
control.HeadingLabel.Text = newValue.ToString();
}
public PageHeadingView()
{
InitializeComponent();
}
}
<StackLayout>
<customcontrol:PageHeadingView HeadingText="{Binding Name}" />
<Label Text="{Binding Name}" />
</StackLayout>
You are binding the wrong element in PageHeadingView.
Option 1:
Add "Content" in PageHeadingView.xaml.cs
this.Content.BindingContext = this;
Option 2:
Remove "Content" in PageHeadingView.xaml
<!--<ContentView.Content>-->
<StackLayout Orientation="Vertical" BackgroundColor="Red">
<Label x:Name="HeadingLabel" Text="{Binding HeadingText}" />
</StackLayout>
<!--</ContentView.Content>-->
Edited as quoted the wrong class previously.

Xamarin: Set multiple viewmodels by reference from code behind

I need to set two ViewModels from the code behind in the xaml code. Or if there is better way doing would be great to.
When I do it like this way the application crashes. When I set ProductDetailViewModel in the code behind (BindingContext = ViewModel) everything works fine.
update
It's not an good idea to pass viewModels as parameters.
I have now one class "ViewModelLocator" which contains all the ViewModels as static properties. Use Google for more info. This way things are way easier.
example
ViewModelLocator
public static class ViewModelLocator
{
public static AddProductViewModel AddProductViewModel { get; set; } = new AddProductViewModel(App.ProductDataStore, App.NavigationService);
}
end update
update 2
As #Waescher stated, it's better to use FreshMvvm. The static approach is simple and fast but not good for slow devices or larger apps. Thanks.
end update 2
**Xamarin.Forms.Xaml.XamlParseException:** 'Position 9:10. Can not find the object referenced by `ProductDetailViewModel`'
Since I can't set the ViewModels directly in the xaml I need to do it by reference from code behind.
See < *** First ViewModel *** > and < *** Second ViewModel *** > in the xaml code.
<?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:BoerPlaza.Controls"
xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
x:Class="BoerPlaza.Views.Product.ProductCustomerPictures">
<ContentPage.BindingContext>
<x:Reference Name="ProductDetailViewModel" /><!-- *** First ViewModel ***!-->
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<!-- Total image count -->
<Label Text="{Binding Product.UserImages.Total}"
Style="{StaticResource H2}" />
<!-- Title -->
<Label Text="{Binding Product.Title}"
Style="{StaticResource H1}" />
<!-- reviews -->
<StackLayout Orientation="Horizontal">
<controls:StarDisplayTemplateView x:Name="customRattingBar"
SelectedStarValue="{Binding Product.RatingTotal}" />
<Label Text="{Binding Product.RatingAmount, StringFormat='{0} reviews | '}" />
<Label Text="Schrijf een review" />
</StackLayout>
<Label Text="{Binding Product.Title, StringFormat='Heb je een productfoto van {0} die je wilt delen? '}" />
<Button Text="Foto's toevoegen"
Command="{Binding SelectImagesCommand}"
BackgroundColor="{StaticResource neutral-color}"
BorderColor="{StaticResource alt-color}"
BorderWidth="1"
TextColor="{StaticResource primary-color}"
HorizontalOptions="Start"
HeightRequest="40"
FontSize="12" />
<!-- hr -->
<BoxView Style="{StaticResource separator}" />
<flv:FlowListView FlowColumnCount="3"
x:Name="listItems"
FlowItemsSource="{Binding Media}"
SeparatorVisibility="None"
HasUnevenRows="false"
RowHeight="100"
HeightRequest="0">
<flv:FlowListView.BindingContext>
<x:Reference Name="MultiMediaPickerViewModel" /> <!-- *** Second ViewModel ***!-->
</flv:FlowListView.BindingContext>
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Grid>
<ffimageloading:CachedImage DownsampleToViewSize="true"
HeightRequest="100"
Source="{Binding PreviewPath}"
Aspect="AspectFill"
HorizontalOptions="FillAndExpand">
</ffimageloading:CachedImage>
<Image Source="play"
IsVisible="false"
HorizontalOptions="End"
VerticalOptions="End">
<Image.Triggers>
<DataTrigger TargetType="Image"
Binding="{Binding Type}"
Value="Video">
<Setter Property="IsVisible"
Value="True" />
</DataTrigger>
</Image.Triggers>
</Image>
</Grid>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProductCustomerPictures : ContentPage
{
public ProductDetailViewModel ProductDetailViewModel
{
get { return _productDetailViewModel; }
set { _productDetailViewModel = value; }
}
public MultiMediaPickerViewModel MultiMediaPickerViewModel
{
get { return _multiMediaPickerViewModel; }
set { _multiMediaPickerViewModel = value; }
}
private ProductDetailViewModel _productDetailViewModel;
private MultiMediaPickerViewModel _multiMediaPickerViewModel;
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
ProductDetailViewModel = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
}
If I understood this correctly and if you want to keep the pattern to pass in the view model as constructor argument ...
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
ProductDetailViewModel = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
... then you can remove this completely ...
<ContentPage.BindingContext>
...
</ContentPage.BindingContext>
... and this property ...
public ProductDetailViewModel ProductDetailViewModel
{
get { return _productDetailViewModel; }
set { _productDetailViewModel = value; }
}
Instead, just set the BindingContext directly in the constructor.
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel; // <-- here
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}
Now, each and every control in the XAML is binding to the ProductDetailViewModel.
But you still have the FlowListView which should bind to the MultiMediaPickerViewModel. Instead of setting its binding context directly in XAML, it is common to use the binding with a reference, but first you have to give the whole page a name with which we can refer in the binding:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
...
x:Name="thisPage" <--- here
x:Class="BoerPlaza.Views.Product.ProductCustomerPictures">
Now, you can use the name as reference in the binding expression:
<flv:FlowListView FlowColumnCount="3"
x:Name="listItems"
FlowItemsSource="{Binding Source={x:Reference thisPage}, Path=MultiMediaPickerViewModel.Media}"
SeparatorVisibility="None"
HasUnevenRows="false"
RowHeight="100"
HeightRequest="0">
"{Binding Source={x:Reference thisPage}, Path=MultiMediaPickerViewModel.Media}" uses the page itself (by name thisPage) and binds to the property Media of the property MultiMediaPickerViewModel of the page.
With that, you can safely remove this code as well:
<flv:FlowListView.BindingContext>
...
</flv:FlowListView.BindingContext>
By the way, you can condense the properties in the code behind:
public MultiMediaPickerViewModel MultiMediaPickerViewModel { get; private set; }
public ProductCustomerPictures(ProductDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
MultiMediaPickerViewModel = new MultiMediaPickerViewModel(MultiMediaPickerServiceStaticVariableHolder.MultiMediaPickerService);
}

ContentView binding doesn't work properly

I have a resuable control like this to display a loading spinner:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView 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"
x:Class="Framework.Controls.Loading" x:Name="LoadingControl" IsVisible="{Binding LoadingIndicator}"
HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<ContentView.Content>
<ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" Color="DarkBlue"
IsVisible="{Binding LoadingIndicator}"
IsRunning="{Binding LoadingIndicator}">
</ActivityIndicator>
</ContentView.Content>
</ContentView>
I am trying to consume it on a page like this:
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
However, the loading spinner fails to appear on-screen.
When I set the LoadingIndicator property to true, it appears just fine:
<controls:Loading LoadingIndicator="true"></controls:Loading>
My 'IsLoading' binding is definitely working properly, because if I place the following code directly in my XAML page it also works fine:
<ActivityIndicator HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
Color="DarkBlue" IsVisible="{Binding IsLoading}" IsRunning="{Binding IsLoading}">
</ActivityIndicator>
Therefore, what is it about this that's wrong?
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
The 'IsLoading' property gets set on each of my pages from my view model. Here is a snippet from the view model:
public ICommand OnSave => new Command(async () =>
{
IsLoading = true;
await CreateItem();
IsLoading = false;
});
The code-behind for my control looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
public static readonly BindableProperty LoadingIndicatorProperty =
BindableProperty.Create(
propertyName: nameof(LoadingIndicator), typeof(bool),
typeof(Loading), default(string), BindingMode.OneWayToSource);
public bool LoadingIndicator
{
get => (bool)GetValue(LoadingIndicatorProperty);
set => SetValue(LoadingIndicatorProperty, value);
}
public Loading()
{
InitializeComponent();
BindingContext = this;
}
}
Do I need to write code to handle the change if the IsLoading binding gets updated?
This is the full code for the page where I am using the control:
ItemCreatePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage Title="{Binding PageTitle}"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:userControls="clr-namespace:Framework.UserControls"
xmlns:converters="clr-namespace:Framework.ValueConverters"
xmlns:controls="clr-namespace:Framework.Controls;assembly=Framework.Android"
x:Class="Framework.Views.Item.ItemCreatePage">
<ContentPage.Resources>
<ResourceDictionary>
<converters:DoubleConverter x:Key="DoubleConverter"></converters:DoubleConverter>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<ScrollView>
<Grid RowSpacing="0" VerticalOptions="Start">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackLayout Grid.Row="1" Padding="20,20,20,0" VerticalOptions="Start">
<Label Text="Category" />
<userControls:BindablePicker
ItemsSource="{Binding Categories}"
SelectedItem="{Binding Path=Item.CategoryName, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.CategoryId, Mode=TwoWay}"/>
<Label Text="Description" />
<Editor Text="{Binding Item.Description}" HeightRequest="100"/>
<Label Text="Area"/>
<Entry Text="{Binding Item.LineNumber}"/>
<Label Text="Identifier"/>
<Entry Text="{Binding Item.Identifier}"/>
<Label Text="Code"/>
<Entry Text="{Binding Item.Code}"/>
<Label Text="Priority" />
<userControls:BindablePicker
ItemsSource="{Binding Priorities}"
SelectedItem="{Binding Path=Item.ItemPriority, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.ItemPriorityCode, Mode=TwoWay}"/>
<Label Text="Owner" />
<userControls:BindablePicker
ItemsSource="{Binding Users}"
SelectedItem="{Binding Path=Item.OwnerName, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.OwnerId, Mode=TwoWay}"/>
<Label Text="Due Date" />
<DatePicker Date="{Binding Item.DateDue}" />
<Label Text="Date Identified" />
<DatePicker Date="{Binding Item.DateIdentified}" />
<Label Text="Status" />
<userControls:BindablePicker
ItemsSource="{Binding Statuses}"
SelectedItem="{Binding Path=Item.Status, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Item.StatusCode, Mode=TwoWay}"/>
<Label Text="Comment" />
<Editor Text="{Binding Item.Comment}" HeightRequest="100"/>
<Label Text="IOM" />
<Entry Text="{Binding Item.OutcomeMeasurementInitial, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="FOM" />
<Entry Text="{Binding Item.OutcomeMeasurementFinal, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="Longitude" />
<Entry Text="{Binding Item.Longitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Label Text="Latitude" />
<Entry Text="{Binding Item.Latitude, Mode=TwoWay, Converter={StaticResource DoubleConverter}}" Keyboard="Numeric" />
<Button Margin="0,20,0,20" Command="{Binding OnSave}" BackgroundColor="{StaticResource Primary}"
BorderRadius="2" Text="Save" VerticalOptions="End" TextColor="White" ></Button>
</StackLayout>
</Grid>
</ScrollView>
<controls:Loading LoadingIndicator="{Binding IsLoading}"></controls:Loading>
</Grid>
</ContentPage.Content>
</ContentPage>
ItemCreatePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ItemCreatePage : ContentPage
{
public ItemCreatePage ()
{
InitializeComponent ();
}
protected override async void OnAppearing()
{
var vm = BindingContext as ItemCreateViewModel;
vm.Item = new Data.Entities.Item();
await vm?.GetDeviceLocation();
base.OnAppearing();
}
}
The view model code:
public class ItemCreateViewModel : FormViewModel<Data.Entities.Item>
{
public async Task GetDeviceLocation()
{
this.Item = await this.Item.AddDeviceLocation();
OnPropertyChanged(nameof(this.Item));
}
public ILookupService LookupService { get; set; }
public IItemService ItemService { get; set; }
#region selectLists
public List<EnumListItem<ItemPriority>> Priorities => EnumExtensions.ToEnumList<ItemPriority>();
public List<EnumListItem<ItemStatus>> Statuses => EnumExtensions.ToEnumList<ItemStatus>();
public string PageTitle => $"{PageTitles.ItemCreate}{this.OfflineStatus}";
public List<Data.Entities.User> Users => UserService.GetAll(this.Offline);
public List<Data.Entities.Lookup> Categories => LookupService.GetLookups(this.Offline, LookupTypeCode.ItemCategories);
#endregion
public Data.Entities.Item Item { get; set; }
public ICommand OnSave => new Command(async () =>
{
await Loading(CreateItem);
});
private async Task CreateItem()
{
// ... Save logic is here
}
FormViewModel:
public class FormViewModel<T> : BaseViewModel
{
public IValidator<T> Validator => Resolve<IValidator<T>>();
public bool IsLoading { get; set; }
/// <summary>
/// Render a loading spinner whilst we process a request
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public async Task Loading(Func<Task> method)
{
IsLoading = true;
await method.Invoke();
IsLoading = false;
}
}
BaseViewModel:
public class BaseViewModel : IViewModelBase
{
public BaseViewModel()
{
if (this.GetCurrentUserToken() != null && !UserService.IsActive())
{
SettingService.ClearToken();
Bootstrapper.MasterDetailPage.IsPresented = false;
Application.Current.MainPage = new LoginPage();
}
}
public T Resolve<T>() => AutofacBootstrapper.Container.Resolve<T>();
public IUserService UserService => Resolve<IUserService>();
public INavigator Navigator => AutofacBootstrapper.Navigator;
public IDisplayAlertFactory DisplayAlert { get; set; }
public INavigation MasterNavigation => Bootstrapper.MasterDetailPage?.Detail?.Navigation;
public bool Offline => SettingService.GetSetting<bool>(CacheProperties.Offline);
public string OfflineStatus => this.Offline ? " - Offline" : string.Empty;
public Token GetCurrentUserToken() => SettingService.GetToken() ?? null;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
You don't need to set your custom control's BindingContext here:
public Loading()
{
InitializeComponent();
BindingContext = this;//It's wrong!
//because the custom control's BindingContext will
//automatically be set to the BindingContext of
//the page where it's used which is what we usually want.
}
Here is a way to achieve what you want:
Your Custom Control's XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView 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"
x:Class="Framework.Controls.Loading" x:Name="LoadingControl"
HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<ContentView.Content>
<ActivityIndicator x:Name="TheIndicator" HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand" Color="DarkBlue"/>
</ContentView.Content>
</ContentView>
And here is its code-behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Loading : ContentView
{
public static readonly BindableProperty LoadingIndicatorProperty =
BindableProperty.Create(propertyName:nameof(LoadingIndicator),
returnType: typeof(bool), declaringType: typeof(Loading), defaultValue: default(bool),
defaultBindingMode:BindingMode.Default, propertyChanged:LoadingBindingChanged);
private static void LoadingBindingChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var view = (Loading)(bindable);
view.SetLoadingVisibility((bool)newvalue);
}
public Loading()
{
InitializeComponent();
IsVisible = false; // we do this because by default a view' IsVisible property is true
}
public bool LoadingIndicator
{
get => (bool)GetValue(LoadingIndicatorProperty);
set => SetValue(LoadingIndicatorProperty, value);
}
public void SetLoadingVisibility(bool show)
{
IsVisible = show;
TheIndicator.IsVisible = show;
TheIndicator.IsRunning = show;
}
}
You are not invoking PropertyChanged event when you change IsLoading property. If you want UI to refresh you need to invoke this event for the chosen property.
Change implementation of IsLoading property to:
private bool _isLoading;
public bool IsLoading
{
get=> _isLoading;
set
{
_isLoading=value;
OnPropertyChanged(nameof(IsLoading));
}
}
and it should work

How to Bind Static Class property to UI component in XAML / Xamarin

In Xamarin application, I am not able to Bind the static property of the C# user defined static Class property (Colors.BackgroundColor) to XAML. I need to set the background of the color of grid by static value defined in static class.
But I am getting the error
Type UserInterfaceDefinitions not found in xmlns
on this XAML
BackgroundColor = "{Binding Source = {x:Static MyNamespace.Mobile:UserInterfaceDefinitions.Colors} }"
Static Class code
namespace MyNamespace.Mobile
{
public static class UserInterfaceDefinitions
{
public static class Colors
{
public static string BackgroundColor = "#DCECE";
}
}
}
XAML Code
<?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:buttons="clr-namespace:MyNamespace.Mobile.UI.Buttons"
xmlns:Status="clr-namespace:MyNamespace.Mobile.UI.StatusDetails"
x:Class="MyNamespace.Mobile.UI.TestAndDemoSelection">
<ContentPage.Content Margin="0,0,0,0" BackgroundColor="White">
<Grid x:Name="ChildGrid" Grid.Row="1" Grid.Column="0" ColumnSpacing="10" BackgroundColor="White" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- I am getting the error as Type UserInterfaceDefinitions not found in xmlns-->
<BoxView Grid.Column="0" BackgroundColor = "{Binding Source = {x:Static MyNamespace.Mobile:UserInterfaceDefinitions.Colors} }" />
</Grid>
</ContentPage.Content>
</ContentPage>
Code Behind .cs
using MyNamespace.Mobile.UI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyNamespace.Mobile.UI
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestAndDemoSelection : ContentPage
{
public TestAndDemoSelection()
{
InitializeComponent();
}
}
}
How to bind the static class property to XAML ?
I have got the resolutions. It was because of Nested Static class was not accessible inside the XAML the correct code as below.
user defined static class:
namespace MyNamespace.Mobile
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public static class UserInterfaceDefinitions
{
public static string BackgroundColor { get; } = "#DCECEC";
}
}
XAML file:
<?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:MyNamespace.Mobile"
x:Class="MyNamespace.Mobile.UI.TestAndDemoSelection">
<ContentPage.Content Margin="0,0,0,0" BackgroundColor="White">
<Grid x:Name="ChildGrid" Grid.Row="1" Grid.Column="0" ColumnSpacing="10" BackgroundColor="White" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<BoxView Grid.Column="0" BackgroundColor = "{Binding Source = {x:Static local:UserInterfaceDefinitions.BackgroundColor}}" />
</Grid>
</ContentPage.Content>
</ContentPage>
In order to bind on a Static Property:
1) Declare the namespace to import using xmlns
2) Use the xmlns accordingly in Source
=>
<?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:buttons="clr-namespace:MyNamespace.Mobile.UI.Buttons"
xmlns:Status="clr-namespace:MyNamespace.Mobile.UI.StatusDetails"
xmnlns:local="clr-namespace:MyNamespace.Mobile"
x:Class="MyNamespace.Mobile.UI.TestAndDemoSelection">
<ContentPage.Content Margin="0,0,0,0" BackgroundColor="White">
<Grid x:Name="ChildGrid" Grid.Row="1" Grid.Column="0" ColumnSpacing="10" BackgroundColor="White" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<BoxView Grid.Column="0" BackgroundColor = "{x:Static local:UserInterfaceDefinitions.Colors.BackgroundColor}" />
</Grid>
</ContentPage.Content>
</ContentPage>
Moreover, BackgroundColor should be a property in order to be accessible:
public static string BackgroundColor {get;} = "#DCECE";
XAML works very poorly with nested classes.
Yes, and in general, a public nested class is often a very bad technique.
Example:
namespace MyNamespace.Mobile
{
public static class Colors
{
public static string BackgroundColor { get; } = "Red";
}
}
XAML:
<StackPanel xmlns:Circassia.Mobile="clr-namespace:MyNamespace.Mobile"
Background ="{Binding Source={x:Static Circassia.Mobile:Colors.BackgroundColor}}"/>
Second example:
namespace MyNamespace.Mobile
{
public static class UserInterfaceDefinitions
{
public static ColorsClass Colors{ get; } = new ColorsClass();
public class ColorsClass
{
private static readonly string s_BackgroundColor = "Red";
public static string BackgroundColor { get; } = s_BackgroundColor;
}
}
}
XAML:
<StackPanel xmlns:Circassia.Mobile="clr-namespace:MyNamespace.Mobile"
Background ="{Binding BackgroundColor, Source={x:Static Circassia.Mobile:UserInterfaceDefinitions.Colors}}"/>

ListView not triggering ItemTapped

I've been working on a school assignment, where you need to load data into a ListView. According to the course manual, you need to use 'ObservableCollection' which I figured out. However, I can't seem to get the ItemTapped to work.
I'm creating the following table using SQLite.
public class Settings
{
[Primarykey]
public string Name { get; set; }
[MaxLength(255)]
public string Value { get; set; }
}
Then on the OnAppearing I initialise the database and add a row of data.
public partial class SQL : ContentPage
{
private SQLiteAsyncConnection _connection;
private ObservableCollection<Settings> _settings;
public SQL()
{
InitializeComponent();
_connection = DependencyService.Get<ISQLiteDb>().GetConnection();
}
protected override async void OnAppearing()
{
await _connection.CreateTableAsync<Settings>();
var settings_name = new Settings { Name = "Meaning of Life", Value = "42" };
await _connection.InsertAsync(settings_name);
await DisplayAlert("Alert", "Value Added to database!", "OK");
var settings = await _connection.Table<Settings>().ToListAsync();
_settings = new ObservableCollection<Settings>(settings);
ListView.ItemsSource = _settings;
base.OnAppearing();
}
void MyItemTapped (object sender, System.EventArgs e)
{
DisplayAlert("Alert", "You Pressed Something!", "OK");
}
void MyItemSelected (object sender, System.EventArgs e)
{
DisplayAlert("Alert", "You Selected Something!", "OK");
}
}
In my XAML file, I have the following, with the ItemTapped going to the function above.
<?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="BudgetBuddy.SQL">
<ContentPage.Content>
<ListView x:Name="ListView">
<ListView.ItemTemplate ItemTapped="MyItemTapped" ItemSelected="MyItemSelected">
<DataTemplate>
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
I can't figure out what I'm doing wrong. Why doesn't my ItemTapped and ItemSelected work? Also, what would be the best way to access the Value associated with the Name I pressed in the ListView from the ObservableCollection
The reason your ItemTapped and ItemSelected aren't working is because they are in the wrong location.
This is what you have:
<?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="BudgetBuddy.SQL">
<ContentPage.Content>
<ListView x:Name="ListView">
<ListView.ItemTemplate ItemTapped="MyItemTapped" ItemSelected="MyItemSelected">
<DataTemplate>
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
This is what you should do:
<?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="BudgetBuddy.SQL">
<ContentPage.Content>
<ListView x:Name="ListView" ItemTapped="MyItemTapped" ItemSelected="MyItemSelected">
<ListView.ItemTemplate >
<DataTemplate>
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>