I have a class for animation behavior like:
public class BaseAnimationBehavior : Behavior<View>
{
protected View associatedObject;
protected Easing _easingFunction;
private readonly BindableProperty EasingFunctionProperty = BindableProperty.Create(nameof(EasingFunction), typeof(string), typeof(BaseAnimationBehavior), defaultValue: "Linear", propertyChanged: (b, o, n) => OnEasingFunctionChanged(b, (string)o, (string)n));
private readonly BindableProperty ScaleProperty = BindableProperty.Create(nameof(Scale), typeof(double), typeof(BaseAnimationBehavior), defaultValue: 1.25);
public string EasingFunction
{
get { return (string)GetValue(EasingFunctionProperty); }
set { SetValue(EasingFunctionProperty, value); }
}
public double Scale
{
get { return (double)GetValue(ScaleProperty); }
set { SetValue(ScaleProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
associatedObject = bindable;
associatedObject.SizeChanged += AnimateItem;
}
protected override void OnDetachingFrom(View bindable)
{
associatedObject.SizeChanged -= AnimateItem;
}
private static Easing GetEasing(string easingName)
{
switch (easingName)
{
case "BounceIn": return Easing.BounceIn;
case "BounceOut": return Easing.BounceOut;
case "CubicInOut": return Easing.CubicInOut;
case "CubicOut": return Easing.CubicOut;
case "Linear": return Easing.Linear;
case "SinIn": return Easing.SinIn;
case "SinInOut": return Easing.SinInOut;
case "SinOut": return Easing.SinOut;
case "SpringIn": return Easing.SpringIn;
case "SpringOut": return Easing.SpringOut;
default: throw new ArgumentException(easingName + " is not valid");
}
}
private static void OnEasingFunctionChanged(BindableObject bindable, string oldvalue, string newvalue)
{
var obj = bindable as BaseAnimationBehavior;
if (obj == null)
{
return;
}
obj.EasingFunction = newvalue;
obj._easingFunction = GetEasing(newvalue);
}
private void AnimateItem(object sender, EventArgs e)
{
await associatedObject.TranslateTo(associatedObject.TranslationX, associatedObject.TranslationY - 10, 10, _easingFunction);
await associatedObject.TranslateTo(associatedObject.TranslationX, associatedObject.TranslationY + 10, 900, _easingFunction);
}
}
in XAML page, I apply animation like:
<StackLayout IsVisible="{Binding AProp}">
<Entry Text="{Binding AField}" IsEnabled="True">
<Entry.Behaviors>
<Entry.Behaviors:BaseAnimationBehavior EasingFunction="SinInOut" />
</Entry.Behaviors>
</Entry>
</StackLayout>
When AProp becomes true then animation occur because of SizeChanged event.
When AProp becomes false then animation won't run or won't be visible because entire StackLayout is hidden.
I use MVVM and I have a view model and it is binded to view like:
public class MyViewModel
{
public bool AProp { get { return _prop; } set { _prop = value; RaisePropertyChanged ....; } }
public string AField { get { return _field; } set { _field = value; RaisePropertyChanged ....; } }
public void DoSomething() {
if(condition) { AProp = true; } else { AProp = false; }
}
}
How to achieve the situation when hide element, to animate first and then hide it ?
If you are hiding using the View Model you would need to send some kind of signal to the view that the property has changed.
If you have to hide the control from the View Model for some reason I would suggest
overriding the RaisePropertyChanged in your view model
rise an event on the property name is AProb
subscribe to that event in the view
finally start your animation manually by giving the Entry a name
Related
I have been trying to set a bindable property value in my Element from my native control through a custom renderer. My native control is a view (painview) where you can draw and I am trying to get the drawing and set it, as a base64 string, to a bindable property Signature in my Element.
This is my Native Control
public class PaintView : View
{
Canvas _drawCanvas;
Bitmap _canvasBitmap;
readonly Paint _paint;
readonly Dictionary<int, MotionEvent.PointerCoords> _coords = new Dictionary<int, MotionEvent.PointerCoords>();
public Bitmap CanvasBitmap { get => _canvasBitmap; private set => _canvasBitmap = value; }
private readonly string TAG = nameof(PaintView);
public event EventHandler OnLineDrawn;
public PaintView(Context context) : base(context, null, 0)
{
_paint = new Paint() { Color = Color.Blue, StrokeWidth = 5f, AntiAlias = true };
_paint.SetStyle(Paint.Style.Stroke);
}
public PaintView(Context context, IAttributeSet attrs) : base(context, attrs) { }
public PaintView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle) { }
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_canvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888); // full-screen bitmap
_drawCanvas = new Canvas(_canvasBitmap); // the canvas will draw into the bitmap
}
public override bool OnTouchEvent(MotionEvent e)
{
switch (e.ActionMasked)
{
case MotionEventActions.Down:
{
int id = e.GetPointerId(0);
var start = new MotionEvent.PointerCoords();
e.GetPointerCoords(id, start);
_coords.Add(id, start);
return true;
}
case MotionEventActions.PointerDown:
{
int id = e.GetPointerId(e.ActionIndex);
var start = new MotionEvent.PointerCoords();
e.GetPointerCoords(id, start);
_coords.Add(id, start);
return true;
}
case MotionEventActions.Move:
{
for (int index = 0; index < e.PointerCount; index++)
{
var id = e.GetPointerId(index);
float x = e.GetX(index);
float y = e.GetY(index);
_drawCanvas.DrawLine(_coords[id].X, _coords[id].Y, x, y, _paint);
_coords[id].X = x;
_coords[id].Y = y;
OnLineDrawn?.Invoke(this, EventArgs.Empty);
}
Invalidate();
return true;
}
case MotionEventActions.PointerUp:
{
int id = e.GetPointerId(e.ActionIndex);
_coords.Remove(id);
return true;
}
case MotionEventActions.Up:
{
int id = e.GetPointerId(0);
_coords.Remove(id);
return true;
}
default:
return false;
}
}
protected override void OnDraw(Canvas canvas)
{
// Copy the off-screen canvas data onto the View from it's associated Bitmap (which stores the actual drawn data)
canvas.DrawBitmap(_canvasBitmap, 0, 0, null);
}
public void Clear()
{
_drawCanvas.DrawColor(Color.Black, PorterDuff.Mode.Clear); // Paint the off-screen buffer black
Invalidate(); // Call Invalidate to redraw the view
}
public void SetInkColor(Color color)
{
_paint.Color = color;
}
}
The property PaintView._canvasBitmap is the one I want to be set in my Xamarin.Form Element through my custom renderer.
This is my Custom Renderer
public class SketchViewRenderer : ViewRenderer<SketchView, PaintView>
{
public SketchViewRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<SketchView> e)
{
if (Control == null)
{
var paintView = new PaintView(Context);
paintView.SetInkColor(Element.InkColor.ToAndroid());
SetNativeControl(new PaintView(Context));
MessagingCenter.Subscribe<SketchView>(this, nameof(SketchView.OnClear), OnMessageClear);
Control.OnLineDrawn += PaintViewLineDrawn;
}
}
private void PaintViewLineDrawn(object sender, EventArgs e)
{
var sketchCrl = (ISketchViewController)Element;
if (sketchCrl == null) return;
try
{
Element.SetValueFromRenderer(SketchView.SignatureProperty, Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
sketchCrl.SendSketchUpdated(Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == SketchView.InkColorProperty.PropertyName)
{
Control.SetInkColor(Element.InkColor.ToAndroid());
}
if (e.PropertyName == SketchView.ClearProperty.PropertyName)
{
if (Element.Clear) OnMessageClear(Element);
}
}
private void OnMessageClear(SketchView sender)
{
if (sender == Element) Control.Clear();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
MessagingCenter.Unsubscribe<SketchView>(this, nameof(SketchView.OnClear));
Control.OnLineDrawn -= PaintViewLineDrawn;
}
base.Dispose(disposing);
}
}
I have tried changing my Element.Signature property through the SketchViewRenderer.PaintViewLineDrawn(...) method without success. This has been prove when debugging my view model where the property has not been set as expected.
My Xamarin.Forms Element looks as follow
public class SketchView : View, IDoubleTappedController, ISketchViewController
{
public static readonly BindableProperty SignatureProperty = BindableProperty.Create(nameof(Signature), typeof(string), typeof(SketchView), null, defaultBindingMode: BindingMode.TwoWay);
public string Signature
{
get => (string)GetValue(SignatureProperty);
set => SetValue(SignatureProperty, value);
}
public static readonly BindableProperty MultiTouchEnabledProperty = BindableProperty.Create(nameof(MultiTouchEnabled), typeof(bool), typeof(SketchView), false);
public bool MultiTouchEnabled
{
get => (bool)GetValue(MultiTouchEnabledProperty);
set => SetValue(MultiTouchEnabledProperty, value);
}
public static readonly BindableProperty InkColorProperty = BindableProperty.Create(nameof(InkColor), typeof(Xamarin.Forms.Color), typeof(SketchView), Xamarin.Forms.Color.Azure);
public Xamarin.Forms.Color InkColor
{
get => (Xamarin.Forms.Color)GetValue(InkColorProperty);
set => SetValue(InkColorProperty, value);
}
public static readonly BindableProperty ClearProperty = BindableProperty.Create(nameof(Clear), typeof(bool), typeof(SketchView), false, defaultBindingMode: BindingMode.TwoWay);
public bool Clear
{
get => (bool)GetValue(ClearProperty);
set
{
SetValue(ClearProperty, value);
if (value) { OnClear(); }
}
}
public void OnClear()
{
MessagingCenter.Send(this, nameof(OnClear));
}
public void SetSignature(string signature)
{
Signature = signature;
}
void IDoubleTappedController.DoubleTapped()
{
throw new NotImplementedException();
}
void ISketchViewController.SendSketchUpdated(string signature)
{
Clear = false;
Signature = signature;
}
}
I have also tried using the SetValueFromRenderer() method from my Custom renderer, again, without success.
May you suggest to me what is the way to set an Element value from a Custom Renderer?
Thanks and kind regards,
Temo
The problem was that the field in my view model was set to null when comparing it with the value. Then throwing a TargetException letting the source buggy unable to be updated by the target.
public bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = default)
{
if (value == null) return false;
if (field != null && field.Equals(value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
Now, I make sure the field is not null before using the Equals operator.
I have an object class that derives from BoxView. The object has TouchEffect attached and when the OnTouchAction starts, I change the object's properties.
I would also like to change the property of a label based on the text attached to the string that label is bound to.
What I did is I created an instance of the page that contains bindable string and label, then, I tried to change the value of string by referencing it in code inside the OnTouchAction method.
I don't get errors and the Breakpoint tells me that code arrives to the line, but the label is not being updated.
I am trying to update the string from the class that is not the same where the string is.
Is there anyone who could help me out here?
class Element : BoxView
{
List<long> ids = new List<long>();
MainPage mainPage = new MainPage();
public event EventHandler StatusChanged;
public Element()
{
TouchEffect effect = new TouchEffect();
effect.TouchAction += OnTouchEffectAction;
Effects.Add(effect);
}
public Color DefaultColor { set; get; }
public Color HighlightColor { set; get; }
public bool IsPressed { private set; get; }
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
AddToList(args.Id);
mainPage.LeftLabelText = "entered";
break;
case TouchActionType.Entered:
if (args.IsInContact)
{
AddToList(args.Id);
mainPage.LeftLabelText = "entered";
}
break;
case TouchActionType.Moved:
break;
case TouchActionType.Released:
case TouchActionType.Exited:
RemoveFromList(args.Id);
break;
}
}
void AddToList(long id)
{
if (!ids.Contains(id))
{
ids.Add(id);
}
CheckList();
}
void RemoveFromList(long id)
{
if (ids.Contains(id))
{
ids.Remove(id);
}
CheckList();
}
void CheckList()
{
if (IsPressed != ids.Count > 0)
{
IsPressed = ids.Count > 0;
Color = IsPressed ? HighlightColor : DefaultColor;
mainPage.LeftLabelText = "entered";
StatusChanged?.Invoke(this, EventArgs.Empty);
}
}
}
and the MainPage relevant code:
Element element = new Element();
element.HighlightColor = Color.Accent;
element.DefaultColor = Color.Transparent;
element.Color = bar.DefaultColor;
element.HeightRequest = sGrid.Height;
element.VerticalOptions = LayoutOptions.End;
(...) // adding element view to the grid.
The string:
public string _leftLabelText = "testing";
public string LeftLabelText
{
get => _leftLabelText;
set
{
_leftLabelText = value;
NotifyPropertyChanged("LeftLabelText");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged2;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged2?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
<Label x:Name="leftLabel" x:FieldModifier="public" Text="{Binding LeftLabelText, Mode=TwoWay}" TextColor="Black" FontSize="10" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
I have a custom Behavior class inheriting from a bindable behavior class
where I have ItemSource property and the propertyChanged event on the BindableProperty only invokes at creation time not on changed even I call RaisePorpertyChanged
BindableBehaviorClass
public abstract class BindableBehavior<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T visualElement)
{
base.OnAttachedTo(visualElement);
AssociatedObject = visualElement;
if (visualElement.BindingContext != null)
BindingContext = visualElement.BindingContext;
visualElement.BindingContextChanged += OnBindingContextChanged;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnDetachingFrom(T view)
{
view.BindingContextChanged -= OnBindingContextChanged;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
CustomListBehavior
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource),
typeof(IEnumerable<IPerson>),
typeof(CustomListBehavior),
defaultValue: null,
propertyChanged: ItemsSourceChanged);
public IEnumerable<IPerson> ItemsSource
{
get { return (IEnumerable<IPerson>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
ViewModel
private ObservableCollection<IPreson> people = new ObservableCollection<IPerson>();
public ObservableCollection<IPerson> People
{
get => people;
set => SetProperty(ref people , value, nameof(People));
}
In my view Model I changed instead of instantiating the the local variable like
this private ObservableCollection<IPreson> people = new ObservableCollection<IPerson>();
I do it in before I assign it the new values after creation of the class.
I am new at Xamarin forms and renderers, Can you please help me out in this,
Thank you in advance.
I have created a checkbox but I want to change its background color like in picture.
Now you can see in image default checkbox is there I want to change its background and style how can I do that?
<local:Checkbox x:Name="chkBrand" AutomationId="AutoIdCheckBox"
Content="{Binding LblBrand}"
Checked="{Binding Chk,Mode=TwoWay}">
</local:Checkbox>
Checkbox.cs
public class Checkbox : Xamarin.Forms.View
{
public static readonly BindableProperty CheckedProperty =
BindableProperty.Create("Checked",
typeof(bool),
typeof(Checkbox),
false, BindingMode.TwoWay, propertyChanged: OnCheckedPropertyChanged);
public static readonly BindableProperty ContentProperty =
BindableProperty.Create("Content",
typeof(string),
typeof(Checkbox),
"Content", BindingMode.OneWay);
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("FontSize",
typeof(double),
typeof(Checkbox),
default(double), BindingMode.OneWay);
public bool Checked
{
get
{
return (bool)GetValue(CheckedProperty);
}
set
{
if (this.Checked != value)
{
SetValue(CheckedProperty, value);
if (CheckedChanged != null)
CheckedChanged.Invoke(this, new CheckedChangedEventArgs(value));
}
}
}
public string Content
{
get
{
return (string)GetValue(ContentProperty);
}
set
{
SetValue(ContentProperty, value);
}
}
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
public event EventHandler<CheckedChangedEventArgs> CheckedChanged;
private static void OnCheckedPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var checkBox = (Checkbox)bindable;
checkBox.Checked = newvalue != null ? (bool)newvalue : false;
}
}
and platform wise renderer are used. Adding UWP renderer for reference
public class CheckboxRenderer : ViewRenderer<Checkbox, CheckBox>
{
public new static void Init()
{
var temp = DateTime.Now;
}
protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
{
var checkBox = new CheckBox();
checkBox.Checked += CheckBox_Checked;
checkBox.Unchecked += CheckBox_Unchecked;
SetNativeControl(checkBox);
}
if (e.NewElement != null)
{
Control.Content = e.NewElement.Content;
Control.IsChecked = e.NewElement.Checked;
Control.IsEnabled = e.NewElement.IsEnabled;
if (default(double) != e.NewElement.FontSize)
Control.FontSize = e.NewElement.FontSize;
}
}
private void CheckBox_Unchecked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Checked(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
private void CheckBox_Content(object sender)
{
Element.Checked = Control.IsChecked.GetValueOrDefault();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
//case "IsVisible":
// Control.Hidden = Element.IsVisible;
// break;
case "IsEnabled":
Control.IsEnabled = Element.IsEnabled;
break;
case "Checked":
Control.IsChecked = Element.Checked;
break;
case "Content":
Control.Content = Element.Content;
break;
}
}
There are two ways to do this,
Use nuget package to show check box or
A simple way to change the style is to use the image control for checkbox.
Take two images as per your colour, one checked and other one is unchecked and do hide & show on click.
I have a WPF application using MVVM. I have the IsChecked value bound to a boolean on my model instance on my ViewModel. I also need to bind a method on the ViewModel to the Checked and Unchecked events. (This is so I can track unsaved changes and change the background to give my users visual indication of the need to save. I tried:
<CheckBox
Content="Enable"
Margin="5"
IsChecked="{Binding Enabled}"
Checked="{Binding ScheduleChanged}"
Unchecked="{Binding ScheduleChanged}"
/>
But I get a 'Provide value on 'System.Windows.Data.Binding' threw an exception.' error. Advice?
Here is the Model I am working with:
public class Schedule : IEquatable<Schedule>
{
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private bool _enabled;
private string _url;
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
_scheduledStart = value;
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(value < ScheduledStart)
{
throw new ArgumentException("Scheduled End cannot be earlier than Scheduled Start.");
}
else
{
_scheduledEnd = value;
}
}
}
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public string Url
{
get { return _url; }
set { _url = value; }
}
public bool Equals(Schedule other)
{
if(this.ScheduledStart == other.ScheduledStart && this.ScheduledEnd == other.ScheduledEnd
&& this.Enabled == other.Enabled && this.Url == other.Url)
{
return true;
}
else
{
return false;
}
}
}
My viewModel contains a property that has an ObservableCollection. An ItemsControl binds to the collection and generates a list. So my ViewModel sort of knows about my Model instance, but wouldn't know which one, I don't think.
Checked and Unchecked are events, so you can not bind to them like you can IsChecked, which is a property. On a higher level it is also probably wise for your view model not to know about a checkbox on the view.
I would create an event on the view model that fires when Enabled is changed, and you can subscribe to that and handle it any way you like.
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled != value)
{
_enabled = value;
RaisePropertyChanged("Enabled");
if (EnabledChanged != null)
{
EnabledChanged(this, EventArgs.Empty);
}
}
}
}
public event EventHandler EnabledChanged;
// constructor
public ViewModel()
{
this.EnabledChanged += This_EnabledChanged;
}
private This_EnabledChanged(object sender, EventArgs e)
{
// do stuff here
}
You should be able to just handle this in the setter for Enabled...
public class MyViewModel : ViewModelBase
{
private bool _isDirty;
private bool _enabled;
public MyViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
public ICommand SaveCommand { get; }
private void Save()
{
//TODO: Add your saving logic
}
private bool CanSave()
{
return IsDirty;
}
public bool IsDirty
{
get { return _isDirty; }
private set
{
if (_isDirty != value)
{
RaisePropertyChanged();
}
}
}
public bool Enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
IsDirty = true;
}
//Whatever code you need to raise the INotifyPropertyChanged.PropertyChanged event
RaisePropertyChanged();
}
}
}
You're getting a binding error because you can't bind a control event directly to a method call.
Edit: Added a more complete example.
The example uses the MVVM Lite framework, but the approach should work with any MVVM implementation.