How can I check the setting of a bindable property inside an IsPropertyChanged method in Xamarin.Forms? - xamarin

I have this code that sets a label to one or another color depending on a property called IsEnabled:
public class LinkLabel : Label
{
public LinkLabel()
{
PropertyChanged += LinkLabel_PropertyChanged;
SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
}
private void LinkLabel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsEnabled")
{
var label = sender as LinkLabel;
var newValue = label.IsEnabled;
if ((bool)newValue)
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelDisabledColor");
}
}
}
What I would like to do is to add another property called IsImportant
so that if that is set to true and IsEnabled = true, then the LinkLabelColor would be set to red
Can someone give me some suggestions on how I can do this? I do know how to add things like bindable properties but I am not sure in this case how to combine it with the code that I already have?

Add a new property
public static readonly BindableProperty IsImportantProperty = BindableProperty.Create(nameof(IsImportant), typeof(bool), typeof(CustomLabel), false,/*Can remove property changed as well*/ propertyChanged: IsImportantPropertyChanged);
public bool IsImportant
{
get { return (bool)GetValue(IsImportantProperty); }
set { SetValue(IsImportantProperty, value); }
}
New Method UPDATE
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (propertyName == IsEnabledProperty.PropertyName || propertyName == IsImportantProperty.PropertyName)
{
if (this.IsImportant && this.IsEnabled)
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelDisabledColor");
}
}

Some issues with the other answer not working. I revisited the code and came up with this which works good:
public class LinkLabel : Label
{
public LinkLabel()
{
SetDynamicResource(Label.FontFamilyProperty, "Default-Regular");
SetDynamicResource(Label.FontSizeProperty, "LabelTextFontSize");
SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
VerticalOptions = LayoutOptions.CenterAndExpand;
VerticalTextAlignment = TextAlignment.Center;
}
public static readonly BindableProperty IsImportantProperty =
BindableProperty.Create(nameof(IsImportant), typeof(bool), typeof(LinkLabel), false);
public bool IsImportant
{
get { return (bool)GetValue(IsImportantProperty); }
set { SetValue(IsImportantProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IsEnabledProperty.PropertyName ||
propertyName == IsImportantProperty.PropertyName)
{
if (this.IsEnabled) {
if (this.IsImportant)
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelImportantColor");
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
}
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelDisabledColor");
}
}
}

Related

Convert code behind arguments into mvvm xamarin

got a question on how to convert this code behind into a mvvm style, Here's a sample
void CameraView_MediaCaptured(object sender, MediaCapturedEventArgs e)
{
switch (cameraView.CaptureMode)
{
default:
case CameraCaptureMode.Default:
case CameraCaptureMode.Photo:
previewVideo.IsVisible = false;
previewPicture.IsVisible = true;
previewPicture.Rotation = e.Rotation;
previewPicture.Source = e.Image;
doCameraThings.Text = "Snap Picture";
break;
case CameraCaptureMode.Video:
previewPicture.IsVisible = false;
previewVideo.IsVisible = true;
previewVideo.Source = e.Video;
doCameraThings.Text = "Start Recording";
break;
}
}
A combination of Jason's and Karas's answer .
Bind with the corresponding properties between xaml and viewmodel .
//xaml
IsVisible = "{Binding IsVisible}"
Implement INotifyPropertyChanged inside viewmodel .
public class ViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool isVisible;
public bool IsVisible {
get
{
return isVisible;
}
set
{
isVisible = value;
NotifyPropertyChanged();
}
}
Replace the event with EventToCommandBehavior and change the properties value.
//xaml
<xct:EventToCommandBehavior
EventName="MediaCaptured"
Command="{Binding MyCommand}" />
//viewmodel
public ICommand MyCommand { get; set; }
public ViewModel()
{
MyCommand = new Command((obj)=> {
var cameraView = obj as CameraView;
switch (cameraView.CaptureMode)
{
default:
case CameraCaptureMode.Default:
case CameraCaptureMode.Photo:
IsVisible = false;
break;
case CameraCaptureMode.Video:
IsVisible = false;
break;
}
});
}
First you create a BaseViewModel, which you can now inherit on every other ViewModel. Then your ViewModel and bind it via BindingContext = new YourPageVM() to your page via YourPage.cs. You can now create properties in your ViewModel and Binding them in the XAML. e.g .:
Here Is the BaseViewModel:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string
propertyName =
"")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
}
//In Your VM:
public class YourPageVM : BaseViewModel
{
bool isVisPreVideo;
public bool IsVisPreVideo{
get=> isVisPreVideo;
set=> SetProperty(ref isVisPreVideo,value);}
//set the Value in Constructor or in Your Method
public YourPageVM()
{
IsVisPreVideo = false;
}
//........
}
//At Xaml:
xmlns:viewmodel="clr-namespace:YourProject.ViewModel"
x:DataType="viewmodel:YourPageVM"
IsVisible = "{Binding IsVisPreVideo}"
And this you can do also with the other values Rotation, Source and Text.

Is PropertyChanged += LinkLabel_PropertyChanged; same as protected override void OnPropertyChanged(string propertyName = null)

In a Xamarin template like this. I think there are two ways to check if a property has changed.
Adding PropertyChanged += LinkLabel_PropertyChanged;
Overriding, calling base
If I want to do something when more than one property has changed is there any difference between these two ways of calling a method?
public class LinkLabel : Label
{
public LinkLabel()
{
PropertyChanged += LinkLabel_PropertyChanged;
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
// Check property name and do action here
}
private void LinkLabel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Check property name and do action here
}
}
For reference here is what I coded and I am wondering if that's a good solution:
public class LinkLabel : Label
{
public LinkLabel()
{
SetDynamicResource(Label.FontFamilyProperty, "Default-Regular");
SetDynamicResource(Label.FontSizeProperty, "LabelTextFontSize");
SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
VerticalOptions = LayoutOptions.CenterAndExpand;
VerticalTextAlignment = TextAlignment.Center;
}
public static readonly BindableProperty IsImportantProperty =
BindableProperty.Create(nameof(IsImportant), typeof(bool), typeof(LinkLabel), false);
public bool IsImportant
{
get { return (bool)GetValue(IsImportantProperty); }
set { SetValue(IsImportantProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IsEnabledProperty.PropertyName ||
propertyName == IsImportantProperty.PropertyName)
{
if (this.IsEnabled) {
if (this.IsImportant)
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelImportantColor");
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
}
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelDisabledColor");
}
}
}
Yes, the difference is that registering for the PropertyChanged event works from outside, overriding the protected(!) OnPropertyChanged method works only from within derived classes of Label.
So you would normally only create a new derived LinkLabel class if you want to change the behavior of the label. There, you'd override the OnPropertyChanged (if you need to).
If you want to get informed about a change in your main form, you would register the event directly there. No need to create a derived class.

How to set a null value to TimePicker?

I use the TimePicker and appear with a default value of "12:00 AM", I want to set the field in blank.
<TimePicker x:Name="timepicker" />
You must create your own class of TimePicker that will allow a NullableTime on XAML.
The Class should be as following:
public class MyTimePicker : TimePicker
{
private string _format = null;
public static readonly BindableProperty NullableDateProperty =
BindableProperty.Create<MyTimePicker, TimeSpan?>(p => p.NullableTime, null);
public TimeSpan? NullableTime
{
get { return (TimeSpan?)GetValue(NullableDateProperty); }
set { SetValue(NullableDateProperty, value); UpdateTime(); }
}
private void UpdateTime()
{
if (NullableTime.HasValue) { if (null != _format) Format = _format; Time = NullableTime.Value; }
else { _format = Format; Format = "pick ..."; }
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
UpdateTime();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Time") NullableTime = Time;
}
}
Then on XAML you should create your control and use it as below:
<local:MyTimePicker NullableTime="{x:Null}" />
If you use this sample you will see that the default value should be pick...

Image - long tap

Is there a way to detect a long tap on an image control in Xamarin Forms?
I'm using the carousel view to display images and would like to give the option to delete them by selecting with a long tap.
Based on your suggestions in the comments this is what I did:
(The purpose of the control is to be able to select an Image with a LongTap)
I defined my own Image control in the PCL:
IsSelected BindableProperty.
LongTap event.
public class MyImage:Image
{
private BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected", typeof(bool), typeof(MyImage), false);
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
public event EventHandler LongClick;
public void OnLongClick()
{
IsSelected = !IsSelected;
if(IsSelected)
{
Opacity = 0.5;
}
else
{
Opacity = 1;
}
if (LongClick != null)
{
LongClick(this, EventArgs.Empty);
}
}
}
And this is my custom renderer: (Defined in the Android project)
[assembly: ExportRenderer(typeof(MyImage), typeof(MyImageRenderer))]
namespace PRISMCarouselView.Droid.Renderes
{
public class MyImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
MyImage myImage = e.NewElement as MyImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
myImage.OnLongClick();
};
}
}
}
}
Edit 1:
Here's a slightly updated version, I use the BindingPropertyChangedDelegate to change the opacity of the image:
public class SelectableImage : Image
{
public SelectableImage()
{
}
private static BindableProperty IsSelectedProperty = BindableProperty.Create("IsSelected",
typeof(bool),
typeof(SelectableImage),
false, BindingMode.Default, null, (sender, o1, o2) => {
SelectableImage imageControl = sender as SelectableImage;
if(imageControl != null)
{
if(imageControl.IsSelected)
{
imageControl.Opacity = 0.5;
}else
{
imageControl.Opacity = 1;
}
}
});
public bool IsSelected {
get {
return (bool)GetValue(IsSelectedProperty);
}
set {
SetValue(IsSelectedProperty, value);
}
}
}
And the renderer:
[assembly: ExportRenderer(typeof(SelectableImage), typeof(SelectableImageRenderer))]
namespace Muserma.Apps.Droid.Renderer
{
public class SelectableImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (Control != null)
{
ImageView androidSource = Control as ImageView;
SelectableImage selectableImage = e.NewElement as SelectableImage;
androidSource.LongClick += (object sender, LongClickEventArgs ee) =>
{
selectableImage.IsSelected = !selectableImage.IsSelected;
};
}
}
}
}

Create an ICommand Bindable property on Xamarin Forms

I have a custom checkbox control that I created with an ICommand property and the corresponding bindable property (my checkbox is a Xamarin.Forms XAML Page), the code is:
CheckBox.xaml
<Image x:Name="imgCheckBox"
WidthRequest="20"
HeightRequest="20"/>
<Label x:Name="lblCheckBox"
TextColor="Black"
VerticalOptions="CenterAndExpand"/>
<TapGestureRecognizer Tapped="OnCheckBoxTapped"/>
CheckBox.xaml.cs
public partial class CheckBox : ContentView
{
private static ImageSource uncheckedImage;
private static ImageSource checkedImage;
public CheckBox()
{
InitializeComponent();
uncheckedImage = ImageSource.FromResource("cbUnchecked.png");
checkedImage = ImageSource.FromResource("cbChecked.png");
imgCheckBox.Source = uncheckedImage;
}
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create<CheckBox, bool>(
checkbox =>
checkbox.IsChecked,
false,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, newValue);
}
});
public bool IsChecked
{
set { SetValue(IsCheckedProperty, value); }
get { return (bool)GetValue(IsCheckedProperty); }
}
void OnCheckBoxTapped(object sender, EventArgs args)
{
IsChecked = !IsChecked;
if (IsChecked)
{
imgCheckBox.Source = checkedImage;
}
else
{
imgCheckBox.Source = uncheckedImage;
}
}
public static readonly BindableProperty CheckBoxCommandProperty =
BindableProperty.Create<CheckBox, ICommand>(
checkbox =>
checkbox.CheckBoxCommand,
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
CheckBox checkbox = (CheckBox)bindable;
EventHandler<bool> eventHandler = checkbox.CheckedChanged;
if (eventHandler != null)
{
eventHandler(checkbox, checkbox.IsChecked);
}
});
public event EventHandler<bool> CheckedChanged;
public ICommand CheckBoxCommand
{
get { return (ICommand)GetValue(CheckBoxCommandProperty); }
set { SetValue(CheckBoxCommandProperty, value); }
}
}
This checkbox implementation is on another Page called TermsAndConditionsPage, that is also a a Xamarin.Forms XAML Page, the code of the implementation is:
<toolkit:CheckBox Text="{Binding txtCheckBox}"
FontSize="Small"
CheckBoxCommand="{Binding OnCheckBoxTapChanged}"
IsChecked="{Binding IsCheckedChanged, Mode=TwoWay}"/>
<Button Text="Next"
Command="{Binding Next_OnClick}"
IsEnabled="{Binding Next_IsEnabled}"
HorizontalOptions="CenterAndExpand"
Clicked="OnNextClicked"/>
The Code Behind of this page is empty (Constructur with InitializeComponent()).
I also have the ViewModel of this page with this code:
TermsAndConditionsViewModel.cs
private string _txtCheckBox;
public string txtCheckBox
{ get { return _txtCheckBox; }
set
{
_txtCheckBox = value;
OnPropertyChanged("txtCheckBox");
}
}
private bool _Next_IsEnabled;
public bool Next_IsEnabled
{
get { return _Next_IsEnabled; }
set
{
_Next_IsEnabled = value;
OnPropertyChanged("Next_IsEnabled");
}
}
private bool _IsCheckedChanged;
public bool IsCheckedChanged
{
get { return _IsCheckedChanged; }
set
{
_IsCheckedChanged = value;
OnPropertyChanged("IsCheckedChanged");
}
}
public ICommand Next_OnClick { get; set; }
public ICommand OnCheckBoxTapChanged { get; set; }
public TermsAndConditionsViewModel()
{
txtCheckBox = "I agree with the terms and conditions";
Next_OnClick = new Command(NextClicked);
OnCheckBoxTapChanged = new Command(CheckBoxTapped);
}
private void CheckBoxTapped()
{
if (IsCheckedChanged)
{ Next_IsEnabled = true; }
else
{ Next_IsEnabled = false; }
}
private void NextClicked()
{ App.Current.MainPage = new Views.HelloWorld(); }
#region INPC
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
Now, the question time: the problem I'm having is the CheckBoxTapped Command is not working, I mean, it doesn't do anything, although the checkbox image changes every time I touch it, it does not change the Next_IsEnabled property of my button. I'd like to know what I am missing here to make this command work properly.
EDIT
What I'm looking for is a Command that behaves similarly to the one that Buttons have.
Thanks all for your time!
Since the original answer is now obsolete, here is the new method:
using System.Windows.Input;
public partial class MyControlExample : ContentView
{
// BindableProperty implementation
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(MyControlExample), null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Helper method for invoking commands safely
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
public MyControlExample()
{
InitializeComponent();
}
// this is the command that gets bound by the control in the view
// (ie. a Button, TapRecognizer, or MR.Gestures)
public Command OnTap => new Command(() => Execute(Command));
}
Something like that (pseudocode):
public class YourClassName : View
{
public YourClassName()
{
var gestureRecognizer = new TapGestureRecognizer();
gestureRecognizer.Tapped += (s, e) => {
if (Command != null && Command.CanExecute(null)) {
Command.Execute(null);
}
};
var label = new Label();
label.GestureRecognizers.Add(gestureRecognizer);
}
public static readonly BindableProperty CommandProperty =
BindableProperty.Create<YourClassName, ICommand>(x => x.Command, null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}

Resources