Reputation: 169
I'm currently redesigning a page that displays contact information in a Xamarin.Forms app. The page will display a list of sections (address, phone numbers, email addresses etc.), each with an icon and relevant information. The sections should be separated by a line but no lines should be above the first and below the last section. Also, empty sections are not displayed at all.
The markup looks basically like this:
<ScrollView>
<StackLayout>
<Label Text="{Binding Contact.Name}" />
<controls:ContactSection Icon="address.png">
<!-- address-type stuff -->
</controls:ContactSection>
<controls:ContactSection Icon="phone.png">
<!-- phone numbers -->
</controls:ContactSection>
<!-- further sections -->
</StackLayout>
</ScrollView>
I've got it working for the most part except for the lines. (I just use BoxView
with a HeightRequest
of 1.) In order to make them work properly I'd need to tell the renderer to draw a line below every visible section except for the last one. In essence, I'd need a CSS3-style :not(:last-of-type)
selector (or a :not(:first-of-type)
one with the lines above).
What's the best way of doing that in XAML? (Or in the code-behind if necessary?)
Upvotes: 1
Views: 293
Reputation: 37057
You just reminded me that I've been wanting this for a while, so I wrote one (with snippets, it's a ten minute job). Let me know how/if this works with Xamarin; I'm not able to test with that.
UPDATE: I must be half asleep today. I read "StackLayout" as "StackPanel". OP adapted it to Xamarin and posted that working code as another answer.
using System;
using System.Windows;
using System.Windows.Controls;
namespace HollowEarth.AttachedProperties
{
public static class PanelBehaviors
{
public static void UpdateChildFirstLastProperties(Panel panel)
{
for (int i = 0; i < panel.Children.Count; ++i)
{
var child = panel.Children[i];
SetIsFirstChild(child, i == 0);
SetIsLastChild(child, i == panel.Children.Count - 1);
}
}
#region PanelExtensions.IdentifyFirstAndLastChild Attached Property
public static bool GetIdentifyFirstAndLastChild(Panel panel)
{
return (bool)panel.GetValue(IdentifyFirstAndLastChildProperty);
}
public static void SetIdentifyFirstAndLastChild(Panel panel, bool value)
{
panel.SetValue(IdentifyFirstAndLastChildProperty, value);
}
/// <summary>
/// Behavior which causes the Panel to identify its first and last children with attached properties.
/// </summary>
public static readonly DependencyProperty IdentifyFirstAndLastChildProperty =
DependencyProperty.RegisterAttached("IdentifyFirstAndLastChild", typeof(bool), typeof(PanelBehaviors),
// Default MUST be false, or else True won't be a change in
// the property value, so PropertyChanged callback won't be
// called, and nothing will happen.
new PropertyMetadata(false, IdentifyFirstAndLastChild_PropertyChanged));
private static void IdentifyFirstAndLastChild_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Panel panel = (Panel)d;
((Panel)d).LayoutUpdated += (s, e2) => UpdateChildFirstLastProperties(panel);
}
#endregion PanelExtensions.IdentifyFirstAndLastChild Attached Property
#region PanelExtensions.IsFirstChild Attached Property
public static bool GetIsFirstChild(UIElement obj)
{
return (bool)obj.GetValue(IsFirstChildProperty);
}
public static void SetIsFirstChild(UIElement obj, bool value)
{
obj.SetValue(IsFirstChildProperty, value);
}
/// <summary>
/// True if UIElement is first child of a Panel
/// </summary>
public static readonly DependencyProperty IsFirstChildProperty =
DependencyProperty.RegisterAttached("IsFirstChild", typeof(bool), typeof(PanelBehaviors),
new PropertyMetadata(false));
#endregion PanelExtensions.IsFirstChild Attached Property
#region PanelExtensions.IsLastChild Attached Property
public static bool GetIsLastChild(UIElement obj)
{
return (bool)obj.GetValue(IsLastChildProperty);
}
public static void SetIsLastChild(UIElement obj, bool value)
{
obj.SetValue(IsLastChildProperty, value);
}
/// <summary>
/// True if UIElement is last child of a Panel
/// </summary>
public static readonly DependencyProperty IsLastChildProperty =
DependencyProperty.RegisterAttached("IsLastChild", typeof(bool), typeof(PanelBehaviors),
new PropertyMetadata(false));
#endregion PanelExtensions.IsLastChild Attached Property
}
}
Usage example:
<StackPanel
xmlns:heap="clr-namespace:HollowEarth.AttachedProperties"
heap:PanelBehaviors.IdentifyFirstAndLastChild="True"
HorizontalAlignment="Left"
Orientation="Vertical"
>
<StackPanel.Resources>
<Style TargetType="Label">
<Setter Property="Content" Value="Blah blah" />
<Setter Property="Background" Value="SlateGray" />
<Setter Property="Margin" Value="4" />
<Style.Triggers>
<Trigger Property="heap:PanelBehaviors.IsFirstChild" Value="True">
<Setter Property="Background" Value="DeepSkyBlue" />
<Setter Property="Content" Value="First Child" />
</Trigger>
<Trigger Property="heap:PanelBehaviors.IsLastChild" Value="True">
<Setter Property="Background" Value="SeaGreen" />
<Setter Property="Content" Value="Last Child" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Label />
<Label />
<Label />
<Label />
</StackPanel>
Upvotes: 1
Reputation: 169
After Ed Plunkett supplied a solution for WPF I decided to post the Xamarin.Forms equivalent I built from his code.
namespace Foo.Behaviors
{
using System.Linq;
using Xamarin.Forms;
/// <summary>
/// Identifies the first and last child of a <see cref="Layout{View}"/>.
/// </summary>
public class FirstAndLastChildBehavior
{
/// <summary>
/// Identifies the first and last child of the given <see cref="Layout{View}"/>.
/// </summary>
/// <param name="layout">The <see cref="Layout{View}"/>.</param>
public static void UpdateChildFirstLastProperties(Layout<View> layout)
{
// This is just here to provide a convenient place to do filtering, e.g. .Where(v => v.IsVisible).
var children = layout.Children;
for (var i = 0; i < children.Length; ++i)
{
var child = children[i];
SetIsFirstChild(child, i == 0);
SetIsLastChild(child, i == children.Length - 1);
}
}
#region PanelExtensions.IdentifyFirstAndLastChild Attached Property
/// <summary>
/// Gets a value that controls whether the child-identifying functionality is enabled for the given <see cref="Layout{View}"/>.
/// </summary>
/// <param name="layout">The <see cref="Layout{View}"/>.</param>
/// <returns><c>True</c> if functionality has been enabled, <c>false</c> otherwise.</returns>
public static bool GetIdentifyFirstAndLastChild(Layout<View> layout)
{
return (bool)layout.GetValue(IdentifyFirstAndLastChildProperty);
}
/// <summary>
/// Sets a value that controls whether the child-identifying functionality is enabled for the given <see cref="Layout{View}"/>.
/// </summary>
/// <param name="layout">The <see cref="Layout{View}"/>.</param>
/// <param name="value">The value.</param>
public static void SetIdentifyFirstAndLastChild(Layout<View> layout, bool value)
{
layout.SetValue(IdentifyFirstAndLastChildProperty, value);
}
/// <summary>
/// Identifies the <see cref="IdentifyFirstAndLastChild"/> property.
/// </summary>
/// <remarks>
/// The behavior can't be turned off; once the value is set to <c>true</c> the behavior will stick even if it's set back to
/// <c>false</c> later.
/// </remarks>
public static readonly BindableProperty IdentifyFirstAndLastChildProperty = BindableProperty.CreateAttached(
"IdentifyFirstAndLastChild",
typeof(bool),
typeof(FirstAndLastChildBehavior),
false,
BindingMode.OneWay,
null,
IdentifyFirstAndLastChildPropertyChanged);
/// <summary>
/// Gets called when IdentifyFirstAndLastChildProperty changes.
/// </summary>
/// <param name="bindable">The object we're bound to.</param>
/// <param name="oldValue">This parameter is not used.</param>
/// <param name="newValue">This parameter is not used.</param>
private static void IdentifyFirstAndLastChildPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var layout = (Layout<View>)bindable;
((Layout<View>)bindable).LayoutChanged += (a, b) => UpdateChildFirstLastProperties(layout);
}
#endregion PanelExtensions.IdentifyFirstAndLastChild Attached Property
#region PanelExtensions.IsFirstChild Attached Property
/// <summary>
/// Gets a value that determines whether the given <see cref="View"/> is the first child of its parent.
/// </summary>
/// <param name="obj">The <see cref="View"/>.</param>
/// <returns><c>True</c> if the <see cref="View"/> is the first child, <c>false</c> otherwise.</returns>
public static bool GetIsFirstChild(View obj)
{
return (bool)obj.GetValue(IsFirstChildProperty);
}
/// <summary>
/// Sets a value that determines whether the given <see cref="View"/> is the first child of its parent.
/// </summary>
/// <param name="obj">The <see cref="View"/>.</param>
/// <param name="value">The value.</param>
public static void SetIsFirstChild(View obj, bool value)
{
obj.SetValue(IsFirstChildProperty, value);
}
/// <summary>
/// Identifies the <see cref="IsFirstChild"/> property.
/// </summary>
public static readonly BindableProperty IsFirstChildProperty = BindableProperty.CreateAttached(
"IsFirstChild",
typeof(bool),
typeof(FirstAndLastChildBehavior),
false);
#endregion PanelExtensions.IsFirstChild Attached Property
#region PanelExtensions.IsLastChild Attached Property
/// <summary>
/// Gets a value that determines whether the given <see cref="View"/> is the last child of its parent.
/// </summary>
/// <param name="obj">The <see cref="View"/>.</param>
/// <returns><c>True</c> if the <see cref="View"/> is the last child, <c>false</c> otherwise.</returns>
public static bool GetIsLastChild(View obj)
{
return (bool)obj.GetValue(IsLastChildProperty);
}
/// <summary>
/// Sets a value that determines whether the given <see cref="View"/> is the last child of its parent.
/// </summary>
/// <param name="obj">The <see cref="View"/>.</param>
/// <param name="value">The value.</param>
public static void SetIsLastChild(View obj, bool value)
{
obj.SetValue(IsLastChildProperty, value);
}
/// <summary>
/// Identifies the <see cref="IsLastChild"/> property.
/// </summary>
public static readonly BindableProperty IsLastChildProperty = BindableProperty.CreateAttached(
"IsLastChild",
typeof(bool),
typeof(FirstAndLastChildBehavior),
false);
#endregion PanelExtensions.IsLastChild Attached Property
}
}
Upvotes: 1