Wednesday, August 23, 2017

How can I add a <ViewCell> with a <Grid> to a TableView in C#

Leave a Comment

I'm constructing a dynamic TableView. So far I have this:

var section = new TableSection("Available Categories"); foreach (var category in categoryGroups) {    var name = (string)category.Name;    var cell = new TextCell { Text = name };    section.Add(cell); } tableView.Root.Add(section); 

This works but instead of using a TextCell I would like to use a ViewCell with a grid same as I currently have in XAML:

< ViewCell >    < Grid VerticalOptions = "CenterAndExpand" Padding = "20, 0" >       < Grid.ColumnDefinitions >          < ColumnDefinition Width = "*" />          < ColumnDefinition Width = "Auto" />          < ColumnDefinition Width = "20" />       </ Grid.ColumnDefinitions >       < Label Style = "{DynamicResource ListItemTextStyle}" Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />       < Label Style = "{DynamicResource ListItemTextStyle}" Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />       < Label Grid.Column = "2" Text = "{x:Static local:FontAwesome.FACheck}" HorizontalTextAlignment = "End" HorizontalOptions = "End" FontFamily = "FontAwesome" XAlign = "Center" FontSize = "13" IsVisible = "{Binding IsToggled}" TextColor = "#1E90FF" />    </ Grid > </ ViewCell > 

Can anyone give me advice on how I can add this to my C# code. I know how to do it in XAML only.

Note

Here is where I learned about Dynamic Styles:

https://developer.xamarin.com/guides/xamarin-forms/user-interface/styles/device/

1 Answers

Answers 1

Option 1: Define custom ViewCell in C#

This is what C# equivalent of the XAML template you shared would look like:

public class CustomViewCell : ViewCell {     public CustomViewCell()     {         var label1 = new Label         {             HorizontalOptions = LayoutOptions.StartAndExpand         };          //or, label1.Style = Device.Styles.ListItemTextStyle;         label1.SetDynamicResource(VisualElement.StyleProperty, "ListItemTextStyle");         Grid.SetColumn(label1, 0);         label1.SetBinding(Label.TextProperty, "Name");          var label2 = new Label         {             HorizontalOptions = LayoutOptions.End,             //XAlign = TextAlignment.End, //not needed             VerticalOptions = LayoutOptions.Center,             TextColor = Color.Gray         };          //or, label2.Style = Device.Styles.ListItemTextStyle;         label2.SetDynamicResource(VisualElement.StyleProperty, "ListItemTextStyle");         Grid.SetColumn(label2, 1);         label2.SetBinding(Label.TextProperty, "TotalWordCount");          var label3 = new Label         {             HorizontalOptions = LayoutOptions.End,             HorizontalTextAlignment = TextAlignment.End,             VerticalOptions = LayoutOptions.Center,             //XAlign = TextAlignment.Start, //not needed             FontFamily = "FontAwesome",             FontSize = 13,             TextColor = Color.FromHex("#1E90FF"),             Text = FontAwesome.FACheck,         };         Grid.SetColumn(label3, 2);         label3.SetBinding(VisualElement.IsVisibleProperty, "IsToggled");          var grid = new Grid         {             VerticalOptions = LayoutOptions.CenterAndExpand,             Padding = new Thickness(20, 0),             ColumnDefinitions = new ColumnDefinitionCollection()             {                 new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },                 new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) },                 new ColumnDefinition() { Width = new GridLength(20) },             },             Children = {                 label1,                 label2,                 label3             }         };          View = grid;     } } 

Option 2: Define custom ViewCell in XAML

Even if you are dynamically creating your TableView you can still use the XAML based approach. Just create a new XAML control as following:

Sample ViewCell XAML

<ViewCell      xmlns="http://xamarin.com/schemas/2014/forms"      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"      x:Class="AppNamespace.MyViewCell">     <Grid VerticalOptions="CenterAndExpand" Padding = "20, 0" >         <Grid.ColumnDefinitions>             <ColumnDefinition Width="*" />             <ColumnDefinition Width="Auto" />             <ColumnDefinition Width="75" />         </Grid.ColumnDefinitions>         <Label Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />         <Label Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />         <Switch Grid.Column = "2" HorizontalOptions = "End"  IsToggled = "{Binding IsToggled}"  />     </Grid> </ViewCell> 

Code-behind

public partial class MyViewCell : ViewCell {     public MyViewCell()     {         InitializeComponent();     } } 

and you can create your TableView as following:

var section = new TableSection("Available Categories"); foreach (var category in categoryGroups) {    var cell = new MyViewCell { BindingContext = category };    section.Add(cell); } tableView.Root.Add(section); 

Option 3. Create your own custom TableView with ItemSource support like ListView

public class DynamicTableView : TableView {     /// <summary>     /// Bindable property for the data source     /// </summary>     public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(         "ItemsSource", typeof(IDictionary), typeof(DynamicTableView), propertyChanging: OnItemsSourceChanged);      /// <summary>     /// Gets or sets the items source - can be any collection of elements.     /// </summary>     /// <value>The items source.</value>     public IDictionary ItemsSource     {         get { return (IDictionary)GetValue(ItemsSourceProperty); }         set { SetValue(ItemsSourceProperty, value); }     }      /// <summary>     /// Bindable property for the data template to visually represent each item.     /// </summary>     public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(         "ItemTemplate", typeof(DataTemplate), typeof(DynamicTableView));      /// <summary>     /// Gets or sets the item template used to generate the visuals for a single item.     /// </summary>     /// <value>The item template.</value>     public DataTemplate ItemTemplate     {         get { return (DataTemplate)GetValue(ItemTemplateProperty); }         set { SetValue(ItemTemplateProperty, value); }     }      /// <summary>     /// Initializes an ItemsControl.     /// </summary>     public DynamicTableView()     {      }      /// <summary>     /// This is called when the underlying data source is changed.     /// </summary>     /// <param name="bindable">ItemsSource</param>     /// <param name="oldValue">Old value.</param>     /// <param name="newValue">New value.</param>     static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)     {         ((DynamicTableView)bindable).OnItemsSourceChangedImpl((IDictionary)oldValue, (IDictionary)newValue);     }      /// <summary>     /// Instance method called when the underlying data source is changed through the     /// <see cref="ItemsSource"/> property. This re-generates the list based on the      /// new collection.     /// </summary>     /// <param name="oldValue">Old value.</param>     /// <param name="newValue">New value.</param>     void OnItemsSourceChangedImpl(IDictionary oldValue, IDictionary newValue)     {         Root.Clear();         if(newValue != null)         {             FillContainer(newValue);         }     }      /// <summary>     /// This method takes our items source and generates visuals for     /// each item in the collection; it can reuse visuals which were created     /// previously and simply changes the binding context.     /// </summary>     /// <param name="newValue">New items to display</param>     void FillContainer(IDictionary newValue)     {         Root.Clear();          var template = ItemTemplate;          foreach(var key in newValue.Keys)         {             var tableSection = new TableSection() { Title = key.ToString() };             var innerList = newValue[key] as IList;             if (innerList == null)                 innerList = Enumerable.Repeat(newValue[key], 1).ToList();              foreach(var dataItem in innerList)             {                 if (template != null)                 {                     var view = InflateTemplate(template, dataItem);                     if (view != null)                         tableSection.Add(view);                 }                 else                 {                     var label = new TextCell { Text = dataItem.ToString() };                     tableSection.Add(label);                 }             }              Root.Add(tableSection);         }     }      /// <summary>     /// Inflates the visuals for a data template or template selector     /// and adds it to our StackLayout.     /// </summary>     /// <param name="template">Template.</param>     /// <param name="item">Item.</param>     ViewCell InflateTemplate(DataTemplate template, object item)     {         // Pull real template from selector if necessary.         var dSelector = template as DataTemplateSelector;         if (dSelector != null)             template = dSelector.SelectTemplate(item, this);          var view = template.CreateContent() as ViewCell;         if (view != null)         {             view.BindingContext = item;             return view;         }          return null;     } } 

and usage will look like:

<local:DynamicTableView ItemsSource="{Binding AllCategories}">     <local:DynamicTableView.ItemTemplate>         <DataTemplate>             <ViewCell>                 <Grid VerticalOptions="CenterAndExpand" Padding = "20, 0" >                     <Grid.ColumnDefinitions>                         <ColumnDefinition Width="*" />                         <ColumnDefinition Width="Auto" />                         <ColumnDefinition Width="75" />                     </Grid.ColumnDefinitions>                     <Label Grid.Column = "0" HorizontalOptions = "StartAndExpand" Text = "{Binding Name}" />                     <Label Grid.Column = "1" HorizontalOptions = "End" XAlign = "End" Text = "{Binding TotalWordCount}" VerticalOptions = "Center" TextColor = "Gray" />                     <Switch Grid.Column = "2" HorizontalOptions = "End"  IsToggled = "{Binding IsToggled}"  />                 </Grid>             </ViewCell>         </DataTemplate>     </local:DynamicTableView.ItemTemplate> </local:DynamicTableView> 

and sample data-set:

public class SettingsViewModel {     public Categories AllCategories => new Categories(); }  public class Category {     public string Name { get; set; }     public int TotalWordCount { get; set; }     public bool IsToggled { get; set; } } public class Categories : Dictionary<string, List<Category>> {     public Categories()     {         this.Add("Available Categories", new List<Category>(new []{             new Category(){ Name = "Test1", TotalWordCount = 10, IsToggled = true },             new Category(){ Name = "Test2", TotalWordCount = 25, IsToggled = true },             new Category(){ Name = "Test3", TotalWordCount = 20, IsToggled = false }         }));          this.Add("Other Categories", new List<Category>(new[]{             new Category(){ Name = "Test-N1", TotalWordCount = 30, IsToggled = true },             new Category(){ Name = "Test-N2", TotalWordCount = 50, IsToggled = false }         }));     } } 

Older answer based on older question (no longer valid)

If you just need to specify BackgroundColor or FontSize for a particular platform - you can do that by using OnPlatform - you shouldn't need a custom renderer.

<ListView.Header>     <!-- don't forget to override spacing and padding properties to avoid default spacing -->    <StackLayout Spacing="0" Padding="0" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">       <StackLayout.BackgroundColor>          <OnPlatform x:TypeArguments="Color"              Android=""              WinPhone=""                                                       iOS="#000000">       </StackLayout.BackgroundColor>       <StackLayout Padding="10,35,10,10" Orientation="Horizontal" HorizontalOptions="FillAndExpand">          <local:ExtLabel ExtStyleId="Body" Text="Custom body Label"></local:ExtLabel>          <local:ExtLabel ExtStyleId="Header" Text="Custom hdr Label"></local:ExtLabel>       </StackLayout>    </StackLayout> </ListView.Header> 

Also, on referencing the source code for listview and renderer, and specifically here - it looks like the header/header-template property just functions as a placeholder for a set of controls in ListView. If you supply a custom-control in header, the framework will instantiate and use the renderer for it.

So if you really need a custom-renderer based approach, then you can then create a custom control, (for e.g. CustomListViewHeader); and implement a iOS renderer for it.

You can then use this control in your ListView header or header-template.

<ListView.Header>    <local:CustomListViewHeader /> </ListView.Header> 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment