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