I want to apply some validation on the entry input, I went to the docs page of the attached behaviors
and did this:
public enum TextType { Email, Phone, }
public static class Validator
{
public static readonly BindableProperty TextTypeProperty = BindableProperty.CreateAttached(
"TextType", typeof(TextType), typeof(Validator), TextType.Email, propertyChanged: ValidateText);
public static TextType GetTextType(BindableObject view)
{
return (TextType)view.GetValue(TextTypeProperty);
}
public static void SetTextType(BindableObject view, TextType textType)
{
view.SetValue(TextTypeProperty, textType);
}
private static void ValidateText(BindableObject bindable, object oldValue, object newValue)
{
var entry = bindable as Entry;
entry.TextChanged += Entry_TextChanged;
}
private static void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
var entry = sender as Entry;
bool isValid = false;
switch (GetTextType(sender as Entry))
{
case TextType.Email:
isValid = e.NewTextValue.Contains("#");
break;
case TextType.Phone:
isValid = Regex.IsMatch(e.NewTextValue, #"^\d+$");
break;
default:
break;
}
if (isValid)
entry.TextColor = Color.Default;
else
entry.TextColor = Color.Red;
}
}
in XAML:
<Entry beh:Validator.TextType="Email" Placeholder="Validate Email"/>
but it doesn't work, the setter nor the propertyChanged call back are never called,
also what is the difference between this "Attached behavior" and the attached property, the two pages are pretty identical
what does the logic have to do with the propertyChanged method is not called?
From this MSDN Attached Behaviors,you can see that An attached property can define a propertyChanged delegate that will be executed when the value of the property changes.
According to your code, you set TextType=TextType.Email firstly, then you also set
<Entry beh:Validator.TextType="Email" Placeholder="Validate Email"/>
Validator.TextType="Email", the attached property doesn't change, so PropertyChanged method is not call.
You can modify your code like this, then you will find the propertychanged will be called.
<Entry beh:Validator.TextType="Phone" Placeholder="Validate Email"/>
Comparing to the sample you ignore most of the logic from OnAttachBehaviorChanged in your ValidateText and it is quite necessary:
static void OnAttachBehaviorChanged (BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null) {
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior) {
entry.TextChanged += OnEntryTextChanged;
} else {
entry.TextChanged -= OnEntryTextChanged;
}
}
Considering the differences, attached properties are something that is applied to a specific control, you can apply behavior technically to any control though probably it will work only on some of them but it may work on multiple control types properly.
Related
In a Xamarin.Forms project, I'm trying to repeatedly translate an image from a position A(x,y) to a position B(x,y) and back, from B to A. To achieve this, I read that is possible to customize behaviors.
I extend Behavior class, overriding OnAttachedTo and OnDetachingFrom. And in the OnAttachedTo method I start a Task which repeatedly does the two translations.
This is my Behavior class:
public class MoveImageBehavior : Behavior<Image>
{
private Image _Image = null;
public static readonly BindableProperty AnimatedProperty = BindableProperty.Create("Animated", typeof(bool), typeof(ImageAnimatedBehavior), defaultValue: false);
public bool Animated
{
get { return (bool)GetValue(AnimatedProperty); }
set { SetValue(AnimatedProperty, value); }
}
protected override void OnAttachedTo(Image image)
{
base.OnAttachedTo(image);
_Image = image;
Animated = true;
Task.Run(AnimateImage);
}
protected override void OnDetachingFrom(Image image)
{
base.OnDetachingFrom(image);
_Image = null;
}
private async void AnimateImage()
{
while (_Image != null && Animated)
{
await _Image.TranslateTo(100, 100, 1000);
await _Image.TranslateTo(0, 0, 1000);
}
}
}
The image in the xaml file:
<ContentView>
<Grid>
<Image x:Name="image_translating" Source="my_icon" Aspect="AspectFit">
<Image.Behaviors>
<behaviors:MoveImageBehavior Animated="{Binding ImageTranslating}" BindingContext="{Binding BindingContext, Source={x:Reference image_translating}}"/>
</Image.Behaviors>
</Image>
</Grid>
</ContentView>
The Image repeatedly translates correctly as I want, but I'm not able to stop the while routine. The property binding doesn't work when Animated is set to false in the ViewModel and OnDetachingFrom is never called.
What am I doing wrong? Any suggestions?
Through the document, we can see that:
The OnDetachingFrom method is fired when the behavior is removed from
the control. This method receives a reference to the control to which
it is attached, and is used to perform any required cleanup. For
example, you could unsubscribe from an event on a control to prevent
memory leaks.
It will only fired when you remove the behavior from the image. I would give you an example about how to stop the animation:
I defined an bool property in the code behind to control stop or not stop:
public bool showA = true;
And I add a button as an example to stop the animation:
private void Button_Clicked(object sender, EventArgs e)
{
showA = !showA;
if (showA)
{
image_translating.Behaviors.Add(new MoveImageBehavior());
}
else
{
var toRemove = image_translating.Behaviors.FirstOrDefault(b => b is MoveImageBehavior);
if (toRemove != null)
{
image_translating.Behaviors.Remove(toRemove);
}
}
}
Also in your OnDetachingFrom method, do not set the image to null, it will cause a null expection, just set the Animated to false :
protected override void OnDetachingFrom(Image image)
{
base.OnDetachingFrom(image);
Animated = false;
}
You can convert my click event to some binding in your project and make it work.
Refer: creating-a-xamarinforms-behaviorer
Could you please let me know how can I recognize long press gesture in Xamarin Forms application?
A few days before I used TapGestureRecognizer
TapGestureRecognizer imageTap = new TapGestureRecognizer();
imageTap.Tapped += (sender, args) => this.OnClickImage;
image.GestureRecognizers.Add(imageTap);
But I don't know how to make long press gesture according to this thread from xamarin forum
It should looks something like this, but it does not work.
var dumpParam = new RelayGesture((g, x) => DisplayAlert("Title", "Hello message", "Cancel"));
book.Cover.SetValue(Gestures.InterestsProperty, new GestureCollection() {
new GestureInterest
{
GestureType = GestureType.LongPress
GestureCommand = // what should I set?
GestureParameter = dumpParam
}
});
How to set my custom handler method?
You can do it cross platform way by attaching the below behavior, as long as it is Xamarin.Forms.Button or a sub-type of it.
using System;
using System.Threading;
using System.Windows.Input;
using Xamarin.Forms;
namespace App.Controls.Behaviors
{
public class LongPressBehavior : Behavior<Button>
{
private readonly object _syncObject = new object();
private const int Duration = 1000;
//timer to track long press
private Timer _timer;
//the timeout value for long press
private readonly int _duration;
//whether the button was released after press
private volatile bool _isReleased;
/// <summary>
/// Occurs when the associated button is long pressed.
/// </summary>
public event EventHandler LongPressed;
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command),
typeof(ICommand), typeof(LongPressBehavior), default(ICommand));
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(LongPressBehavior));
/// <summary>
/// Gets or sets the command parameter.
/// </summary>
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
/// <summary>
/// Gets or sets the command.
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
protected override void OnAttachedTo(Button button)
{
base.OnAttachedTo(button);
this.BindingContext = button.BindingContext;
button.Pressed += Button_Pressed;
button.Released += Button_Released;
}
protected override void OnDetachingFrom(Button button)
{
base.OnDetachingFrom(button);
this.BindingContext = null;
button.Pressed -= Button_Pressed;
button.Released -= Button_Released;
}
/// <summary>
/// DeInitializes and disposes the timer.
/// </summary>
private void DeInitializeTimer()
{
lock (_syncObject)
{
if (_timer == null)
{
return;
}
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_timer.Dispose();
_timer = null;
Debug.WriteLine("Timer disposed...");
}
}
/// <summary>
/// Initializes the timer.
/// </summary>
private void InitializeTimer()
{
lock (_syncObject)
{
_timer = new Timer(Timer_Elapsed, null, _duration, Timeout.Infinite);
}
}
private void Button_Pressed(object sender, EventArgs e)
{
_isReleased = false;
InitializeTimer();
}
private void Button_Released(object sender, EventArgs e)
{
_isReleased = true;
DeInitializeTimer();
}
protected virtual void OnLongPressed()
{
var handler = LongPressed;
handler?.Invoke(this, EventArgs.Empty);
if (Command != null && Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
public LongPressBehavior()
{
_isReleased = true;
_duration = Duration;
}
public LongPressBehavior(int duration) : this()
{
_duration = duration;
}
private void Timer_Elapsed(object state)
{
DeInitializeTimer();
if (_isReleased)
{
return;
}
Device.BeginInvokeOnMainThread(OnLongPressed);
}
}
}
In the XAML UI:
<Button x:Name="MyButton" Text="Long Press Me!">
<Button.Behaviors>
<behaviors:LongPressBehavior LongPressed="MyButton_LongPressed"/>
</Button.Behaviors>
</Button>
XAML UI with Command Binding:
<Button x:Name="MyButton" Text="Long Press Me!">
<Button.Behaviors>
<behaviors:LongPressBehavior Command="{Binding CommandInViewModel}"/>
</Button.Behaviors>
</Button>
Make use of XLabs.Forms nuget package, which make long press and other gesture in PCL code only.
Use of XLabs.Forms package will reduce the need of custom rendering in individual platforms...
Add XAML code in .xaml file and attached event handler in .xaml.cs file..
It is working fine in Android..
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MultiImage.Page1"
xmlns:lc="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
xmlns:lb="clr-namespace:XLabs.Forms.Behaviors;assembly=XLabs.Forms">
<ContentPage.Content>
<lc:GesturesContentView ExcludeChildren="False" GestureRecognized="GesturesContentView_GestureRecognized">
<lb:Gestures.Interests>
<lb:GestureCollection>
<lb:GestureInterest GestureType="SingleTap"/>
<lb:GestureInterest GestureType="LongPress"/>
<lb:GestureInterest GestureType="DoubleTap"/>
</lb:GestureCollection>
</lb:Gestures.Interests>
<Image Source="Myimage.png" Aspect="AspectFit" HeightRequest="100"/>
</lc:GesturesContentView>
</ContentPage.Content>
C# backend code:
private void GesturesContentView_GestureRecognized(object sender, GestureResult e)
{
switch (e.GestureType)
{
case GestureType.LongPress:
//Add code here
break;
case GestureType.SingleTap:
// Add code here
break;
case GestureType.DoubleTap:
// Add code here
break;
default:
break;
}
I recently came across this problem and found a useful post on the topic https://alexdunn.org/2017/12/27/xamarin-tip-xamarin-forms-long-press-effect/
This makes use of the RoutingEffect and goes through an example of how to create both iOS and Android implementation. The simplicity of this allows you to attach it to any view in your app without recreating code.
Surfing the internet I found the solution. There are few steps which you should reproduce.
1) Inherit the control you need the gestures on (i.e. if you want to add gesture to Xamarin.Forms.Image, create you own ImageWithLongPressGesture class).
public class ImageWithLongPressGesture : Xamarin.Forms.Image
{
public EventHandler LongPressActivated;
public void HandleLongPress(object sender, EventArgs e)
{
//Handle LongPressActivated Event
}
}
2) Expose public events for the needed gestures.
3) Create a Renderer for each platform.
4) In the Renderer, handle the gestures and bubble them to your control.
[assembly: ExportRenderer(typeof(ImageWithLongPressGesture), typeof(LongPressGestureRecognizerImageRenderer))]
namespace App1.Droid.DroidRenderers
{
public class LongPressGestureRecognizerImageRenderer : ImageRenderer
{
ImageWithLongPressGesture view;
public LongPressGestureRecognizerImageRenderer()
{
this.LongClick += (sender, args) => {
Toast.MakeText(this.Context, "Long press is activated.", ToastLength.Short).Show();
};
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if(e.NewElement != null)
{
view = e.NewElement as ImageWithLongPressGesture;
}
}
}
}
This solution is a hybrid of answer on xamarin forms forum and Touch and Gestures presentation by Telerik.
//To Add Programatically:
StackLayout _Containter = new StackLayout();
StackLayout _StackLayout = new StackLayout();
_StackLayout.Children.Add(new Label(){Text="Execute Me"});
GesturesContentView Gv = new GesturesContentView();
_StackLayout.SetValue(XLabs.Forms.Behaviors.Gestures.InterestsProperty, new GestureCollection() {
new GestureInterest() { GestureType = GestureType.SingleTap },
new GestureInterest() { GestureType = GestureType.LongPress },
new GestureInterest() { GestureType = GestureType.DoubleTap }
});
Gv.GestureRecognized += Gv_GestureRecognized;
Gv.ExcludeChildren = false;
Gv.Content = _StackLayout;
_Containter.Children.Add(Gv);
In order to get this to work properly on iOS, you need to use XLabs.Forms.XFormsAppiOS.Init(); in your AppDelegate.cs file just before the LoadApplication(new App()); statement.
The posted code from #zafar works if you register BindingContextChanged event.
(My post is only an add, to the original post from #zafar.)
Problem was:
if using CommandParameter="{Binding .}" resulting Parameter was always null.
You need to Register BindingContextChanged event in the OnAttachedTo function.
[...]
protected override void OnAttachedTo(Button button)
{
base.OnAttachedTo(button);
this.BindingContext = button.BindingContext;
button.BindingContextChanged += handleBindingContextChanged; //this was missing
button.Pressed += Button_Pressed;
button.Released += Button_Released;
}
private void handleBindingContextChanged(object sender, EventArgs e)
{
this.BindingContext = ((Button)sender).BindingContext;
}
protected override void OnDetachingFrom(Button button)
{
base.OnDetachingFrom(button);
this.BindingContext = null;
button.Pressed -= Button_Pressed;
button.Released -= Button_Released;
button.BindingContextChanged -= handleBindingContextChanged; //also don't forget this
}
[...]
sry for the errors, this is my first post (not enough Reputation for commenting).
hopefully you can help me. First of all, let me explain what my problem is.
I have two ViewModels. The first one has e.g. stored information in several textboxes.
For example
private static string _tbxCfgLogfile;
public string TbxCfgLogfile
{
get { return _tbxCfgLogfile; }
set
{
_tbxCfgLogfile = value;
NotifyOfPropertyChange(() => TbxCfgLogfile);
}
}
The other ViewModel has a Button where i want to save this data from the textboxes.
It does look like this
public bool CanBtnCfgSave
{
get
{
return (new PageConfigGeneralViewModel().TbxCfgLogfile.Length > 0 [...]);
}
}
public void BtnCfgSave()
{
new Functions.Config().SaveConfig();
}
How can i let "CanBtnCfgSave" know that the condition is met or not?
My first try was
private static string _tbxCfgLogfile;
public string TbxCfgLogfile
{
get { return _tbxCfgLogfile; }
set
{
_tbxCfgLogfile = value;
NotifyOfPropertyChange(() => TbxCfgLogfile);
NotifyOfPropertyChange(() => new ViewModels.OtherViewModel.CanBtnCfgSave);
}
}
It does not work. When i do remember right, i can get the data from each ViewModel, but i cannot set nor Notify them without any effort. Is that right? Do i have to use an "Event Aggregator" to accomplish my goal or is there an alternative easier way?
Not sure what you are doing in your viewmodels - why are you instantiating viewmodels in property accessors?
What is this line doing?
return (new PageConfigGeneralViewModel().TbxCfgLogfile.Length > 0 [...]);
I can't be sure from your setup as you haven't mentioned much about the architecture, but sincce you should have an instance of each viewmodel, there must be something conducting/managing the two (or one managing the other)
If you have one managing the other and you are implementing this via concrete references, you can just pick up the fields from the other viewmodel by accessing the properties directly, and hooking the PropertyChanged event of the child to notify the parent
class ParentViewModel : PropertyChangedBase
{
ChildViewModel childVM;
public ParentViewModel()
{
// Create child VM and hook up event...
childVM = new ChildViewModel();
childVM.PropertyChanged = ChildViewModel_PropertyChanged;
}
void ChildViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// When any properties on the child VM change, update CanSave
NotifyOfPropertyChange(() => CanSave);
}
// Look at properties on the child VM
public bool CanSave { get { return childVM.SomeProperty != string.Empty; } }
public void Save() { // do stuff }
}
class ChildViewModel : PropertyChangedBase
{
private static string _someProperty;
public string SomeProperty
{
get { return _someProperty; }
set
{
_someProperty = value;
NotifyOfPropertyChange(() => SomeProperty);
}
}
}
Of course this is a very direct way to do it - you could just create a binding to CanSave on the child VM if that works, saving the need to create the CanSave property on the parent
In my windows phone app, I need to track some events to get a good flow. But I'm not sure how to handle them in good sequence.
What needs to be done at startup of the app:
Main view is loaded and corresponding view model instantiated
In the constructor of the view model I initiate a login sequence that signals when completed with an eventhandler
Now when the login sequence has finished AND the view is completely loaded I need to startup another sequence.
But here is the problem, the order of these 2 events 'completing' is not always the same...
I've use the EventToCommand from MVVMLight to signal the view model that the view has 'loaded'.
Any thoughts on how to synchronize this.
As you should not use wait handles or something similar on the UI thread. You will have to sync the two method using flags in your view model and check them before progressing.
So, implement two boolean properties in your view model. Now when the login dialog is finished set one of the properties (lets call it IsLoggedIn) to true, and when the initialization sequence is finished you set the other property (how about IsInitialized) to true. The trick now lies in the implementation of the setter of these two properties:
#region [IsInitialized]
public const string IsInitializedPropertyName = "IsInitialized";
private bool _isInitialized = false;
public bool IsInitialized {
get {
return _isInitialized;
}
set {
if (_isInitialized == value)
return;
var oldValue = _isInitialized;
_isInitialized = value;
RaisePropertyChanged(IsInitializedPropertyName);
InitializationComplete();
}
}
#endregion
#region [IsLoggedIn]
public const string IsLoggedInPropertyName = "IsLoggedIn";
private bool _isLoggedIn = false;
public bool IsLoggedIn {
get {
return _isLoggedIn;
}
set {
if (_isLoggedIn == value)
return;
var oldValue = _isLoggedIn;
_isLoggedIn = value;
RaisePropertyChanged(IsLoggedInPropertyName);
InitializationComplete();
}
}
#endregion
public void InitializationComplete() {
if (!(this.IsInitialized && this.IsLoggedIn))
return;
// put your code here
}
Alternatively you can remove the InitializationComplete from the setters and change InitializationComplete to:
public void InitializationComplete() {
// put your code here
}
Then subscribe to the 'PropertyChanged' event use the following implementation:
private void Class1_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
if (e.PropertyName == IsInitializedPropertyName || e.PropertyName == IsLoggedInPropertyName) {
if (this.IsInitialized && this.IsLoggedIn)
InitializationComplete();
}
}
In a SL4 application i need to restyle my TabItems (actually add a button in the header).
So i took the TabItem's control template from here and added the functionality i wanted.
This seems to work fine, (i could dynamically add tabitems) with one exception:
i think this posted control template is behaving somehow "arbitrary": every time the mouse hoovers over a non selected TabItem header, this gets selected WHITHOUT clicking!! (afaik this is not the default behavior: the user user has to click a header to make this tabitem the selected one).
I tried to find why it is behaving like this, with no luck!
Is there someone who can enlighten my darkness???
Thanks in advance!
Well it turns out the error was not in the control template but in the class, the style was applied to.
In detail: the class the style was applied to is the following (in it you will see my comment about the "wrong behavior"):
public class WorkspaceViewModel : TabItem
{
public WorkspaceViewModel()
{
DefaultStyleKey = typeof(WorkspaceViewModel);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button closeButtonSel = base.GetTemplateChild("PART_CloseTopSelected") as Button;
Button closeButtonUnsel = base.GetTemplateChild("PART_CloseTopUnSelected") as Button;
if (closeButtonSel != null)
closeButtonSel.Click += new RoutedEventHandler(closeButtonSel_Click);
if (closeButtonUnsel != null)
closeButtonUnsel.Click += new RoutedEventHandler(closeButtonSel_Click);
//this part is causing the effect i was complaining about!
//and has to be removed
this.MouseEnter += delegate(object sender, MouseEventArgs e)
{
IsSelected = true;
};
}
void closeButtonSel_Click(object sender, RoutedEventArgs e)
{
//this is the close request method used in the CloseTabItemCommand
OnRequestClose();
}
#region CloseTabItemCommand
private RelayCommand closeTabItemCommand;
public ICommand CloseTabItemCommand
{
get
{
if (this.closeTabItemCommand == null)
this.closeTabItemCommand = new RelayCommand(p => this.OnRequestClose(), p => this.CanCloseTabItem());
return this.closeTabItemCommand;
}
}
private bool CanCloseTabItem()
{
return true;
}
public event EventHandler RequestClose;
private void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
#endregion
}