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>
0 comments:
Post a Comment