This is a migrated thread and some comments may be shown as answers.

Pull to refresh in viewmodel

14 Answers 957 Views
ListView
This is a migrated thread and some comments may be shown as answers.
Gagan
Top achievements
Rank 1
Gagan asked on 08 Jun 2016, 08:02 PM

Is there a way i can make PullToRefresh a command in my viewmodel?

Or is there another way to make it more MVVM?

14 Answers, 1 is accepted

Sort by
0
Pavel R. Pavlov
Telerik team
answered on 09 Jun 2016, 08:38 AM
Hi,

I already answered this question in your support ticket. However I will copy/paste the same answer here so that others from the community can benefit from it.

Pull to refresh is a feature allowing end customers to manually update a list of items. This is an UI operation and it should not be triggered from code. If developers need to update that same list they should directly add items to the list's ItemsSource. 

As for the other way, you can create a custom behavior which can be attached to the RadListView. That behavior can listen for the RefreshRequested event and can trigger your custom command as well as the EndRefresh() method. Using behavior does not break any MVVM approach and is frequently used.

If you ask if there is out of the box approach for implementing pull to refresh command - the answer is no. Currently we do not support such command. However, there are many third party behaviors (e.g. event to command behavior) that can fit your scenario. You can try looking for them as well.

Regards,
Pavel R. Pavlov
Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 15 Jan 2018, 09:26 PM

Sorry for hijacking this old post. I currently use behaviours to hook some ListView commend to my Viewmodel. But can you point me the way to call the viewmodel Event with ICommand AND do a EndRefresh() on the local side? DO you pass the ListviewObject to the viewmodel for that?
Thanks

0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 15 Jan 2018, 09:29 PM

I am using this EventToCommand

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)
        {
            base.OnDetachingFrom (bindable);
            DeregisterEvent (EventName);
        }
 
        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);
        }
    }
0
Lance | Senior Manager Technical Support
Telerik team
answered on 16 Jan 2018, 07:36 PM
Hello Pierre,

You need a reference to the RadListView in order to call EndRefresh.

How you pass that to the ViewModel is up to you, but since you already have a behavior set up, you can just pass the RadListView as the CommandParameter instead of the event args (event args is the default).

For example:

<telerikDataControls:RadListView x:Name="radListView"
                    ItemsSource="{Binding MyItems}"
                    IsPullToRefreshEnabled="True">
    <telerikDataControls:RadListView.Behaviors>
        <portable:EventToCommandBehavior EventName="RefreshRequested"
                        Command="{Binding PullToRefreshCommand}"
                        CommandParameter="{x:Reference radListView}" />
    </telerikDataControls:RadListView.Behaviors>
</telerikDataControls:RadListView>

public class ViewModel
{
    public ViewModel()
    {
        PullToRefreshCommand = new Command(async (rlv) => await RefreshData(rlv));
    }
 
    private async Task RefreshData(object obj)
    {
        // this just simulates a network delay
        await Task.Delay(1000);
             
        // 1 - refresh your data
        MyItems.Add($"Item {MyItems.Count + 1}");
             
        // 2 - end refresh
        (obj as RadListView)?.EndRefresh();
    }
 
    public Command PullToRefreshCommand { get; set; }
 
    public ObservableCollection<string> MyItems { get; set; } = new ObservableCollection<string> {"Item 1", "Item 2", "Item 3"};
}

I've attached a demo using the above code.

If you have any further trouble, open a support ticket here, attach the code we can use to reproduce the problem and we'll investigate further.

Regards,
Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 25 Jan 2018, 09:31 PM

Thanks, you code work great. But The PullToRefresh work only on IOS. In UWP I can't slide to make the refresh zone appears.

Any idea?

De we have a way to haev a template of the PullToRefresh zone? For now I only get an big black zone with a loading indicator. Can we write some text (like the last time we update the list)

0
Lance | Senior Manager Technical Support
Telerik team
answered on 26 Jan 2018, 05:09 PM
Hi Pierre,

If the UWP app is deployed to mobile device family, it should be working as expected. However on desktop, you'd need touch to pull it as the mouse won't trigger the gesture. In this case, you can dynamically show a refresh button if the runtime platform is UWP.

Regarding customizing the pull to refresh area, you'll need to write a custom renderer as we don't expose the API in Xamarin.Forms, see this forum thread for a discussion and example.

For your convenience, I've updated the cmeo to use the latest version and remove the deprecated code. Here's a short video of the attached demo at runtime and here's the entirety of the renderer in the AppDelegate class:

using Foundation;
using Telerik.XamarinForms.DataControls;
using UIKit;
using ListViewPullToRefreshStyle.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
 
[assembly: ExportRenderer(typeof(RadListView), typeof(CustomListViewRenderer))]
namespace ListViewPullToRefreshStyle.iOS
{
    [Register("AppDelegate")]
    public partial class AppDelegate : FormsApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            Forms.Init();
 
            LoadApplication(new Portable.App());
 
            return base.FinishedLaunching(app, options);
        }
    }
 
    public class CustomListViewRenderer : Telerik.XamarinForms.DataControlsRenderer.iOS.ListViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<RadListView> e)
        {
            base.OnElementChanged(e);
 
            this.Control.PullToRefreshView.ActivityIndicator.ActivityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge;
 
            // and or
 
            this.Control.PullToRefreshView.ActivityIndicator.Color = UIColor.White;
            this.Control.PullToRefreshView.BackgroundColor = UIColor.Green;
        }
    }
}


As far as using text instead of the busy indicator animation, this is not currently a feature. If you're familiar with Xamarin.iOS, you can create a subview and use UITextView to manually create it, but this is not supported and a completely custom approach which falls outside the scope of Telerik Support. 

Lastly, you'll need to create custom renderers for each platform and keep in mind not all platforms have the same customization features for the PullToRefresh indicator and you'll have to account for these nuances. Here is the documentation for the other platforms: here for UI for UWP and here UI for Xamarin.Android.

If you'd like us to add an API that lets you customize it without a custom renderer, submit a feature request here.

Regards,
Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 29 Jan 2018, 05:04 PM

[quote]Lance | Tech Support Engineer, Sr. said:Hi Pierre,

If the UWP app is deployed to mobile device family, it should be working as expected. However on desktop, you'd need touch to pull it as the mouse won't trigger the gesture. In this case, you can dynamically show a refresh button if the runtime platform is UWP.

[/quote]

It is a limitation of UWP itself? Or a limitation on Telerik RadListview?My app will be deployed on IOS and UWP (mobile and desktop). I can add a Refresh Button UWP view, but I got the sale problem with cellSwipe in the listview... Any workaround?

0
Lance | Senior Manager Technical Support
Telerik team
answered on 29 Jan 2018, 11:07 PM
Hi Pierre,

Correct, this isn't related to Xamarin or UI for Xamarin. It's a platform-specific approach, not necessarily a limitation, because many desktop PCs now have touchscreens. As a UWP developer you'd build your apps expecting certain inputs to be available and surface contextual interactions accordingly.

We have been adding some features to the native RadListView to overcome some of these like the Handle reorder mode for the RadListView. On desktop, the handle allows you to reorder the item with mouse.

There have been requests to bring more pointer base gestures to the native control, you can find the native control's home here on GitHubAdd an Issue to the repo here and requesting that swipe and pull to refresh to be supported. As you can see this Issue was the one that requested the Reorder mode support and was promptly added to the product.


Advanced Workaround

There is one workaround you can try, but it's a more advanced scenario, you could try to reroute pointer input into touch input (here's an example for WPF). I couldn't find any examples for UWP, but this is the Pointer input information you'd need to build it.

Regards,
Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 07 Feb 2018, 04:14 PM

I use your solution for the listview SellSwipe too. I pass the LV instance to be able to do the EndItemSwipe().

But I can't detect the swipeOffset and know if the swipe is right or left. Normally in the code behind we got an ItemSwipeCompletedEventArgs that contains an e.Offset for that. But I can't see anything like that directly in the LV object.

For now I do:

private async void OnItemSwipeCompleted(RadListView lv)
{
    if (lv.IsSwipingInProgress == true)
    {
        await Task.Delay(1000);
        var item = lv.SelectedItem;
        var tt = lv.SwipeOffset;
        lv.EndItemSwipe();
    }
}

 

But lv.SwipeOffet contained the general offset information.

Do we have another way? Can we pass the ItemSwipeCompletedEventArgs when commanding?

Thanks

0
Lance | Senior Manager Technical Support
Telerik team
answered on 07 Feb 2018, 06:07 PM
Hello Pierre,

Yes, if you use an EventToCommandBehavior, you'll get the ItemSwipeCompletedEventArgs passed to the command's Action (take a look at the EventToCommandBehavior in my GitHub demo here). 

Example

Here's a small demo to convey the approach:

In the View:

<dataControls:RadListView ...>
    <dataControls:RadListView.Behaviors>
        <behaviors:EventToCommandBehavior EventName="ItemSwipeCompleted"
                               Command="{Binding MySwipeCommand}"/>
    </dataControls:RadListView.Behaviors>
</dataControls:RadListView>

In the ViewModel

public class ViewModel
{
    public ViewModel()
    {
        MySwipeCommand = new Command(ExecuteOnSwipeCompete);
    }
 
    public Command MySwipeCommand { get; }
 
    private void ExecuteOnSwipeComplete(object obj)
    {
        var args = obj as ItemSwipeCompletedEventArgs;
        // this is the same args you get in a code-behind event handler
    }
}


Further Assistance

If you have any trouble implementing it, please open a support ticket here and attach your code directly to the ticket (you have priority support). I will see the ticket come in and will investigate further.

Regards,
Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Pierre
Top achievements
Rank 2
Iron
Iron
answered on 07 Feb 2018, 07:06 PM

Hello Lance,

Ok I understand that, but in this case, I lost the LV object so I can't do the EndItemSwipe. In you exemple can I get the "sender" object to?

0
Lance | Senior Manager Technical Support
Telerik team
answered on 08 Feb 2018, 04:19 PM

Hello Pierre,

EventToCommand isn't designed to also pass the sender because this breaks MVVM.  If you don't mind tightly coupling the view to the viewmodel, you can pass the RadListView reference to the view model in the page's code behind.

For example:

ViewModel

public class ViewModel
{
    public ViewModel()
    {
        MySwipeCommand = new Command(ExecuteOnSwipeCompete);
    }
  
    public RadListView MyListViewReference { get; set; }
   
    public Command MySwipeCommand { get; }
   
    private void ExecuteOnSwipeComplete(object obj)
    {
        var args = obj as ItemSwipeCompletedEventArgs;
        // this is the same args you get in a code-behind event handler
  
        MyListViewReference.xxx
    }
}


View Markup

<ContentPage..>
    <telerik:RadListView x:Name="MyListView" />
</ContentPage>

View Code behind:

public partial class MyPage : ContentPage
{
    public MyPage()
    {
        InitializeComponent();
 
        (BindingContext as ViewModel).MyListViewReference = this.MyListView;
    }
}



Regards,

Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
darrell
Top achievements
Rank 1
answered on 13 Feb 2018, 08:27 PM

The answers on this post are nasty hacks and this is disappointing from Telerik.

It's doubly disappointing because the out of the box Xamarin.Forms ListView has Commanding for PullToRefresh where as the RadListView does not - it's worse than the out of the box component in this area.

So here is once way to work around this:

 

```csharp

    public interface IGridAdaptor
    {
        void Cancel();
        void EndRefresh();      
    }

    public class RadGridAdaptor : IGridAdaptor
    {
        private readonly PullToRefreshRequestedEventArgs _args;
        private readonly RadListView _listView;

        public RadGridAdaptor(PullToRefreshRequestedEventArgs args, RadListView listView)
        {
            _args = args;
            _listView = listView;
        }
        public void Cancel()
        {
            _args.Cancel = true;
        }

        public void EndRefresh()
        {
            _listView.EndRefresh();
        }
    }

    public class RadGridPullToRefreshArgsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var args = value as PullToRefreshRequestedEventArgs;
            var listView = parameter as RadListView;
            return new RadGridAdaptor(args, listView);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

```

 

Then in your xaml:

 

```xml

 <telerikDataControls:RadListView.Behaviors>
                                <b:EventToCommandBehavior EventName="RefreshRequested" 
                                          Command="{Binding RefreshCommand}"  
                                          EventArgsConverterParameter="{x:Reference listView}"
                                          EventArgsConverter="{converters:RadGridPullToRefreshArgsConverter}"/>
                            </telerikDataControls:RadListView.Behaviors>

```

The idea is basically the `Behavior` handles the event, and then passes the Grid and the Event Args to a Converter.

The Converter creates an `IGridAdaptor` which wraps those, and can be passed into your `ICommand`. This way you can potentially switch the ListView implementation in future without having to refactor your commands, by implementing a new Converter.

The command accepts the `IGridAdaptor` as an argument does its business, then calls EndRefresh (or Cancel).

```

            RefreshCommand = new DelegateCommand<IGridAdaptor>(async (arg) =>
            {
                await DoStuff();

                 _deviceService.BeginInvokeOnMainThread(() =>
                {
                    arg.EndRefresh();
                });               

            });

 

```

Be careful, you must call the EndRefresh() from a UI thread.

 

 

 

0
Lance | Senior Manager Technical Support
Telerik team
answered on 14 Feb 2018, 07:33 PM
Hi Darrel,

Initially the RadListView didn't have support for commands, which was why we had to suggest such workarounds. We understood the need for this and thus we've recently added MVVM Command support for all the relevant events.

You just need to add to the RadListView.Commands collection using an ID to identify which event the command is for (as opposed to directly wiring up the command to a property).

Here's a list of the available IDs:





You can use the default command as seen above (a ListViewUserCommand), or use a custom command by extending ListViewCommand and overriding the command methods.


Custom Command Example

In the custom command you have access to the Owner (RadListView), so you can call a method on the BindingContext as well as EndRefresh().

<telerikDataControls:RadListView BindingContext="{StaticResource viewModel}"
                   ItemsSource="{Binding Items}"
                   IsPullToRefreshEnabled="True">
    <telerikDataControls:RadListView.Commands>
        <portable:CustomRefreshRequestedCommand />
    </telerikDataControls:RadListView.Commands>
</telerikDataControls:RadListView>


Command

public class CustomRefreshRequestedCommand : ListViewCommand
{
    public CustomRefreshRequestedCommand()
    {
        this.Id = CommandId.PullToRefreshRequested;
    }
         
    public override bool CanExecute(object parameter)
    {
        return true;
    }
 
    public override void Execute(object parameter)
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            (Owner?.BindingContext as ViewModel)?.RefreshItems();
            Owner?.EndRefresh();
        });
    }
}


Documentation

The documentation and SDKExample examples still needs to be updated with these options. I have informed the development team so that they can make sure these are in the next updates.

In the meantime, the RadDataGrid does have a Commands article that is similar to explain what some of the properties are, but it's not an exact match (e.g. the IDs are different).

Further support

If you have any difficulty implementing a command, open a support ticket here and I'll make sure the approriate engineering team follows up with you.

Regards,
Lance | Tech Support Engineer, Sr.
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Tags
ListView
Asked by
Gagan
Top achievements
Rank 1
Answers by
Pavel R. Pavlov
Telerik team
Pierre
Top achievements
Rank 2
Iron
Iron
Lance | Senior Manager Technical Support
Telerik team
darrell
Top achievements
Rank 1
Share this question
or