Can't set up binding | MAUI - c#

I'm developing a page that has a Collection View. Inside CollectionView.Footer, when I click on the button, I need to bind to Some Command, but I can't do it, because now there is a binding to SomeModel. Please tell me how to set up binding to ViewModel in CollectionView.Footer
.xaml:
<?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"
xmlns:viewmodels="clr-namespace:Paraglider.MobileApp.ViewModels"
xmlns:models="clr-namespace:Paraglider.MobileApp.Models"
x:Class="Paraglider.MobileApp.Pages.SomePage"
x:DataType="viewmodels:SomePageViewModel">
<Grid>
...
<CollectionView
Grid.Row="2" Grid.ColumnSpan="2"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemsLayout>
...
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:SomeModel">
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Footer>
<Border
Margin="10, 0"
Padding="10"
Stroke="Transparent"
StrokeShape="RoundRectangle 15,15,15,15">
<Border.Shadow>
<Shadow Brush="Black" Opacity="0.1" Radius="5" />
</Border.Shadow>
<StackLayout Orientation="Horizontal">
...
<Button
Padding="0"
CornerRadius="10"
HeightRequest="35" WidthRequest="60"
BackgroundColor="#FF8787"
FontFamily="geometria_medium" FontSize="24"
HorizontalOptions="End"
Text="+"
Command="{Binding SomeCommand}"/>
</StackLayout>
</Border>
</CollectionView.Footer>
</CollectionView>
</Grid>
</ContentPage>
.xaml.cs:
using Paraglider.MobileApp.ViewModels;
namespace Paraglider.MobileApp.Pages;
public partial class SomePage : ContentPage
{
public SomePage (SomePageViewModel viewModel)
{
BindingContext = viewModel;
InitializeComponent();
}
}
viewmodel.cs:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Paraglider.MobileApp.Infrastructure.Abstractions;
using Paraglider.MobileApp.Models;
using Paraglider.MobileApp.Services;
using System.Collections.ObjectModel;
namespace Paraglider.MobileApp.ViewModels;
public partial class SomePageViewModel : BaseViewModel
{
#region services
private readonly SomeService someService;
#endregion
#region fields
[ObservableProperty]
private ObservableCollection<SomeModel> someModels;
#endregion
public SomePageViewModel(SomeService someService)
{
this.someService= someService;
InitAsync();
}
public override async void InitAsync()
{
var someModels = await someService.GetAsync() ?? new();
SomeModels = new ObservableCollection<SomeModels>(components);
}
[RelayCommand]
private async Task SomeAsync()
{
...
}
}

To make sure that you can bind correctly to SomeCommand, you can use a RelativeSource for the binding and refer to the SomePageViewModel:
<Button Command="{Binding SomeCommand, Source={RelativeSource AncestorType={x:Type viewmodels:SomePageViewModel}}}" />
You can also browse the documentation on relative bindings and compiled bindings.

Related

'ViewModel' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter MAUI

I am not sure that I understand MVVM correctly.
This is what I did:
BaseViewModel
public class BaseViewModel : ObservableValidator
{
public event NotifyWithValidationMessages? ValidationCompleted;
public BaseViewModel() : base()
{}
public virtual ICommand ValidateCommand => new RelayCommand(() =>
{
ClearErrors();
ValidateAllProperties();
var validationMessages = this.GetErrors()
.ToDictionary(k => k.MemberNames.First().ToLower(), v => v.ErrorMessage);
ValidationCompleted?.Invoke(validationMessages);
});
[IndexerName("ErrorDictionary")]
public ValidationStatus this[string propertyName]
{
get
{
var errors = this.GetErrors()
.ToDictionary(k => k.MemberNames.First(), v => v.ErrorMessage) ?? new Dictionary<string, string?>();
var hasErrors = errors.TryGetValue(propertyName, out var error);
return new ValidationStatus(hasErrors, error ?? string.Empty);
}
}
}
RegisterModel
public class RegisterModel : BaseViewModel
{
[Required(ErrorMessage = "User Name is required")]
public string? Username { get; set; }
[EmailAddress]
[Required(ErrorMessage = "Email is required")]
public string? Email { get; set; }
[Required(ErrorMessage = "Password is required")]
[DataType(DataType.Password)]
public string? Password { get; set; }
public RegisterModel() : base()
{
}
}
RegisterViewModel
public class RegisterViewModel : RegisterModel
{
private readonly ISecurityClient securityClient;
public RegisterViewModel(ISecurityClient securityClient) : base()
{
this.securityClient = securityClient;
}
public ICommand NavigateToLoginPageCommand => new RelayCommand(async() =>
await Shell.Current.GoToAsync(PageRoutes.LoginPage, true)
);
public ICommand RegisterCommand => new RelayCommand(OnRegisterCommand);
private async void OnRegisterCommand()
{
if (this?.HasErrors ?? true)
return;
var requestParam = this.ConvertTo<RegisterModel>();
var success = await securityClient.RegisterAsync(requestParam);
if (!success)
{
await Application.Current.MainPage.DisplayAlert("", "Register faild", "OK");
return;
}
await Application.Current.MainPage.DisplayAlert("", "Registered successfully.\nYou can now login.", "OK");
await Shell.Current.GoToAsync(PageRoutes.LoginPage, true);
}
}
RegisterPage (code-behind)
public partial class RegisterPage : ContentPage
{
public RegisterViewModel ViewModel => BindingContext as RegisterViewModel;
public RegisterPage(RegisterViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
viewModel.ValidationCompleted += OnValidationHandler;
}
private void OnValidationHandler(Dictionary<string, string?> validationMessages)
{
if (validationMessages is null)
return;
lblValidationErrorUserName.Text = validationMessages.GetValueOrDefault("username");
lblValidationErrorEmail.Text = validationMessages.GetValueOrDefault("email");
lblValidationErrorPassword.Text = validationMessages.GetValueOrDefault("password");
}
}
When I add the following line to the XAML:
<ContentPage.BindingContext>
<vm:RegisterViewModel />
</ContentPage.BindingContext>
I get the the following error:
'RegisterViewModel' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.
Whole XAML:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MauiUI.Pages.RegisterPage"
xmlns:vm="clr-namespace:MauiUI.ViewModels"
Title="Register">
<ContentPage.BindingContext>
<vm:RegisterViewModel />
</ContentPage.BindingContext>
<ScrollView>
<VerticalStackLayout Spacing="25" Padding="20,0"
VerticalOptions="Center">
<VerticalStackLayout>
<Label Text="Welcome to Amazons of Vollyeball" FontSize="28" TextColor="Gray" HorizontalTextAlignment="Center" />
</VerticalStackLayout>
<Image Source="volleyball.png"
HeightRequest="250"
WidthRequest="250"
HorizontalOptions="Center" />
<StackLayout Orientation="Horizontal">
<Frame ZIndex="1" HasShadow="True" BorderColor="White"
HeightRequest="55" WidthRequest="55" CornerRadius="25"
Margin="0,0,-32,0">
<Image Source="user.png" HeightRequest="30" WidthRequest="30" />
</Frame>
<Frame HasShadow="True" Padding="0" BorderColor="White" HeightRequest="55" HorizontalOptions="FillAndExpand">
<Entry x:Name="username" Margin="35,0,20,0" VerticalOptions="Center" Placeholder="email" Keyboard="Email"
Text="{Binding Username, Mode=TwoWay}"
toolkit:SetFocusOnEntryCompletedBehavior.NextElement="{x:Reference email}"
ReturnType="Next">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Username].HasError}" />
</Entry.Behaviors>
</Entry>
</Frame>
<Label x:Name="lblValidationErrorUserName" Text="{Binding [Username].Error}" TextColor="Red" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Frame ZIndex="1" HasShadow="True" BorderColor="White"
HeightRequest="55" WidthRequest="55" CornerRadius="25"
Margin="0,0,-32,0">
<Image Source="email.png" HeightRequest="30" WidthRequest="30" />
</Frame>
<Frame HasShadow="True" Padding="0" BorderColor="White" HeightRequest="55" HorizontalOptions="FillAndExpand">
<Entry x:Name="email" Margin="35,0,20,0" VerticalOptions="Center" Placeholder="email" Keyboard="Email"
Text="{Binding Email, Mode=TwoWay}"
toolkit:SetFocusOnEntryCompletedBehavior.NextElement="{x:Reference password}"
ReturnType="Next">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Email].HasError}" />
</Entry.Behaviors>
</Entry>
</Frame>
<Label x:Name="lblValidationErrorEmail" Text="{Binding [Email].Error}" TextColor="Red" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Frame ZIndex="1" HasShadow="True" BorderColor="White"
HeightRequest="55" WidthRequest="55" CornerRadius="25"
Margin="0,0,-32,0">
<Image Source="password.jpg" HeightRequest="30" WidthRequest="30"/>
</Frame>
<Frame HasShadow="True" Padding="0" BorderColor="White" HeightRequest="55" HorizontalOptions="FillAndExpand">
<Entry x:Name="password" Margin="35,0,20,0" VerticalOptions="Center" Placeholder="password" IsPassword="True"
Text="{Binding Password, Mode=TwoWay}">
<Entry.Behaviors>
<toolkit:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding [Password].HasError}" />
</Entry.Behaviors>
</Entry>
</Frame>
<Label x:Name="lblValidationErrorPassword" Text="{Binding [Password].Error}" TextColor="Red" />
</StackLayout>
<Button Text="Register" WidthRequest="120" CornerRadius="25" HorizontalOptions="Center" BackgroundColor="Blue"
Command="{Binding RegisterCommand}" />
<StackLayout Orientation="Horizontal" Spacing="5" HorizontalOptions="Center">
<Label Text="Have an account?" TextColor="Gray"/>
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Login" TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateToLoginPageCommand}" />
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>
</StackLayout>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
I registered the page and the view model in DI container.
//viewModels
builder.Services.AddSingleton<RegisterViewModel>();
//pages
builder.Services.AddSingleton<RegisterPage>();
Any guidance is welcome.
Problem
Your problem is that you're trying to instantiate a ViewModel without passing arguments to the constructor although your ViewModel only defines a constructor with a required parameter. You either need to define a parameterless constructor or provide arguments to the constructor.
Here is your constructor:
public class RegisterViewModel : RegisterModel
{
private readonly ISecurityClient securityClient;
public RegisterViewModel(ISecurityClient securityClient) : base()
{
this.securityClient = securityClient;
}
//...
}
You're trying to instantiate it without arguments here:
<ContentPage.BindingContext>
<vm:RegisterViewModel />
</ContentPage.BindingContext>
This instantiation is unnecessary, because you're already setting the BindingContext in the code-behind using the ViewModel instance that is passed in via dependency injection:
public partial class RegisterPage : ContentPage
{
public RegisterViewModel ViewModel => BindingContext as RegisterViewModel;
public RegisterPage(RegisterViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
viewModel.ValidationCompleted += OnValidationHandler;
}
//...
}
Solution
You can safely remove the code that sets the BindingContext in the XAML:
<!-- remove this -->
<ContentPage.BindingContext>
<vm:RegisterViewModel />
</ContentPage.BindingContext>
And make sure that the required argument somehow gets passed into the ViewModel's constructor.
Here are a few ways to do this:
var registerVm = new RegisterViewModel(securityClient);
builder.Services.AddSingleton<RegisterViewModel>(registerVm);
You could also pass the ISecurityClient into the IoC container:
builder.Services.AddSingleton<ISecurityClient>(new SecurityClient());
builder.Services.AddSingleton<RegisterViewModel>();
Alternatively, depending on how the ISecurityClient implementation is defined, assuming it's something like "SecurityClient", you could also register it like this (avoiding any constructor calls altogether provided that the constructor of "SecurityClient" doesn't take any parameters):
builder.Services.AddSingleton<SecurityClient>();
builder.Services.AddSingleton<RegisterViewModel>();
This should automatically resolve the constructor with the required argument.
Further notes
If you want Intellisense support for your ViewModel inside your XAML, you could add compiled bindings to your XAML like this (using the x:DataType attribute):
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MauiUI.Pages.RegisterPage"
xmlns:vm="clr-namespace:MauiUI.ViewModels"
x:DataType="vm:RegisterViewModel"
Title="Register">
or use design-time XAML.

RefreshView won't stop refreshing - Xamarin Forms

For my Xamarin Forms application I am working on developing a calendar page. I have a No-SQL database set up and working and I have my main calendar view page (EventManagerPage) and the backend of this page (EventManagerViewPage).
The issue I am facing is how to let the system know that the RefreshView is refreshing?
The normal Xamarin.Forms.IsBusy indicator has not been working and my page will continue to refresh without stopping. I can tell the calendar events are there as they appear below the calendar after I pull down the page to refresh and click on a date, but the calendar itself does not show the events present (blue square per event on date).
My ViewModel is based off of a custom calendar ViewModel that extends both the INotifyPropertyChanged interface and the BaseViewModel.
Using IsBusy does not result in any error being thrown nor any messages in the debug output. I've tried some other ways around this to try and get the reload to stop once done but those have all resulted in errors that prevent the app from compiling.
So far I have tried creating a custom Boolean to act as the IsBusy Xamarin.Forms indicator but this resulted in the error of the member not being found in the data context.
I additionally attempted to follow the example in this Microsoft Doc on RefreshViews. This also resulted in the error of the member not being found in the data context and I was unable to set refreshView.Command = refreshCommand; (I'm not sure if this is important for the error).
I have put my code below. As a note, the calendar I am using is the Plugin.XCalendar by author MarvinE.
I appreciate any help/suggestions anyone has to offer!
CalendarBaseViewModel.cs
using MvvmHelpers;
using PropertyChanged;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace PPA.ViewModels
{
[AddINotifyPropertyChangedInterface]
public abstract class CalendarBaseViewModel : BaseViewModel, INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
#endregion
}
}
EventManagerViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
using PPA.Models;
using System.Threading.Tasks;
using PPA.Views;
using PPA.Services;
using System.Diagnostics;
namespace PPA.ViewModels
{
public class EventManagerViewModel : CalendarBaseViewModel
{
#region Properties
public ObservableRangeCollection<Event> Events { get; }
public ObservableRangeCollection<DateTime> SelectedDates { get; }
public ObservableRangeCollection<Event> SelectedEvents { get; }
#endregion
public AsyncCommand AddEventCommand { get; }
public AsyncCommand LoadEventsCommand { get; }
IEventDataStore EventService;
#region Constructors
public EventManagerViewModel()
{
AddEventCommand = new AsyncCommand(OnAddEvent);
LoadEventsCommand = new AsyncCommand(LoadEvents);
EventService = DependencyService.Get<IEventDataStore>();
Events = new ObservableRangeCollection<Event>();
SelectedDates = new ObservableRangeCollection<DateTime>();
SelectedEvents = new ObservableRangeCollection<Event>();
SelectedDates.CollectionChanged += SelectedDates_CollectionChanged;
}
#endregion
#region Methods
private void SelectedDates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SelectedEvents.ReplaceRange(Events.Where(x => SelectedDates.Any(y => x.DateTime.Date == y.Date)).OrderByDescending(x => x.DateTime));
}
private async Task OnAddEvent()
{
await Shell.Current.GoToAsync(nameof(NewEventPage));
}
async Task LoadEvents()
{
IsBusy = true;
// refreshview.IsRefreshing = true;
try
{
Events.Clear();
var events = await EventService.GetEventsAsync();
foreach (var ev in events)
{
Events.Add(ev);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
IsBusy = false;
}
public void OnAppearing()
{
IsBusy = true;
}
#endregion
}
}
EventManagerPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="PPA.Views.EventManagerPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Converters="clr-namespace:PPA.Converters"
xmlns:Models="clr-namespace:PPA.Models"
xmlns:ViewModels="clr-namespace:PPA.ViewModels"
xmlns:xc="clr-namespace:XCalendar;assembly=XCalendar"
xmlns:xcModels="clr-namespace:XCalendar.Models;assembly=XCalendar"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:xcConverters="clr-namespace:XCalendar.Converters;assembly=XCalendar"
x:DataType="ViewModels:EventManagerViewModel"
x:Name="This"
Title="Event Calendar"
xct:SafeAreaEffect.SafeArea="True"
>
<ContentPage.Resources>
<!-- Limits a string to a certain amount of characters -->
<xcConverters:StringCharLimitConverter x:Key="StringCharLimitConverter"/>
<!-- Returns true if all bindings evaluate to true -->
<xct:VariableMultiValueConverter x:Key="AllTrueConverter" ConditionType="All"/>
<!-- Inverts a binded boolean value -->
<xct:InvertedBoolConverter x:Key="InvertedBoolConverter"/>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEventCommand}" />
</ContentPage.ToolbarItems>
<RefreshView x:DataType="ViewModels:EventManagerViewModel" Command="{Binding LoadEventsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<Grid
ColumnSpacing="0"
RowDefinitions="Auto,*"
RowSpacing="0">
<Frame
Margin="10"
Padding="0"
BackgroundColor="White"
CornerRadius="15">
<xc:CalendarView
x:Name="MainCalendarView"
Grid.Row="0"
DayNameTextColor="{StaticResource ContentTextColor}"
NavigationArrowColor="{StaticResource ContentTextColor}"
NavigationBackgroundColor="Transparent"
NavigationTextColor="{StaticResource ContentTextColor}"
SelectedDates="{Binding SelectedDates}"
SelectionAction="Modify"
SelectionType="Single">
<xc:CalendarView.DayTemplate>
<DataTemplate x:DataType="{x:Type xcModels:CalendarDay}">
<!-- ContentView so that the margin is respected by the MonthView -->
<ContentView>
<xc:CalendarDayView
Margin="2.5"
HeightRequest="43"
CalendarView="{Binding ., Source={x:Reference MainCalendarView}}"
CurrentMonthTextColor="{StaticResource CalendarBackgroundTextColor}"
DateTime="{Binding DateTime}"
OutOfRangeTextColor="{StaticResource CalendarTertiaryColor}"
SelectedTextColor="{StaticResource CalendarPrimaryTextColor}"
TodayBorderColor="{StaticResource CalendarPrimaryColor}"
TodayTextColor="{StaticResource CalendarBackgroundTextColor}">
<xc:CalendarDayView.ControlTemplate>
<ControlTemplate>
<!-- Using a Grid to stack views on the z axis -->
<Grid RowSpacing="2">
<Grid.RowDefinitions>
<RowDefinition Height="1.5*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- ContentPresenter displays the default content for the control -->
<ContentPresenter
Grid.Row="0"
Grid.RowSpan="2"
VerticalOptions="Center"/>
<StackLayout
Grid.Row="1"
HorizontalOptions="Center"
Orientation="Horizontal"
Spacing="2.5">
<!-- I want the event indicators to only be visible when the DateTime is in the currently navigated month -->
<StackLayout.IsVisible>
<MultiBinding Converter="{StaticResource AllTrueConverter}">
<!-- TemplatedParent refers to the view that the ControlTemplate resides in -->
<Binding Path="IsCurrentMonth" Source="{RelativeSource TemplatedParent}"/>
<Binding
Converter="{StaticResource InvertedBoolConverter}"
Path="IsOutOfRange"
Source="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</StackLayout.IsVisible>
<BindableLayout.ItemsSource>
<Binding Path="DateTime.Date" Source="{RelativeSource TemplatedParent}">
<Binding.Converter>
<Converters:EventWhereConverter
Items="{Binding BindingContext.Events, Source={x:Reference This}}"
UseTimeComponent="False"
WhiteList="True"/>
</Binding.Converter>
</Binding>
</BindableLayout.ItemsSource>
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="{x:Type Models:Event}">
<BoxView
CornerRadius="100"
HeightRequest="7"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Center"
WidthRequest="7"
Color="Blue"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</ControlTemplate>
</xc:CalendarDayView.ControlTemplate>
</xc:CalendarDayView>
</ContentView>
</DataTemplate>
</xc:CalendarView.DayTemplate>
</xc:CalendarView>
</Frame>
<CollectionView Grid.Row="1" ItemsSource="{Binding SelectedEvents}">
<CollectionView.EmptyView>
<Label
FontAttributes="Bold"
FontSize="20"
HorizontalTextAlignment="Center"
Text="No Events on Selected Date(s)"
TextColor="{StaticResource ContentTextColor}"
VerticalTextAlignment="Center"/>
</CollectionView.EmptyView>
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="0" Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type Models:Event}">
<ContentView Padding="5">
<Frame
Padding="0"
BackgroundColor="{StaticResource ContentBackgroundColor}"
CornerRadius="10">
<StackLayout Orientation="Horizontal" Spacing="0">
<BoxView BackgroundColor="CornflowerBlue" WidthRequest="20"/>
<StackLayout Padding="10" Spacing="0">
<Label
FontAttributes="Bold"
FontSize="20"
Text="{Binding DateTime, StringFormat='{0: dd MMMM HH:mm}'}"
TextColor="{StaticResource ContentTextColor}"
VerticalTextAlignment="Center"/>
<Label
FontSize="16"
Text="{Binding Title}"
TextColor="{StaticResource ContentTextColor}"
Margin="5,0,0,0"/>
<Label
Margin="5,10,0,0"
FontSize="14"
Text="{Binding Description}"
TextColor="{StaticResource ContentTextColor}"/>
</StackLayout>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</RefreshView>
</ContentPage>
EventManagerPage.xaml.cs
using PPA.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace PPA.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventManagerPage : ContentPage
{
EventManagerViewModel _viewModel;
public EventManagerPage()
{
InitializeComponent();
BindingContext = _viewModel = new EventManagerViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
Have you created binding property for "IsBusy" in the ViewModel as below:
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}

XAML Having a Labeltext from AppResources inside a ListView

I try to populate a Labeltext inside a Listview in XAML. But i want the Labeltext coming from the AppResources. I am shure i forgot somewhere a tiny lil detail like a using or namespace.
Anway, here's the XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Arbeitszeitrechner_Forms.Views.WorkdayListPage"
Style="{StaticResource PageStyle}"
Title="{Binding Title}"
xmlns:local="clr-namespace:Arbeitszeitrechner_Forms.ViewModels"
xmlns:model="clr-namespace:Arbeitszeitrechner_Forms.Models"
xmlns:appres="clr-namespace:Arbeitszeitrechner_Forms.Resources">
<!--Titel im WorkdaysViewModel zugewiesen und im AppResources definiert-->
<RefreshView x:DataType="local:WorkdaysViewModel" Command="{Binding LoadWorkdaysCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView x:Name="WorkdaysListView"
ItemsSource="{Binding Workdays}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:WorkdayList">
<Label
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16">
<Label.Text>
<MultiBinding StringFormat="{}{0} {1:d}">
<Binding Path="Day" />
<Binding Path="Date" />
</MultiBinding>
</Label.Text>
</Label>
<StackLayout Orientation="Horizontal">
<Label x:Name="LblTotalTime"/> <!-- **************THIS LABEL IS THE **** THING THAT I CAN'T POPULATE****************-->
<Label Text="{Binding TimeTotal, StringFormat='{0:hh}:{0:mm}'}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="13" />
</StackLayout>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:WorkdaysViewModel}}, Path=WorkdayTapped}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
and the xaml.cs Code behind:
namespace Arbeitszeitrechner_Forms.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WorkdayListPage : ContentPage
{
WorkdaysViewModel _viewModel;
public WorkdayListPage()
{
InitializeComponent();
BindingContext = _viewModel = new WorkdaysViewModel();
//Normally i would do something like this here, but for some reason i can't:
//LblTotalTime.Text = AppResources.LblTotalTime
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
The Bot asked me to provide some more details to compensate the much Code i copied. So please ignore the gibbery gabberish i am Texting here.
use the x:Static extension
<Label Text="{x:Static resources:AppResources.LblTotalTime}" />

Xamarin.Forms - Binding a command

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.

Xamarin.Forms ListView Deletion of Items holds old values

I have a ListView and populate it via DataBinding to a Lists Property in my ViewModel. Additionally, I have a menu for the ListView, with a Delete Command, also bound to my ViewModel.
My Problem is now, if I have the ListView initialized, I can delete the lists in it. If I add new lists, I can delete all lists. But then, if I add new items, I can't delete them, because the List I get from the DeleteCommand is the old, already deleted list.
So, after deleting lists, they seem to be somehow, somewhere still present and I can only delete new lists, if the total amount of current lists is higher, than any previous amount of deleted lists.
I hope this is a somehow understandable explanation of my problem.
The Binding is working and Lists Property in my ViewModel holds the correct values, but the "sender" ItemList in the DeleteListCommand is the old ItemList.
Here is my XAML for my ListView:
<ListView x:Name="listView" ItemsSource="{Binding Lists}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ViewCell.ContextActions>
<MenuItem Command="{Binding BindingContext.RenameListCommand, Source={x:Reference listView}}" CommandParameter="{Binding .}" Text="Rename" />
<MenuItem Command="{Binding BindingContext.DeleteListCommand, Source={x:Reference listView}}" CommandParameter="{Binding .}" IsDestructive="True" Text="Delete" />
</ViewCell.ContextActions>
<ContentView Margin="0,2,0,2"
HeightRequest="50"
BackgroundColor="{Binding Color}">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ListTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<ContentView.Content>
<Label Text="{Binding Name}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
TextColor="White"
IsEnabled="True"/>
</ContentView.Content>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is my ViewModel:
...
public ObservableCollection<ItemList> lists = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> Lists
{
get { return lists; }
set
{
lists = value;
OnPropertyChanged("Lists");
}
}
public event PropertyChangedEventHandler PropertyChanged;
...
this.DeleteListCommand = new Command<ItemList>((sender) =>
{
OnDeleteList(sender);
});
...
public ICommand DeleteListCommand { get; set; }
private void OnDeleteList(ItemList itemList)
{
Lists.Remove(itemList);
}
...
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Based on sample project here is what you need to do: Name you cell "viewCell" because you need to pass cell to your model to be able to reset ContextAction.
Change binding on menu item to pass the cell instead of ItemList. Then in model reset context action and get an item from binding context of the cell
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:local="clr-namespace:TobyList_XamarinForms"
xmlns:localVM="clr-namespace:TobyList_XamarinForms.ViewModels"
Title="Toby"
x:Class="TobyList_XamarinForms.Views.MasterPage">
<StackLayout Padding="5" VerticalOptions="FillAndExpand" BackgroundColor="#F9F9F9">
<StackLayout.BindingContext>
<localVM:MasterPageViewModel />
</StackLayout.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Lists}" CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ViewCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteListCommand, Source={x:Reference Name=listView}}" CommandParameter="{Binding Source={x:Reference viewCell}}" Text="Delete" />
</ViewCell.ContextActions>
<ContentView Margin="0,2,0,2"
HeightRequest="50"
BackgroundColor="{Binding Color}">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ListTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<ContentView.Content>
<Label Text="{Binding Name}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
TextColor="White"/>
</ContentView.Content>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal" HeightRequest="30" Margin="7">
<Label Text="Add">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding AddListCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
Model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using TobyList_XamarinForms.Models;
using Xamarin.Forms;
using System.Linq;
namespace TobyList_XamarinForms.ViewModels
{
public class MasterPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemList> lists = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> Lists
{
get { return lists; }
set
{
lists = value;
OnPropertyChanged("Lists");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MasterPageViewModel()
{
this.AddListCommand = new Command(() =>
{
OnAddList();
});
//this.DeleteListCommand = new Command<ItemList>((sender) =>
//{
// OnDeleteList(sender);
//});
this.DeleteListCommand = new Command<ViewCell>((sender) =>
{
OnDeleteList(sender);
});
}
public ICommand AddListCommand { get; protected set; }
private void OnAddList()
{
ItemList itemList = new ItemList() { Id = Guid.NewGuid().ToString().ToUpper(), Name = "Lorem Ipsum", Color = "#000000" };
Lists.Add(itemList);
}
public ICommand DeleteListCommand { get; set; }
//public void OnDeleteList(ItemList itemList)
// {
// Lists.Remove(itemList);
// }
public void OnDeleteList(ViewCell viewCell)
{
viewCell.ContextActions.Clear();
Lists.Remove((ItemList)viewCell.BindingContext);
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Categories

Resources