I want get the value of a BindableProperty, because i want that have my BindableProperty two states for i can differentiate
<local:MyEntry event="{Binding Flag, Mode=TwoWay}">
and in my custom entry:
public static BindableProperty EventProperty =
BindableProperty.Create("event", typeof(string), typeof(MyEntry), "sigle", propertyChanged: (obj, oldValue, newValue) =>
{ });
private string flag;
public string Flag
{
get { return flag; }
set
{
if (flag != value)
{
flag = value;
NotifyPropertyChanged("event");
}
}
}
and in my viewModel, this is the code:
private string flag;
public string Flag
{
get { return flag; }
set
{
if (flag != value)
{
flag = value;
RaisePropertyChanged(() => Flag);
}
}
}
private async Task Execute()
{
Flag = "Change";
}
in my custom renderer
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
Console.WriteLine("HERE "+ (entry as MyEntry).Flag);
//here i want to get the value
with help of answer
if (e.PropertyName == MyEntry.EventProperty.PropertyName)
{
var entry = Element as MyEntry;
var Flag = entry.Flag;
Console.WriteLine("... "+ Flag);
in Custom Renderer
public static BindableProperty FlagProperty = BindableProperty.Create("Flag", typeof(string), typeof(MyEntry), string.Empty, propertyChanged: (obj, oldValue, newValue) =>
{
var entry = obj as MyEntry;
entry.Flag = newValue.ToString();
});
private string flag;
public string Flag
{
get { return flag; }
set
{
if (flag != value)
{
flag = value;
NotifyPropertyChanged("Flag");
}
}
}
in Custom Renderer
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyEntry.EventProperty.PropertyName)
{
// will been invoked when the value changed
var entry = Element as MyEntry;
var Flag = entry.Flag;
}
}
Related
I have an observrable collection in my class that contains checkboxes. I implemented a button to check all checkboxes at once. I tried just cycling through all elements and checking the box via binding:
void selectAll_clicked(System.Object sender, System.EventArgs e)
{
var x = sender as Button;
if (!allSelected)
{
allSelected = true;
x.Text = AppResources.DeselectAll;
foreach (var elem in contactList)
elem.isChecked = true;
}
else
{
allSelected = false;
x.Text = AppResources.SelectAll;
foreach (var elem in contactList)
elem.isChecked = false;
}
}
}
I am sure this effects the list, but the UI isnt updated at all.
How can I make sure the observablecollection "updates" visibly?
I also tried adding propertychanged handler:
private void SetList()
{
listview_contacts.ItemsSource = contactList;
contactList.CollectionChanged += items_CollectionChanged;
}
static void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += item_PropertyChanged;
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
BUt this just says that the cast isnt valid...
Thank you
I was able to achieve that by altering my type like so:
public class ContactType : INotifyPropertyChanged
{
private string _name;
private bool _isChecked;
public string name
{
get => _name; set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(name)));
}
}
public string phone { get; set; }
public string initials { get; set; }
public bool isChecked
{
get => _isChecked; set
{
_isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(isChecked)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I have implemented the nullable date picker to show the placeholder to date field as well as the null date in it, You can see the code below
Now the issue here is when I click on the current date then the DateSelected event is not getting trigger, it triggers when you select the previous date or next dates but not getting selected for the current date.
Thanks in advance.
NDateControl.cs
public class NDatePicker : DatePicker
{
public NDatePicker()
{
Format = "MM'/'dd'/'yyyy";
}
public string _originalFormat = null;
public static readonly BindableProperty PlaceHolderProperty =
BindableProperty.Create(nameof(PlaceHolder), typeof(string), typeof(c), "'MM/DD/YYYY'");
public string PlaceHolder
{
get { return (string)GetValue(PlaceHolderProperty); }
set { SetValue(PlaceHolderProperty, value); }
}
public static readonly BindableProperty NullableDateProperty =
BindableProperty.Create(nameof(NullableDate), typeof(DateTime?), typeof(NDatePicker), null, defaultBindingMode: BindingMode.TwoWay);
public DateTime? NullableDate
{
get { return (DateTime?)GetValue(NullableDateProperty); }
set
{
SetValue(NullableDateProperty, value);
UpdateDate();
}
}
private void UpdateDate()
{
if (NullableDate != null)
{
if (_originalFormat != null)
{
Format = _originalFormat;
}
}
else
{
Format = PlaceHolder;
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext != null)
{
_originalFormat = Format;
UpdateDate();
}
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == DateProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName
&& !IsFocused && (Date.ToString(Constant.DATE_FORMAT) == DateTime.Now.ToString(Constant.DATE_FORMAT))))
{
AssignValue();
}
if (propertyName == NullableDateProperty.PropertyName && NullableDate.HasValue)
{
Date = NullableDate.Value;
if (Date.ToString(_originalFormat) == DateTime.Now.ToString(_originalFormat))
{
//this code was done because when date selected is the actual date the"DateProperty" does not raise
UpdateDate();
}
}
}
public void AssignValue()
{
NullableDate = Date;
UpdateDate();
}
}
NDatePickerDroid.cs
public class NDatePickerDroid : ViewRenderer<NDatePicker, EditText>
{
DatePickerDialog _dialog;
public NDatePickerDroid(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<NDatePicker> e)
{
base.OnElementChanged(e);
SetNativeControl(new EditText(Context));
if (Control == null || e.NewElement == null)
return;
Control.TextSize = 14;
Control.Click += OnPickerClick;
Control.Text = Element.Date.ToString(Element.Format);
Control.KeyListener = null;
Control.FocusChange += OnPickerFocusChange;
Control.Enabled = Element.IsEnabled;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Xamarin.Forms.DatePicker.DateProperty.PropertyName || e.PropertyName == Xamarin.Forms.DatePicker.FormatProperty.PropertyName)
SetDate(Element.Date);
}
void OnPickerFocusChange(object sender, Android.Views.View.FocusChangeEventArgs e)
{
if (e.HasFocus)
{
ShowDatePicker();
}
}
protected override void Dispose(bool disposing)
{
if (Control != null)
{
Control.Click -= OnPickerClick;
Control.FocusChange -= OnPickerFocusChange;
if (_dialog != null)
{
_dialog.Hide();
_dialog.Dispose();
_dialog = null;
}
}
base.Dispose(disposing);
}
void OnPickerClick(object sender, EventArgs e)
{
ShowDatePicker();
}
void SetDate(DateTime date)
{
Control.Text = date.ToString(Element.Format);
Element.Date = date;
}
private void ShowDatePicker()
{
CreateDatePickerDialog(Element.Date.Year, Element.Date.Month - 1, Element.Date.Day);
_dialog.Show();
}
void CreateDatePickerDialog(int year, int month, int day)
{
NDatePicker view = Element;
_dialog = new DatePickerDialog(Context, (o, e) =>
{
view.Date = e.Date;
((IElementController)view).SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
Control.ClearFocus();
_dialog = null;
}, year, month, day);
_dialog.SetButton(Constant.DONE, (sender, e) =>
{
SetDate(_dialog.DatePicker.DateTime);
Element.Format = Element._originalFormat;
Element.AssignValue();
});
}
}
NDatePickeriOS.cs
public class NDatePickeriOS : DatePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control != null)
{
AddClearButton();
Control.Font = Control.Font.WithSize(14);
Control.BorderStyle = UITextBorderStyle.Line;
Control.Layer.BorderColor = UIColor.LightGray.CGColor;
Control.Layer.BorderWidth = 1;
if (Device.Idiom == TargetIdiom.Tablet)
{
Control.Font = UIFont.SystemFontOfSize(25);
}
}
}
private void AddClearButton()
{
UIToolbar originalToolbar = Control.InputAccessoryView as UIToolbar;
if (originalToolbar != null && originalToolbar.Items.Length <= 2)
{
var clearButton = new UIBarButtonItem(Constant.CLEAR, UIBarButtonItemStyle.Plain, ((sender, ev) =>
{
NDatePicker baseDatePicker = Element as NDatePicker;
Element.Unfocus();
Element.Date = DateTime.Now;
//baseDatePicker.CleanDate();
}));
var newItems = new List<UIBarButtonItem>();
foreach (var item in originalToolbar.Items)
{
newItems.Add(item);
}
newItems.Insert(0, clearButton);
originalToolbar.Items = newItems.ToArray();
originalToolbar.SetNeedsDisplay();
}
}
}
The XAML is
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" Margin="0,2" HeightRequest="60">
<control:NDatePicker NullableDate="{Binding DateAnswer}" DateSelected="DateChanged" HorizontalOptions="FillAndExpand" VerticalOptions="Center"/>
</StackLayout>
You could use MessagingCenter to send a message to your page when you select the current date.
_dialog.SetButton(Constant.DONE, (sender, e) =>
{
SetDate(_dialog.DatePicker.DateTime);
Element.Format = Element._originalFormat;
Element.AssignValue();
if (_dialog.DatePicker.DateTime == DateTime.Today)
{
MessagingCenter.Send<Object,DateTime>(this, "SameDate", view.Date);
}
});
in your page.xaml.cs:
public YourPage()
{
InitializeComponent();
MessagingCenter.Subscribe<Object,DateTime>(this, "SameDate", (sender,args) =>
{
DateTime dateTime = args; //triggered here
});
}
I realized recently that a switch doesn't have a command. And i need to bind the toggling event to my view model. How do i go about it?
I tried to bind the command to Toggled event but the code is running into error
You can use EventToCommandBehavior to convert the event to command
create the EventToCommandBehavior class
using System;
using Xamarin.Forms;
namespace xxx
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null)
{
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom(T bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
}
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace xxx
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent(EventName);
}
protected override void OnDetachingFrom(View bindable)
{
DeregisterEvent(EventName);
base.OnDetachingFrom(bindable);
}
void RegisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
}
void DeregisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (eventHandler == null)
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent(object sender, object eventArgs)
{
if (Command == null)
{
return;
}
object resolvedParameter;
if (CommandParameter != null)
{
resolvedParameter = CommandParameter;
}
else if (Converter != null)
{
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
}
else
{
resolvedParameter = eventArgs;
}
if (Command.CanExecute(resolvedParameter))
{
Command.Execute(resolvedParameter);
}
}
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null)
{
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent(oldEventName);
behavior.RegisterEvent(newEventName);
}
}
}
in your xaml
<Switch >
<Switch.Behaviors>
<local:EventToCommandBehavior EventName="Toggled" Command="{Binding ToggledCommand}"/>
</Switch.Behaviors>
</Switch>
And in your ViewModel
public class MyViewModel
{
public ICommand ToggledCommand { get; private set; }
public MyViewModel()
{
ToggledCommand = new Command(() => {
// do some thing you want
});
}
}
I bind a bool property on my viewmodel to the IsToggled property on the switch, then handle when this changes in the viewmodel.
I want to use tap and long tap together with Custom Gridview.
Tap is working but long tap not working.
it also works when one tap turns off the long tap.
Please help me.
Thank you.
public class GridView : Grid
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GridView), default(IList), BindingMode.TwoWay);
public static readonly BindableProperty ItemTappedCommandProperty = BindableProperty.Create(nameof(ItemTappedCommand), typeof(ICommand), typeof(GridView), null);
public static readonly BindableProperty ItemLongTappedCommandProperty = BindableProperty.Create(nameof(ItemLongTappedCommand), typeof(ICommand), typeof(GridView), null);
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GridView), default(DataTemplate));
public static readonly BindableProperty MaxColumnsProperty = BindableProperty.Create(nameof(MaxColumns), typeof(int), typeof(GridView), 2);
public static readonly BindableProperty TileHeightProperty = BindableProperty.Create(nameof(TileHeight), typeof(float), typeof(GridView), 220f);//adjusted here reuired height
public GridView()
{
PropertyChanged += GridView_PropertyChanged;
PropertyChanging += GridView_PropertyChanging;
}
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public ICommand ItemTappedCommand
{
get { return (ICommand)GetValue(ItemTappedCommandProperty); }
set { SetValue(ItemTappedCommandProperty, value); }
}
public ICommand ItemLongTappedCommand
{
get { return (ICommand)GetValue(ItemLongTappedCommandProperty); }
set { SetValue(ItemLongTappedCommandProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public int MaxColumns
{
get { return (int)GetValue(MaxColumnsProperty); }
set { SetValue(MaxColumnsProperty, value); }
}
public float TileHeight
{
get { return (float)GetValue(TileHeightProperty); }
set { SetValue(TileHeightProperty, value); }
}
private void BuildColumns()
{
ColumnDefinitions.Clear();
for (var i = 0; i < MaxColumns; i++)
{
ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
}
}
private View BuildTile(object item1)
{
var template = ItemTemplate.CreateContent() as View;
template.BindingContext = item1;
if (ItemTappedCommand != null)
{
var tapGestureRecognizer = new TapGestureRecognizer
{
Command = ItemTappedCommand,
CommandParameter = item1,
};
template.GestureRecognizers.Add(tapGestureRecognizer);
}
// Tap komutu eziyor.
if (ItemLongTappedCommand != null)
{
template.Effects.Add(new LongPressedEffect());
LongPressedEffect.SetCommand(template, ItemLongTappedCommand);
//LongPressedEffect.SetCommandParameter(template, item1);
}
return template;
}
private void BuildTiles()
{
// Wipe out the previous row & Column definitions if they're there.
if (RowDefinitions.Any())
{
RowDefinitions.Clear();
}
BuildColumns();
Children.Clear();
var tiles = ItemsSource;
if (tiles != null)
{
var numberOfRows = Math.Ceiling(tiles.Count / (float)MaxColumns);
for (var i = 0; i < numberOfRows; i++)
{
RowDefinitions.Add(new RowDefinition { Height = new GridLength(0, GridUnitType.Auto) });
}
for (var index = 0; index < tiles.Count; index++)
{
var column = index % MaxColumns;
var row = (int)Math.Floor(index / (float)MaxColumns);
var tile = BuildTile(tiles[index]);
Children.Add(tile, column, row);
}
}
}
private void GridView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == ItemsSourceProperty.PropertyName)
{
var items = ItemsSource as INotifyCollectionChanged;
if (items != null)
items.CollectionChanged += ItemsCollectionChanged;
BuildTiles();
}
if (e.PropertyName == MaxColumnsProperty.PropertyName || e.PropertyName == TileHeightProperty.PropertyName)
{
BuildTiles();
}
}
private void GridView_PropertyChanging(object sender, Xamarin.Forms.PropertyChangingEventArgs e)
{
if (e.PropertyName == ItemsSourceProperty.PropertyName)
{
var items = ItemsSource as INotifyCollectionChanged;
if (items != null)
items.CollectionChanged -= ItemsCollectionChanged;
}
}
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
BuildTiles();
}
}
public class LongPressedEffect : RoutingEffect
{
public LongPressedEffect() : base("MyApp.LongPressedEffect")
{ }
public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
public static ICommand GetCommand(BindableObject view)
{
//do something you want
Console.WriteLine("long press Gesture recognizer has been striked");
return (ICommand)view.GetValue(CommandProperty);
}
public static void SetCommand(BindableObject view, ICommand value)
{
view.SetValue(CommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
public static object GetCommandParameter(BindableObject view)
{
return view.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(BindableObject view, object value)
{
view.SetValue(CommandParameterProperty, value);
}
}
I noticed you used Effect to create your own long pressed command. But if you consumed a TapGestureRecognizer at the same time, it will intercept the effect. Then your long pressed command won't be triggered.
You can either define the single tap click event in the effect to achieve them both. Here is my effect:
public class PressedEffect : RoutingEffect
{
public PressedEffect() : base("MyApp.PressedEffect")
{
}
public static readonly BindableProperty LongTapCommandProperty = BindableProperty.CreateAttached("LongTapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
public static ICommand GetLongTapCommand(BindableObject view)
{
return (ICommand)view.GetValue(LongTapCommandProperty);
}
public static void SetLongTapCommand(BindableObject view, ICommand value)
{
view.SetValue(LongTapCommandProperty, value);
}
public static readonly BindableProperty LongParameterProperty = BindableProperty.CreateAttached("LongParameter", typeof(object), typeof(PressedEffect), (object)null);
public static object GetLongParameter(BindableObject view)
{
return view.GetValue(LongParameterProperty);
}
public static void SetLongParameter(BindableObject view, object value)
{
view.SetValue(LongParameterProperty, value);
}
public static readonly BindableProperty TapCommandProperty = BindableProperty.CreateAttached("TapCommand", typeof(ICommand), typeof(PressedEffect), (object)null);
public static ICommand GetTapCommand(BindableObject view)
{
return (ICommand)view.GetValue(TapCommandProperty);
}
public static void SetTapCommand(BindableObject view, ICommand value)
{
view.SetValue(TapCommandProperty, value);
}
public static readonly BindableProperty TapParameterProperty = BindableProperty.CreateAttached("TapParameter", typeof(object), typeof(PressedEffect), (object)null);
public static object GetTapParameter(BindableObject view)
{
return view.GetValue(TapParameterProperty);
}
public static void SetTapParameter(BindableObject view, object value)
{
view.SetValue(TapParameterProperty, value);
}
}
Android implementation:
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidPressedEffect), "PressedEffect")]
namespace PressedEffectDemo.Droid
{
public class AndroidPressedEffect : PlatformEffect
{
private bool _attached;
public static void Initialize() { }
public AndroidPressedEffect()
{
}
protected override void OnAttached()
{
if (!_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick += Control_LongClick;
Control.Click += Control_Click;
}
else
{
Container.LongClickable = true;
Container.LongClick += Control_LongClick;
Container.Click += Control_Click;
}
_attached = true;
}
}
private void Control_Click(object sender, EventArgs e)
{
var command = PressedEffect.GetTapCommand(Element);
command?.Execute(PressedEffect.GetTapParameter(Element));
}
private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
{
var command = PressedEffect.GetLongTapCommand(Element);
command?.Execute(PressedEffect.GetLongParameter(Element));
}
protected override void OnDetached()
{
if (_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick -= Control_LongClick;
Control.Click -= Control_Click;
}
else
{
Container.LongClickable = true;
Container.LongClick -= Control_LongClick;
Control.Click -= Control_Click;
}
_attached = false;
}
}
}
}
iOS implementation:
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSPressedEffect), "PressedEffect")]
namespace PressedEffectDemo.iOS
{
public class iOSPressedEffect : PlatformEffect
{
private bool _attached;
private readonly UILongPressGestureRecognizer _longPressRecognizer;
private readonly UITapGestureRecognizer _tapRecognizer;
public iOSPressedEffect()
{
_longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
_tapRecognizer = new UITapGestureRecognizer(HandleClick);
}
protected override void OnAttached()
{
if (!_attached)
{
Container.AddGestureRecognizer(_longPressRecognizer);
Container.AddGestureRecognizer(_tapRecognizer);
_attached = true;
}
}
private void HandleClick()
{
var command = PressedEffect.GetTapCommand(Element);
command?.Execute(PressedEffect.GetTapParameter(Element));
}
private void HandleLongClick(UILongPressGestureRecognizer recognizer)
{
if (recognizer.State == UIGestureRecognizerState.Ended)
{
var command = PressedEffect.GetLongTapCommand(Element);
command?.Execute(PressedEffect.GetLongParameter(Element));
}
}
protected override void OnDetached()
{
if (_attached)
{
Container.RemoveGestureRecognizer(_longPressRecognizer);
Container.RemoveGestureRecognizer(_tapRecognizer);
_attached = false;
}
}
}
}
At last, you can consume them on XAML like:
<StackLayout>
<Grid HeightRequest="200"
BackgroundColor="Green"
local:PressedEffect.TapCommand="{Binding TapCommand}"
local:PressedEffect.LongTapCommand="{Binding LongTapCommand}">
<Grid.Effects>
<local:PressedEffect />
</Grid.Effects>
</Grid>
</StackLayout>
You could refer to my sample here.
I've got the following code:
Shared project:
using System;
using Xamarin.Forms;
namespace XamarinForms.Framework.Controls
{
public enum DrawerPosition
{
Left,
Right
}
public interface ISideDrawerNativeEventProxy
{
void RaiseSlideChanged(float percentage);
}
public class SideDrawer : Grid, ISideDrawerNativeEventProxy
{
#region DrawerSize
public static BindableProperty DrawerSizeProperty = BindableProperty.Create<SideDrawer, MaxedPercentSize>(d => d.DrawerSize, new MaxedPercentSize(MaxedPercentSizeType.Width, 80, 400));
public MaxedPercentSize DrawerSize
{
get { return (MaxedPercentSize) GetValue(DrawerSizeProperty); }
set { SetValue(DrawerSizeProperty, value); }
}
#endregion DrawerSize
#region IsOpen
public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool));
public bool IsOpen
{
get { return (bool) GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
#endregion IsOpen
#region DrawerPosition
public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));
public DrawerPosition DrawerPosition
{
get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
set { SetValue(DrawerPositionProperty, value); }
}
#endregion DrawerPosition
public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View));
public View MainContent
{
get { return (View) GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View));
public View DrawerContent
{
get { return (View) GetValue(DrawerContentProperty); }
set { SetValue(DrawerContentProperty, value); }
}
#region DrawerLength
public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);
private static int DrawerLengthDefault(SideDrawer bindable)
{
return 300;
}
public int DrawerLength
{
get { return (int) GetValue(DrawerLengthProperty); }
set { SetValue(DrawerLengthProperty, value); }
}
#endregion DrawerLength
#region IsContentTranslated
public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);
public bool IsContentTranslated
{
get { return (bool) GetValue(IsContentTranslatedProperty); }
set { SetValue(IsContentTranslatedProperty, value); }
}
#endregion IsContentTranslated
void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
{
}
}
}
Android:
using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Mobile.XamarinForms.Droid.Controls.SideDrawer;
using Mobile.XamarinForms.Framework.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Application = Android.App.Application;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace Mobile.XamarinForms.Droid.Controls.SideDrawer
{
// public class SideDrawerRenderer : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
public class SideDrawerRenderer : ViewRenderer<Framework.Controls.SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
{
private DrawerLayout _nativeDrawerLayout;
private MarginLayoutParams _contentLayoutParameters;
private RelativeLayout _contentView;
private TextView _drawerView;
protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
InitializeNativeControl();
}
if (e.OldElement != null)
{
_nativeDrawerLayout.SetDrawerListener(null);
}
if (e.NewElement != null)
{
_nativeDrawerLayout.SetDrawerListener(this);
}
}
private void InitializeNativeControl()
{
_nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
_nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
_contentLayoutParameters = new MarginLayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, LinearLayout.LayoutParams.MatchParent);
layoutParamsDrawer.Gravity = GetDrawerGravity();
_drawerView = GetDrawerView(layoutParamsDrawer);
_contentView = GetContentView(_contentLayoutParameters);
// this one works, but i need the content from my forms property
var contentChild = new RelativeLayout(Context.ApplicationContext);
var contentChildLParams = new RelativeLayout.LayoutParams(300, 300);
contentChild.SetBackgroundColor(Color.Yellow);
_contentView.AddView(contentChild, contentChildLParams);
// i need to figure out how to make this work
// var contentRenderer = RendererFactory.GetRenderer(Element.MainContent);
// _contentView.AddView(contentRenderer.ViewGroup, new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent));
_nativeDrawerLayout.AddView(_drawerView);
_nativeDrawerLayout.AddView(_contentView);
SetNativeControl(_nativeDrawerLayout);
}
private int GetDrawerGravity()
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
return (int)GravityFlags.Start;
case DrawerPosition.Right:
return (int)GravityFlags.End;
default:
throw new ArgumentOutOfRangeException();
}
}
private RelativeLayout GetContentView(LayoutParams layoutParameters)
{
var view = new RelativeLayout(Context.ApplicationContext);
view.LayoutParameters = layoutParameters;
view.SetBackgroundColor(Color.Red);
return view;
}
private TextView GetDrawerView(LayoutParams layoutParameters)
{
var view = new TextView(Context.ApplicationContext);
view.LayoutParameters = layoutParameters;
view.SetBackgroundColor(Color.Purple);
view.SetTextColor(Color.Blue);
view.SetText("just some text", TextView.BufferType.Editable);
return view;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
case nameof(Framework.Controls.SideDrawer.Height):
break;
case nameof(Framework.Controls.SideDrawer.Width):
break;
}
}
public void OnDrawerClosed(View drawerView)
{
Element.IsOpen = false;
System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
}
public void OnDrawerOpened(View drawerView)
{
Element.IsOpen = true;
System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
}
public void OnDrawerSlide(View drawerView, float slideOffset)
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
_contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset);
break;
case DrawerPosition.Right:
_contentView.TranslationX = (int) Math.Abs(Element.DrawerLength*slideOffset)*-1;
break;
default:
throw new ArgumentOutOfRangeException();
}
_nativeDrawerLayout.BringChildToFront(_drawerView);
_nativeDrawerLayout.RequestLayout();
((ISideDrawerNativeEventProxy) Element)?.RaiseSlideChanged(slideOffset);
}
public void OnDrawerStateChanged(int newState)
{
// not really needed
// System.Diagnostics.Debug.WriteLine($"OnDrawerStateChanged {newState}");
}
}
}
How do i output the contents of e.g. MainContent (Shared project property)?
I was unable to find anything in xamarin docs about this and support is rather quiet about this topic so far (guess they are too busy).
Does anyone have experience with this issue?
Update: Reproduction solution
Android container:
internal sealed class FormsElementWrapper : FormsViewGroup
{
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return false;
}
private readonly IVisualElementRenderer _view;
public FormsElementWrapper(Xamarin.Forms.View content) : base(Application.Context)
{
_view = content != null ? Platform.CreateRenderer(content) : null;
if (_view == null)
return;
AddView(_view.ViewGroup);
}
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
if (_view == null)
return;
_view.Element.Layout(new Rectangle(0.0, 0.0, ContextExtensions.FromPixels(Context, right - left), ContextExtensions.FromPixels(Context, bottom - top)));
_view.UpdateLayout();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
MeasureSpecMode widthMode = MeasureSpec.GetMode(widthMeasureSpec);
MeasureSpecMode heightMode = MeasureSpec.GetMode(heightMeasureSpec);
int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
int heightSize = MeasureSpec.GetSize(heightMeasureSpec);
int pxHeight = (int) ContextExtensions.ToPixels(Context, _view.Element.HeightRequest);
int pxWidth = (int) ContextExtensions.ToPixels(Context, _view.Element.WidthRequest);
var measuredWidth = widthMode != MeasureSpecMode.Exactly ? (widthMode != MeasureSpecMode.AtMost ? pxHeight : Math.Min(pxHeight, widthSize)) : widthSize;
var measuredHeight = heightMode != MeasureSpecMode.Exactly ? (heightMode != MeasureSpecMode.AtMost ? pxWidth : Math.Min(pxWidth, heightSize)) : heightSize;
SetMeasuredDimension(measuredWidth, measuredHeight);
}
}
android drawer:
using System;
using System.ComponentModel;
using Android.Support.V4.Widget;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Color = Android.Graphics.Color;
using RelativeLayout = Android.Widget.RelativeLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(SideDrawer), typeof(SideDrawerRenderer))]
namespace MyNamespace.SideDrawer
{
public class SideDrawerRenderer : ViewRenderer<SideDrawer, DrawerLayout>, DrawerLayout.IDrawerListener
{
private DrawerLayout _nativeDrawerLayout;
private FormsElementWrapper _contentView;
private FormsElementWrapper _drawerView;
protected override void OnElementChanged(ElementChangedEventArgs<Framework.Controls.SideDrawer> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
InitializeNativeControl();
}
if (e.OldElement != null)
{
_nativeDrawerLayout.SetDrawerListener(null);
}
if (e.NewElement != null)
{
_nativeDrawerLayout.SetDrawerListener(this);
}
}
private void InitializeNativeControl()
{
_nativeDrawerLayout = new DrawerLayout(Context.ApplicationContext);
_nativeDrawerLayout.SetBackgroundColor(Element.BackgroundColor.ToAndroid());
AddDrawerLayer(_nativeDrawerLayout);
AddContentLayer(_nativeDrawerLayout);
SetNativeControl(_nativeDrawerLayout);
}
private void AddContentLayer(DrawerLayout nativeDrawerLayout)
{
_contentView = new FormsElementWrapper(Element.MainContent);
nativeDrawerLayout.AddView(_contentView);
}
private void AddDrawerLayer(DrawerLayout nativeDrawerLayout)
{
_drawerView = new FormsElementWrapper(Element.DrawerContent);
UpdateDrawerLength();
nativeDrawerLayout.AddView(_drawerView);
}
private int GetDrawerGravity()
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
return (int)GravityFlags.Start;
case DrawerPosition.Right:
return (int)GravityFlags.End;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName)
{
case nameof(Framework.Controls.SideDrawer.Height):
break;
case nameof(Framework.Controls.SideDrawer.Width):
break;
case nameof(Framework.Controls.SideDrawer.MainContent):
_contentView = new FormsElementWrapper(Element.MainContent);
break;
case nameof(Framework.Controls.SideDrawer.DrawerContent):
_drawerView = new FormsElementWrapper(Element.DrawerContent);
break;
case nameof(Framework.Controls.SideDrawer.IsOpen):
UpdateDrawerStateProgramatically();
break;
case nameof(Framework.Controls.SideDrawer.DrawerLength):
case nameof(Framework.Controls.SideDrawer.DrawerPosition):
UpdateDrawerLength();
break;
}
}
private void UpdateDrawerLength()
{
var layoutParamsDrawer = new DrawerLayout.LayoutParams(Element.DrawerLength, ViewGroup.LayoutParams.MatchParent);
layoutParamsDrawer.Gravity = GetDrawerGravity();
_drawerView.LayoutParameters = layoutParamsDrawer;
}
private void UpdateDrawerStateProgramatically()
{
if (Element.IsOpen)
{
Control.OpenDrawer(GetDrawerGravity());
}
else
{
Control.CloseDrawer(GetDrawerGravity());
}
}
public void OnDrawerClosed(View drawerView)
{
Element.IsOpen = false;
System.Diagnostics.Debug.WriteLine("OnDrawerClosed");
}
public void OnDrawerOpened(View drawerView)
{
Element.IsOpen = true;
System.Diagnostics.Debug.WriteLine("OnDrawerOpened");
}
public void OnDrawerSlide(View drawerView, float slideOffset)
{
switch (Element.DrawerPosition)
{
case DrawerPosition.Left:
_contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset);
break;
case DrawerPosition.Right:
_contentView.TranslationX = (int)Math.Abs(Element.DrawerLength * slideOffset) * -1;
break;
default:
throw new ArgumentOutOfRangeException();
}
_nativeDrawerLayout.BringChildToFront(_drawerView);
_nativeDrawerLayout.RequestLayout();
((ISideDrawerNativeEventProxy)Element)?.RaiseSlideChanged(slideOffset);
}
public void OnDrawerStateChanged(int newState)
{
}
}
}
Xamarin.Forms Control:
public enum DrawerPosition
{
Left,
Right
}
public interface ISideDrawerNativeEventProxy
{
void RaiseSlideChanged(float percentage);
}
public class SideDrawer : Grid, ISideDrawerNativeEventProxy
{
#region IsOpen
public static BindableProperty IsOpenProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsOpen, default(bool), defaultBindingMode: BindingMode.TwoWay);
public bool IsOpen
{
get { return (bool) GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
#endregion IsOpen
#region DrawerPosition
public static BindableProperty DrawerPositionProperty = BindableProperty.Create<SideDrawer, DrawerPosition>(d => d.DrawerPosition, default(DrawerPosition));
public DrawerPosition DrawerPosition
{
get { return (DrawerPosition) GetValue(DrawerPositionProperty); }
set { SetValue(DrawerPositionProperty, value); }
}
#endregion DrawerPosition
public static BindableProperty MainContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.MainContent, default(View), propertyChanging: MainContentPropertyChanging, propertyChanged: MainContentPropertyChanged);
private static void MainContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
{
if (newValue == null)
return;
newValue.Parent = (View)bindable;
}
private static void MainContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
{
if (oldValue == null)
return;
oldValue.Parent = null;
}
public View MainContent
{
get { return (View) GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
public static BindableProperty DrawerContentProperty = BindableProperty.Create<SideDrawer, View>(d => d.DrawerContent, default(View), propertyChanging: DrawerContentPropertyChanging, propertyChanged: DrawerContentPropertyChanged);
private static void DrawerContentPropertyChanged(BindableObject bindable, View oldValue, View newValue)
{
if (newValue == null)
return;
newValue.Parent = (View)bindable;
}
private static void DrawerContentPropertyChanging(BindableObject bindable, View oldValue, View newValue)
{
if (oldValue == null)
return;
oldValue.Parent = null;
}
public View DrawerContent
{
get { return (View) GetValue(DrawerContentProperty); }
set { SetValue(DrawerContentProperty, value); }
}
#region DrawerLength
public static BindableProperty DrawerLengthProperty = BindableProperty.Create<SideDrawer, int>(d => d.DrawerLength, default(int), defaultValueCreator: DrawerLengthDefault);
private static int DrawerLengthDefault(SideDrawer bindable)
{
return 300;
}
public int DrawerLength
{
get { return (int) GetValue(DrawerLengthProperty); }
set { SetValue(DrawerLengthProperty, value); }
}
#endregion DrawerLength
#region IsContentTranslated
public static BindableProperty IsContentTranslatedProperty = BindableProperty.Create<SideDrawer, bool>(d => d.IsContentTranslated, true);
public bool IsContentTranslated
{
get { return (bool) GetValue(IsContentTranslatedProperty); }
set { SetValue(IsContentTranslatedProperty, value); }
}
#endregion IsContentTranslated
void ISideDrawerNativeEventProxy.RaiseSlideChanged(float percentage)
{
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (MainContent != null)
SetInheritedBindingContext(MainContent, BindingContext);
if (DrawerContent != null)
SetInheritedBindingContext(DrawerContent, BindingContext);
}
}
The major flaws in my original implementation were:
lack of measurement data being forwarded
not forwarding parent hierarchy
not inheriting bindingcontext