Asked by:
How to create complex bindings?

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 theTemplate
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, eachViewCell
can provide aHeight
which overrides theListView.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 theListView
. So, if you want a uniform (same) height for all cells, but have different content, then you will need to set theListView.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 theHasUnevenRows
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
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 specifiedPage
resources for aDataTemplate
with a key that matches the type name of the object in theListView
- 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