List of Buttons with Binding and Command in Xamarin - c#

I'm working on a Xamarin App where I want to dynamically display a list of Registry Numbers from a Class Registry.
After the list of numbers is displayed, the user should choose one of them to login in the App.
I have decided that this list will be displayed as a list of Buttons, because once the user clicks it, nothing else needs to be done. However, most of documentation regarding Binding and ListView does not use Buttons as displaying element.
I have decided to follow the steps on this excellent video but I keep receiving the following error:
Binding: 'LocalCommand' property not found on '31348', target property: 'Xamarin.Forms.Button.Command'
Binding: 'LocalCommand' property not found on '10227', target property: 'Xamarin.Forms.Button.Command'
Actually, 31348 and 10227 are the numbers that I want to display. And indeed I indicated them as the Binding context at some point. But I would like to "change" that Binding so I can invoke the LocalCommand method. Probably implementing the LocalCommand in the object would solve the issue, but I definitely don't want to do that!
Questions:
Is there a better and simpler way to do this?
How can I do to "bring back" the Binding to the LocalCommand?
I'm still learning about Binding, so any tips would be really useful!
Thanks!
RegistryPage.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:behavior1="clr-namespace:SPConsigMobile.Utils"
x:Class="App.Views.RegistryPage"
BackgroundColor="{StaticResource AppBackgroundColor}"
Title="App"
NavigationPage.HasNavigationBar="false">
<ContentPage.Content>
<ListView x:Name="RegistryView"
ItemsSource="{Binding User.Registry}"
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Number}"
Command="{Binding LocalCommand}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
RegistryPage.xaml.cs
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegistryPage : ContentPage
{
public RegistryPage()
{
InitializeComponent();
RegistryView.ItemsSource = User.Registries;
}
public ICommand LocalCommand => new Command(CommandClicked);
private void CommandClicked()
{
Console.WriteLine("Command Button Clicked!");
}
}
}

In general when you set the ItemSource property of a control (in this case of your ListView), you have also to set the DataTemplate.
Now, the BindingContext of view inside the DataTemplate is an item of the collection you have binded to. In your case, because you have set the ItemSource to be a Collection of PhoneNumber, the bindingContext of each view is a PhoneNumber.
So when you are trying to acess your command with 'Command="{Binding LocalCommand}"', what you are doing is to search a LocalCommand Property inside a PhoneNumber class. What you need instead is to search it inside your Page class. So, give a name to your ContentPage with x:Name, then reference the source to your command binding to be the Root Page, and the Path to be the path to the command, starting from the Source (so NumberSelectedCommand in my example). the command parameter should be instead exactly the number, so it's an empty path Binding.
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"
x:Class="App1.RegistryPage"
x:Name="Root"
>
<StackLayout>
<ListView x:Name="RegistryView"
ItemsSource="{Binding User.Registry}"
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Number}"
Command="{Binding Source={x:Reference Root}, Path=NumberSelectedCommand}"
CommandParameter="{Binding}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
RegistryPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegistryPage : ContentPage
{
public RegistryPage()
{
InitializeComponent();
RegistryView.ItemsSource = User.Registries;
NumberSelectedCommand = new Command<PhoneNumber>(OnNumberSelected);
}
// Commands
public ICommand NumberSelectedCommand { get; }
// Commands Handlers
private void OnNumberSelected(PhoneNumber selectedNumber)
{
// Do what you need with selected number.
}
}

Related

In Xamarin.Forms, how to Bind to list of custom class, for ListView and print custom class properties?

In .xaml file I am trying to bind to a listed custom class as ObeservableCollection object.
I can successfully update my variables and get the ObservableCollection updated. I can check it rendering it as:
<ListView ItemSource="{Binding myCustomObservableCollection}"/>
However, even if I can determine the number of the entries in the list, I cannot access the properties of my custom class.
I tried with this, with no success as list's rows are empty. Even using Text="{Binding Id}" doesn't work since it tells me that "Id" is not a property inside myCustomViewModel:
<ListView
x:DataType="vm:CustomtViewModel"
BackgroundColor="LightSlateGray"
HasUnevenRows="True"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding myCustomObservableCollection}"
SeparatorColor="Black">
<ListView.ItemTemplate>
<DataTemplate>
<label Text="{Binding Source={StaticSource myCustomClass}", Path=Id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Of course I have inserted my custom class into the .xaml with:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
And Id is one of the properties I need into the public class in my Models
namespace myApp.Models {
public class myCustomClass : INotifyPropertyChanged
{
private string _id;
public event PropertyChangedEventHandler PropertyChanged;
public string Id
{
get => _id;
set {
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
}
}
So I wonder how to effectively read every entry of the list as an object which I could parse the properties in it.
Thanks so much
Did you check the official document about Binding Cells in the ListView? The myCustomClass didn't have to inherit from the INotifyPropertyChanged interface.
Just make sure there is public ObservableCollection<myCustomClass> { get; set; } in your viewmodel. Such as:
public class CustomtViewModel
{
public ObservableCollection<myCustomClass> myCustomObservableCollection { get; set; }
public CustomtViewModel()
{
// you can initialize the myCustomObservableCollection's data in the construction method.
}
}
In adddition, I see you used the x:DataType="vm:CustomtViewModel" for the listview. The official document said:
Set an x:DataType attribute on a VisualElement to the type of the object that the VisualElement and its children will bind to.
So you can just binding the Id like Jason said:
<ListView.ItemTemplate>
<DataTemplate>
<Label Text={Binding Id}/>
</DataTemplate>
</ListView.ItemTemplate>
In addition, you can refer to the official sample about listview mvvm binding on the github.This is the viewmodel's code and the page's code.
Also thanks to Liyun Zhang and ToolmakerSteve I came up with a solution.
Indeed it's important to set the correct x:DataType and I found out it can be done even multiple times pointing at different classes, linking different types of data
Here's my ListView in xaml now:
<ListView
x:Name="customListName"
x:DataType="vm:CustomViewModel"
ItemsSource="{Binding myCustomObservableCollection}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:myCustomClass"> <!--THIS SAVED THE DAY-->
<ViewCell>
<Label Text="{Binding Id}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now the object extracted from list is correctly read referencing to its own class.
The trick is about adding x:DataType="local:myCustomClass" to the DataTemplate tag after I added a reference in the xaml like this:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
(I insert this also here for ease of reading if someone else met the same issue)
It worked like a charm!
Hope this can save someone else from headache! Cheers.

Bind Properties onto custom controls

I want to create a very basic custom control that will update its label if the label changes in the containing view model
My setup is like this:
public partial class BindTest : ContentView
{
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(BindTest), string.Empty, propertyChanged: TitleChanged);
static void TitleChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (BindTest)bindable;
control.Title = newValue as string;
}
public string Title
{
get => (string)GetValue(BindTest.TitleProperty);
set => SetValue(BindTest.TitleProperty, value);
}
public BindTest()
{
InitializeComponent();
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiDemo.BindTest" x:Name="this">
<StackLayout BindingContext="{x:Reference this}">
<Label TextColor="White" Text="{Binding Title}" />
</StackLayout>
</ContentView>
And i consume my custom control like this and bind my property from my VM (annotated as ObservableProperty)
<controls:BindTest Title="{Binding TestString}"/>
<Label Text="{Binding TestString}"/>
If i press a Button that executes a command my TestString will be overriden and updated correctly. the separate label beneath my custom controls displays the changing value correctly.
did i misinterpret the use case of custom controls or is something wrong the way i setup things?
Ok, so i found out, that i indeed messed up my setup for creating a custom control.
As i already stated, the problem was that the custom control wouldn't get updated even if the BindableProperty would update.
The BindableProperty would always properly update but the propertyChanged callback would never be called again after the initial value override.
So now i was wondering, why would the propertyChanged callback only be called once?
The root of my problem was the line control.Title = value as string;
Removing just this one line and leaving the rest of the setup as-is resulted in the custom control correctly updating if the BindableProperty was updated.
Which means, that by calling the setter of the property used by the custom control somehow interrupts the way maui connects the bindings for the properties.
Therefore i missinterpreted the use of the propertyChanged callback, due to the influence on random samples out there in the web.
I will leave the working class and uses down below so u can just test/try out and see how bindings work in custom controls if anybody happens to stumble across the same or similar problems.
BindTest.xaml.cs
public partial class BindTest : ContentView
{
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(BindTest), string.Empty);
public string Title
{
get => (string)GetValue(BindTest.TitleProperty);
set => SetValue(BindTest.TitleProperty, value);
}
public BindTest()
{
InitializeComponent();
}
}
BindTest.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiDemo.Controls.BindTest" x:Name="this">
<StackLayout BindingContext="{x:Reference this}">
<Label TextColor="White" Text="{Binding Title}" />
</StackLayout>
</ContentView>
usage in different view
<?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:controls="clr-namespace:MauiDemo.Controls"
x:Class="MauiDemo.Test">
<StackLayout>
<controls:BindTest Title="{Binding TestString}"/>
</StackLayout>
</ContentView>
This is not the correct way to create custom controls because this way you will always run into BindingContext related issues as soon as your control becomes a part of another view and the BindingContext gets shared to the branches of that View.
So your code should look like below:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiDemo.BindTest" x:Name="this">
<StackLayout>
<Label TextColor="White" Text="{Binding Title, Source={x:Reference this}}" />
</StackLayout>
</ContentView>
You can check my MAUI library that handles different kinds of custom controls https://github.com/FreakyAli/Maui.FreakyControls.
Hope it helps :)

.Net MAUI: View not drawing data from ViewModel - Intellisense shows a connection

My VIEW refuses to draw data from the VIEWMODEL. The Visual Studio 2022 intellisense fills in the connections as if there is a connection, and it compiles just fine...but no data displays. I've stepped away from XAML for several years, and I'm trying to jump back in.
I am trying to have an entry field update the VIEWMODEL, and then the label to display the text being written to the VIEWMODEL. I keep paring the code back, trying to get ANYTHING to work before trying to execute more complicated bindings, but for now I can't get the VIEW to connect to the VIEWMODEL.
The following XAML is the extremely stripped down VIEW code.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:MauiCalendar.Model"
xmlns:viewmodel="clr-namespace:MauiCalendar.ViewModel"
x:Class="MauiCalendar.View.DaySingleView"
x:DataType="viewmodel:DaySingleViewModel"
Title="{Binding Title}">
<VerticalStackLayout>
<Entry
Text="{Binding Title}" />
<Label
Text="{Binding Title}" />
<Label
Text="Test" />
<Button
Text="Load Days" />
</VerticalStackLayout>
</ContentPage>
The VIEWMODEL is called "DaySingleViewModel" located inside a ViewModel directory. I've got multiple fields and methods, but I'm stripping them out to keep this question lean.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Diagnostics;
using System.Diagnostics;
using System.ComponentModel;
using MauiCalendar.Model;
using MauiCalendar.Services;
namespace MauiCalendar.ViewModel
{
[ObservableObject]
public partial class DaySingleViewModel
{
public DaySingleViewModel()
{ }
[ObservableProperty]
string title = "Starting Value";
}
}
The above code outputs the following:
Would anyone with more experience be willing to take a look at what I'm doing wrong?
I tested your code, and you need add BindingContext to connect the View with ViewModel firstly. Here's the code snippet below for your reference. Just keep the code neat and easy to understand.
View:
<?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="MauiCalendar.View.DaySingleView">
<VerticalStackLayout>
<Entry Text="{Binding Title}" />
<Label Text="{Binding Title}" />
<Label Text="Test"/>
<Button Text="Load Days"/>
</VerticalStackLayout>
</ContentPage>
View's code-behind:
using MauiCalendar.ViewModel;
namespace MauiCalendar.View;
public partial class DaySingleView : ContentPage
{
      public DaySingleView()
      {
BindingContext = new DaySingleViewModel();
InitializeComponent();
      }
}
ViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
namespace MauiCalendar.ViewModel
{
[ObservableObject]
public partial class DaySingleViewModel
{
[ObservableProperty]
string title = "Starting Value";
public DaySingleViewModel() { }
}
}
Starting Page:
using MauiCalendar.View;
namespace MauiCalendar;
public partial class App : Application
{
      public App()
      {
            InitializeComponent();
            MainPage = new DaySingleView();
      }
}

XAML WebView binding to string not working in Xamarin Forms

I'm new to C# and Xamarin Forms. I'm having a webview and getting source url from an API. (For this question , I have hardcode the value). I binded source url instead of adding the value to Source in XAML. But it's not working. There are few solutions in stack and forums. I tried. But didn't work. Someone please help me to sovle this.
This is my 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" x:Class="MyProject.Views.NewRegistration.PrivacyWebView">
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView Source="{Binding WebViewSource}" HeightRequest= "300" WidthRequest="250" Navigated="Handle_Navigated" VerticalOptions="FillAndExpand" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" />
<ActivityIndicator x:Name="loader" IsRunning="true" IsVisible="true" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1"/>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
This is how I bind the source. (Tried this in Codebehind and ViewModel too)
public HtmlWebViewSource WebViewSource
{
get
{
return new HtmlWebViewSource { Html = "https://www.stackoverflow.com" };
}
}
You're using it wrong, when using the HtmlWebViewSource you need to specify actual HTML instead of the URL where you want to go to. If you want to navigate to a URL, specify it in the Source property.
If you want to bind it, you have to implement something like this.
In your view model create a string property:
public string UrlToGoTo { get; set; }
Then set it like you normally would, make sure to have INotifyPropertyChanged is implemented somehow.
Then, wire up your WebView like this:
<WebView Source="{Binding UrlToGoTo}"
HeightRequest= "300"
WidthRequest="250"
Navigated="Handle_Navigated"
VerticalOptions="FillAndExpand"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1" />

Xamarin.Forms ListView change Cell on ContextMenu open

I use Xamarin.Forms to define a ListView. This ListView defines some ContextActions on inside the ViewCell. Depending on the platform, these context actions are then presented to the user. In Android, this is triggered by long-pressing the specific item. Sadly, this Item will not be (properly) highlighted, as can be seen in this screenshot (I long-pressed Third Item, sadly I can't yet embed images).
Is there a way to modify the Cell when the context menu opens? Specifically asking for a solution for Android, but a general answer is welcome as well. The goal eventually is to improve highlighting, e.g. by changing the cell's background color. Modifying the cell, when one ContextAction is pressed, is not what I am looking for.
I browsed through the source code of Xamarin.Forms and thought about somehow inheriting from e.g. the ViewCell class, but couldn't find an event or command that would be triggered / called upon long-pressing an item. I have set up a simple repository to which illustrates the behavior: GitHub repository
The most important code snippets
ListView definition in 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:ListViewContextMenu" x:Class="ListViewContextMenu.ListViewContextMenuPage">
<ListView x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Action" Command="{Binding OnAction}" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<Label Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
MyItem definition (MVVM)
using System.Diagnostics;
using Xamarin.Forms;
namespace ListViewContextMenu
{
public class MyItem
{
public string Name { get; set; }
public Command OnAction { get; set; }
public MyItem()
{
OnAction = new Command((obj) => Debug.WriteLine($"Item {obj.ToString()} clicked"));
}
}
}
No need for custom renderer - you can simply add following tag(s) to styles.xml (location: Android project > Resources > values > styles.xml)
<style name="MyTheme" parent="MyTheme.Base">
<item name="android:colorLongPressedHighlight">#color/ListViewHighlighted</item>
</style>
<color name="ListViewHighlighted">#A8A8A8</color>
More details can be found at this post.

Categories