#c# #xaml #xamarin.forms
#c# #xaml #xamarin.forms
Вопрос:
В настоящее время я перепроектирую страницу, на которой отображается контактная информация в Xamarin.Приложение Forms. На странице будет отображен список разделов (адрес, номера телефонов, адреса электронной почты и т.д.), Каждый со значком и соответствующей информацией. Разделы должны быть разделены строкой, но никакие строки не должны быть выше первого и ниже последнего раздела. Кроме того, пустые разделы вообще не отображаются.
Разметка выглядит в основном так:
<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>
У меня это работает по большей части, за исключением строк. (Я просто использую BoxView
с HeightRequest
числом 1.) Чтобы заставить их работать должным образом, мне нужно было бы указать программе визуализации нарисовать линию под каждым видимым разделом, кроме последнего. По сути, мне нужен селектор в стиле CSS3 :not(:last-of-type)
(или селектор :not(:first-of-type)
со строками выше).
Каков наилучший способ сделать это в XAML? (Или в коде, если необходимо?)
Комментарии:
1. Это больной вопрос. Однажды я написал подкласс ListBox, который устанавливал логические свойства FirstItem / LastItem, прикрепленные к контейнерам элементов, которыми он владел. Я полагаю, вы могли бы написать поведение, которое делает то же самое с дочерними элементами StackPanel . Получив это, вы можете легко написать стиль с триггером для свойства LastItem.
Ответ №1:
Вы только что напомнили мне, что я давно этого хотел, поэтому я написал один (с фрагментами, это десятиминутная работа). Дайте мне знать, как / если это работает с Xamarin; Я не могу протестировать это.
ОБНОВЛЕНИЕ: сегодня я, должно быть, наполовину сплю. Я прочитал «StackLayout» как «StackPanel». OP адаптировал его к Xamarin и опубликовал этот рабочий код в качестве другого ответа.
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
}
}
Пример использования:
<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>
Комментарии:
1. Спасибо, это работает! Я добавлю версию вашего кода на Xamarin в качестве собственного ответа для полноты, но ваш, безусловно, «правильный».
2. @TimOkrongli Это здорово. И спасибо за публикацию версии Xamarin.
Ответ №2:
После того, как Эд Планкетт предоставил решение для WPF, я решил опубликовать Xamarin.Эквивалентные формы я построил из его кода.
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
}
}