我正在尝试创建一排水平按钮。我知道我可以使用水平方向的 StackLayout 来实现这一点。但是,我希望将按钮绑定到底层 BindingContext。
StackLayout 不提供 Children 作为可绑定属性。
我该怎么做呢?
我正在尝试创建一排水平按钮。我知道我可以使用水平方向的 StackLayout 来实现这一点。但是,我希望将按钮绑定到底层 BindingContext。
StackLayout 不提供 Children 作为可绑定属性。
我该怎么做呢?
如果有人仍然对此感兴趣,您可能想从此处找到的 WrapLayout 开始: https ://github.com/conceptdev/xamarin-forms-samples/blob/master/Evolve13/Evolve13/Controls/WrapLayout.cs
我使用了这段代码并使用可绑定的 ItemsSource 属性对其进行了扩展。这是我最终想出的:
using System;
using System.Linq;
using System.Collections.Generic;
using Xamarin.Forms;
using System.Collections;
namespace Maiersoft.Mobile.Core.Layouts
{
/// <summary>
/// New WrapLayout
/// </summary>
/// <author>Jason Smith</author>
public class WrapLayout : Layout<View>
{
Dictionary<View, SizeRequest> layoutCache = new Dictionary<View, SizeRequest> ();
/// <summary>
/// Backing Storage for the Spacing property
/// </summary>
public static readonly BindableProperty SpacingProperty =
BindableProperty.Create<WrapLayout, double> (w => w.Spacing, 5,
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayout)bindable).layoutCache.Clear());
/// <summary>
/// Spacing added between elements (both directions)
/// </summary>
/// <value>The spacing.</value>
public double Spacing {
get { return (double)GetValue (SpacingProperty); }
set { SetValue (SpacingProperty, value); }
}
public WrapLayout ()
{
VerticalOptions = HorizontalOptions = LayoutOptions.FillAndExpand;
}
#region My Changes to make WrapLayout Usable from XAML
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create<WrapLayout, IList> (
view => view.ItemsSource,
null,
propertyChanging: (bindableObject, oldValue, newValue) => {
((WrapLayout)bindableObject).ItemsSourceChanging ();
},
propertyChanged: (bindableObject, oldValue, newValue) => {
((WrapLayout)bindableObject).ItemsSourceChanged ();
}
);
public IList ItemsSource {
get {
return (IList)GetValue (ItemsSourceProperty);
}
set {
SetValue (ItemsSourceProperty, value);
}
}
void ItemsSourceChanging ()
{
if (ItemsSource == null) return;
// _selectedIndex = ItemsSource.IndexOf (SelectedItem);
}
void ItemsSourceChanged ()
{
base.Children.Clear ();
if (ItemsSource != null) {
foreach (var item in ItemsSource) {
var view = (View)ItemTemplate.CreateContent ();
var bindableObject = view as BindableObject;
if (bindableObject != null)
bindableObject.BindingContext = item;
base.Children.Add (view);
}
}
}
public DataTemplate ItemTemplate {
get;
set;
}
#endregion
protected override void OnChildMeasureInvalidated ()
{
base.OnChildMeasureInvalidated ();
layoutCache.Clear ();
}
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
double lastX;
double lastY;
var layout = NaiveLayout (widthConstraint, heightConstraint, out lastX, out lastY);
return new SizeRequest (new Size (lastX, lastY));
}
protected override void LayoutChildren (double x, double y, double width, double height)
{
double lastX, lastY;
var layout = NaiveLayout (width, height, out lastX, out lastY);
foreach (var t in layout) {
var offset = (int) ((width - t.Last ().Item2.Right) / 2);
foreach (var dingus in t) {
var location = new Rectangle(dingus.Item2.X + x + offset, dingus.Item2.Y + y, dingus.Item2.Width, dingus.Item2.Height);
LayoutChildIntoBoundingRegion (dingus.Item1, location);
}
}
}
private List<List<Tuple<View, Rectangle>>> NaiveLayout (double width, double height, out double lastX, out double lastY)
{
double startX = 0;
double startY = 0;
double right = width;
double nextY = 0;
lastX = 0;
lastY = 0;
var result = new List<List<Tuple<View, Rectangle>>> ();
var currentList = new List<Tuple<View, Rectangle>> ();
foreach (var child in Children) {
SizeRequest sizeRequest;
if (!layoutCache.TryGetValue (child, out sizeRequest)) {
layoutCache[child] = sizeRequest = child.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
}
var paddedWidth = sizeRequest.Request.Width + Spacing;
var paddedHeight = sizeRequest.Request.Height + Spacing;
if (startX + paddedWidth > right) {
startX = 0;
startY += nextY;
if (currentList.Count > 0) {
result.Add (currentList);
currentList = new List<Tuple<View, Rectangle>> ();
}
}
currentList.Add (new Tuple<View, Rectangle> (child, new Rectangle (startX, startY, sizeRequest.Request.Width, sizeRequest.Request.Height)));
lastX = Math.Max (lastX, startX + paddedWidth);
lastY = Math.Max (lastY, startY + paddedHeight);
nextY = Math.Max (nextY, paddedHeight);
startX += paddedWidth;
}
result.Add (currentList);
return result;
}
}
/// <summary>
/// Simple Layout panel which performs wrapping on the boundaries.
/// </summary>
public class WrapLayoutOld : Layout<View>
{
/// <summary>
/// Backing Storage for the Orientation property
/// </summary>
public static readonly BindableProperty OrientationProperty =
BindableProperty.Create<WrapLayoutOld, StackOrientation> (w => w.Orientation, StackOrientation.Vertical,
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayoutOld)bindable).OnSizeChanged ());
/// <summary>
/// Orientation (Horizontal or Vertical)
/// </summary>
public StackOrientation Orientation {
get { return (StackOrientation)GetValue (OrientationProperty); }
set { SetValue (OrientationProperty, value); }
}
/// <summary>
/// Backing Storage for the Spacing property
/// </summary>
public static readonly BindableProperty SpacingProperty =
BindableProperty.Create<WrapLayoutOld, double> (w => w.Spacing, 6,
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayoutOld)bindable).OnSizeChanged());
/// <summary>
/// Spacing added between elements (both directions)
/// </summary>
/// <value>The spacing.</value>
public double Spacing {
get { return (double)GetValue (SpacingProperty); }
set { SetValue (SpacingProperty, value); }
}
/// <summary>
/// This is called when the spacing or orientation properties are changed - it forces
/// the control to go back through a layout pass.
/// </summary>
private void OnSizeChanged()
{
this.ForceLayout();
}
//http://forums.xamarin.com/discussion/17961/stacklayout-with-horizontal-orientation-how-to-wrap-vertically#latest
// protected override void OnPropertyChanged
// (string propertyName = null)
// {
// base.OnPropertyChanged(propertyName);
// if ((propertyName == WrapLayout.OrientationProperty.PropertyName) ||
// (propertyName == WrapLayout.SpacingProperty.PropertyName)) {
// this.OnSizeChanged();
// }
// }
/// <summary>
/// This method is called during the measure pass of a layout cycle to get the desired size of an element.
/// </summary>
/// <param name="widthConstraint">The available width for the element to use.</param>
/// <param name="heightConstraint">The available height for the element to use.</param>
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
if (WidthRequest > 0)
widthConstraint = Math.Min (widthConstraint, WidthRequest);
if (HeightRequest > 0)
heightConstraint = Math.Min (heightConstraint, HeightRequest);
double internalWidth = double.IsPositiveInfinity (widthConstraint) ? double.PositiveInfinity : Math.Max (0, widthConstraint);
double internalHeight = double.IsPositiveInfinity (heightConstraint) ? double.PositiveInfinity : Math.Max (0, heightConstraint);
return Orientation == StackOrientation.Vertical
? DoVerticalMeasure(internalWidth, internalHeight)
: DoHorizontalMeasure(internalWidth, internalHeight);
}
/// <summary>
/// Does the vertical measure.
/// </summary>
/// <returns>The vertical measure.</returns>
/// <param name="widthConstraint">Width constraint.</param>
/// <param name="heightConstraint">Height constraint.</param>
private SizeRequest DoVerticalMeasure(double widthConstraint, double heightConstraint)
{
int columnCount = 1;
double width = 0;
double height = 0;
double minWidth = 0;
double minHeight = 0;
double heightUsed = 0;
foreach (var item in Children)
{
var size = item.GetSizeRequest(widthConstraint, heightConstraint);
width = Math.Max (width, size.Request.Width);
var newHeight = height + size.Request.Height + Spacing;
if (newHeight > heightConstraint) {
columnCount++;
heightUsed = Math.Max(height, heightUsed);
height = size.Request.Height;
} else
height = newHeight;
minHeight = Math.Max(minHeight, size.Minimum.Height);
minWidth = Math.Max (minWidth, size.Minimum.Width);
}
if (columnCount > 1) {
height = Math.Max(height, heightUsed);
width *= columnCount; // take max width
}
return new SizeRequest(new Size(width, height), new Size(minWidth,minHeight));
}
/// <summary>
/// Does the horizontal measure.
/// </summary>
/// <returns>The horizontal measure.</returns>
/// <param name="widthConstraint">Width constraint.</param>
/// <param name="heightConstraint">Height constraint.</param>
private SizeRequest DoHorizontalMeasure(double widthConstraint, double heightConstraint)
{
int rowCount = 1;
double width = 0;
double height = 0;
double minWidth = 0;
double minHeight = 0;
double widthUsed = 0;
foreach (var item in Children)
{
var size = item.GetSizeRequest(widthConstraint, heightConstraint);
height = Math.Max (height, size.Request.Height);
var newWidth = width + size.Request.Width + Spacing;
if (newWidth > widthConstraint) {
rowCount++;
widthUsed = Math.Max(width, widthUsed);
width = size.Request.Width;
} else
width = newWidth;
minHeight = Math.Max(minHeight, size.Minimum.Height);
minWidth = Math.Max (minWidth, size.Minimum.Width);
}
if (rowCount > 1) {
width = Math.Max(width, widthUsed);
height = (height + Spacing) * rowCount - Spacing; // via MitchMilam
}
return new SizeRequest(new Size(width, height), new Size(minWidth,minHeight));
}
/// <summary>
/// Positions and sizes the children of a Layout.
/// </summary>
/// <param name="x">A value representing the x coordinate of the child region bounding box.</param>
/// <param name="y">A value representing the y coordinate of the child region bounding box.</param>
/// <param name="width">A value representing the width of the child region bounding box.</param>
/// <param name="height">A value representing the height of the child region bounding box.</param>
protected override void LayoutChildren (double x, double y, double width, double height)
{
if (Orientation == StackOrientation.Vertical) {
double colWidth = 0;
double yPos = y, xPos = x;
foreach (var child in Children.Where(c => c.IsVisible))
{
var request = child.GetSizeRequest (width, height);
double childWidth = request.Request.Width;
double childHeight = request.Request.Height;
colWidth = Math.Max(colWidth, childWidth);
if (yPos + childHeight > height) {
yPos = y;
xPos += colWidth + Spacing;
colWidth = 0;
}
var region = new Rectangle (xPos, yPos, childWidth, childHeight);
LayoutChildIntoBoundingRegion (child, region);
yPos += region.Height + Spacing;
}
}
else {
double rowHeight = 0;
double yPos = y, xPos = x;
foreach (var child in Children.Where(c => c.IsVisible))
{
var request = child.GetSizeRequest (width, height);
double childWidth = request.Request.Width;
double childHeight = request.Request.Height;
rowHeight = Math.Max(rowHeight, childHeight);
if (xPos + childWidth > width) {
xPos = x;
yPos += rowHeight + Spacing;
rowHeight = 0;
}
var region = new Rectangle (xPos, yPos, childWidth, childHeight);
LayoutChildIntoBoundingRegion (child, region);
xPos += region.Width + Spacing;
}
}
}
}
}
我遇到了类似的困境,最终使用了 RepeaterView,它包含在 Xamarin Forms Labs 项目的当前 Xamarin Forms 控件集中。你可以在这里从 Github 获取它。虽然它的使用不是我所期望的(来自 WPF 和 Win8 XAML),但效果很好:
<controls:RepeaterView ItemsSource="{Binding Articles}"
x:TypeArguments="m:Article"
ItemClickCommand="{Binding SelectArticleCommand}"
Spacing="20"
Padding="20">
<controls:RepeaterView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</controls:RepeaterView.ItemTemplate>
</controls:RepeaterView>
我认为这可能会引导您朝着正确的方向前进 http://www.maiersoft.de/blog/creating-a-xamarin-forms-carousel-part-1-the-basic-parts/
您需要创建一个可以命名的可绑定对象 ItemsSource。
实际上我自己也在做类似的事情。还没有找到完美的解决方案