how to create a bindable property in a custom contentView - xamarin.forms

I created a custom ContentView with a Image and a Label.
I also created a property of ImageUrl and LabelText.
I want this ContentView an receive a Binding value in a listview
<MyCustomContentView ImageUrl="{Binding Image}" LabelText="{Binding Text}" />
but it says there is no bindable property.how to create it?

You can use this code and paste it below the constructor of the MyCustomContentView class in the code behind file.
public static readonly BindableProperty LabelTextProperty = BindableProperty.Create(nameof(LabelText), typeof(string), typeof(MyCustomContentView), default(string));
public static readonly BindableProperty ImageUrlProperty = BindableProperty.Create(nameof(ImageUrl), typeof(string), typeof(MyCustomContentView), default(string));
public string LabelText { get => (string)GetValue(LabelTextProperty); set => SetValue(LabelTextProperty, value); }
public string ImageUrl { get => (string)GetValue(ImageUrlProperty); set => SetValue(ImageUrlProperty, value); }
Let me know if you have further difficulties

Related

Xamarin - How do I inject a property into a ContentView from the ContentPage or ContentPageViewModel

Update: I've updated this a bit to remove the reference to the error. #michal-diviš gave the correction solution to that. However, my larger issue still remains.
I'm new to Xamarin and trying to learn by making a simple email client. I'm trying to set a property on a ContentPage I have created.
The Setup
The MainPage simply has a grid with two columns; the left side features an CollectionView of the inbox, the right side is my custom ContentPage MessageDisplayView. When an email is clicked in the CollectionView, the CurrentMessage property on the MainPageViewModel is updated to the selected item.
The Issue
I'm trying to bind the property MessageDisplayView.Message to the MainPageViewModel.CurrentMessage property, but the contentpage never updates. I've tried with and without BindableProperty, as well as other ideas found while searching Google and Stackoverflow.
The Question
How do I handle setting and updating a property that I would like to live with the ContentPage?
The Code
MainPage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c="Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:vm="clr-namespace:Project.ViewModel"
xmlns:view="clr-namespace:Project.View"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.MainPage">
<ContentPage.BindingContext>
<vm:MainPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<ResourceDictionary Source="ResourceDictionaries/EmailResourceDictionary.xaml"/>
</ResourceDictionary>
</ContentPage.Resources>
<Grid x:Name="MainPageGrid">
<!-- other xaml code -->
<view:MessageDisplayView
x:Name="MyDisplayView"
Grid.Column="1"
Message="{Binding CurrentMessage}" <!-- Error -->
/>
</Grid>
</ContentPage>
MainPageViewModel.cs
using MimeKit;
using Project.EmailLogic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.ViewModel
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public class MainPageViewModel: ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value, nameof(MessageDisplayView.Message));
}
public MainPageViewModel()
{
}
}
}
MessageDisplayView.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:view="clr-namespace:Project.View"
xmlns:vm="clr-namespace:Project.ViewModel"
x:DataType="view:MessageDisplayView"
xmlns:fa="clr-namespace:FontAwesome"
x:Class="Project.View.MessageDisplayView">
<ContentView.Content>
<Grid>
<!-- Various standard xaml things, for example... -->
<!-- Subject Line -->
<Label x:Name="SubjectLine"
Grid.Row="1"
Text="{Binding Message.Subject}"
/>
</Grid>
</ContentView.Content>
</ContentView>
MessageDisplayView.xaml.cs
using MimeKit;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Project.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessageDisplayView : ContentView
{
private MimeMessage message;
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
BodyHtmlViewSource.Html = Message.HtmlBody;
}
}
public BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
public HtmlWebViewSource BodyHtmlViewSource { get; set; }
public MessageDisplayView()
{
InitializeComponent();
}
}
}
The problem was the BindableObject was not hearing the notifications of the property changing.
The solution was to add the OnPropertyChanged method to the code behind of the ContentView, not the ContentPageViewModel.
This "solution" correctly updates the property in the code, but it does not update the xaml/UI. I think this might a separate issue.
This confused me at first, when #michal-diviš pointed out the OnPropertyChanged calls, as I thought I was suppose to wire up the event subscription myself in the ContentView code behind. But after stumbling across this article, I realized that the method was required elsewhere.
I feel like a major issue is that there isn't a lot of information about passing data or properties between elements/UserControls/ContentPages, etc. Over the last two days, I've read and watched a fair amount on BindableProperties, but seen very little use of OnPropertyChanged or updating the properties from elsewhere. Perhaps I'm missing the places where it's talked about, or maybe it's more easy or obvious than I realize, but in hindsight, this seems like something that should have been mentioned in every BindableProperty 101.
Beyond the official documentation of course, if anyone knows a good article or video going over sharing/binding/updating properties between classes/views/whatever, I'd love to check that out.
Here's an example of the final, working code:
public partial class MessageDisplayView : ContentView
{
public MimeMessage Message
{
get
{
return (MimeMessage)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
public static BindableProperty MessageProperty =
BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView), new MimeMessage(),
BindingMode.TwoWay);
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == MessageProperty.PropertyName)
{
if(Message != null)
{
// Update ContentView properties and elements.
}
}
}
Thank you again to #michal-diviš for your help!
Fix
It's the BindableProperty definition!
You have (in the MessageDisplayView.xaml.cs):
public BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
you need to make it static readonly like this:
public static readonly BindableProperty MessageProperty = BindableProperty.Create(nameof(Message), typeof(MimeMessage), typeof(MessageDisplayView));
Usage of INotifyPropertyChanged
The CurrentMessage property in your MainPageViewModel seems to be the problem. You've created it as a BindableProperty, however, that's meant to be used by user controls, not view models.
What you need in the view model is to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event in the property setter. That is done so the UI will update itseld whenever the CurrentMessage property changes.
Tweak your MainViewModel.cs like this:
using MimeKit;
using Project.EmailLogic;
using Xamarin.Forms;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Project.ViewModel
{
public class MainPageViewModel : INotifyPropertyChanged
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set {
currentMessage = value;
OnPropertyChanged(nameof(CurrentMessage))
};
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example, I've implemented the INotifyPropertyChanged directly in you view model, but a better way to do it is to inherit from a base class that already has that implemented, like this one: ObservableObject from James Montemagno's MVVM Helpers library. The resulting view model would look like this:
using MimeKit;
using Project.EmailLogic;
using MvvmHelpers;
namespace Project.ViewModel
{
public class MainPageViewModel : ObservableObject
{
private MimeMessage currentMessage;
public MimeMessage CurrentMessage
{
get => currentMessage;
set => SetProperty(ref currentMessage, value);
}
}
}
EDIT:
Lately I've been using the CommunityToolkit.Mvvm library instead of Refactored.MvvmHelpers as it's more updated and feature rich.

BindableProperty issue in customized Entry control

I am trying to add a bindable property to an entry in Xamarin.Forms. This should allow me to set/unset the keyboard focus for the Entry by assigning a boolean to the HasFocus property. I am using ReactiveUI as a MVVM framework and the RaiseAndSetIfChanged method raises the INotifyPropertyChanged event implicitly (which works in many other places).
I am not able to hit any breakpoints in my FocusedEntry class and I am not seeing the keyboard coming up. What am I missing?
// XAML
<controls:FocusedEntry Text="My custom Entry"
HasFocus="{Binding EntryHasFocus, Mode=TwoWay}"/>
// View Model
private bool _entryHasFocus;
public bool EntryHasFocus
{
get => _entryHasFocus;
private set => this.RaiseAndSetIfChanged(ref _entryHasFocus, value);
}
// Custom View
public class FocusedEntry : Entry
{
public static readonly BindableProperty HasFocusProperty = BindableProperty.Create(
nameof(HasFocus), typeof(bool), typeof(FocusedEntry), false, BindingMode.TwoWay, propertyChanged: OnHasFocusedChanged);
public bool HasFocus
{
get => (bool)GetValue(HasFocusProperty);
set => SetValue(HasFocusProperty, value);
}
private static void OnHasFocusedChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is FocusedEntry entry)
{
bool hasFocus = (bool)newValue;
bool wasFocused = (bool)oldValue;
if (hasFocus == wasFocused) return;
if (hasFocus)
entry.Focus();
else
entry.Unfocus();
}
}
}
The code actually worked all along. For some reason, Visual Studio was not updating the App on my iPad and I was testing with an old version of my app.

Datepicker Binding Xamarin Forms

<DatePicker Date="date" Format="d MMM yyyy" HorizontalOptions="EndAndExpand" x:Name="date"/>
By having that line of code in my xaml code. how can i set the date to NOW when i create a new data?
You can either change the Date attribute to something like this: Date="{Binding MyDate}" and then in your code-behind assign a view model to the BindingContext property. For example:
public class MyViewModel
{
public DateTime MyDate { get; set; }
}
and in your page:
public class MyPage
{
public MyPage()
{
InitializeComponent();
var viewModel = new MyViewModel();
viewModel = DateTime.Now;
BindingContext = viewModel;
}
}
Or, if you don't need to use a view model or MVVM (which I would highly recommend), you can just set it directly from your code-behind. Because you gave the DatePicker an x:Name attribute, you can refer to it by that name.
public class MyPage
{
public MyPage()
{
InitializeComponent();
date.Date = DateTime.Now;
}
}
You can define it in two ways
Using the "x:Name" you can define the date in XAML.cs.
Using the Binding property for "Date" you can define the date in ViewModel class
In your case in xaml.cs
date.Date = DateTime.Now;

Xamarin forms Picker Binding error

I have a picker control:
<Picker Title="Number of People"
ItemsSource="{Binding SomeList, Source={x:Static local:MyModelHandler.MyModel}}"
SelectedItem="{Binding SomeListSelectedIndex, Source={x:Static local:MyModelHandler.MyModel}}">
</Picker>
when trying to build i get "No property, bindable property, or event found for 'ItemsSource'" error.
Above that i have a label:
<Label Text ="{Binding SomeLabel, Source={x:Static local:MyModelHandler.MyModel}, Mode=OneWay}"></Label>
And that binding works perfectly
MyModelHandler is an static class that allowes only one Model
public static class MyModelHandler
{
private static MyModel myModel = new MyModel();
public static MyModel MyModel
{
get
{
return myModel;
}
}
}
And Model is simple:
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int selectedNumber = 1;
private string someLabel = "";
public IList<int> SomeList
{
get
{
return Enumerable.Range(1, 10).ToList();
}
}
public int SomeListSelectedIndex
{
get
{
return SomeList.IndexOf(this.selectedNumberOfPeople);
}
set
{
this.selectedNumber = SomeList[value];
}
}
public double SomeLabel
{
get
{
return this.someLabel;
}
set
{
this.someLabel= value;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...
}
Edit: Using Xamarin 4.3
You might check your Xamarin.Forms version.
The ItemsSource property was introduced in Xamarin.Forms 2.3.4.184-pre1, see release notes here:
https://developer.xamarin.com/releases/xamarin-forms/xamarin-forms-2.3/2.3.4-stable/#2.3.4.184-pre1.
If you are using an older Xamarin.Forms version you will get the Xamarin.Forms XAML error "No property, bindable property, or event found for 'ItemsSource'".
That is a very strange way to set up the binding context for a view. The fact you have to specify the source for each element adds a lot of extra boilerplate code.
Try setting the bindingcontext to the model in the the view constructor
BindingContext = new MyModel ();
Then the XAML becomes
<Picker Title="Number of People"
ItemsSource="{Binding SomeList}"
SelectedItem="{Binding SomeListSelectedIndex}">
</Picker>
Or use a proper MVVM framework and save yourself a lot of grief. I can recommend FreshMvvm.
https://github.com/rid00z/FreshMvvm

How to bind usercontrol to applications viewmodel

I am using the standard pivot template in my WP7 app.
I have the MainViewModel class defined with a few extra properties:
public class MainViewModel : INotifyPropertyChanged
{
...
private MyClass selectedKey_m;
public MyClass SelectedKey
{
get
{
...
}
set
{
if (value != this.selectedKey_m)
{
this.selectedKey_m = value;
NotifyPropertyChanged("SelectedKey");
}
}
}
}
The App class has a view model instance:
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
My MainPage.xaml.cs sets the DataContext:
DataContext = App.ViewModel;
From here, I can set up two way bindings on ListBoxes and I know it works because if I put a breakpoint on the SelecetdKey property in my viewmodel I can see the setter get called.
My problem is that I have my own user control, with a bindable property, bound to the SelectedKey property of the view model, but the property in my user control never gets set when the viewmodel gets updated and I can't figure out why.
Here is my user control:
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty SelectedKeyProperty = DependencyProperty.Register(
"SelectedKey", typeof(MyClass), typeof(MyUserControl), new PropertyMetadata(null));
public MyClass SelectedKey
{
get { return (MyClass)this.GetValue(SelectedKeyProperty); }
set { this.SetValue(SelectedKeyProperty, value); }
}
}
And here is the xaml in my main page:
<local:MyUserControl x:Name="myUC" SelectedKey="{Binding Path=SelectedKey}">
I would expect that the setter for the SelectedKey property of my user control to get called when the SelectedKey property of the view model gets changed, but it doesn't.
I've also tried setting the datacontext of my user control in the xaml:
DataContext="{Binding Path=App.ViewModel}"
The debugger does not step into the setter, don't know why.
Try adding a callback invoked on property value changes :
public static readonly DependencyProperty SelectedKeyProperty = DependencyProperty.Register(
"SelectedKey", typeof(MyClass), typeof(MyUserControl), new PropertyMetadata(MyPropertyChanged));
private static void MyPropertyChanged( object sender, DependencyPropertyChangedEventArgs args)
{
}
Solved. I had to add the static method as ptauzen suggested, but also remove the DataContext binding statement from my xaml :
DataContext="{Binding Path=App.ViewModel}"
Because the MainPage sets the datacontext in the constructor, so because my user control is a child of the main page, it inherits the data context. All I needed was to ensure the binding of my user controls properties were set up:
SelectedKey="{Binding SelectedKey}"

Resources