Xamarin android C# ScrollView OnScrollChanged event - c#

In Xamarin Android how can we extend the ScrollView in order to use the protected OnScrollChanged event?
Specifically,
How do we extend the ScrollView to allow an EventHandler to be registered to the OnScrollChanged event?
What other methods of ScrollView need to be implemented in a class that extends ScrollView?
Reasons:
The Android ScrollView does not have a way to listen for a scroll event.
There have been various questions regarding how to extend ScrollView in native Android Java, however, there is not a question and answer addressing how to apply this to Xamarin.

In order to extend ScrollView in this way we should implement 3 constructors
public UsefulScrollView(Context context)
public UsefulScrollView(Context context, IAttributeSet attrs)
public UsefulScrollView(Context context, IAttributeSet attrs, int defStyle)
We also need to override the OnDraw method
protected override void OnDraw(Android.Graphics.Canvas canvas)
To achieve the functionality of an event that we can respond to when the user scrolls we need to override the OnScrollChanged method.
protected override void OnScrollChanged(int l, int t, int oldl, int oldt)
There are multiple ways to allow event listening and handling, but in order to be consistent with Xamarin we can add a public EventHandler property to our class.
public EventHandler<T> ScrollEventHandler { get; set; }
We will want to pass along the values from OnScrollChanged to the EventHandler, so let's extend EventArgs
public class UsefulScrollEventArgs : EventArgs{
public int l { get; set; }
public int t { get; set; }
public int oldl { get; set; }
public int oldt { get; set; }
}
Finally, don't forget to initialize our handler in each of our constructors
ScrollEventHandler = (object sender, UsefulScrollEventArgs e) => {};
Put it all together and it might look something like this
Extended EventArgs class
public class UsefulScrollEventArgs : EventArgs{
public int l { get; set; }
public int t { get; set; }
public int oldl { get; set; }
public int oldt { get; set; }
}
Extended ScrollView class
public class UsefulScrollView : ScrollView
{
public EventHandler<UsefulScrollEventArgs> ScrollEventHandler { get; set; }
public UsefulScrollView (Context context)
: base(context)
{
ScrollEventHandler = (object sender, UsefulScrollEventArgs e) => {};
}
public UsefulScrollView (Context context, IAttributeSet attrs)
: base(context, attrs) {
ScrollEventHandler = (object sender, UsefulScrollEventArgs e) => {};
}
public UsefulScrollView(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle) {
ScrollEventHandler = (object sender, UsefulScrollEventArgs e) => {};
}
protected override void OnScrollChanged(int l, int t, int oldl, int oldt)
{
ScrollEventHandler (this,
new UsefulScrollEventArgs ()
{l=l,t=t,oldl=oldl,oldt=oldt});
base.OnScrollChanged (l, t, oldl, oldt);
}
protected override void OnDraw(Android.Graphics.Canvas canvas)
{
}
}
This Q&A was helpful in figuring this problem out: Scrollview listener is not working in Xamarin for Android?

I extended ScrollView but used EventHandlers. One for ScrollChanged, one for ReachedBottom.
This goes in your extended class:
public delegate void ScrollChangedEventHandler(object sender, EventArgs e);
public event ScrollChangedEventHandler ScrollChanged;
public delegate void ScrollReachedBottomEventHandler(object sender, EventArgs e);
public event ScrollReachedBottomEventHandler ScrollReachedBottom;
protected override void OnScrollChanged(int l, int t, int oldl, int oldt)
{
base.OnScrollChanged(l, t, oldl, oldt);
if (ScrollChanged != null)
{
ScrollChanged.Invoke(this, new EventArgs());
}
// Grab the last child placed in the ScrollView, we need it to determinate the bottom position.
var view = GetChildAt(ChildCount - 1);
// Calculate the scrolldiff
var diff = (view.Bottom - (Height + ScrollY));
// if diff is zero, then the bottom has been reached
if (diff == 0 && ScrollReachedBottom != null)
{
ScrollReachedBottom.Invoke(this, new EventArgs());
}
}
You would then simply call
myScrollView.ScrollChanged += delegate { /* your code here */ }
and
myScrollView.ScrollReachedBottom += delegate { /* your code here */ }
enjoy.

Related

Xamarin Forms get cursor position in editor control

How can I get the cursor position inside Editor control?
Ive been looking for an answer but the best I could find was the Cursor class, but that doesnt seem to exist in xamarin.
You could custom a Editor,and use custom renderer to get the SelectionPosition of the EditText.
custom a FormEditor in your fomrs project:
public class FormEditor:Editor
{
public int SelectionPosition;
public EventHandler SelectChanageEvent { get; set; }
}
create AndroidEditor in your Android project:
class AndroidEditor : EditorRenderer, EditTextSelectChange
{
private Context mContext;
public AndroidEditor(Context context) : base(context)
{
mContext = context;
}
public void Change(int lastPos, int curPos)
{
((FormEditor)Element).SelectionPosition = curPos;
((FormEditor)Element).SelectChanageEvent.Invoke(this, null);
}
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
MyEditText myEditText = new MyEditText(mContext);
myEditText.SetEditTextSelectChange(this);
SetNativeControl(myEditText);
}
}
custom MyEditText in your Android project:
public class MyEditText : FormsEditText
{
private int mLastPos = 0;
private int mCurPos = 0;
private EditTextSelectChange editTextSelectChange;
public void SetEditTextSelectChange(EditTextSelectChange editTextSelectChange)
{
this.editTextSelectChange = editTextSelectChange;
}
public MyEditText(Context context) : base(context)
{
}
protected override void OnSelectionChanged(int selStart, int selEnd)
{
base.OnSelectionChanged(selStart, selEnd);
if (editTextSelectChange != null)
{
mCurPos = selEnd;
editTextSelectChange.Change(mLastPos, mCurPos);
mLastPos = mCurPos;
}
}
public interface EditTextSelectChange
{
void Change(int lastPos, int curPos);
}
}
then use in your page.xaml:
<local:FormEditor x:Name="editor" Placeholder="Hello"></local:FormEditor>
in your page.xaml.cs:
public YourPage()
{
InitializeComponent();
editor.SelectChanageEvent += SelectEvent;
}
private void SelectEvent(object sender, EventArgs e)
{
// you could get the Curson Position by editor.SelectionPosition
Console.WriteLine("curPos = {0}", editor.SelectionPosition);
}

'Object' does not contain a definition for

I'm trying to create an event handler and I am getting the error : 'Object' does not contain a definition for ListCountLastH " and same for listCountLH.
Changing private List to public List makes no difference and I am pretty sure I have to initialise the lists in protected override void OnStateChange() but anyway public /*override*/ void OnStateChange() makes no difference, so any help would be greatly appreciated.
public class Incrementer : DivergenceList5m throws ListCountLastH and listCountLH inaccessible due to protection level
DivergenceList5m.ListCountLastH.Add(DivergenceList5m.listCountLH); throws additionally "An object reference is required for the non-static field, method or property'Namwspace.Class.ListCountLastH " and same for listCountLH and I just don't know what else to try...
Thank you
public class Divergence5min : Strategy
{
public int listCountLH;
private List<int> ListCountLastH;
private List<double> LastHSwDMI;
protected override void OnStateChange()
{
ListCountH = new List<int>();
LastHSwDMI = new List<double>();
}
...
protected override void OnBarUpdate()
{
if (!LHsDmiAdd && b > 1 && HSwDMI[b-1] - HSwDMI[b] > 0.001 && HSwDMI[b-1] - HSwDMI[b-2] > 0.001)
{
LastHSwDMI.Add(HSwDMI[b-1]);
listCountLH = LastHSwDMI.Count;
ListCountLastH.Add(listCountLH);
...
}
}
public class Incrementer
{
public EventHandler ItemAdded;
public void AddItem()
{
base.ListCountLastH.Add(base.listCountLH);
if (ItemAdded != null)
ItemAdded(this, null);
}
}
}
Modifying like this with the private List still throws the 'inaccessible due to its protection level' error. The subscriber class does not throw any errors, though
public class Incrementer : DivergenceList5m
{
public event EventHandler ItemAdded;
public void AddItem()
{
var divList = new DivergenceList5m();
divList.ListCountLastH.Add(divList.listCountLH);
if (ItemAdded != null)
ItemAdded(this, null);
}
}
class AddingItems
{
public int ItemsCount {get; private set;}
public void Items (Incrementer incrementer)
{
ItemsCount = 0;
incrementer.ItemAdded += AddItems;
}
void AddItems (object source, EventArgs e)
{
ItemsCount++;
}
}
Last code version
public class Divergence5min : Strategy
{
public int listCountLH;
public List<int> ListCountLastH;
public List<double> LastHSwDMI;
...
public class Incrementer : DivergenceList5m
{
public event EventHandler ItemAdded;
public void AddItem()
{
var divList = new DivergenceList5m();
divList.ListCountLastH.Add(divList.listCountLH);
if (ItemAdded != null)
ItemAdded(this, null);
}
}
class AddingItems
{
public int ItemsCount {get; private set;}
public void Items (Incrementer inc)
{
ItemsCount = 0;
inc.ItemAdded += AddItems;
}
void AddItems (object source, EventArgs e)
{
ItemsCount++;
}
}
Looks like you are calling base.listCountLH in a class which inherits from object (i.e. you have not inherited it from anything - object is the default type). You need to inherit from Divergence5min like this:
public class Incrementer: Divergence5min
In order to now call ListCountLastH from here, that property should be at least protected in order to access it from an inherited class:
protected List<int> ListCountLastH;
To access non-static members on a class you need to instantiate it first with:
var divList = new DivergenceList5m();
divList.ListCountLastH.Add(divList.listCountLH);

Xamarin slider custom renderer DragCompleted event

I am trying to put in function an IOS custom renderer for Slider control. Due to wrapping class SlideriOS the event are not firing and I need DragCompleted event. Somebody has an idea how to trigger/route the event to the control?
[assembly: ExportRenderer(typeof(CustomGradientSlider), typeof(CustomGradientSliderRenderer))]
namespace HealMate.iOS.Renderers
{
public class CustomGradientSliderRenderer : SliderRenderer
{
public CGColor StartColor { get; set; }
public CGColor CenterColor { get; set; }
public CGColor EndColor { get; set; }
private UIImage GradImage { get; set; }
public string SelectionColor1 { get; set; }
public string SelectionColor2 { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
if (Control == null)
{
var customSlider = e.NewElement as CustomGradientSlider;
StartColor = customSlider.StartColor.ToCGColor();
CenterColor = customSlider.CenterColor.ToCGColor();
EndColor = customSlider.EndColor.ToCGColor();
var slider = new SlideriOS
{
Continuous = true,
Height = (nfloat)customSlider.HeightRequest
};
SetNativeControl(slider);
Control.ValueChanged += OnControlValueChanged;
}
base.OnElementChanged(e);
}
...
public class SlideriOS : UISlider
{
public nfloat Height { get; set; }
public override CGRect TrackRectForBounds(CGRect forBounds)
{
var rect = base.TrackRectForBounds(forBounds);
return new CGRect(rect.X, rect.Y, rect.Width, Height);
}
}
}
How can I fire the DragCompleted event for the slider control ?
Thanks!
There are some Events can be invoked in UISlider :
TouchDragExit : Raised on TouchDragExit events.
TouchUpInside : Raised on TouchUpInside events.
TouchUpOutside : Raised on TouchUpOutside events.
You can invoke one of them to have a try as follow :
Control.TouchDragExit += Slider_TouchDragExit;
private void Slider_TouchDragExit(object sender, EventArgs e)
{
throw new NotImplementedException();
}
===================================Update====================================
After testing in local project , above three methods only TouchUpInside works ,and not finding the reason why other methods not working in iOS renderer . I also test renderer methods in Android, it works . It is a strange phenomenon.
However , I found a workaround to invoke methods something as drag ended function.
[assembly: ExportRenderer(typeof(CustomSlider), typeof(CustomSliderRenderer))]
namespace App8.iOS
{
public class CustomSliderRenderer : SliderRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
if (null != Control)
{
CustomSlider customSlider = (CustomSlider)e.NewElement;
customSlider.DragCompleted += CustomSlider_DragCompleted;
}
}
private void CustomSlider_DragCompleted(object sender, EventArgs e)
{
Console.WriteLine("------CustomSlider_DragCompleted-------");
}
}
Here , CustomSlider is my custom Slider in Xamarin.Forms . If later find the reason why TouchDragExit not woking will update here.

How to read added record on list add on event

i have a list/Event by following code
class MyList<T> : List<T>
{
public event EventHandler OnAdd;
public void Add(T item)
{
if (null != OnAdd)
{
OnAdd(this, null);
}
base.Add(item);
}
}
MyList<connectedUser> myListUsers = new MyList<connectedUser>();
myListUsers.OnAdd += new EventHandler (myListUsers_OnAdd );
private void myListUsers_OnAdd(object sender, EventArgs e)
{
}
public class connectedUser
{
public string userID { get; set; }
public string marketID { get; set; }
}
In my on OnAdd event (myListUsers_OnAdd), i want my inserted object as Parameter in other word how i can read that inserted object inside event
You should use ObservableCollection, this has already an event that gets called when objects are added or removed.
Example:
var collection = new ObservableCollection<int>();
collection.CollectionChanged += CollectionOnCollectionChanged;
and your method looks like this:
private static void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) {
//notifyCollectionChangedEventArgs.NewItems <= this are the items that got added
//notifyCollectionChangedEventArgs.OldItems <= this are the items that got removed
}

Raise an event via reflection

well ive read a lot of posts here and there about why it isnt reliable to raise events via reflection.. my problem is this.. im using PostSharp to define an interface which allows a class to notify before and after a property is changed..
the NotifyAttribute ive created needs to be able to raise the PropertyBeforeChange and PropertAfterChange events.. thing is, even though i can retrieve the event, its GetRaiseMethod() returns null and hence i cannot raise the said events.. how can i go about doing that?
using PostSharp.Aspects;
namespace Core
{
public delegate void PropertyBeforeChangeEventHandler(object sender, string sPropertyName, object oCurrentValue, ref object oProposedValue, ref bool bCancel);
public delegate void PropertyAfterChangeEventHandler(object sender, string sPropertyName, object oOldValue, object oNewValue);
public interface INotify
{
event PropertyBeforeChangeEventHandler PropertBeforeChange;
event PropertyAfterChangeEventHandler PropertyAfterChange;
}
[Serializable]
public sealed class NotifyAttribute : LocationInterceptionAspect, IInstanceScopedAspect
{
bool _NotifyBefore { get; set; }
bool _NotifyAfter { get; set; }
public NotifyAttribute() { _NotifyAfter = true; }
public NotifyAttribute(bool bNotifyBefore, bool bNotifyAfter) { _NotifyBefore = bNotifyBefore; _NotifyAfter = bNotifyAfter; }
public override void OnSetValue(LocationInterceptionArgs args)
{
INotify oNotify = args.Instance as INotify;
if (oNotify == null) return;
object oCurrentValue = args.GetCurrentValue();
object oProposedValue = args.Value;
if (object.Equals(oCurrentValue, oProposedValue)) return;
bool bCancel = false;
if (_NotifyBefore)
{
var oObj = args.Instance.GetType().GetEvent("PropertyBeforeChange");
// RAISE EVENT HERE
}
if (bCancel) return;
args.Value = oProposedValue;
args.ProceedSetValue();
if (_NotifyAfter)
{
var oObj = args.Instance.GetType().GetEvent("PropertyAfterChange");
// RAISE EVENT HERE
}
}
public object CreateInstance(AdviceArgs adviceArgs) { return this.MemberwiseClone(); }
public void RuntimeInitializeInstance() { }
}
}
having defined this interface and this attribute, i can use it as follows..
public class Test : INotify
{
public event PropertyBeforeChangeEventHandler PropertyBeforeChange;
public event PropertyAfterChangeEventHandler PropertyAfterChange;
[Notify]
public string Name { get; set; }
}
Test oTest = new Test();
oTest.PropertyBeforeChange += Test_PropertBeforeChange;
oTest.PropertyAfterChange += Test_PropertyAfterChange;
oTest.Name = "Asim";
void Test_PropertBeforeChange(object sender, string sPropertyName, object oCurrentValue, ref object oProposedValue, ref bool bCancel)
{
}
void Test_PropertyAfterChange(object sender, string sPropertyName, object oOldValue, object oNewValue)
{
}

Categories