将StackLayout的子项绑定到数据

时间:2015-05-06 19:43:23

标签: xamarin xamarin-forms

我正在尝试创建一排水平的按钮。我知道我可以使用水平方向的StackLayout来实现这一目标。但是,我希望按钮绑定到底层的BindingContext。

StackLayout不提供Children作为可绑定属性。

我该怎么做呢?

3 个答案:

答案 0 :(得分:1)

如果有人仍然对此感兴趣,您可能想要从这里找到的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;
                }

            }
        }
    }
}

答案 1 :(得分:0)

我遇到了类似的困境,最终使用了RepeaterView,它包含在Xamarin Forms Labs项目的当前Xamarin Forms控件集中。你可以从Github here抓住它。虽然它的用法并不像我期望的那样(来自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>

答案 2 :(得分:0)

我认为这可能会引导您朝着正确的方向前进 http://www.maiersoft.de/blog/creating-a-xamarin-forms-carousel-part-1-the-basic-parts/

您需要创建一个可以命名的bindableobject ItemsSource。

我自己正在做类似的事情。还没有忘记完美的解决方案