简体   繁体   中英

Using LINQ in XAML, with Xamarin Forms

I'm writing an MVVM-based app, and ran into a small issue.

I have two list views to create within each other. Classes A and B are to be shown in these. The list that contains A's views (let's call them AView's) is built from an ObservableCollection within the viewmodel.

Each AView contains a list of B's (BView), technically, the whole screen is a list of lists.

The issue is, the underlying data is not A->B, but B->A (A does not contain a list of B's, but B has a reference to its parent A), and I have a global list of B's.

In code it wouldn't be hard, as I can easily run a ListB.Where(x => xA == A), but I'm yet to find a way to do so in XAML.

Is it possible, at all, to do so, and if yes, what would be the method? I do not wish to create separate Pages with separate BindingContexts just to make such a logically simple display.

Nesting listviews is not support in Xamarin.Forms. You can try, but the inner one will not be scrollable and therefor useless.

If ListA has a list of ListB's, would it suite your needs to do grouping? 这是显示分组外观的快速图像。

Essentially, you are grouping your lists of B within items of A (where in that image I showed your list A is a list of mobile devices and your lists of B are specific types within the generic OS).

To do this, when you create your listview and set your data template/source, you must now also set IsGroupingEnabled to true and GroupHeaderTemplate to the datatemplate created to show how it will be displayed visually (exactly the same as you normally would make a datatemplate with custom viewcells).

The only difference now is instead of your source being a list of A or a list of B, it is an list A (which extends ObservableCollection) which contains a list B.

You basically need to make your A viewmodel extend ObserableCollection so A is A but A is also a list/can be used as a list.

Now you just feed your list A (where each A has a list B) as the source of your ListView.

Nesting two ListViews in Xamarin Forms is not recommended (or even supported I think).

The best way would probably be to use a grouped ListView, bound to a nested ObservableCollection<T>. Here the outer collection would be an ObservableCollection<ViewModelA>. Each ViewModelA then derives itself from ObservableCollection<ViewModelB>. So it's a collection of collections.

Additionally, because of your ViewModelB.Parent = some ViewModelA requirement, the inner collections should be updated to the right subset of ViewModelB items (ie having Parent==some ViewModelA), whenever your flat, global collection of all ViewModelB items changes. This will require some additional logic in your ViewModelA implementation.

Concretely a solution could be as follows:

First define three viewmodel classes (ViewModelMain, ViewModelA and ViewModelB) as follows:

ViewModelMain:

using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace XamLab1
{
    public class ViewModelMain
    {
        public ObservableCollection<ViewModelA> AItems { get; set; }
        public ObservableCollection<ViewModelB> AllBItems { get; set; } 

        public ViewModelMain()
        {
            AItems = new ObservableCollection<ViewModelA>();
            AllBItems = new ObservableCollection<ViewModelB>();

            this.UpdateCommand = new Command<string>((key) =>
            {
                Update();
            });
        }

        public ICommand UpdateCommand { protected set; get; }

        public void Update()
        {
            // filling collections with dummy test data...

            AItems.Clear();
            AllBItems.Clear();

            for (int i = 0; i < 10; i++)
            {
                long timestamp = DateTime.Now.Ticks;
                var a = new ViewModelA("A-" + i + "-" + timestamp, AllBItems);
                AItems.Add(a);

                for (int j = 0; j < 3; j++)
                {
                    AllBItems.Add(new ViewModelB { NameB = "B-" + i + "-" + j + "-" + timestamp, Parent = a });
                }
            }            
        }
    }
}

ViewModelA:

using System.Collections.ObjectModel;
using System.Linq;

namespace XamLab1
{
    public class ViewModelA : ObservableCollection<ViewModelB>
    {
        public string NameA { get; private set; }

        public ViewModelA(string name, ObservableCollection<ViewModelB> allBItems)
        {
            NameA = name;
            _allBItems = allBItems;
            _allBItems.CollectionChanged += _allBItems_CollectionChanged;
        }

        private ObservableCollection<ViewModelB> _allBItems;

        private void _allBItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            ClearItems();
            foreach (var item in _allBItems.Where(x => x.Parent == this))
            {
                Add(item);
            }
        }

    }

}

and ViewModelB:

namespace XamLab1
{
    public class ViewModelB
    {
        public string NameB { get; set; }
        public ViewModelA Parent { get; set; }

    }
}

Finally, bind the view models to XAML like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamLab1;assembly=XamLab1"
             x:Class="XamLab1.MainPage">

  <ContentPage.BindingContext>
    <local:ViewModelMain />
  </ContentPage.BindingContext>

  <StackLayout>
      <Button Text="Update" Command="{Binding UpdateCommand}"></Button>
      <ListView ItemsSource="{Binding AItems}" IsGroupingEnabled="True" GroupDisplayBinding="{Binding NameA}" GroupShortNameBinding="{Binding NameA}" HasUnevenRows="True">
        <ListView.GroupHeaderTemplate>
          <DataTemplate>
            <ViewCell>
              <StackLayout VerticalOptions="FillAndExpand" Padding="5" BackgroundColor="#707070">
                <Label Text="{Binding NameA}"/>
              </StackLayout>
            </ViewCell>
          </DataTemplate>
        </ListView.GroupHeaderTemplate>
        <ListView.ItemTemplate>
          <DataTemplate>
            <ViewCell>
              <StackLayout VerticalOptions="FillAndExpand" Padding="20, 5, 5, 5">
                <Label Text="{Binding NameB}" />
              </StackLayout>
            </ViewCell>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    </StackLayout>

</ContentPage>

Now run the resulting app and press the Update button: you should see the ListView being refreshed with new ViewModel data.

Please note: this solution could still use some optimization. For instance, it's probably not very efficient to update all the inner collections whenever the global BItems collection changes. Some kind of more lazy update would probably be in order. Also, if your AllBItems collection is large, you might consider some indexing mechanism on the Parent property in order to speed up the filtering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM