Silverlight: небольшой, но неприятный баг с сортировкой в DataGrid

    • Silverlight
    • DataGrid
    • Bug
  • modified:
  • reading: 3 minutes

Неделю назад напоролся на баг в Silverlight, точнее в DataGrid контроле, а он пришел, вроде, с Toolkit. Потребовалось больше часа, чтобы понять в чем проблема.

Мы в приложении для части администрирования используем свои контролы, которые могут по схемы таблицы базы данных построить контролы списка объектов и форму для редактирования объектов. Более того, даже строятся join’ы. Сделано это для того, чтобы быстро в административную часть нашего решения быстро добавлять настройки только поменяв метаданные на интерфейсе, не трогая сервисы, и любые другие слои нашего приложения. Пользователям мы эти интерфейсы не показываем, а работаем чаще всего с ними только мы-разработчики и немного наши консультанты и менеджеры, так что внешний вид там не важен.

Все данные объекта содержатся в словаре, и если описать по-простому структуру наших объектов, с которыми мы работаем в административной части, то она, примерно, следующая:

public class Entity
{    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
     public Entity(Dictionary<string, object> values)
    {        _properties = values;
    }
     public string this[string column]
    {        get { return (_properties[column] ?? string.Empty).ToString(); }
    }
}

Это уж очень примерно. Понятно, что у нас там есть куча валидации, методов работы с метаданными, подстановка дефолтных значений для вновь созданных объектов и т.п. Но принцип такой, есть индексируемое свойство по строке, к которому осуществляется байдинг.

Обрисую тестовую ViewModel:

public class TestViewModel
{    public TestViewModel()
    {        Collection = new ObservableCollection<Entity>();
         for (int i = 0; i < 5; i++)
        {            Collection.Add(new Entity(new Dictionary<string, object>()
                                          {                                              {"Person.Name",      "User " + i}, 
                                              {"Person.PersonID",  i          }
                                          }));
        }
    }
     public ObservableCollection<Entity> Collection { get; set; }
}

Тут все просто, коллекция с 5-ю элементами. Ключи для коллекции у меня называют так, потому что они строятся из имен таблицы и имени колонки.

Дальше накидаем простую форму с одним элементом DataGrid:

<UserControl.DataContext>
    <SilverlightApplication1:TestViewModel />
</UserControl.DataContext>
 <Controls:DataGrid x:Name="LayoutRoot"  ItemsSource="{Binding Collection}" AutoGenerateColumns="False">
    <Controls:DataGrid.Columns>
        <Controls:DataGridTextColumn Binding="{Binding [Person.PersonID]}" CanUserSort="True" Header="ID" />
        <Controls:DataGridTextColumn Binding="{Binding [Person.Name]}" CanUserSort="True" Header="Name" />
    </Controls:DataGrid.Columns>
</Controls:DataGrid>

На этапе создания контрола, который работал с нашим динамическим объектом я не добавил поддержку сортировки. Просто объектов было не очень много, да и не нужно было, забыл. Недавно менеджеры попросили добавить сортировку. Я прикинул, что в простом случае, когда все элементы подгружаются за раз, сделать сортировку очень просто, нужно всего добавить CanUserSort к колонкам и все. Задачу оценили в час (вместе с тестированием и развертыванием).

Добавил, чтобы при создании колонок выставлялось true в свойство CanUserSort, а не работает сортировка. И тут началось, я начал пересматривать весь код, пытаясь осознать в чем же проблема, как ее можно побороть. В Output, вообще, ничего не выводилось, что может какой-то байдинг не верно установлен или еще чего. 

Попробовал создать новый проект, в котором все было, примерно, так как показал выше. И все работало! Тут до меня дошло в чем может быть проблема. В ново созданном проекте я использовал в качестве имен колонок простые “PersonID”, “Name”, вместо “Person.PersonID”, “Person.Name”. Видимо ребята, кто писал DataGrid сначала пытаются создать путь, по которому нужно осуществлять поиск объекта, а только потом задумываются о том, что поля могут быть еще и с индексами. Попытался посмотреть в исходниках, но кода у DataGrid контрола слишком много. Свою проблему решил заменой для байдинга точек на символ ‘@’, ну и в свойстве-индексе меняю обратно этот символ на точки, чтобы получить верное значение.

Проверял, в WPF все работает отлично. В общем, создал баг на connect. Бага не критичная, но в целом складывает не очень хорошее впечатление о качестве контролов в Silverlight.

See Also