locked
How to create complex bindings? RRS feed

  • Question

  • User54879 posted

    I'm setting up DataTemplate for ListView. It's easy to create a binding for a single property. For example:

    listView.ItemTemplate = new DataTemplate(() =>
    {
        var cell = new TextCell();
        cell.SetBinding<MyModel>(TextCell.TextProperty, m => m.Name);
        return cell;
    });
    

    But how to create a complex binding? For example:

    cell.SetBinding<MyModel>(TextCell.TextProperty, m => string.Format("{0}: {1}", m.Name, m.Description));

    (if you do so, ArgumentException is thrown)

    Friday, May 30, 2014 9:19 PM

All replies

  • User33 posted

    The ArgumentException is thrown because that's not a valid argument. The expression argument is provided simply as a replacement for the string method of defining the path, allowing for compiler verification and refactoring support.

    While we do support string formatting with bindings (new Binding ("Name") { StringFormat = "{0}" };), there is not currently a way to bind in two separate properties. We do plan to add this, but for now we recommend create a property on your view model to do the formatting for you.

    Friday, May 30, 2014 9:39 PM
  • User54879 posted

    @ermau, thanks!

    Friday, May 30, 2014 9:41 PM
  • User2773 posted

    Is it possible to set listView.ItemTemplate depending on a property of listview item? e.x.

    listView.ItemTemplate = new DataTemplate(() =>
    {
        if (item.Property1)
        {
            // return view1
        }
        // return view2
    });
    
    Thursday, June 5, 2014 10:57 AM
  • User39542 posted

    You can't change the assigned DataTemplate on a per-item basis - there's only one. You can, however, adjust the visual created based on the item itself. For example, the below code will display all the colors as name/color labels EXCEPT red which is shown as a box. The key is to know what the binding context is - once you have that, you can customize the visuals to be specific to any property you like.

    public class ColorsPage : ContentPage
    {
        public ColorsPage()
        {
            Content = new ListView() {
                ItemsSource = typeof(Color).GetTypeInfo().DeclaredFields
                    .Where(p => p.FieldType == typeof(Color) && p.IsStatic && !p.Name.Contains("<"))
                    .Select(p => new {
                        Name = p.Name,
                        Color = (Color)p.GetValue(null)
                    }).ToList(),
                ItemTemplate = new DataTemplate(typeof(DynamicTemplateLayout))
            };
        }
    }
    
    public class DynamicTemplateLayout : ViewCell
    {
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
    
            dynamic c = BindingContext;
    
            if (c.Name == "Red") {
                View = new BoxView() {
                    WidthRequest = 100,
                    HeightRequest = 100,
                    Color = Color.Red,
                };
                return;
            }
    
            View = new Label() {
                TextColor = c.Color,
                Text = c.Name,
                Font = Font.SystemFontOfSize(20),
            };
        }
    }
    
    Thursday, June 5, 2014 12:13 PM
  • User2773 posted

    Great! Thank You.

    Thursday, June 5, 2014 1:35 PM
  • User39542 posted

    @Daniel? Keep in mind this is really more of a hack, the row sizes must be consistent - you can't have differently sized cells in a ListView today. Also, you could create a more complex View (in XAML for example) that has an AbsoluteLayout root with 2+ container children where one is visible and the others are collapsed. Then use a binding + value converter to turn the views on and off based on your propert(ies).

    That would be the XAML way to do that :)

    Thursday, June 5, 2014 3:07 PM
  • User39542 posted

    @Daniel Here's a better generic solution which would do what you wanted originally; thanks to @StephaneDelcroix for the idea.

    public class DataTemplateSelector
    {
        public Func<object, DataTemplate> Selector {get;set;}
    
        public DataTemplate Template
        {
            get {  return new DataTemplate(GetHookedCell); }
        } 
    
        Cell GetHookedCell()
        {
            var content = new ViewCell();
            content.BindingContextChanged += OnBindingContextChanged;
            return content;
        }
    
        internal void OnBindingContextChanged (object sender, EventArgs e)
        {
            var cell = (ViewCell)sender;
            var template = Selector (cell.BindingContext);
            cell.View = ((ViewCell)template.CreateContent ()).View;
        }
    }
    

    Then you do something like this:

    ListView lv = new ListView {
       ItemTemplate = new DataTemplateSelector { 
          Selector = OnSelectTemplate
       }.Template
    };
    ...
    
    Cell OnSelectTemplate(object dataOb)
    {
        if (dataOb is Employee) {
           return new DataTemplate(() => {
               var label = new Label();
               label.SetBinding(Label.TextProperty, new Binding("Name"));
               return new ViewCell() { View = label };
        });
       }
       // Manager has edit button.
       return new DataTemplate(() => {
          var button = new Button();
          button.SetBinding(Button.TextProperty, new Binding("Name"));
          return new ViewCell { View = button };
       });
    }
    

    It's a bit of a stretch in terms of how the system is designed to work, but this will do exactly what you originally wanted - supply a DataTemplate specific to the object. Even better, you could define the DataTemplates in XAML and then return them from your selector method. You could even expose the Template property from a ViewModel and data bind to it (ListView.ItemTemplate is a bindable property).

    Also, I misspoke, it turns out there is a HasUnevenRows property which you can set to true, when you do that, each ViewCell can provide a Height which overrides the ListView.RowHeight property on a row-by-row basis.

    I hope that gets you started!

    Thursday, June 5, 2014 4:46 PM
  • User2773 posted

    Thank You again for such a detailed description! Binding is a great feature of Xamarin.Forms!

    Actually these two paths have their usages. The first one (DynamicTemplateLayout) is great for simple solutions for e.x. parameter based enabling/disabling of views, changing colors. The second is more complex, possibility to change row templates gives a lot of capabilities. I'll definitely use both.

    Also, I misspoke, it turns out there is a HasUnevenRows property which you can set to true, when you do that, each ViewCell can provide a Height which overrides the ListView.RowHeight property on a row-by-row basis.

    Is it possible to sum all Height property values of sub-views to get an accurate ListView.RowHeight value?

    Thursday, June 5, 2014 5:15 PM
  • User39542 posted

    @Daniel?

    Is it possible to sum all Height property values of sub-views to get an accurate ListView.RowHeight value?

    The problem is that the ViewCell doesn't have a calculated height until it's been put into the ListView. So, if you want a uniform (same) height for all cells, but have different content, then you will need to set the ListView.RowHeight to something large enough to handle any of them. It's possible that the control will pick the largest height, but I don't think it does that today :)

    Saturday, June 7, 2014 11:26 AM
  • User2773 posted

    Ok. I think it's enough for my needs :)

    Saturday, June 7, 2014 11:50 AM
  • User112 posted

    Just an idea for the first post. You might use a ValueConverter in that case. One that takes m as input and spits out formated text.

    Saturday, June 7, 2014 6:08 PM
  • User54879 posted

    By the way, in the current version of Xamarin.Forms you should create a separate class to implement value converter. I use the extension to write value converters as lambda expressions. So you can make binding like this:

    cell.SetBinding<YourModel>(TextCell.TextColorProperty, c => c.IsRed, BindingMode.Default,
      ValueConverter.OneWay<bool,Color>((isRead) => isFav ? Color.Red : Color.Black));
    

    Extension class:

    public class ValueConverter
    {
        public static IValueConverter OneWay<TFrom,TTo>(Func<TFrom,TTo> converter)
        {
            return new ValueConverterImpl<TFrom,TTo>(converter, null);
        }
    
        public static IValueConverter TwoWay<TFrom,TTo>(Func<TFrom,TTo> converter, Func<TTo,TFrom> backConverter)
        {
            return new ValueConverterImpl<TFrom,TTo>(converter, backConverter);
        }
    
        private class ValueConverterImpl<TSource,TDestination> : IValueConverter
        {
            private Func<TSource, TDestination> _converter;
            private Func<TDestination,TSource> _backConverter;
    
            public ValueConverterImpl(Func<TSource,TDestination> converter, Func<TDestination,TSource> backConverter)
            {
                _converter = converter;
                _backConverter = backConverter;
            }
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return _converter((TSource)value);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (_backConverter == null)
                    throw new NotImplementedException();
    
                return _backConverter((TDestination)value);
            }
        }
    }
    
    Sunday, June 8, 2014 2:13 PM
  • User112 posted

    Nice sampke, but unfortunately that won't work with XAML.

    Monday, June 9, 2014 6:53 AM
  • User20490 posted

    @msmith? - seriously, a great solution to having a per-row DataTemplate. Wish I would have found this post hours ago :)

    Monday, July 7, 2014 10:02 PM
  • User39542 posted

    @chris_riesgo? Glad it was helpful!

    Tuesday, July 8, 2014 4:50 PM
  • User69553 posted

    This is great. I created 2 separate ViewCell classes. My first cell is very large (height-250) and the remaining cells are more like height-50. I am getting the 2 separate cells to load, but the height is not changing. In each ViewCell, I am setting the heights as you say. public class EventCellSmall : ViewCell { public EventCellSmall () { .... this.Height = 50; this.View =myView; ...

    Any idea why the height isn't changing? I did set hasunevenrows=true on my listview.

    Also, in the example msmith provides, this line: Cell OnSelectTemplate(object dataOb)

    is actually: DataTemplate OnSelectTemplate(object dataOb)

    Monday, August 25, 2014 8:50 PM
  • User14 posted

    Check out the uneven rows example in the working with listview doc, see if that works for you. Setting the Height on the cell and also the HasUnevenRows property should be enough.

    Monday, August 25, 2014 9:04 PM
  • User69553 posted

    I am actually able to set it here:

    internal void OnBindingContextChanged (object sender, EventArgs e) { var cell = (ViewCell)sender; var template = Selector (cell.BindingContext);

            cell.Height = 300;
            cell.View = ((ViewCell)template.CreateContent ()).View;
        }</code>
    

    How do I get a reference to the custom object in here? I need to get a reference to the 'object' in Func Selector

    EDIT:

    Nevermind, I got it from the BindingContext...

    MyCustomObject myObject = (MyCustomObject)cell.BindingContext

    Monday, August 25, 2014 9:28 PM
  • User63623 posted

    that was my solution , thanks @MarkSmith.6714

    Sunday, September 21, 2014 6:42 PM
  • User105293 posted

    Any one got @mark.smith 's example to work?

    It seems that the OnSelectTemplates return type is a cell but the example returns a DataTemplate. Am i missing something ?

    Friday, April 10, 2015 2:36 PM
  • User39542 posted

    No, sorry it's a typo, change it to a DataTemplate return type. That's what happens when you type code directly into Markdown :)

    Here's an example solution using XAML with a DataTemplateSelectorExtension if you'd like to try it. This version looks in a specified Page resources for a DataTemplate with a key that matches the type name of the object in the ListView - similar to the implicit lookup done by Microsoft's XAML stack.

    Or you can use the code approach shown above, that works too.

    https://dl.dropboxusercontent.com/u/2174873/Xamarin.Samples/DataTemplateSelectorExtension.zip

    Friday, April 10, 2015 5:04 PM
  • User31385 posted

    @mark.smith I've tried this in a new project and I can't seem to get the ListView to work with uneven rows. Is this supported?

    Wednesday, April 22, 2015 1:40 AM
  • User31385 posted

    @mark.smith Never mind! Updating Forms to 1.4.2.6355 did the trick.

    Wednesday, April 22, 2015 1:43 AM
  • User89114 posted

    @mark.smith This example was extremely helpful. I would love to see a feature like this in Xamarin itself (support for multiple DataTemplates based on some sort of selection function). It is especially helpful for search results that contain cells for all different types of objects.

    Saturday, April 25, 2015 7:50 PM
  • User135539 posted

    Hi @mark.smith the extention provided to work with XAML is breaking on WinPhone as I reported on https://bugzilla.xamarin.com/show_bug.cgi?id=31541

    Wednesday, July 1, 2015 9:51 PM
  • User114692 posted

    Could you anyone suggest how to display variant GroupHeader cells...

    Suppose, if we had value of a "C" as "ABC" then I need to show three label cell... Otherwise show a cell with 2 images.

    Friday, November 27, 2015 9:46 AM
  • User114692 posted

    Hello everyone,

    Could anyone suggest how to achieve this.

    Monday, December 14, 2015 6:11 AM
  • User2148 posted

    @mani1010 I think you can have two stacklayout, one with the image and one with two labels, then bind IsVisible stacklayout property

    Monday, December 14, 2015 1:07 PM
  • User114692 posted

    Hi @AlessandroCaliaro , I'm using the expandable list view from this https://github.com/jamesmontemagno/Xamarin.Forms-Monkeys I can able to apply only one header with this approach. Could we get any binding call back so I can return my custom views?

    Wednesday, December 16, 2015 8:20 AM
  • User2148 posted

    Sorry @mani1010 but I have not understand what are you doing . On your mokeup seems you need a group headers, correct?

    Wednesday, December 16, 2015 9:35 AM
  • User114692 posted

    Yes @AlessandroCaliaro ,

    But with above approach I can place one type of Group header only for all Headers. Could you have any suggestion to place different types of Group headers.

    Wednesday, December 16, 2015 9:43 AM
  • User2148 posted

    Can you assign to your group header a stacklayout? If YES, you can try to add to the first stacklayout other stacklayouts each one with his own controls. Then you should set IsVisible property of the SL you have to visualize

    Wednesday, December 16, 2015 10:04 AM
  • User112335 posted

    Hi all,
    I have to show two types of view cells in list view. For that i am used DataTemplateselectror . Now i need to select multiple items and retrieve the selected items data.

    Monday, May 9, 2016 9:31 AM
  • User106238 posted

    @everyone on the thread

    The OnBindingContextChanged solution remains the best for highly changeable layouts that cannot be designed beforehand.

    However, as some of you might have found out already, it breaks from Forms 2.1 on. Here is the thread I started on the issue:

    https://forums.xamarin.com/discussion/65586/whats-up-with-this-new-system-runtime-exceptionservices-exceptiondispatchinfo-throw

    Tuesday, June 21, 2016 9:15 AM
  • User179215 posted

    I stumbled here hoping to implement what was questioned on this post from 2014. Is the binding of 2 properties that was like on the question still not doable right now?

    If not how can i implement the formatting on the ViewModel upon binding

    but for now we recommend create a property on your view model to do the formatting for you.

    ....

    @alxbog said: I'm setting up DataTemplate for ListView. It's easy to create a binding for a single property. For example:

    listView.ItemTemplate = new DataTemplate(() =>
    {
        var cell = new TextCell();
        cell.SetBinding<MyModel>(TextCell.TextProperty, m => m.Name);
        return cell;
    });
    

    But how to create a complex binding? For example:

    cell.SetBinding<MyModel>(TextCell.TextProperty, m => string.Format("{0}: {1}", m.Name, m.Description));

    (if you do so, ArgumentException is thrown)

    @EricMaupin said: The ArgumentException is thrown because that's not a valid argument. The expression argument is provided simply as a replacement for the string method of defining the path, allowing for compiler verification and refactoring support.

    While we do support string formatting with bindings (new Binding ("Name") { StringFormat = "{0}" };), there is not currently a way to bind in two separate properties. We do plan to add this, but for now we recommend create a property on your view model to do the formatting for you.

    Wednesday, August 31, 2016 1:23 AM
  • User308644 posted

    perfect @"MarkSmith.8123"

    Friday, March 31, 2017 2:29 AM