RecyclerView don't have Item click and other events - c#

Earlier I used ListView and there was lots of events in this class, but now I started to use RecyclerView and here I can't find any event.
for example:
RecyclerView mView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
mView.ItemClick += delegate {
// action
};
is not working with this class - RecyclerView.
Actually these events whitch I want exist in RecyclerView.Adapter so I tried this:
public class ListItemArgs : EventArgs
{
public int itemPosition;
public ListItemArgs(int pos)
{
itemPosition = pos;
}
}
public class MyAdapter : RecyclerView.Adapter
{
public event EventHandler<ListItemArgs> OnItemLongClick;
// some overrides skipped (not needed anyway)
public class MyView : RecyclerView.ViewHolder
{
public View mMainView { get; set; }
public TextView mName { get; set; }
public TextView mEventCount { get; set; }
public CheckBox mFavorite { get; set; }
public MyView(View view) : base(view)
{
mMainView = view;
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyView mHolder = holder as MyView;
mHolder.mMainView.LongClick += delegate
{
Snackbar.Make(mRecyclerView, "Ar tikrai norite trinti pasirinkimÄ…?", Snackbar.LengthLong).Show();
OnItemLongClick.Invoke(mHolder.mMainView, new ListItemArgs(position));
};
}
}
and declarations in other class:
RecyclerView mView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
RecyclerView.Adapter mAdapter new AddNewDayAdapter(mView);
And now I should be able to launch this event from mAdapter object, which leter I use to SetAdapter for my RecyclerView. (code below)
mAdapter.OnItemLongClick += (object sender, ListItemArgs e) =>
{
// action
}
mView.SetAdapter(mAdapter);
but mAdapter.OnItemLongClick is not working.
Main question: how can I get an event in which args would be view of row and selected item position ?

Related

In Winform why ALL bound properties are updated when I call PropertyChanged on ONE data source?

I have two buttons and bind their property to two properties of a data object.
But every property is updated when I call PropertyChanged of the data object.
public partial class Form1 : Form
{
private DataClass data = new DataClass();
public Form1()
{
InitializeComponent();
ButtonA.DataBindings.Add("Text", data, "DataA");
ButtonB.DataBindings.Add("Text", data, "DataB");
ButtonB.Click += new EventHandler(OnButtonBClicked);
}
private void OnButtonBClicked(object sender, EventArgs e)
{
data.DataA += "1";
data.DataB += "1";
data.Notify("DataB");
}
}
public class DataClass : INotifyPropertyChanged
{
public string DataA { get; set; }
public string DataB { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataClass() {}
public void Notify(string property_name)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
When I press ButtonB (which means I call PropertyChanged(this, new PropertyChangedEventArgs("DataB"))), both ButtonA and ButtonB show new text.
If I call PropertyChanged(this, new PropertyChangedEventArgs("DataA")), both buttons are updated.
If I don't change value of DataA / DataB and just call PropertyChanged(this, new PropertyChangedEventArgs("DataB")), still both buttons are updated (can be noticed by breakpoint debugging).
If I call PropertyChanged(this, new PropertyChangedEventArgs("QQQ")), then no button is updated.
PropertyChangedEventArgs has a property named propertyName, I thought it's used to specify one property to notify but it doesn't.
In my real code, DataB changes much more frequently than DataA. I don't want to update ButtonA each time DataB is changed, it takes too much time.
Question: why would this happen? When a data source property is changed, how can I only update properties really connected to it?
(All code is .Net Framework 4.7.1 on Windows.)
#Jimi's method works.Simple and effective.I put each property in a shell class and bind data to the shell:
public class MyProperty<T>: INotifyPropertyChanged
{
public T Content { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MyProperty(T _content)
{
Content = _content;
}
public void Notify()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public class DataClass
{
public MyProperty<string> DataA = new MyProperty<string>("");
public MyProperty<string> DataB = new MyProperty<string>("");
public DataClass() {}
}
But in this way I must use DataA.Content+="1" instead of DataA+="1" every where.
I decide to use a base class to create all shells.But my real DataClass must inherit from other class and C# don't support multi-inherit.So I have to use a extension class.
public class BindHandle<T> : INotifyPropertyChanged
{
public T Content { get { return (T)parent.GetType().GetProperty(prop_name).GetValue(parent); } }
private object parent;
private string prop_name;
public event PropertyChangedEventHandler PropertyChanged;
public BindHandle(object _parent, string _prop_name)
{
parent = _parent;
prop_name = _prop_name;
}
public void NotifyChange()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public interface IBindHandleProvider
{
BindHandleProvider provider { get; set; }
}
public class BindHandleProvider
{
private Dictionary<string, object> handle_map = new Dictionary<string, object>();
public BindHandle<T> GetBindHandle<T>(object obj,string property_name)
{
if (!handle_map.ContainsKey(property_name))
handle_map.Add(property_name, new BindHandle<T>(obj, property_name));
return (BindHandle<T>)handle_map[property_name];
}
public void NotifyChange<T>(string property_name)
{
if (handle_map.ContainsKey(property_name))
((BindHandle<T>)handle_map[property_name]).NotifyChange();
}
}
public static class BindHandleProviderExtension
{
public static void NotifyChange<T>(this IBindHandleProvider obj, string property_name)
{
obj.provider.NotifyChange<T>(property_name);
}
public static BindHandle<T> GetBindHandle<T>(this IBindHandleProvider obj, string property_name)
{
return obj.provider.GetBindHandle<T>(obj,property_name);
}
}
public class DataClass:IBindHandleProvider
{
public BindHandleProvider provider { get; set; } = new BindHandleProvider();
public string DataA { get; set; } = "";
public string DataB { get; set; } = "";
public DataClass(){ }
}
Then bind it like
ButtonA.DataBindings.Add("Text", data.GetBindHandle<string>("DataA"), "Content");
And notify like
data.NotifyChange<string>("DataB");
It's kinda complex but works well.

In Xamarin.Forms, how to notify the changes of the same viewmodel back to the previous page? (can pass to the second page, but not back)

I got two pages, "HomePage", "SettingPage", including the same "MyView" (some Pickers there).
When I click "Go Setting"(or show more settings) Button from Homepage, the values syncs to the setting page. But When I click "Apply" on the setting page, the values did not come back.
I am new in c# and Xamarin and tried to search online and Microsoft docs. But I couldn't find a way to fix this issue.
Also I was following this link: How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms?
and did the same global value in my code.
MyView (ContentView)
public MyView()
{
InitializeComponent();
BindingContext = GlobalVar.MyViewModel;
Setting1.SetBinding(Picker.ItemsSourceProperty, "ObList1");
Setting1.ItemDisplayBinding = new Binding("obj_text");
Setting1.SetBinding(Picker.SelectedItemProperty, "SelectedItem1");
//also other pickers
}
HomePage (including the MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
SettingPage (including the same MyView)
public partial class SettingPage : ContentPage
{
MyViewModel viewModel { get; set; } = GlobalVar.MyViewModel;
public SettingPage ()
{
BindingContext = viewModel;
}
private async void Click_ApplySetting(object sender, EventArgs e)
{
await Navigation.PopAsync(true);
}
//some other method deal with viewModel
}
GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
ViewModel
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
public obj SelectedItem1 { get; set; }
public obj SelectedItem2 { get; set; }
public obj SelectedItem3 { get; set; }
public MyViewModel()
{
ObList1 = new ObservableCollection<obj>();
ObList2 = new ObservableCollection<obj>();
ObList3 = new ObservableCollection<obj>();
}
}
Maybe I should notify the changes on my SettingPage to viewmodel? or do something in the "set" in viewmodel?
The confusing point is that two pages embed the same view using the same viewmodel, but notify the change from Page1 to Page2 only, not Page2 to Page1.
Any ideas, thx in advance.
Solution One:
Using Event can pass value back to Previous Page.
Define Event in SecondPage :
public delegate void EventHandler(string status);
public event EventHandler EventPass;
Invoke Event when Page disappear:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
In FirstPage, when Naviagtion place need to add the Event here:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
Now value will be passed here:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
Solution Two:
Using Properties Dictionary to store easy and small size data in Application, when enter in page will invoke it to get data from which has been stored.
In Second Page Where you want to store data, writing as bellow:
Application.Current.Properties ["value"] = valuedata;
When back to First Page, override OnAppearing method to update UI:
protected override void OnAppearing()
{
base.OnAppearing();
if (Application.Current.Properties.ContainsKey("value"))
{
var ValueGet = Application.Current.Properties ["value"] as DataType;
// do something with other things
}
}
Note: ViewModel if want to dynamic update data , need to use INotifyPropertyChanged .
Sample Implementation:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModelBase suggest implementing ICommand as a Dictionary structure like:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
So all todo in your ViewModel is just inherit the ViewModelBase class and use it:
class LoginViewModel : ViewModelBase
{
string userName;
string password;
public string UserName
{
get {return userName;}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get{return password;}
set
{
password = value;
OnPropertyChanged("Password");
}
}
#endregion
#region ctor
public LoginViewModel()
{
//Add Commands
Commands.Add("Login", new Command(CmdLogin));
}
#endregion
#region UI methods
private void CmdLogin()
{
// do your login jobs here
}
#endregion
}
Solved.
MyViewModel (updated)
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
private obj _selectedItem1 = new obj();
public obj SelectedItem1
{
get { return _selectedItem1; }
//this is the line solved the problem
//but still not understood thoroughly
set { SetProperty(ref _selectedItem1, value); }
}
//same for _selectedItem2 _selectedItem3
}
ps: BaseViewModel codes here (not changed, from template codes)
public class BaseViewModel : INotifyPropertyChanged
{
//some other attributes
//...
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
It seems that by calling SetProperty, OnPropertyChanged will also be revoked.
But still a little bit confusing about why the previous codes go like kind of "one-way" binding.

Access Parent level object

public class Activity
{
public games _Games {get;set;}
public sports _Sports {get;set;}
}
public class games : PropertyChangedBase
{
public int player
{
get;
set; //have if- else statement
}
}
public class sports : PropertyChangedBase
{
public int sub{get;set;}
}
Aim: when the games player is more than 2, I would like to update sports sub variable to 10.
Question: How can I access the parent class and update the sports class variable?
You could use an event that would signal to the Activity class that it is time to update.
public class games
{
public event UpdatePlayerSubDelegate UpdatePlayerSub;
public delegate void UpdatePlayerSubDelegate();
private int _player;
public int player
{
get { return _player; }
set
{
_player = value;
if (_player > 2)
{
// Fire the Event that it is time to update
UpdatePlayerSub();
}
}
}
}
In the Activity class you can register the event in the constructor and write in to the event handler the necessary update. In your case sub to 10:
public class Activity
{
public games _Games { get; set; }
public sports _Sports { get; set; }
public Activity()
{
this._Games = new games();
this._Games.UpdatePlayerSub += _Games_UpdatePlayerSub;
this._Sports = new sports();
}
private void _Games_UpdatePlayerSub()
{
if (_Sports != null)
{
_Sports.sub = 10;
}
}
}
EDIT
I just saw the tag INotifyPropertyChanged. Of course you could also use this interface and the provided event. Implement the interface as the following:
public class games : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _player;
public int player
{
get { return _player; }
set
{
_player = value;
if (_player > 2)
{
// Fire the Event that it is time to update
PropertyChanged(this, new PropertyChangedEventArgs("player"));
}
}
}
}
And in the Activity class register again to the event in the constructor:
public Activity()
{
this._Games = new games();
this._Games.PropertyChanged += _Games_PropertyChanged;
this._Sports = new sports();
}
and declare the body of the event handler:
private void _Games_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_Sports != null)
{
_Sports.sub = 10;
}
}
And _Sports.sub will get updated automatically. Hope it helps. There are of course other ways to accomplish this update. It is just the first the came to my mind
You need to create an instance of Activity. You also need to initialize _Sports in it
Activity activity = new Activity();
activity._Sports = new sports();
activity._Sports.sub = 10;
Or using object tantalizer
Activity activity = new Activity
{
_Sports = new sports()
};
activity._Sports.sub = 10;
By the way, Activity is not parent class of sports. Activity holds sports object as a member. In your example PropertyChangedBase is parent class of games.

How to implement chained/proxied databindings

I have this problem in my mvvm wpf application where I want to create proxied/chained databindings.
My ModelViewController looks like this:
public class ListViewModel
{
public ObservableCollection<Contact> GridData { get; private set; }
public ObservableCollection<Contact> SelectedEntities { get; private set; }
public bool IsSingeselect { get { return SelectedEntities.Count == 1; } }
public bool IsMultiSelect { get { return SelectedEntities.Count > 0; } }
public ObservableCollection<MenuComandModel> ContextMenuItems { get; private set; }
}
GridData and SelectedEntities is bound to a datagrid and works like a charm. I'm using the ContextMenuItems collection to create BarButtonItems for the datagrids contextmenu, this works very good. The MenuComandModel class has a Enabled attribute and I want to bind this on the IsSingeselect or IsMultiSelect attribute to the BarButtonItems member IsEnabled . How would I archive this?
Since you are using DevExpress you can use all the benefits of DevExpress MVVM Framework and their POCO-ViewModels:
using DevExpress.Mvvm.POCO;
//...
public class ListViewModel {
public ObservableCollection<Contact> GridData { get; private set; }
// mark the SelectedEntities property as virtual to be notified on initializing/replacing
public virtual ObservableCollection<Contact> SelectedEntities { get; private set; }
// unsubscribe the CollectionChanged event in changing-callback
protected void OnSelectedEntitiesChanging() {
if(SelectedEntities != null)
SelectedEntities.CollectionChanged -= SelectedEntities_CollectionChanged;
}
// subscribe the CollectionChanged event in changed-callback
protected void OnSelectedEntitiesChanged() {
if(SelectedEntities != null)
SelectedEntities.CollectionChanged += SelectedEntities_CollectionChanged;
UpdateSelectedEntitiesDependencies();
}
void UpdateSelectedEntitiesDependencies() {
// Raise INPC for dependent properties
this.RaisePropertyChanged(x => x.IsSingeselect);
this.RaisePropertyChanged(x => x.IsMultiSelect);
// Raise INPC for dependent properties of child ViewModels
foreach(var item in ContextMenuItems)
item.RaisePropertyChanged(x => x.Enabled);
}
void SelectedEntities_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
if(e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Reset)
UpdateSelectedEntitiesDependencies();
}
public bool IsSingeselect { get { return SelectedEntities.Count == 1; } }
public bool IsMultiSelect { get { return SelectedEntities.Count > 0; } }
public ObservableCollection<MenuComandViewModel> ContextMenuItems { get; private set; }
}
public class MenuComandViewModel {
public bool Enabled {
get {
var parentViewModel = this.GetParentViewModel<ListViewModel>();
return parentViewModel.IsMultiSelect; // Some implementation
}
}
}
Then you can bind you bar items to the ContextMenuItems collection using the approach described in MVVM Support in DXBars, DXRibbon and GalleryControl help-article.

Xamarin passing context from OnClick event

I m a beginner in android dev, I m struggling with passing string Clicked_Message from Click event in Recycle Adapter Class to the other activity. Is it a good way to use Intent? If so how can I pass context to click event? Thanks
public class RecyclerAdapter : RecyclerView.Adapter
{
private RecyclerView mRecyclerView;
private List<NotificationClass> mEmails;
public RecyclerAdapter(List<NotificationClass> emails, RecyclerView recyclerView)
{
mEmails = emails;
mRecyclerView = recyclerView;
}
public class MyView : RecyclerView.ViewHolder
{
public View mMainView { get; set; }
public TextView mName { get; set; }
public TextView mSubject { get; set; }
public TextView mMessage { get; set; }
public MyView(View view) : base(view)
{
mMainView = view;
}
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
View row = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.row, parent, false);
TextView txtName = row.FindViewById<TextView>(Resource.Id.txtName);
TextView txtSubject = row.FindViewById<TextView>(Resource.Id.txtSubject);
TextView txtMessage = row.FindViewById<TextView>(Resource.Id.txtMessage);
MyView view = new MyView(row) { mName = txtName, mSubject = txtSubject, mMessage = txtMessage };
return view;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyView myHolder = holder as MyView;
int indexPosition = (mEmails.Count - 1) - position;
myHolder.mMainView.Click += mMainView_Click;
myHolder.mName.Text = mEmails[position].Name;
myHolder.mSubject.Text = mEmails[position].Subject;
myHolder.mMessage.Text = mEmails[position].Message;
}
public override int ItemCount
{
get { return mEmails.Count; }
}
public void OnClick(int position)
{
if (ItemClick != null)
ItemClick(this, position);
}
public void mMainView_Click(object sender, EventArgs e,Context context)
{
int position = mRecyclerView.GetChildPosition((View)sender);
int indexPosition = (mEmails.Count - 1) - position;
Console.WriteLine(mEmails[indexPosition].Message);
string Clicked_Message = (mEmails[indexPosition].Message);
var activity2 = new Intent(context, typeof(ContactActivity));
activity2.PutExtra("MyData", Clicked_Message);
context.StartActivity(activity2);
}
}
You don't need to pass a context. Just use an intent and put the information you want to pass as extras into the intent.
In case your adapter needs a context, pass it in through the constructor and store it as a field member.
This is my typical implementation of the RecyclerView.Adapter with a view holder...
public class ContactsAdapter : V7.RecyclerView.Adapter
{
private List<Contact> _contacts;
public event EventHandler ItemClick;
public void OnItemClick(ContactViewHolder holder)
{
if (ItemClick != null)
{
ItemClick(holder, EventArgs.Empty);
}
}
public ContactsAdapter(List<Contact> contacts)
: base()
{
_contacts = contacts;
}
public override void OnBindViewHolder(V7.RecyclerView.ViewHolder holder, int position)
{
var contactHolder = (ContactViewHolder)holder;
contactHolder.BindUI(_contacts[position]);
}
public override V7.RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var view = LayoutInflater.FromContext(parent.Context).Inflate(Resource.Layout.ContactsListItem, parent, false);
return new ContactViewHolder(view)
{
Adapter = this
};
}
public override int ItemCount
{
get
{
return _contacts.Count;
}
}
}
View Holder (typically in the same file as the adapter)
public class ContactViewHolder : V7.RecyclerView.ViewHolder, View.IOnClickListener
{
public TextView ContactNameTextView { get; set; }
public TextView ContactPhoneTextView { get; set; }
public TextView ContactIntialsTextView { get; set; }
public Contact Contact { get; set; }
private WeakReference _adapter;
public ContactsAdapter Adapter
{
get { return (ContactsAdapter)_adapter.Target; }
set { _adapter = new WeakReference(value); }
}
public ContactViewHolder(View view)
: base(view)
{
GetUI(view);
view.SetOnClickListener(this);
}
private void GetUI(View view)
{
ContactNameTextView = view.FindViewById<TextView>(Resource.Id.ContactName);
ContactPhoneTextView = view.FindViewById<TextView>(Resource.Id.ContactPhone);
ContactIntialsTextView = view.FindViewById<TextView>(Resource.Id.ContactInitialsTextView);
}
public void BindUI(Contact contact)
{
Contact = contact;
ContactNameTextView.Text = contact.ContactName;
ContactPhoneTextView.Text = contact.Phone1;
ContactIntialsTextView.Text = contact.Initials;
}
public void OnClick(View v)
{
Adapter.OnItemClick(this);
}
}
This encapsulates the functionality to the view holder. I also give the instance of the adapter to the view holder as a WeakReference. This allows me to call the OnItemClick, passing the instance of the view holder. If you will notice that the view holder also has an instance of the object that it is representing. This means I don't have to worry about the index that was chosen. I already have the object data available to me.
So the implementation in the Activity/Fragment is like this...
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
_contacts = Contact.GetAllContacts();
_adapter = new ContactsAdapter(_contacts);
_adapter.ItemClick += ContactSelected;
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.ContactsFragment, container, false);
var layoutManager = new V7.LinearLayoutManager(this.Activity) { Orientation = V7.LinearLayoutManager.Vertical };
_contactsView = view.FindViewById<V7.RecyclerView>(Resource.Id.ContactList);
_contactsView.SetAdapter(_adapter);
_contactsView.HasFixedSize = true;
_contactsView.SetLayoutManager(layoutManager);
return view;
}
private void ContactSelected (object sender, EventArgs e)
{
var holder = (ContactViewHolder)sender;
var detailFragment = new ContactDetailsFragment(holder.Contact);
MainActivity.ShowFragment(detailFragment);
}
I give the Contact to a Fragment, but you could do something similar for an activity using an intent.
Now whether this is the most efficient way of handling a click of a row in a RecyclerView, I don't know. But this implementation has been working for me.

Categories