-1

I have 3 classes:

public class ViewModelA
{
    public ObservableCollection<ViewModelB> GroupsB { get; set; }
}

public class ViewModelB
{
    public ObservableCollection<ViewModelC> GroupsC { get; set; }
}

public class ViewModelC
{
    public string Name { get; set; }
}

And a ListView which shows all ViewModelC objects of all ViewModelB objects:

<ListView ItemsSource={Binding DataContext.GroupsB.GroupsC, ElementName=MyWindow}>
    <ListView.View>
        <DataGridView>
            <DataGridView.Columns>
                <DataGridViewColumn Header="Name" DisplayMemberPath="{Binding Name}" />
            </DataGridView.Columns>
        </DataGridView>
    </ListView.View>
</ListView>

where MyWindow is the name of Window:

<Window.DataContext>
    <vm:ViewModelA />
</Window.DataContext>

The Window has the event Loaded which used to create sample:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ViewModelA obj = new();

    // some statements to initialize obj
    obj.GroupsB = new();
    for (int i = 0; i < 3; i += 1)
    {
        ViewModelB b = new();
        for (int j = 0; j < 5; j += 1)
        {
            ViewModelC c = new ViewModelC { Name = $"B: {i}, C: {j}" };
            b.GroupsC.Add(c);
        }
        obj.GroupsC.Add(b);
    }

    this.DataContext = obj;
}

The expected result is that the ListView displays all ViewModelC objects as shown:

// Total 15 rows:
// B: 0, C: 0
// B: 0, C: 1
// B: 0, C: 2
// ...
// B: 1, C: 0
// B: 1, C: 1
// B: 1, C: 2
// ...
// B: 3, C: 2
// B: 3, C: 3
// B: 3, C: 4

However, the actual result is that the ListView displays nothing. Programmatically, we can solve this by:

List<ViewModelC> list = new();

foreach (ViewModelB b in obj.GroupsB)
{
    list.AddRange(b.GroupsC);
}

listView.ItemsSource = new ObservableCollection<ViewModelC>(list);

But what if insists to use MVVM pattern? Can anyone suggest me some corrections?

James Z
  • 12,209
  • 10
  • 24
  • 44
  • @LeiYang Sorry, it's typo. I've edited, `` to ``. – Wonderful Oasis Feb 17 '22 at 05:10
  • @LeiYang Tried. `ListView` still displays nothing. – Wonderful Oasis Feb 17 '22 at 05:46
  • What is DataGridView here? There is nothing like that in WPF. Also note that `DisplayMemberPath="{Binding Name}"` is pointless. It should be `DisplayMemberPath="Name"`. The XAML you are showing here won't compile. Show us your real code instead. – Clemens Feb 17 '22 at 06:54
  • Also the code in the Loaded event handler does not compile, and is missing an assignment of `b.GroupsC`. Finally, if you fix all that, you will get a data binding error message telling you that the object held by property GroupB has no property named GroupC. You should consider using nested ItemsControls. – Clemens Feb 17 '22 at 07:05
  • you could have a try `Binding DataContext.GroupsB[0].GroupsC` – Lei Yang Feb 17 '22 at 07:31
  • @LeiYang How is that supposed to show all elements in the nested collections? – Clemens Feb 17 '22 at 07:49
  • @WonderfulOasis It is unclear what you are actually trying to do. Do you want to show the elements of the inner collection in a single row? – Clemens Feb 17 '22 at 07:52
  • @Clemens i'm not trying to show all, but first let OP see something first, proving data binding works. then we can consider how to show all. of course may need `select many` in viewmodel. – Lei Yang Feb 17 '22 at 07:53

1 Answers1

0

your ItemsSource has hierarchy stucture and needed to be flatted. something line this

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ViewModelA obj = new();

    // some statements to initialize obj
    obj.GroupsB = new();
    for (int i = 0; i < 3; i += 1)
    {
        ViewModelB b = new();
        for (int j = 0; j < 5; j += 1)
        {
            ViewModelC c = new ViewModelC { Name = $"B: {i}, C: {j}" };
            b.GroupsC.Add(c);
        }
        obj.GroupsC.Add(b);
    }

     listView.ItemsSource = obj.GroupsB.SelectMany(s=> s.GroupsC);
}

then, if you want to use Binding, you must create a converter class and add this converter to binding. your ViewModel also need to implement INotifyPropertyChanged to notify View if its property changed.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ViewModelA obj = this.DataContext as ViewModelA;

    // some statements to initialize obj
    obj.GroupsB = new();
    for (int i = 0; i < 3; i += 1)
    {
        ViewModelB b = new();
        for (int j = 0; j < 5; j += 1)
        {
            ViewModelC c = new ViewModelC { Name = $"B: {i}, C: {j}" };
            b.GroupsC.Add(c);
        }
        obj.GroupsC.Add(b);
    }
}

Converter Class :

public class Conv : IValueConverter
{
 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return ((IEnumerable<GroupsB>)value).SelectMany(s=> s.GroupsC);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
}
Ehsan Vali
  • 344
  • 2
  • 9