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 :)
Related
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();
}
}
I have a ContentPage which is assigned to the Xamarin.Forms.Shell class as a ShellContent and require the back button to be displayed in the navigation bar of the ContentPage.
The XAML source of the ContentPage in concern is as follows:
<?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="UI_test.TestPage">
<Shell.NavBarIsVisible>True</Shell.NavBarIsVisible>
<Shell.BackButtonBehavior>
<BackButtonBehavior IsEnabled="True" />
</Shell.BackButtonBehavior>
<Shell.FlyoutBehavior>Disabled</Shell.FlyoutBehavior>
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
I found that that assigning a custom value to the attributes TextOverride and IconOverride of the <BackButtonBehavior> tag will display the back button, but I am looking for a way to display the platform's default back button (rather than a custom one) as the above ContentPage does not display as back button in its navigation bar as seen in the screenshot below.
Thanks in advance.
Your project seems to contain one page only, It won't appear if no navigation has been made, and it makes sense. If you do a Shell.GotoAsync(..) or Shell.Navigation.PushAsync(..) to navigate to another page it will then appears (the default native back button) allowing to go back to the previous page in the navigation stack.
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.
}
}
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" />
I want to implement a custom accordion ui element but the code has been written in C# ( https://github.com/Kimserey/AccordionView ) but I want to use xaml. I tried to add Accordion view in my content but while I am compiling I get following error
HomePage.xaml : error : The given key was not present in the dictionary.
Here is the my 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:customaccordionmenu="clr-namespace:CustomAccordionMenu"
x:Class="CustomAccordionMenu.HomePage" Title="Accordion Example" >
<ContentPage.Content>
<StackLayout VerticalOptions = "Center">
<customaccordionmenu:AccordionView/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
So I just want to create an accordionview and add the content in xaml, not in c#
PS: The project is for Xamarin.Forms
You are trying to instantiate an empty AccordionView. Judging by the code in github, the accordionview doesn't have a parameterless constructor, which could cause a problem.
Also the AccordionSectionView class calls
private ImageSource _arrowRight = ImageSource.FromFile("ic_keyboard_arrow_right_white_24dp.png");
as well as
private ImageSource _arrowDown = ImageSource.FromFile("ic_keyboard_arrow_down_white_24dp.png");
so if these files aren't in your app's resources and named in the very same way, this is also a possible cause for a crash.