Xamarin Forms bind a command in a custom controler with Prism - c#

I'm facing an issue and I can't figure it out. I have a custom control, to simplify it let's say I have a button inside of a frame inside of a frame. I want the command of the button to be bindable and the button is private. So, here is my code :
CustomControl.cs :
public System.Windows.Input.ICommand CommandInButton
{
get { return ButtonInFrame.Command; }
set { ButtonInFrame.Command = value; }
}
public static readonly BindableProperty CommandInButtonProperty =
BindableProperty.Create(
propertyName:"CommandInButton",
returnType: typeof(System.Windows.Input.ICommand),
declaringType: typeof(CustomControl),
defaultBindingMode: BindingMode.TwoWay);
private Button ButtonInFrame;
Myview.xaml :
<local:FrameButtonImage Grid.Column="0" Grid.Row="0"
ColorInButton="LightBlue"
SourceImageInButton="male.png"
IsSelected="{Binding IsMenSelected}"
CommandInButton="{Binding SelectMenCommand}"
/>
MyViewModel.cs : (I'm using Prism)
public DelegateCommand SelectMenCommand { get; private set; }
public MainPageViewModel()
{
SelectMenCommand = new DelegateCommand(SelectMen, CanSelectMen);
}
private void SelectMen()
{
System.Diagnostics.Debug.WriteLine("Hello men");
}
private bool CanSelectMen()
{
return !IsMenSelected;
}
My problem : it's never trigger SelectMen().
If I bind the command in a simple button like that :
<Button Grid.Column="1" Grid.Row="0" Grid.RowSpan="3"
Text=">"
FontSize="Large"
BackgroundColor="Transparent"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding SelectMenCommand}"/>
It's work like a charm ! So I supposed I do mess in the CustomControl.cs... Maybe someone can help me ? Thanks !

I found a workaround but I'm sure it's possible to do better. I set my command to be a property of my custom control and add a method to set it to be the command of the button when the command is set.
CustomControl.cs :
public System.Windows.Input.ICommand CommandInButton
{
get; set;
}
public static readonly BindableProperty CommandInButtonProperty =
BindableProperty.Create(
propertyName: "CommandInButton",
returnType: typeof(System.Windows.Input.ICommand),
declaringType: typeof(CustomControl),
defaultValue: null,
propertyChanged: CommandInButtonPropertyChanged);
private static void CommandInButtonPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CustomControl)bindable;
control.ButtonInFrame.Command = (System.Windows.Input.ICommand)newValue;
}

Related

Custom control bindable Property not getting hit when set from a view

I've created a control and it has a bindable property, but when I try to set its value, it does not set, when I check its setter, it's not getting hit while debugging, not sure what am I doing wrong.
public decimal MetricValue
{
get => (decimal)GetValue(MetricValueProperty);
set => SetValue(MetricValueProperty, value);
}
public static readonly BindableProperty MetricValueProperty =
BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(HeightSelection),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MetricValuePropertyChanged);
I also have a propertychanged, which is not getting raised
<controls:customControl
CurrentSystemOfMeasure="{Binding CurrentSystemOfMeasure}"
MetricValue="{Binding CurrentHeight}"
TextAlignment="Start"
OnHeightSelectedCommand="{Binding HeightSelectionCommand}"
IsValid="True" />
any inputs would be helpful
have you set context in the xaml view?
Here my way:
public static readonly BindableProperty CancelButtonVisibleAvailableProperty = BindableProperty.Create(
propertyName: "CancelButtonVisible",
returnType: typeof(bool),
declaringType: typeof(SelphiDetailPhotosView),
defaultValue: true);
public bool CancelButtonVisible
{
get { return (bool)GetValue(CancelButtonVisibleAvailableProperty); }
set { SetValue(CancelButtonVisibleAvailableProperty, value); }
}
ContentView needs to have a x:name to set the context in the property IsVisible in your case you need to set the context in Text Property of label to show decimal value
I have set the context making reference to my custom view selphiDetailPhotosView
<Button IsVisible="{Binding Source={x:Reference selphiDetailPhotosView}, Path=CancelButtonVisible}"
Margin="27,0,27,18"
BackgroundColor="{StaticResource PlatinumColor}"
Command="{Binding CancelCommand}"
Text="Cancelar"
TextColor="White"
TextTransform="None" />
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="confia.Views.Views.OnBoardingAndLivenessSharedViews.SelphiDetailPhotosView"
xmlns:svg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
xmlns:transformation="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
xmlns:views="clr-namespace:confia.Views.Views"
xmlns:converters="clr-namespace:confia.Views.Converters"
x:Name="selphiDetailPhotosView"
>
Finally call your customcontrol in your ContentPage and bind with your ViewModel
<customControl:SelphiDetailPhotosView CancelButtonVisible="{Binding CanCancel}"/>
I have replicated your scenario and this work for me.
CodeBehind Custom Control
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static readonly BindableProperty MetricValueProperty = BindableProperty.Create(
propertyName: nameof(MetricValue),
returnType: typeof(decimal),
declaringType: typeof(CustomControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: 0m);
public decimal MetricValue
{
get { return (decimal)this.GetValue(MetricValueProperty); }
set { this.SetValue(MetricValueProperty, value); }
}
}
CustomControl Xaml
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ferreplace.Views.Controls.CustomControl"
x:Name="this"
>
<ContentView.Content>
<StackLayout HorizontalOptions="CenterAndExpand" >
<Label Text="Metric Value" FontSize="40"/>
<Label FontSize="20" Text="{Binding Source={x:Reference this}, Path=MetricValue}" ></Label>
</StackLayout>
</ContentView.Content>
ContenPage to call Custom control
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ferreplace.Views.Controls"
x:Class="ferreplace.MainPage">
<StackLayout>
<controls:CustomControl MetricValue="{Binding MetricValueBinding}"/>
</StackLayout>
</ContentPage>
ViewModel
public class MainPageViewModel: BindableBase, IInitializeAsync
{
private decimal _metricValueBinding;
public decimal MetricValueBinding
{
get
{
return _metricValueBinding;
}
set
{
SetProperty(ref _metricValueBinding, value);
}
}
public MainPageViewModel()
{
}
public async Task InitializeAsync(INavigationParameters parameters)
{
MetricValueBinding = 30m;
}
}
Decimal Binding Result
Maybe you forgot default value in bindable property creation

How to implement custom BindableProperty from XAML local:imageresource

(EDITED: clarity)
I want a BindableProperty for a custom control which will ultimately display an image. In my implementation (extracts below), the property Source gets assigned the value
Xamarin.Forms.StreamImageSource
instead of something resembling a path to the file.
My XAML has the following:
<controls:StretchImage
x:Name="Folder"
HeightRequest="25"
HorizontalOptions="StartAndExpand"
VerticalOptions="Center"
BackgroundColor="Gold"
Source="{local:ImageResource CloudTest.Assets.icons.folder_tab.png}" />
In StretchImage.cs I have:
class StretchImage : SKCanvasView
{
public static readonly BindableProperty LabelProperty =
BindableProperty.Create(nameof(Source), typeof(string), typeof(StretchImage),
null, Xamarin.Forms.BindingMode.OneWay);
public string Source
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
}
What am I not understanding ?

Xamarin Forms / ICommand Bindable property is not working on custom view

I've created a custom view called HeaderTemplate. This control has an image. What am I trying to achieve, is to click on Image and perform some action using MVVM.
Please find below the xml and cs of that control.
HeaderTemplate.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
x:Class="PS.Views.Templates.HeaderTemplate"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:PS.Behaviors">
<ContentView.Content>
<StackLayout
Padding="10"
BackgroundColor="Transparent"
Orientation="Horizontal">
<Image
x:Name="ImageSource_2"
HeightRequest="50"
HorizontalOptions="EndAndExpand"
Source="{Binding ImageSource2}">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="ImageSource2_Tapped" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ContentView.Content>
</ContentView>
HeaderTemplate.xml.cs
public partial class HeaderTemplate : ContentView
{
public HeaderTemplate()
{
InitializeComponent();
BindingContext = this;
}
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2), typeof(string), typeof(HeaderTemplate));
public string ImageSource2
{
get => (string)GetValue(ImageSource2Property);
set => SetValue(ImageSource2Property, value);
}
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(nameof(ImageSource2TapCommand),
typeof(ICommand),
typeof(HeaderTemplate),
null);
public ICommand ImageSource2TapCommand
{
get => (ICommand)GetValue(ImageSource2TapCommandProperty);
set => SetValue(ImageSource2TapCommandProperty, value);
}
private void ImageSource2_Tapped(object sender, EventArgs e)
{
if (ImageSource2TapCommand == null) return;
if (ImageSource2TapCommand.CanExecute(null))
{
ImageSource2TapCommand.Execute(null);
}
}
}
My Page (HolidaysView) has this custom control along with Image click/tap command.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="PS.Views.HolidaysView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviors="clr-namespace:PS.Behaviors"
xmlns:templates="clr-namespace:PS.Views.Templates"
xmlns:viewModelBase="clr-namespace:PS.ViewModels.Base"
viewModelBase:ViewModelLocator.AutoWireViewModel="true">
<ContentPage.Content>
<StackLayout>
<templates:HeaderTemplate
HeightRequest="60"
ImageSource2="upload.png"
ImageSource2TapCommand="{Binding NavigateToCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
and binded view model of this page contains the command
public class HolidaysViewModel : ViewModelBase
{
public HolidaysViewModel()
{
}
public ICommand NavigateToCommand => new Command(async () => await NavigateTo());
private async Task NavigateTo()
{
await NavigationService.NavigateToAsync<HolidayRequestViewModel>();
}
}
It's not working. I don't know where I'm wrong.
Am I missing something?
I've researched alots on this but still I'm not able to find any solution uptill now.
Thank you!
To make it work you will need to do just a couple of changes.
First, in the ContenView class file remove the BindingContext = this; line.
Then you will need to add PropertyChanged handlers to both of your BindableProperty
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2),
typeof(string),
typeof(HeaderTemplate),
defaultValue: default(string),
propertyChanged: OnImageSourcePropertyChanged);
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(
propertyName: nameof(ImageSource2TapCommand),
returnType: typeof(ICommand),
declaringType: typeof(HeaderTemplate),
defaultValue: default(ICommand),
propertyChanged: OnTapCommandPropertyChanged);
If you can't spot the difference I am talking about these: OnImageSourcePropertyChanged and OnTapCommandPropertyChanged. The other changes in the Create method are not necessary I just added the propertyName so it's more clear.
You need to implement those two methods of course:
static void OnTapCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is HeaderTemplate headerTemplate && newValue is ICommand command)
{
headerTemplate.ImageSource2TapCommand = command;
}
}
static void OnImageSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderTemplate headerTemplate && newValue is string imageSource)
{
headerTemplate.ImageSource_2.Source = ImageSource.FromFile(imageSource);
}
}
With these changes you should be able to tap on your Image and navigate as you want.
The reason why?
Since you are Binding values on your "Main Page" to your Custom Control when the latter is first created these values are null, this is why you need to listen to the value changes and this is possible by adding the onPropertyChanged implementation on the Create methods.
There's also a very good explanation here in this post.
Your full class should look something similar to:
public partial class HeaderTemplate : ContentView
{
public HeaderTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty ImageSource2Property =
BindableProperty.Create(nameof(ImageSource2),
typeof(string),
typeof(HeaderTemplate),
defaultValue: default(string),
propertyChanged: OnImageSourcePropertyChanged);
public string ImageSource2
{
get => (string)GetValue(ImageSource2Property);
set => SetValue(ImageSource2Property, value);
}
public static readonly BindableProperty ImageSource2TapCommandProperty =
BindableProperty.Create(
propertyName: nameof(ImageSource2TapCommand),
returnType: typeof(ICommand),
declaringType: typeof(HeaderTemplate),
defaultValue: default(ICommand),
propertyChanged: OnTapCommandPropertyChanged);
public ICommand ImageSource2TapCommand
{
get => (ICommand)GetValue(ImageSource2TapCommandProperty);
set => SetValue(ImageSource2TapCommandProperty, value);
}
private void ImageSource2_Tapped(object sender, EventArgs e)
{
if (ImageSource2TapCommand == null) return;
if (ImageSource2TapCommand.CanExecute(null))
{
ImageSource2TapCommand.Execute(null);
}
}
static void OnTapCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is HeaderTemplate headerTemplate && newValue is ICommand command)
{
headerTemplate.ImageSource2TapCommand = command;
}
}
static void OnImageSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderTemplate headerTemplate && newValue is string imageSource)
{
headerTemplate.ImageSource_2.Source = ImageSource.FromFile(imageSource);
}
}
}
Hope this helps.-
Note: Try to follow the example posted in one of the other answers to change your TapEvent with the Command property.
TapGestureRecognizer class has a Command property accepting ICommand
I just tried it in a custom control similar to yours and is working fine.
So, for binding a ICommand to a TapGestureRecognizer, use the Command property, not the Tap event.
<Image
x:Name="ImageSource_2"
HeightRequest="50"
HorizontalOptions="EndAndExpand"
Source="{Binding ImageSource2}">
<Image.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding ImageSource2TapCommand}" />
</Image.GestureRecognizers>
</Image>
Reference Adding a tap gesture recognizer: Using ICommand
Set the Source of the BindingContext for the ICommand to your ContentPage.
MyViewCommand="{{Binding Path=BindingContext.NavigateToCommand, Source={x:Reference MyContentPage}}}"
Example:
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="MyApp.Views.PageWithCustomView"
xmlns:templates="clr-namespace:MyApp.Views.Templates"
x:Name="MyContentPage"
>
<ContentPage.Content>
<StackLayout>
<templates:HeaderTemplate
HeightRequest="60"
TemplateImageSource="upload.png"
TemplateTapCommand="{{Binding Path=BindingContext.NavigateToCommand, Source={x:Reference MyContentPage}}}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>

Databind a button click in a reusable Usercontrol

I'm using Mvvm/Prism to design an interface , i created a user control with code behind that have label textbox boutton (nothing mvvm)
the whole project is on mvvm pattern and i have a page Home that contains 3 of that user control.
In my userControl i databind the label content to a property (string)PROP1 the textbox content (string)PROP2 to a property (code behind) and in my HOME i just bind those properties to the propperties in my HomeViewModel
<local:userControl PROP1="{Binding Text}" PROP2="{Binding Name}" />
Now i want to databind the button click with same method , what want todo in my userControl page is
<Button Content="Button" Command="{Binding Klick}"/>
But i don't know how to store it in property so i can use it later
Here is how i want the final Home something like
<local:userControl Klick="{Binding Commandp}" PROP1="{Binding Textp}" PROP2="{Binding Namep}" />
and the HomeViewModel
class HomeViewModel : BindableBase
{
public ICommand Commandp{ get; private set; }
private readonly IRegionManager _regionManager;
public HomeViewModel (IRegionManager regionManager)
{
Commandp= new DelegateCommand(() => NavigateTo("Docs"));
}
private void NavigateTo(string url)
{
_regionManager.RequestNavigate(Regions.Contentregoin, url);
}
}
PS: I'm using the ViewModelLocator.AutoWireViewModel
That works the same way as PROP1 and PROP2 - create a dependency property and you're good to go.
public static readonly DependencyProperty KlickProperty = DependencyProperty.Register( nameof(Klick), typeof(ICommand), typeof(userControl), new PropertyMetadata( default(ICommand) ) );
public ICommand Klick
{
get { return (ICommand)GetValue( KlickProperty ); }
set { SetValue( KlickProperty, value ); }
}

Bind a custom view to page model in xamarin forms

I am trying to create a custom view that will be used as a header in some of the pages in the application. A custom view has a button to save info, and an image to show if the info was saved, but I can also receive info from the API if the info was saved. (this is a simplified version of the scenario)
So, I have MainPage.xaml (any page that will use the custom view)
ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Messages"
xmlns:controls="clr-namespace:Messages.Controls"
x:Class="Messages.MainPage">
<StackLayout Spacing="5">
<controls:HeaderMenu x:Name="menu" HorizontalOptions="FillAndExpand" VerticalOptions="Start" SaveCommand="{Binding MyCommand}" IsControlClosed="{Binding ControlClosedValue, Mode=TwoWay}" />
.....
</StackLayout>
MainPageViewModel.cs
public class MainPageViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; set; }
private bool _controlClosedvalue;
public bool ControlClosedValue
{
get => _controlClosedvalue;
set
{
_controlClosedvalue = value;
OnPropertyChanged(nameof(ControlClosedValue));
}
}
public MainPageViewModel()
{
MyCommand = new Command(MyCommandExecute);
_controlClosedvalue = false;
}
private void MyCommandExecute()
{
// do stuff
_controlClosedvalue = true; //change value to change the value of control
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
HeaderMenu.xaml
<Grid>
<Image Source="save.png" HeightRequest="25" WidthRequest="25">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="SaveImage_OnTapped" />
</Image.GestureRecognizers>
</Image>
<Image IsVisible="{Binding IsControlClosed}" Source="check.png" HeightRequest="30" WidthRequest="30" />
HeaderMenu.xaml.cs
public partial class HeaderMenu : ContentView
{
public HeaderMenu ()
{
InitializeComponent();
imgControlClosed.BindingContext = this;
}
public static readonly BindableProperty SaveCommandProperty =
BindableProperty.Create(nameof(SaveCommand), typeof(ICommand), typeof(HeaderMenu));
public static readonly BindableProperty IsControlClosedProperty =
BindableProperty.Create(nameof(IsControlClosed), typeof(bool), typeof(HeaderMenu), false, BindingMode.TwoWay, null, ControlClosed_OnPropertyChanged);
public ICommand SaveCommand
{
get => (ICommand) GetValue(SaveCommandProperty);
set => SetValue(SaveCommandProperty, value);
}
public bool IsControlClosed
{
get => (bool) GetValue(IsControlClosedProperty);
set => SetValue(IsControlClosedProperty, value);
}
private static void ControlClosed_OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is HeaderMenu control)
{
control.imgControlClosed.IsVisible = (bool)newValue;
}
}
private void SaveImage_OnTapped(object sender, EventArgs e)
{
if (SaveCommand != null && SaveCommand.CanExecute(null))
{
SaveCommand.Execute(null);
}
}
}
So, what I need is that when the save command is tapped to execute some code in the page that is using control, and binding of SaveCommand works as expected. But after the code is executed, or in some different cases, I wish to change the property in the page model and this should change the property on the custom view, but this does not work.
Does anyone know what is wrong with this code?
If I just put True or False when consuming control it works.
<controls:HeaderMenu x:Name="menu" HorizontalOptions="FillAndExpand" VerticalOptions="Start" SaveCommand="{Binding MyCommand}" IsControlClosed="True" />
But it does not work when binding it to the property.
I have found out what an issue was. A stupid mistake, I was setting the value of the variable instead of property.
In the main page view model, instead of
_controlClosedvalue = false; // or true
it should be
ControlClosedValue = false; // or true

Categories