пятница, 19 декабря 2008 г.

Отчеты и DataBinding

Когда начинал копаться в WPF все выглядело красиво и хорошо. И на экране все красиво (ну если не считать проблем с шрифтами по XP), и отчеты вроде можно делать, и печатать их через XPS. Но вот наконец дело дошло до реализации отчетов. Я конечно предполагал, что есть засады, но такой подлянки не ожиал. Оказывается DataBinding (собсвенно самая интересная особенность в WPF) не работает во Flow-документах. Т.е. на экран вывести красивый список указав шаблон и подсунув свой список объектов - пожалуйста, а вот напечатать с применение такого подхода - фиг вам. Везде на выбор предлагается 2 варианта

  1. Генерить отчет в коде. Ага, сейчас. Последний раз я это делал в 94-м году на клиппере. А сейчас все-таки 21-й век, опять-таки избаловались всякими FastReport-ами и иже с ними.
  2. Генерить статический XAML по шаблону. Например как вот здесь: http://janrep.blog.codeplant.net/WPF-Multipage-Reports--Part-IV--Pagination.aspx. Уже конечно интереснее, но все равно попахивает прошлым веком :)
Мучения гугла дали направление действий. Оказывается реализовать DataBinding все-таки можно. Просто дяденьки в микрософте почему-то забыли сделать свойство Text и элемента Run Dependency-объектом. Это все описано у Filipe Fortes. Еще бы они не забыли, кто же будет убивать корову, несущую золотые яйца в виде кучи сторонних генераторов отчетов. Хотя может быть на это есть и не только религиозные, но и технические соображения. Однако простенький эксперимент показывает, что вроде как оно на самом деле не так сложно сделать, и вроде на первый взгляд работает.

Итак, описание маленького эксперимента. Совсем маленького и простенького, поэтому просьба сильно не пинать.

Так как очевидно, что самое частое применение подхода с ItemsTemplate и ItemSource - это отобразить какие-нибудь табличные данные, то в эту сторону и смотрим. Подходящим кандидатом для надругательств является TableRowGroup. От него и будем плясать.
  • Делаем наследника от TableRowGroup
  • Приделываем к нему 2 Dependency-свойства
public DataTemplate ItemsTemplate
public IEnumerable ItemSource


Что это такое и код целиком пока приводить не буду. Вот разберусь как в блог исходники залить - тогда и качайте :)

Первая засада возникает в том, что у меня с первого раза не получилось в DataTemplate положить TableRow. Ругается, что "Property 'VisualTree' does not support values of type 'TableRow'". Что в общем странно. Пока обошел созданием наследника от FrameworkElement, у кототорого есть только одно свойство:

public TableRow Row


Из-за этого конечно получается довольно большой XAML, но для эксперимента пойдет.

Дальше собсвенно все просто. При установке любого из свойств (либо ItemsTemplate, либо ItemSource) просто перегенерируем заново содержимое TableRowGroup. Примерно следующим образом



void Refresh()
{
if (Rows == null) return;
if (ItemSource == null) return;

Rows.Clear();
foreach (var obj in ItemSource)
{
var content = ItemsTemplate.LoadContent();
if (content is RowTemplate)
{
var rt = (RowTemplate)content;
rt.Row.DataContext = obj;
Rows.Add(rt.Row);
}
}
}


На самом деле код скорее всего не идеолгически правильный, но на скорую руку не нашел как правильно разворачивать ItemsTemplate, а в исходники ItemsControls-а лезть тоже лениво.

Собсвенно этого достаточно. Теперь можно писать примерно вот так



<table>
<table.columns>
<tablecolumn width="160">
<tablecolumn width="80">
<tablecolumn width="80">
</Table.Columns>
<tablerowgroup>
<tablerow> <!-- Тут у нас заголовок таблицы -->
<tablecell borderthickness="1" borderbrush="Black">
<paragraph>Наименование</paragraph>
</tablecell >
<tablecell borderthickness="1" borderbrush="Black">
<paragraph>Цена</paragraph>
</tablecell >
<tablecell borderthickness="1" borderbrush="Black">
<paragraph>Кол-во</paragraph>
</tablecell>
</tablerow>
</tablerowgroup>

<my:templatedtablerowgroup name="MyRow">
<my:templatedtablerowgroup.itemstemplate>
<datatemplate>
<my:rowtemplate>
<my:rowtemplate.row>
<tablerow>
<tablecell>
<paragraph borderthickness="1" borderbrush="Gray">
<my:bindablerun
boundtext="{Binding Name}">
</paragraph>
</tablecell >
<tablecell>
<paragraph borderthickness="1" borderbrush="Gray">
<my:bindablerun
boundtext="{Binding Price}">
</paragraph>
</tablecell >
<tablecell>
<paragraph borderthickness="1" borderbrush="Gray">
<my:bindablerun
boundtext="{Binding Qty}">
</paragraph>
</tablecell>
</tablerow>
</my:RowTemplate.Row>
</my:RowTemplate>
</datatemplate>
</my:TemplatedTableRowGroup.ItemsTemplate>
</my:TemplatedTableRowGroup>
</table>



Осталось выяснить несколько вопросов
  1. Разобраться почему TableRow не может быть элементом в DataTemplate.
  2. Идеологически правильно сделать Refresh, что-то мне подсказывает что биндинг надо по другому настраивать :)
  3. Разобраться как в блогах постить исходные коды, а то запарился вручную <pre> ставить и заменять больше-меньше на lt-gt.
  4. Разобраться куда бы положить архив с исходниками, если кому будет интересно. А думал его к блогу можно приделать, но либо не разобрался как, либо оно умеет только картинки и видео в блог вставлять. А заводить отдельный сайт лениво, потому как никогда не делал, надо думать какой лучше (на корпоративный вроде как некрасиво, на narod.ru стыдно :) ).

Комментариев нет:

Отправить комментарий

Постоянные читатели