Answering my own question here.
I solved this by setting SortMemberPath on my generated columns to their index number in the RowVM.Cells collection, and added a SortOnMe property to my RowVMs.
When the user sorts the datagrid, it will add a SortDescription containing the index number (taken from the SortMemberPath) to the PagedCollectionView. I monitor this by listening to the propertychanged event on the PagedCollectionView with the method below. It adds a new SortDescription telling the PagedCollectionView to sort on "SortOnMe", and copies the data to sort on from the cell in question to row.SortOnMe.
private bool _currentlySorting;
private void PagedCollectionView_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var pcv = (PagedCollectionView)sender;
int columnIndex = 0;
if (!_currentlySorting && e.PropertyName == "SortDescriptions" && pcv.SortDescriptions.Count == 1 && int.TryParse(pcv.SortDescriptions[0].PropertyName, out columnIndex)) {
_currentlySorting = true;
pcv.SortDescriptions.Add(new SortDescription("SortOnMe", pcv.SortDescriptions(0).Direction));
foreach (RowViewModel row in pcv.SourceCollection) {
row.SortOnMe = row.Cells(columnIndex).Content;
}
_currentlySorting = false;
}
}
It's a pretty ugly solution, to be honest. But it works, and I've spent too much time bashing my head against this wall now.
Since PagedCollectionView is a sealed class (why?!), the only other way I could think of doing this would be to create my own PagedCollectionView and handle sorting there.