I am just starting with ReactiveUI and MVVM in C# - WPF.
I have created a test project whose goal is to represent a chained list of objects. A list of universities each has a list of courses. In the courses, exams are submitted anonymously by students. I started by displaying only the list of universities. This works.
But I can't manage to display the list of courses. I see a ListBox, but the entries are empty. (For the time being, I have omitted the presentation of the exams for the sake of clarity.)
I assume that in the UniversityViewModel.cs I have to bind the list of courses somehow, but how?
For starters, I used the example on the ReactiveUI page as a guide: A Compelling Example
AppViewModel.cs
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
namespace UniversityViewer
{
public class AppViewModel : ReactiveObject
{
private readonly IDataProviderService _dataProviderService;
public ReadOnlyObservableCollection<UniversityViewModel> UniversityViewModels { get; }
private string _searchTerm;
public string SearchTerm
{
get => _searchTerm;
set => this.RaiseAndSetIfChanged(ref _searchTerm, value);
}
public AppViewModel()
{
_dataProviderService = new DataProviderService();
Func<University, bool> universityFilter(string text) => university =>
{
return
string.IsNullOrEmpty(text) ||
university.Name.ToLower().Contains(text.ToLower());
};
var filterPredicate = this.WhenAnyValue(x => x.SearchTerm)
.Throttle(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler)
.DistinctUntilChanged()
.Select(universityFilter);
var dataLoader = _dataProviderService.Universities
.Connect()
.Filter(filterPredicate)
.Transform(university => new UniversityViewModel(university))
.Sort(SortExpressionComparer<UniversityViewModel>.Ascending(u => u.Name))
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out var bindingData)
.Subscribe();
UniversityViewModels = bindingData;
}
}
}
University.cs
using System.Collections.Generic;
namespace UniversityViewer
{
public class University
{
public University(string name)
{
Name = name;
Courses = new List<Course>()
{
new Course("1234"),
new Course("2345"),
new Course("3456")
};
}
public string Name { get; set; }
public List<Course> Courses { get; set; }
}
}
UniversityView.xaml
<reactiveui:ReactiveUserControl
x:Class="UniversityViewer.UniversityView"
xmlns:universityViewer="clr-namespace:UniversityViewer"
x:TypeArguments="universityViewer:UniversityViewModel"
xmlns:reactiveui="http://reactiveui.net"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock TextWrapping="WrapWithOverflow"
Margin="6" VerticalAlignment="Center">
<Run FontWeight="SemiBold" x:Name="nameRun"/>
</TextBlock>
<ListBox x:Name="ListBoxCourses"
Grid.Row="1" Margin="5" HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
</Grid>
</reactiveui:ReactiveUserControl>
UniversityView.xaml.cs
using ReactiveUI;
using System.Reactive.Disposables;
namespace UniversityViewer
{
public partial class UniversityView : ReactiveUserControl<UniversityViewModel>
{
public UniversityView()
{
InitializeComponent();
this.WhenActivated(disposableRegistration =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.Name,
view => view.nameRun.Text)
.DisposeWith(disposableRegistration);
this.OneWayBind(ViewModel,
viewModel => viewModel.Courses,
view => view.ListBoxCourses.ItemsSource)
.DisposeWith(disposableRegistration);
});
}
}
}
UniversityViewModel.cs
using ReactiveUI;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace UniversityViewer
{
public class UniversityViewModel : ReactiveObject
{
private University _university;
public ReadOnlyObservableCollection<CourseViewModel> CourseViewModels { get; }
public UniversityViewModel(University university)
{
_university = university;
//var dataLoader = _university.Courses
// .Connect()
// ....
//CourseViewModels = bindingData;
}
public string Name => _university.Name;
public List<Course> Courses => _university.Courses;
}
}
Course.cs
namespace UniversityViewer
{
public class Course
{
public Course(string name)
{
Name = name;
//Exams = new List<Exam>();
}
public string Name { get; set; }
//public List<Exam> Exams { get; set; }
}
}
CourseView.xaml
<reactiveui:ReactiveUserControl
x:Class="UniversityViewer.CourseView"
xmlns:universityViewer="clr-namespace:UniversityViewer"
x:TypeArguments="universityViewer:CourseViewModel"
xmlns:reactiveui="http://reactiveui.net"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock TextWrapping="WrapWithOverflow"
Margin="6" VerticalAlignment="Center">
<Run FontWeight="SemiBold" x:Name="nameRun"/>
</TextBlock>
<!--<ListBox x:Name="ListBoxExams"
Grid.Row="1" Margin="5" HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" />-->
</Grid>
</reactiveui:ReactiveUserControl>
CourseView.xaml.cs
using ReactiveUI;
using System.Reactive.Disposables;
namespace UniversityViewer
{
public partial class CourseView : ReactiveUserControl<CourseViewModel>
{
public CourseView()
{
InitializeComponent();
this.WhenActivated(disposableRegistration =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.Name,
view => view.nameRun.Text)
.DisposeWith(disposableRegistration);
});
}
}
}
CourseViewModel.cs
using ReactiveUI;
namespace UniversityViewer
{
public class CourseViewModel : ReactiveObject
{
private Course _course;
public CourseViewModel(Course course)
{
_course = course;
}
public string Name => _course.Name;
}
}
The UniversityView
should bind to CourseViewModels
instead of Courses
for the CourseView
to be resolved:
this.OneWayBind(ViewModel,
viewModel => viewModel.CourseViewModels,
view => view.ListBoxCourses.ItemsSource)
.DisposeWith(disposableRegistration);
You will then need to populate the CourseViewModels
collection.
The Courses
property should be removed from the UniversityViewModel
. There is no ReactiveUserControl
to be resolved for Course
.