Suppose you have a fixed list of items that you want to display in a Xamarin.Forms TableView, but you want some rows and sections of that table to be hidden. Unfortunately, there isn’t a convenient IsVisible property on the TableSection or Cell elements that we can bind to, and the only way to manipulate them is through code. Here’s a quick look on how to collapse our Cells and Sections using an Attached Property.
Take this example layout:
<ContentPage> <Grid> <TableView Intent="Settings"> <TableRoot> <TableSection Title="Group 1"> <SwitchCell Title="Setting 1" /> <SwitchCell Title="Setting 2" /> </TableSection> <TableSection Title="Group 2"> <SwitchCell Title="Setting 3" /> <SwitchCell Title="Setting 4" /> </TableSection> </TableRoot> </TableView> </Grid> </ContentPage>
In the above, I have two TableSection elements that contain two very simple SwitchCell elements. A lot of the detail has been omitted for clarity, but let's assume that I want the ability to only show certain settings to the user. Perhaps each setting is controlled by a special backend entitlement logic, and I want to bind that visibility for each cell through my ViewModel.
We'll create an attached property that can hide our cells:
public class CellEx { public static BindableProperty CollapsedProperty = BindableProperty.CreateAttached( "Collapsed", typeof(bool?), typeof(CellEx), default(bool?), defaultBindingMode: BindingMode.OneWay, propertyChanged: OnCollapsedChanged); public static bool GetCollapsed(BindableObject target) { return (bool)target.GetValue(CollapsedProperty); } public static void SetCollapsed(BindableObject target, bool value) { target.SetValue(CollapsedProperty, value); } private static void OnCollapsedChanged(BindableObject sender, object oldValue, object newValue) { // do work with cell } }
The OnCollapsedChanged event handler is called when the bound value of the BindableProperty is first set and we’ll use it to obtain a reference to the Cell. As the binding is potentially invoked before the UI is fully initialized we’ll need to defer our changes until it’s ready:
private static void OnCollapsedChanged(BindableObject sender, object oldValue, object newValue) { var view = sender as Cell; bool isVisible = (bool)newValue; if (view != null) { // the parent isn't available until the page has loaded. if (view.Parent == null) { view.Appearing += (o,e) => { ToggleViewCellCollapsedState(view, isVisible); }; } else { ToggleViewCellCollapsedState(view, isVisible); } } }
Once we have an initialized Cell, we need to obtain a reference to the containing TableSection. As a twist, the Parent of the Cell is the root TableView, so we must traverse the entire table downward to find the correct TableSection. Since a TableView only contains a fixed list of cells, scanning the entire table shouldn't be too troublesome at all:
private static void ToggleViewCellCollapsedState(Cell cell, bool isVisible) { var table = (TableView)cell.Parent; TableSection container = FindContainingTableSection(table, cell); if (container != null) { if (!isVisible) { // do work to hide cell } } } private static TableSection FindContainingTableSection(TableView table, Cell cell) { foreach(var section in table.Root) { foreach(var child in section) { if (child == cell) { return section; } } } return null; }
Lastly, once we've obtained the necessary references, we can simply manipulate the TableSection contents. To ensure this works on all platforms, this code must execute on the UI thread:
if (!isVisible) { Device.BeginInvokeOnMainThread(() => { // remove the cell from the section container.Remove(cell); // remove the section from the table if it's empty if (container.Count == 0) { table.Root.Remove(container); } }); }
We can then bind the visibility of our cells to the attached property:
<ContentPage xmlns:ex="clr-namespace:MyNamespace.Behaviors" > <Grid> <TableView> <TableRoot> <TableSection Title="Group 1"> <SwitchCell Title="Setting 1" ex:CellEx.Collapsed={Binding IsSetting1Visible}" /> <SwitchCell Title="Setting 2" ex:CellEx.Collapsed={Binding IsSetting2Visible}" /> </TableSection> <TableSection Title="Group 2"> <SwitchCell Title="Setting 3" ex:CellEx.Collapsed={Binding IsSetting3Visible}" /> <SwitchCell Title="Setting 4" ex:CellEx.Collapsed={Binding IsSetting4Visible}" /> </TableSection> </TableRoot> </TableView> </Grid> </ContentPage>
This works great and can dynamically hide individual cells or an entire section if needed. The largest caveat to this approach is that it only hides cells and won’t re-introduce them into the view when the binding changes. This is entirely plausible as you could cache the cells in a local variable and re-insert them programmatically, but you’d need to remember the containing section and appropriate indexes. I’d leave that to you dear reader, or I may rise to the challenge if I determine I really want to re-activate these cells in my app.
Happy coding!
No comments:
Post a Comment