【问题标题】:A layout which can wrap contents of a collection which is inside a Listview可以包装 Listview 内的集合内容的布局
【发布时间】:2017-09-11 10:22:59
【问题描述】:

我正在尝试实施在部门工作的员工列表。一个部门有几个部门和几个员工。以下是我的代码,我难以滚动和包装内容(员工图像和姓名)。至于包装内容,如果一行没有足够的空间,我希望内容(图像和员工的名字)显示在新的一行中。

到目前为止,我尝试了多种选择,但都无济于事。我正在使用ItemsControl 我还尝试添加StackLayout 而不是WrapLayout

谁能告诉我如何解决滚动问题和内容换行问题?我可以使用任何解决方法或任何其他布局吗?谢谢。

XAML

<ListView ItemsSource="{Binding Departments}" HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Margin="20,20,20,20">
                        <Label Text="{Binding DepartmentName}" />
                        <Label Text="{Binding DepartmentId}" />

                        <local:ItemsControl ItemsSource="{Binding Employees}">
                            <local:ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                            <local:WrapLayout>
                                                <Image Source="{Binding ImageUrl}"
                                                       WidthRequest="60"
                                                       HeightRequest="60"/>
                                                <Label
                                                       Text="{Binding FirstName}"
                                                       HorizontalTextAlignment="Center"
                                                       VerticalTextAlignment="Center"
                                                    LineBreakMode="WordWrap"/>
                                            </local:WrapLayout>
                                    </Grid>
                                </DataTemplate>
                            </local:ItemsControl.ItemTemplate>
                        </local:ItemsControl>

                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>  


更新 1:

Department.cs

public class Department
{
    public int DepartmentID { get; set; }
    public string DepartmentName { get; set; }
    // and several other properties

    public List<Employee> Employees { get; set; }
}

Employee.cs

public class Employee
{
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }
    public string ImageUrl{ get; set; }

    // and several other properties
}  

更新 2

似乎滚动不会仅在我的一个测试设备中发生。但仍然需要一个能够包装控件的布局。

更新 3
我希望数据如下图所示。但是现在,使用上面的代码,WrapLayout 中的内容没有被包装,而是被调整大小以适合一行。如果第一行没有空格,我希望它们被包装起来。

【问题讨论】:

  • ItemsControl是一种ListView组件吗?您不能在滚动视图中使用嵌套列表视图或列表视图,反之亦然。好像你需要一个分组的列表视图。同样的问题见this answer
  • @DiegoRafaelSouza ItemsControl 实际上是 this。我之前使用了嵌套的ListView(没有ScrollView),但它没有正确显示数据,当然它效率不高,从我在大多数帖子中看到的。我尝试了您提到的上述链接。但是我的课有点不同。我已经更新了我的问题。如果可以的话,请看一下。谢谢。
  • @DiegoRafaelSouza 谢谢你的链接。我尝试过这个。然而,它不符合我的主要目的。 :( 这是包装内容。在这种情况下,网格不是动态的(因为这是一个集合)。我希望能够不提及行和列索引。:(你能帮我吗:(
  • 好的,我去看看
  • 抱歉我的英语不好,但是你说“包装内容”是什么意思?这种情况下的网格是针对该上下文选择的布局。根据我给出的答案,我指的是分组列表视图的提议概念,例如,如果我正确理解您的需求,您可以看到 herehere。您应该调整组标题模板

标签: xaml mvvm xamarin.forms itemscontrol


【解决方案1】:

当您提到 WrapLayout 时,我假设您的意思是 this one 定义为 here

另外,由于您已经在使用ItemsControl,我建议您对其进行调整以支持WrapLayout 而不是StackLayout。例如:

public class ItemsControl : WrapLayout
{
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
        "ItemsSource", typeof(IList), typeof(ItemsControl), propertyChanging: OnItemsSourceChanged);

    /// <summary>
    /// Gets or sets the items source - can be any collection of elements.
    /// </summary>
    /// <value>The items source.</value>
    public IList ItemsSource
    {
        get { return (IList)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
        "ItemTemplate", typeof(DataTemplate), typeof(ItemsControl));

    /// <summary>
    /// Gets or sets the item template used to generate the visuals for a single item.
    /// </summary>
    /// <value>The item template.</value>
    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    public ItemsControl()
    {
        Padding = new Thickness(0);
        Margin = new Thickness(0);
    }

    static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((ItemsControl)bindable).OnItemsSourceChangedImpl((IList)oldValue, (IList)newValue);
    }

    void OnItemsSourceChangedImpl(IList oldValue, IList newValue)
    {
        // Unsubscribe from the old collection
        if (oldValue != null)
        {
            INotifyCollectionChanged ncc = oldValue as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged -= OnCollectionChanged;
        }

        if (newValue == null)
        {
            Children.Clear();
        }
        else
        {
            FillContainer(newValue);
            INotifyCollectionChanged ncc = newValue as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged += OnCollectionChanged;
        }
    }

    /// <summary>
    /// This method takes our items source and generates visuals for
    /// each item in the collection; it can reuse visuals which were created
    /// previously and simply changes the binding context.
    /// </summary>
    /// <param name="newValue">New items to display</param>
    /// <exception cref="T:System.ArgumentNullException"></exception>
    void FillContainer(IList newValue)
    {
        var template = ItemTemplate;
        var visuals = Children;

        if (template == null)
            throw new NotSupportedException("ItemTemplate must be specified!");

        var newVisuals = new List<View>(Children); //using a list to avoid multiple layout refresh
        Children.Clear();

        for (int i = 0; i < newVisuals.Count; i++)
        {
            newVisuals[i].IsVisible = i < newValue.Count;
        }

        for (int i = 0; i < newValue.Count; i++)
        {
            var dataItem = newValue[i];

            if (visuals.Count > i)
            {
                if (template != null)
                {
                    var visualItem = visuals[i];
                    visualItem.BindingContext = dataItem;
                }
            }
            else
            {
                if (template != null)
                {
                    // Pull real template from selector if necessary.
                    var dSelector = template as DataTemplateSelector;
                    if (dSelector != null)
                        template = dSelector.SelectTemplate(dataItem, this);

                    var view = template.CreateContent() as View;
                    if (view != null)
                    {
                        view.BindingContext = dataItem;
                        newVisuals.Add(view);
                    }
                }
            }
        }

        foreach (var child in newVisuals) //wish they had a nice AddRange method here
            if(child.IsVisible)
                Children.Add(child);
    }

    /// <summary>
    /// This is called when the data source collection implements
    /// collection change notifications and the data has changed.
    /// This is not optimized - it simply replaces all the data.
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        FillContainer((IList)sender);
    }
}

然后将您的 XAML 更改为:

<ListView ItemsSource="{Binding Departments}" HasUnevenRows="True">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Margin="20,20,20,20">
                    <Label Text="{Binding DepartmentName}" />
                    <Label Text="{Binding DepartmentId}" />

                    <local:ItemsControl ItemsSource="{Binding Employees}">
                        <local:ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackLayout Orientation="Horizontal">
                                    <Image Source="{Binding ImageUrl}"
                                                   WidthRequest="60"
                                                   HeightRequest="60"/>
                                    <Label Text="{Binding FirstName}"
                                           HorizontalTextAlignment="Center"
                                           VerticalTextAlignment="Center" />
                                </StackLayout>
                            </DataTemplate>
                        </local:ItemsControl.ItemTemplate>
                    </local:ItemsControl>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>  

【讨论】:

  • 再次非常感谢。早些时候,我将ItemsControl(在代码中)中的StackLayout 更改为WrapLayout,它也有效。现在再试一次,它也能正常工作。
  • 另外,既然你在我之前的问题中提到使用ItemsControl 存在效率低下,那么this answer 中提到的这个RepeaterView 会是一个有效的替代品吗?
  • 据了解,它们实际上是相同的——尽管ItemsControl 确实有一些回收行为来代替虚拟化;我在RepeaterView 中没有看到这一点
  • 好的。谢谢你的解释。
  • 当我尝试更新集合时,列表或其子项没有显示任何更改。我已将ViewModel 中的Collection 设置为null 并设置一个新值。但这也无济于事。你能告诉我发生了什么吗?提前谢谢你。
猜你喜欢
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多