XForms needs an ItemsControl

EricGroverEricGrover USMember ✭✭

I may be totally missing something, but it seems like the biggest gap in Xamarin Forms that needs to be filled is the lack of an ItemsControl so that DataTemplates can be bound to collections in the ViewModel. I know that you can use a ListView, but that really isn't a good UI fit for many situations.

Anyone have a good workaround for this? I have found myself using the MessagingCenter and then dynamically adding my controls after my data has been loaded. Definitely not the MVVM way.

Best Answers

Answers

  • EricGroverEricGrover USMember ✭✭

    Note, I think it would be pretty trivial to implement such a control by inheriting from ItemsView, but alas the contructor for this abstract class is internal.

  • MitchMilamMitchMilam USMember ✭✭✭

    Well, if you do get ambitious and create such a beast, think about adding it to the Xamarin.Forms Labs project.

  • EricGroverEricGrover USMember ✭✭
    edited August 2014

    @MitchMilam‌ I might build my own, but it would probably be best if Xamarin made the constructor public and allowed us to inherit from ItemsView.

  • rmarinhormarinho PTMember, Insider, Beta Xamurai

    yeah ItemsView be more open will be a great help.

    @EricGrover‌ the thing is don't forget that ItemsControl is a microsoft thing, there isn't a exact same concept on the other platforms. Also without styling like we have in Microsoft xaml it will be really hard to style the items, or specifying a panel template etc..

    that's why i think the Xamarin Forms Labs comunity project is good because if people share this ideias we could all work on the instead of waiting that Xamarin team implements.

  • EricGroverEricGrover USMember ✭✭

    I realize that XForms isn't going to duplicate all of WPF/Silverlight. I just think that for XForms to be a full MVVM framework, this is a rather significant piece that is missing.

    It would be helpful to know if an ItemsControl is on the roadmap for Xamarin, or if it is something the user community should take the time to build ourselves.

  • rmarinhormarinho PTMember, Insider, Beta Xamurai

    but you are seeing it wrong imo. XForms has nothing to do with MVVM, it is a UI platform, although MVVM goes well with XAML and Databinding i don't think we should relate the two.. you can use Forms withought even doing databind or know anything about the MVVM pattern.

    I think the focus of XForms is having native implementations of the controls that already exist in each platform. and abstract there in a common way to be used in PCL.

    Also because one of Xamarin key's and that Miguel defends alot is that Native UI and not one size fit's all ,I must say that i think this is pretty important.

    With that said, i agree that having a roadmap not only for this ItemsControl question, but for the future XForms, knowing where the team is focusing what controls are coming would be great so we can understand whree should we focus and the comunity can help, this is special important for example for the Xamarin Forms Labs project, we don't want' to spend time doing stuff Xamarin Forms teams is already doing.

  • EricGroverEricGrover USMember ✭✭
    edited August 2014

    @rmarinho Really?? So Xamarin builds a XAML based UI framework with 2-way data binding, but it "has nothing to do with MVVM"?

    I think that's a bit silly. :-)

    Oh, and BTW, from the Xamarin website: "•MVVM architecture for clean separation of UI and app logic." https://xamarin.com/forms

  • rmarinhormarinho PTMember, Insider, Beta Xamurai
    edited August 2014

    Eric well i think i never saw that before, so i have to give u that one, If Xamarin says it!

    Cheers :)

  • EricGroverEricGrover USMember ✭✭

    @MitchMilam‌ So I went ahead and created an ItemsControl. I don't know how to go about becoming a contributor at the Xamarin Forms Labs project. Can you point me in the right direction?

  • EricGroverEricGrover USMember ✭✭

    @rmarinho I just created the Pull request. I have called the new control RepeaterView.

  • JaredReevesJaredReeves USMember

    @EricGrover‌ I am trying to use the RepeaterView you made, it there an example of how to use this view? The one in the Xlabs project is real clear.

  • EricGroverEricGrover USMember ✭✭

    @JaredReeves‌ I posted about the RepeaterView on my blog: http://www.ericgrover.com/#!/post/1027

    Also, I have a newer version that has an ItemCreated event (useful for animating items as they are added). The new version also allows you to have any visual tree in the DataTemplate (not just a ViewCell).

    I haven't updated the XForms Labs with the new one yet, but here is the code.

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Text;
    using Xamarin.Forms;
    
    namespace Xamarin.Forms.Labs.Controls
    {
        public class RepeaterView<T> : StackLayout         
        {
            public RepeaterView()
            {
                this.Spacing = 0;
            }
    
    
            public ObservableCollection<T> ItemsSource
            {
                get { return (ObservableCollection<T>)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
    
            public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create<RepeaterView<T>, ObservableCollection<T>>(p => p.ItemsSource, new ObservableCollection<T>(), BindingMode.OneWay, null, ItemsChanged);
    
            private static void ItemsChanged(BindableObject bindable, ObservableCollection<T> oldValue, ObservableCollection<T> newValue)
            {
                var control = bindable as RepeaterView<T>;
                control.ItemsSource.CollectionChanged += control.ItemsSource_CollectionChanged;
                control.Children.Clear();
    
                foreach (var item in newValue)
                {
                    var cell = control.ItemTemplate.CreateContent();
                    control.Children.Add(((ViewCell)cell).View);
                }
    
                control.UpdateChildrenLayout();
                control.InvalidateLayout();
            }
    
            public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
            public event RepeaterViewItemAddedEventHandler ItemCreated;
    
            protected virtual void NotifyItemAdded(View view, object model)
            {
                if (ItemCreated != null)
                {
                    ItemCreated(this, new RepeaterViewItemAddedEventArgs(view, model));
                }
            }
    
            void ItemsSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {                        
                if (e.OldItems != null)
                {
                    this.Children.RemoveAt(e.OldStartingIndex);
                    this.UpdateChildrenLayout();
                    this.InvalidateLayout();
                }
    
                if (e.NewItems != null)
                {
                    foreach (T item in e.NewItems)
                    {
                        var cell = this.ItemTemplate.CreateContent();
    
                        View view;
                        if (cell is ViewCell)
                            view = ((ViewCell)cell).View;
                        else
                            view = (View)cell;
    
                        view.BindingContext = item;
                        this.Children.Insert(ItemsSource.IndexOf(item), view);
                        NotifyItemAdded(view, item);
                    }
    
                    this.UpdateChildrenLayout();
                    this.InvalidateLayout();
                }
            }
    
            public static readonly BindableProperty ItemTemplateProperty =
            BindableProperty.Create<RepeaterView<T>, DataTemplate>(p => p.ItemTemplate, default(DataTemplate));
    
            public DataTemplate ItemTemplate
            {
                get { return (DataTemplate)GetValue(ItemTemplateProperty); }
                set { SetValue(ItemTemplateProperty, value); }
            }
        }
    
        public class RepeaterViewItemAddedEventArgs : EventArgs
        {
            public RepeaterViewItemAddedEventArgs(View view, object model)
            {
                View = view;
                Model = model;
            }
    
            public View View { get; set; }
            public object Model { get; set; }
        }
    
    }
    
  • EricGroverEricGrover USMember ✭✭
    edited October 2014

    @JaredReeves‌ Oh, and here is an example of how I use the new one using XAML markup instead of C#:

    <ScrollView IsClippedToBounds="true">
                    <controls:RepeaterView
                        x:TypeArguments="m:Forecast"
                        Spacing="5"
                        ItemCreated="ForecastItemCreated"
                        ItemsSource="{Binding Forecasts}" >
                        <controls:RepeaterView.ItemTemplate>
                            <DataTemplate>
                                <StackLayout 
                                    BackgroundColor="{x:Static h:AppColors.DarkTranslucent}" 
                                    Spacing="0" 
                                    Padding="10">
                                    <Label
                                        Style="{StaticResource SmallWhiteLabelStyle}"
                                        Text="{Binding Date, Converter={StaticResource DateFormatConverter}}" />
                                    <StackLayout
                                        Orientation="Horizontal" >
                                        <Image
                                            VerticalOptions="Center"
                                            HeightRequest="64"
                                            Source="{Binding Forecast.Description, Converter={StaticResource WeatherIconConverter}" />
                                        <StackLayout                                        
                                            VerticalOptions="Center">
                                            <StackLayout
                                                Orientation="Horizontal"
                                                Spacing="0"
                                                IsVisible="{Binding Forecast, Converter={StaticResource IsNotNullConverter}}">
                                                <Label
                                                    Text="high "
                                                    Style="{StaticResource SmallWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="{Binding Forecast.HighTemperature}"
                                                    Style="{StaticResource ThinWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="&#176;"
                                                    Style="{StaticResource ThinWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="F"
                                                    Style="{StaticResource ThinSmallWhiteLabelStyle}" />
                                                <Label
                                                    Text="  low "
                                                    Style="{StaticResource SmallWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="{Binding Forecast.LowTemperature}"
                                                    Style="{StaticResource ThinWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="&#176;"
                                                    Style="{StaticResource ThinWhiteLabelStyle}" />
                                                <controls:ThinLabel
                                                    Text="F"
                                                    Style="{StaticResource ThinSmallWhiteLabelStyle}" />                                    
                                            </StackLayout>
                                            <Label
                                                Style="{StaticResource MediumWhiteLabelStyle}"
                                                Text="{Binding Description}" />
                                        </StackLayout>
                                    </StackLayout>
                                </StackLayout>
                            </DataTemplate>
                        </controls:RepeaterView.ItemTemplate>
                    </controls:RepeaterView>
                </ScrollView>
    
    public async void ForecastItemCreated(object sender, RepeaterViewItemAddedEventArgs args)
            {
                Forecast item = args.Model as Forecast;
    
            if (Device.OS == TargetPlatform.Android || Device.OS == TargetPlatform.iOS)
            {
                // Set animation start values;
                args.View.Opacity = 0;
                await args.View.LayoutTo(new Rectangle(500, args.View.Y, args.View.Width, args.View.Height), 0);
    
                // Stagger animation for each item added
                await Task.Delay(item.Index * 200);
    
                // Compose and start animations
                var translateAnimation = args.View.LayoutTo(new Rectangle(0, args.View.Y, args.View.Width, args.View.Height), 500, Easing.CubicInOut);
                var fadeInAnimation = args.View.FadeTo(1, 500, Easing.Linear);
                await Task.WhenAll(translateAnimation, fadeInAnimation);
            }
            else
            {
                // Set animation start values;
                args.View.Opacity = 0;
                args.View.TranslationX = 500;
                await Task.Delay(500);
    
                // Stagger animation for each item added
                await Task.Delay(item.Index * 200);
    
                // Compose and start animations
                var translateAnimation = args.View.TranslateTo(0, 0, 500, Easing.CubicInOut);
                var fadeInAnimation = args.View.FadeTo(1, 500, Easing.Linear);
                await Task.WhenAll(translateAnimation, fadeInAnimation);
            }
        }
    
  • EricGroverEricGrover USMember ✭✭

    @JaredReeves‌ Ok, sorry but here is a better updated version of the RepeaterView. I didn't realize that some other guys had improved the original version over at XLabs. I merged my new changes in with theirs, and submitted a pull request. Hopefully it will be all merged in soon.

  • KevinFordKevinFord USUniversity, Certified XTC Partners ✭✭✭

    @EricGrover,
    I made a small bug fix to that one myself over at labs. I've found the repeater view to be quite useful.

  • JaredReevesJaredReeves USMember

    @EricGrover‌, First I want to think you for those examples, they help me figure out one of the issues I was having. However I do have a question about the xaml code that I don't understand. When you are specifying the TypeArguments you are Doing this

    x:TypeArguments="m:Forecast"
    

    I assume that you have an object in a namespace that you have labeled in the head of this xaml page. Some thing like this:

     xmlns:m="clr-namespace:SOMENAMESPACE;assembly=SOMEASSEMBLY"
    

    The Forecast is the model that contains the properties and the Forecasts are a List of that object type. My question how does the repeater know how to access those properties for each item in the repeater. When you are doing this:

    Text="{Binding Forecast.HighTemperature}" 
    

    Does Forecast act like the current item in each repeated section of the code?

    I am trying to do something similar as your example, but the properties are not being accessed correctly but the view is displaying the correct number of repeated content from my list.

    Here is my code(Head):

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:Xamarin.Forms.Labs.Controls;assembly=Xamarin.Forms.Labs"
             xmlns:model="clr-namespace:AIMCore.ViewModels;assembly=AIMCore"
             BackgroundColor="#666666">
    

    Section with repeater:

    <controls:RepeaterView ItemsSource="{Binding Users}" x:TypeArguments="m:User">
        <controls:RepeaterView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.View>
                        <StackLayout Padding="10,5,10,5">
    
                            <controls:IconButton Text="{Binding User.UserName}" BackgroundColor="#000000" TextColor="#FFFFFF"
                                             Orientation="ImageToRight" IconSize="19" Icon="{x:Static icons:Icons.SignIn}"
                                             IconColor="#FFFFFF" WidthRequest="300" HeightRequest="30" HorizontalOptions="Fill" 
                                             VerticalOptions="End" Command="{Binding LogInUser}" CommandParameter="{Binding User.UserName}" />
    
                         </StackLayout>
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </controls:RepeaterView.ItemTemplate>
    </controls:RepeaterView>
    
  • EricGroverEricGrover USMember ✭✭

    @JaredReeves‌ So first off, you don't need to use a ViewCell anymore (with the updated version of the RepeaterView). Second, the binding assumes that the BindingContext for each instance of the DataTemplate will be an item from the ItemsSource collection.

    I can see why it would be confusing to see Forecast.Description because the BindingContext should be a Forecast object. I think that is a copy and paste error. I believe it should just be Description.

  • CharlesHoranCharlesHoran USMember ✭✭
    edited October 2014

    @EricGrover‌ @KevinFord‌ @MitchMilam‌
    I think I had to much coffee this am... I took a look at the repeater control and thought to myself "What this control needs is a template selector" I realize we can do a lot of datatemplate customizations using viewmodel flagging but that has two serious drawbacks imo:
    1) ViewModel flagging ties the viewmodel to the view which is in a word "bad"
    2) It increases the complexity of our Data Templates to non maintainability.

    In order to avoid those 2 issues it would be nice if our various repeater controls (Listview, RepeatView...) could select a datatemplate based on the type of the object to be displayed.

    I ended up with a few new classes and a couple of modifications to the repeaterview :D

    New classes: (I had to wrap the datatemplate rather than inherit since it's default constructor is internal :( )

    public abstract class DataTemplateWrapper : BindableObject
        {
            public abstract bool IsDefault { get; set; }
            public abstract DataTemplate WrappedTemplate { get; set; }
            public abstract Type Type { get; }
        }
        public class DataTemplateWrapper<T> : DataTemplateWrapper
        {
            public static readonly BindableProperty WrappedTemplateProperty = BindableProperty.Create<DataTemplateWrapper<T>, DataTemplate>(x => x.WrappedTemplate, null);
            public static readonly BindableProperty IsDefaultProperty = BindableProperty.Create<DataTemplateWrapper<T>, bool>(x => x.IsDefault, false);
    
            public override bool IsDefault
            {
                get { return (bool)GetValue(IsDefaultProperty); }
                set { SetValue(IsDefaultProperty, value); }
            }
            public override DataTemplate WrappedTemplate
            {
                get { return (DataTemplate)GetValue(WrappedTemplateProperty); }
                set { SetValue(WrappedTemplateProperty, value); }
            }
    
            public override Type Type
            {
                get { return typeof (T); }
            }
        }
    
    public class DataTemplateCollection : ObservableCollection<DataTemplateWrapper> { }
    
        public class TemplateSelector : BindableObject
        {
            public static BindableProperty TemplatesProperty = BindableProperty.Create<TemplateSelector, DataTemplateCollection>(x => x.Templates, new DataTemplateCollection(), BindingMode.OneWay, null, TemplatesChanged);
            public static BindableProperty SelectorFunctionProperty = BindableProperty.Create<TemplateSelector, Func<Type, DataTemplate>>(x => x.SelectorFunction, null);
    
            /// <summary>
            ///  Clears the cache when the set of templates change
            /// </summary>
            /// <param name="bo"></param>
            /// <param name="oldval"></param>
            /// <param name="newval"></param>
            public static void TemplatesChanged(BindableObject bo, DataTemplateCollection oldval, DataTemplateCollection newval)
            {
                var ts = bo as TemplateSelector;
                Contract.Assert(ts != null, "Invalid object passed to TempaltesChanged");
                if (oldval != null) oldval.CollectionChanged -= ts.TemplateSetChanged;
                newval.CollectionChanged += ts.TemplateSetChanged;
                ts.Cache = null;
            }
    
            /// <summary>
            /// Clear the cache on any template set change
            /// If needed this could be optimized to care about the specific
            /// change but I doubt it would be worthwhile.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void TemplateSetChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                Cache = null;
            }
    
            private Dictionary<Type, DataTemplate> Cache { get; set; }
    
            public DataTemplateCollection Templates
            {
                get { return (DataTemplateCollection)GetValue(TemplatesProperty); }
                set {SetValue(TemplatesProperty,value);}
            }
    
            public Func<Type, DataTemplate> SelectorFunction
            {
                get { return (Func<Type, DataTemplate>) GetValue(SelectorFunctionProperty); }
                set { SetValue(SelectorFunctionProperty, value); }
            }
    
            /// <summary>
            /// Matches a type with a datatemplate
            /// Order of matching=>Cache, SelectorFunction, SpecificTypeMatch,InterfaceMatch,BaseTypeMatch DefaultTempalte
            /// </summary>
            /// <param name="type">Type object type that needs a datatemplate</param>
            /// <returns></returns>
            public DataTemplate TemplateFor(Type type)
            {
                if (type == null) return null;//This can happen when we recusively check base types (object.BaseType==null)
                Contract.Assert(Templates != null,"Templates cannot be null");
                Cache = Cache ?? new Dictionary<Type, DataTemplate>();
                //Happy case we already have the type in our cache
                if (Cache.ContainsKey(type)) return Cache[type];
    
                DataTemplate retTemplate = null;
                //Prefer the selector function if present
                if (SelectorFunction != null) retTemplate = SelectorFunction(type);
    
                //check our list
                retTemplate = retTemplate ?? Templates.Where(x => x.Type == type).Select(x => x.WrappedTemplate).FirstOrDefault();
                //Check for interfaces
                retTemplate =retTemplate??type.GetTypeInfo().ImplementedInterfaces.Select(TemplateFor).FirstOrDefault();
                //look at base types
                retTemplate=retTemplate??TemplateFor(type.GetTypeInfo().BaseType);
                //If all else fails try to find a Default Template
                retTemplate = retTemplate ?? Templates.Where(x => x.IsDefault).Select(x => x.WrappedTemplate).FirstOrDefault();                
    
                Cache[type] = retTemplate;
                return retTemplate;
            }
        }
    
    Changes to the RepeaterView:  The only changed code is in two spots I replaced
    

    'control.ItemTemplate.CreateContent' with
    control.GetTemplateFor(item.GetType()).CreateContent...

    //New Code
            public static readonly BindableProperty TemplateSelectorProperty = BindableProperty.Create<RepeaterView<T>, TemplateSelector>(x => x.TemplateSelector, default(TemplateSelector));
    
            public TemplateSelector TemplateSelector
            {
                get { return (TemplateSelector) GetValue(TemplateSelectorProperty); }
                set { SetValue(TemplateSelectorProperty,value);}
            }
      /// <summary>
            /// Select a datatemplate dynamically
            /// Prefer the TemplateSelector then the DataTemplate
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            private DataTemplate GetTemplateFor(Type type)
            {
                DataTemplate retTemplate = null;
                if (TemplateSelector != null)
                    retTemplate = TemplateSelector.TemplateFor(type);
                return retTemplate ?? ItemTemplate;
            }
    
  • JaredReevesJaredReeves USMember
    edited October 2014

    @EricGrover‌, Ok thank you that is how I assumed it was working but was not entirely sure. I am not sure what is happening in my code the repeater works but the binding of each items properties in the ItemsSource (a Observable list of User) is not working. The console displays this: Binding: 'UserName' property not found on 'AIMCore.ViewModels.ConfirmUserViewModel' We are using Xamarin.Forms.Labs.Mvvm to set up our mvvm, and this view model is the correct one for this page, however the object User is a model in a different namespace but I set the Type Argument to the User type, and would expect the bindings to look there and not the view model for properties. Do I need to set the BindingContext to something different?

  • CharlesHoranCharlesHoran USMember ✭✭
    edited October 2014

    Last post was long enough :D

    So how do you use this?

    In Xaml: (I prefer to use the contentpage resources over inline datatemplates, but either method works)

      <ContentPage.Resources>
        <ResourceDictionary>
         <ext:BooleanNegationConverter x:Key="not"/>
    
          <!--Message Model template selector-->
          <controls:TemplateSelector x:Key="messageselector">
            <controls:TemplateSelector.Templates>
              <!--Wrapper for Alert Messages-->
              <controls:DataTemplateWrapper x:TypeArguments="model:AlertMessage">
                <controls:DataTemplateWrapper.WrappedTemplate>
                  <DataTemplate>
                    <ViewCell>
                       <Label Text="Alert"/>
                    </ViewCell>
                </DataTemplate>
                  </controls:DataTemplateWrapper.WrappedTemplate>
              </controls:DataTemplateWrapper>
              <!--Wrapper for Admin Messages-->
              <controls:DataTemplateWrapper x:TypeArguments="model:AdminMessage">
                <controls:DataTemplateWrapper.WrappedTemplate>
                  <DataTemplate>
                    <ViewCell>
                       <Label Text="Admin"/>
                    </ViewCell>
                </DataTemplate>
                  </controls:DataTemplateWrapper.WrappedTemplate>
              </controls:DataTemplateWrapper>
              <!--Wrapper for User Messages-->
              <controls:DataTemplateWrapper x:TypeArguments="model:UserMessage">
                <controls:DataTemplateWrapper.WrappedTemplate>
                  <DataTemplate>
                    <ViewCell>
                       <Label Text="User"/>
                    </ViewCell>
                </DataTemplate>
                  </controls:DataTemplateWrapper.WrappedTemplate>
              </controls:DataTemplateWrapper>
            </controls:TemplateSelector.Templates>
          </controls:TemplateSelector>  
        </ResourceDictionary> 
      </ContentPage.Resources>
    

    Then somewhere else in the page's Xaml:

    <controls:RepeaterView x:TypeArguments="model:BaseMessage" Grid.Row="0" ItemsSource="{Binding Messages}" TemplateSelector="{StaticResource messageselector}" VerticalOptions="FillAndExpand">

    If you prefer to work from codebehind you can set RepeaterView.TemplateSelector.SelectorFunction to a Func<T,DataTemplate> and it will work just fine.

    The result of the above xaml is the attached screenshot. Not an exciting display, but it is a functioning template selector that can deal with type hierarchies and interfaces.

    Thoughts, rotten tomatoes?

    P.S. any idea how to extend RepeaterView to support Tap and LongTap?

  • CharlesHoranCharlesHoran USMember ✭✭

    @EricGrover‌ @KevinFord‌ @MitchMilam‌
    I added a pull request on GitHub....hope I managed that property :D

  • SteeveLeDreauSteeveLeDreau FRMember
    edited January 2015

    Hi, sorry it seems one of the only post about RepeaterView. I red your discussion and tryed this control.
    But i can't make it working but the same template and itemssource work for a listView ... Am I missing something ? Is this control only made to repeat the same content ?

    EDIT :
    Of course the code tag doesn't work xD

    ListView ----
    ItemsSource="{Binding Question.Reponses, Mode=OneWay}"

    RepeaterView ----
    BindingContext="{Binding Question, Mode=OneWay}"
    ItemsSource="{Binding Reponses, Mode=OneWay}"
    x:TypeArguments="model:ResultatReponseModel"

    END EDIT

    Watch this code :
    `

    <xlab:RepeaterView.ItemTemplate>


                    <BoxView Color="{Binding Couleur, Mode=OneWay, Converter={StaticResource ToColor}}"
                             HorizontalOptions="Fill"
                             HeightRequest="10"
                             WidthRequest="20" />
    
                    <ContentView Padding="10,0,0,0"
                                 HorizontalOptions="FillAndExpand">
                        <ctrls:Label Text="{Binding Libelle, Mode=OneWay}"
                                     LineBreakMode="TailTruncation" />
                    </ContentView>
    
                    <ctrls:Label Text="{Binding NombreVotants, Mode=OneWay}"
                                 HorizontalOptions="End" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </xlab:RepeaterView.ItemTemplate>
    


    `

    And compare it with this
    `
    <ctrls:ListView.ItemTemplate>


                    <BoxView Color="{Binding Couleur, Mode=OneWay, Converter={StaticResource ToColor}}"
                             HorizontalOptions="Fill"
                             HeightRequest="10"
                             WidthRequest="20" />
    
                    <ContentView Padding="10,0,0,0"
                                 HorizontalOptions="FillAndExpand">
                        <ctrls:Label Text="{Binding Libelle, Mode=OneWay}"
                                     LineBreakMode="TailTruncation" />
                    </ContentView>
    
                    <ctrls:Label Text="{Binding NombreVotants, Mode=OneWay}"
                                 HorizontalOptions="End" />
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ctrls:ListView.ItemTemplate>
    


    `

    Few things : i was obliged to bind the bindingContext of the repeater because it can't navigate in the path : Question.Reponses while the listView performs it well.

    My items seems to look for a property in the wrong model !! It's looking in QuestionModel instead of ReponseModel.

    What the hell is this ? ^^

  • VincentPoirierVincentPoirier CAMember ✭✭

    I think the RepeaterView doesn't work in the current version of Xamarin.Forms.Labs (1.2.1-pre2)

    Once you get it to compile correctly it just doesn't show anything. If we're not using it correctly, please let us know!

    See: http://forums.xamarin.com/discussion/comment/100305

  • SteeveLeDreauSteeveLeDreau FRMember
    edited February 2015

    Hi Vincent, i was using 1.2.3 and the control shows something. My problem was that my repeater item's binding context wasn't on the expected object.
    Example :

    A class "First" contains an object of type "MyListItem " which is an observableCollection. If i was binding the itemssource to MyFirst.MyList, my datatemplate's binding context wasn't an object of type MyListItem but First.

  • VincentPoirierVincentPoirier CAMember ✭✭

    Hi Steeve!
    Thank you for the information. I'm having a hard time understanding how to relate to your example. I tried binding a ObservableCollection<ImageSource>, and apply Binding Source. Should I expect a different class/object?

  • GeoffArmstrongGeoffArmstrong CAMember ✭✭

    One thing that would be cool would be improving this to take an idea from, say, Knockout.js.

    Let's say you have a bound array of objects, [a, b, c].

    These are bound to an array of screen Views, [a', b', c'].

    Now, when an update comes in, instead of completely recreating a', b', and c', figure out the set of additions, deletions, and moves necessary, and then do that.

    That allows the view controls to have some internal state (say, for instance, there was an edit control bound, and it has internal state like the insertion position that would be helpful to survive data updates).

  • RhyousRhyous USMember

    I spent a few days trying to get RepeaterView with TemplateSelector working. I finally figured it out.

    I was handing it a list of ItemViewModels and GroupViewModel objects, which inherits ItemViewModel. The data model follows a similar pattern with Item, Group.

    I first tried with my data model. Didn't matter if I put in Item or Group it failed. So I switched to ViewModels and regardless of whether I used ItemViewModel or GroupViewModel it failed.

    The crazy part is that the ItemsSource property never even fires. It never even gave me so much as a breakpoint to see why it was failing. No output error to explain the issue. It is just a black hole.

    I finally got it to work when I implemented Repeater. Turns out my list is called an ItemsCollection which inherits List(). Should have done that sooner. Really though, I'd forgotten that I'd implemented IItem and put it in there (I'm working on this at night a few hours here an there).

    So the cause of all the pain: generics. The View shouldn't not care about the type.

    So I updated your code to NOT use generics and it is way easier to use. I submitted a pull request to XLabs Xamarin-forms-labs.

    Here is my pull request: https://github.com/XLabs/Xamarin-Forms-Labs/pull/785

  • RhyousRhyous USMember

    I didn't use code marks in my last posts and some lines got messed up. I can't edit my post, so I use code marks next time. Here are the lines that got messed up.

    ... I finally got it to work when I implemented RepeaterView<IItem> ...
    ... Turns out my list is called an ItemsCollection which inherits List<IItem>.

    Anyway, I love the RepeaterView. Especially now that it doesn't care if I hand it an any object. With the non generic feature, I can use the same view and hand it any of the following . . .

    ObservableCollection<IItem>
    ObservableCollection<Item>
    ObservableCollection<ItemViewModel>

    . . . and they all display the same.

    If I were to list the top features missing from Xamarin Forms, ItemsControl and a DataTemplateSelector would be #1 on the list. Thanks to your work, we have a comparable feature set. And now, without generics, it is even easier to use.

    To me, the #1 missing feature from Xamarin Forms is now "implemented," even if not in Xamarin Forms base install.

  • Stfn11Stfn11 PLMember
    edited June 2016

    Above code seems to be out-of-date as "BindableProperty.Create" that is used there is depricated. I've modified it in proper way. Maybe it will help someone...

    Changes:

    • ItemsSourceProperty and ItemTemplateProperty changed to use up-to-date Create method.
    • ItemsChanged requres now oldValue and newValue to be objects. I've converted it then back to IEnumerable. The way I've done it isn't presumably the cleanest way, but it works ;)

    `

    public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
    
    // in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
    public class RepeaterView : StackLayout
    {
        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
          propertyName: "ItemsSource",
          returnType: typeof(IEnumerable),
          declaringType: typeof(RepeaterView),
          defaultValue: null,
          defaultBindingMode: BindingMode.OneWay,
          propertyChanged: ItemsChanged);
    
        public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
          propertyName: "ItemTemplate",
          returnType: typeof(DataTemplate),
          declaringType: typeof(RepeaterView),
          defaultValue: default(DataTemplate));
    
        public event RepeaterViewItemAddedEventHandler ItemCreated;
    
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
    
        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }
    
        private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
        {
            IEnumerable oldValueAsEnumerable;
            IEnumerable newValueAsEnumerable;
            try
            {
                oldValueAsEnumerable = oldValue as IEnumerable;
                newValueAsEnumerable = newValue as IEnumerable;
            }
            catch (Exception e)
            {
                throw e;
            }
    
            var control = (RepeaterView)bindable;
            var oldObservableCollection = oldValue as INotifyCollectionChanged;
    
            if (oldObservableCollection != null)
            {
                oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
            }
    
            var newObservableCollection = newValue as INotifyCollectionChanged;
    
            if (newObservableCollection != null)
            {
                newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
            }
    
            control.Children.Clear();
    
            if (newValueAsEnumerable != null)
            {
                foreach (var item in newValueAsEnumerable)
                {
                    var view = control.CreateChildViewFor(item);
                    control.Children.Add(view);
                    control.OnItemCreated(view);
                }
            }
    
            control.UpdateChildrenLayout();
            control.InvalidateLayout();
        }
    
        protected virtual void OnItemCreated(View view) =>
            this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
    
        private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            var invalidate = false;
    
            if (e.OldItems != null)
            {
                this.Children.RemoveAt(e.OldStartingIndex);
                invalidate = true;
            }
    
            if (e.NewItems != null)
            {
                for (var i = 0; i < e.NewItems.Count; ++i)
                {
                    var item = e.NewItems[i];
                    var view = this.CreateChildViewFor(item);
    
                    this.Children.Insert(i + e.NewStartingIndex, view);
                    OnItemCreated(view);
                }
    
                invalidate = true;
            }
    
            if (invalidate)
            {
                this.UpdateChildrenLayout();
                this.InvalidateLayout();
            }
        }
    
        private View CreateChildViewFor(object item)
        {
            this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
            return (View)this.ItemTemplate.CreateContent();
        }
    }
    
    public class RepeaterViewItemAddedEventArgs : EventArgs
    {
        private readonly View view;
        private readonly object model;
    
        public RepeaterViewItemAddedEventArgs(View view, object model)
        {
            this.view = view;
            this.model = model;
        }
    
        public View View => this.view;
    
        public object Model => this.model;
    }
    

    `

    And example of usage:
    <customControls:RepeaterView x:Name="emView" > <customControls:RepeaterView.ItemTemplate> <DataTemplate> <StackLayout> <!-- Some stuff you want to display --> <Label Text="{Binding Example123}" /> </StackLayout> </DataTemplate> </customControls:RepeaterView.ItemTemplate> </customControls:RepeaterView>

    In class constructor:
    emView.ItemsSource = SomeIEnumerableData;

  • batmacibatmaci DEMember ✭✭✭✭

    what is the advantage of using RepeaterView instead of Listview or tableview?
    Can you do horizantal repeaterview? Can you disable scrolling?

  • batmacibatmaci DEMember ✭✭✭✭

    @Stfn11 said:
    Above code seems to be out-of-date as "BindableProperty.Create" that is used there is depricated. I've modified it in proper way. Maybe it will help someone...

    Changes:

    • ItemsSourceProperty and ItemTemplateProperty changed to use up-to-date Create method.
    • ItemsChanged requres now oldValue and newValue to be objects. I've converted it then back to IEnumerable. The way I've done it isn't presumably the cleanest way, but it works ;)

    `

    public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);

    // in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
    public class RepeaterView : StackLayout
    {
        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
          propertyName: "ItemsSource",
          returnType: typeof(IEnumerable),
          declaringType: typeof(RepeaterView),
          defaultValue: null,
          defaultBindingMode: BindingMode.OneWay,
          propertyChanged: ItemsChanged);
    
        public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
          propertyName: "ItemTemplate",
          returnType: typeof(DataTemplate),
          declaringType: typeof(RepeaterView),
          defaultValue: default(DataTemplate));
    
        public event RepeaterViewItemAddedEventHandler ItemCreated;
    
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
    
        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }
    
        private static void ItemsChanged(BindableObject bindable, object oldValue, object newValue)
        {
            IEnumerable oldValueAsEnumerable;
            IEnumerable newValueAsEnumerable;
            try
            {
                oldValueAsEnumerable = oldValue as IEnumerable;
                newValueAsEnumerable = newValue as IEnumerable;
            }
            catch (Exception e)
            {
                throw e;
            }
    
            var control = (RepeaterView)bindable;
            var oldObservableCollection = oldValue as INotifyCollectionChanged;
    
            if (oldObservableCollection != null)
            {
                oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
            }
    
            var newObservableCollection = newValue as INotifyCollectionChanged;
    
            if (newObservableCollection != null)
            {
                newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
            }
    
            control.Children.Clear();
    
            if (newValueAsEnumerable != null)
            {
                foreach (var item in newValueAsEnumerable)
                {
                    var view = control.CreateChildViewFor(item);
                    control.Children.Add(view);
                    control.OnItemCreated(view);
                }
            }
    
            control.UpdateChildrenLayout();
            control.InvalidateLayout();
        }
    
        protected virtual void OnItemCreated(View view) =>
            this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
    
        private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            var invalidate = false;
    
            if (e.OldItems != null)
            {
                this.Children.RemoveAt(e.OldStartingIndex);
                invalidate = true;
            }
    
            if (e.NewItems != null)
            {
                for (var i = 0; i < e.NewItems.Count; ++i)
                {
                    var item = e.NewItems[i];
                    var view = this.CreateChildViewFor(item);
    
                    this.Children.Insert(i + e.NewStartingIndex, view);
                    OnItemCreated(view);
                }
    
                invalidate = true;
            }
    
            if (invalidate)
            {
                this.UpdateChildrenLayout();
                this.InvalidateLayout();
            }
        }
    
        private View CreateChildViewFor(object item)
        {
            this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
            return (View)this.ItemTemplate.CreateContent();
        }
    }
    
    public class RepeaterViewItemAddedEventArgs : EventArgs
    {
        private readonly View view;
        private readonly object model;
    
        public RepeaterViewItemAddedEventArgs(View view, object model)
        {
            this.view = view;
            this.model = model;
        }
    
        public View View => this.view;
    
        public object Model => this.model;
    }
    

    `

    And example of usage:
    <customControls:RepeaterView x:Name="emView" > <customControls:RepeaterView.ItemTemplate> <DataTemplate> <StackLayout> <!-- Some stuff you want to display --> <Label Text="{Binding Example123}" /> </StackLayout> </DataTemplate> </customControls:RepeaterView.ItemTemplate> </customControls:RepeaterView>

    In class constructor:
    emView.ItemsSource = SomeIEnumerableData;

    How different is yours than Xlabs RepeaterView?

  • JosefMitJosefMit USMember

    how to include add/remove animation

  • AckAck INMember

    I just tried to create inline items view.

    public class ItemsView : ScrollView
    {
        public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
            "ItemTemplate",
            typeof(DataTemplate),
            typeof(ItemsView),
            null,
            propertyChanged: (bindable, value, newValue) => Populate(bindable));
    
        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
            "ItemsSource",
            typeof(IEnumerable),
            typeof(ItemsView),
            null,
            BindingMode.OneWay,
            propertyChanged: (bindable, value, newValue) => Populate(bindable));
    
        public IEnumerable ItemsSource
        {
            get
            {
                return (IEnumerable)this.GetValue(ItemsSourceProperty);
            }
    
            set
            {
                this.SetValue(ItemsSourceProperty, value);
            }
        }
    
        public DataTemplate ItemTemplate
        {
            get
            {
                return (DataTemplate)this.GetValue(ItemTemplateProperty);
            }
    
            set
            {
                this.SetValue(ItemTemplateProperty, value);
            }
        }
    
        private static void Populate(BindableObject bindable)
        {
            var repeater = (ItemsView)bindable;
    
            // Clean
            repeater.Content = null;
    
            // Only populate once both properties are recieved
            if (repeater.ItemsSource == null || repeater.ItemTemplate == null)
            {
                return;
            }
    
            // Create a stack to populate with items
            var list = new StackLayout();
    
            foreach (var viewModel in repeater.ItemsSource)
            {
                var content = repeater.ItemTemplate.CreateContent();
                if (!(content is View) && !(content is ViewCell))
                {
                    throw new Exception($"Invalid visual object {nameof(content)}");
                }
    
                var view = content is View ? content as View : ((ViewCell)content).View;
                view.BindingContext = viewModel;
    
                list.Children.Add(view);
            }
    
            // Set stack as conent to this ScrollView
            repeater.Content = list;
        }
    }
    
Sign In or Register to comment.