Собственно практически все уже получилось. Правда не идеально, но работает.
Как оказалось DataBinding обновляется не сразу. А только когда пройдет цикл обработки событий (каких не вникал если честно). Выражается это в том, что при экспорте в XPS не вычисляются все свойства на которые есть Binding. Пока не загрузишь этот документ в FlowDocumentReader (ну он его естесвенно на экране отобразит, и когда начнет ждать нажатия кнопок, вот тут событя и пробегают). Причем в отличие от старых добрых средсв разработки, где можно было сказать Apllication.ProcessMessages :) в WPF приложениях пнуть цикл сообщений вручную довольно интересная задача. Решение нашлось не сразу (надо еще знать какие ключевые слова спросить у гугла), но все-таки нашлось. Описано вот тут: http://blog.fohjin.com/2008/07/saving-wpf-xaml-flowdocument-to-xps.html. Насколько я понял вкратце надо выполнить через Invoke (т.е. дернуть и подождать) пустой метод у Dispatcher-а с самым низким приоритетом. А так как приоритет самый низкий, то сначала отработают все "сообщения" (хотя наверно некорректное название) с более высоким приоритетом и таки вычислят все Binding-выражения.
Интересный подход используется и при печати заголовков-подвалов страниц (естественно не сам придумал, честно передрал и рихтанул напильником отсюда). А именно, делается наследник от ReportPaginator, который резервирует место под загловки (устанавливая размер страницы для оригинального Paginator-а на размер страницы минус размер заголовка). И при формировании каждой страницы складывает ее из 3-х частей (заголовок, сама страница, подвал).
Немного прояснена засада с тем, что в DataTemplate не получается положить ContentElement. На самом деле ругается дизайнер студии. Но при этом все компилируется и работает. А так как дизайнер студии все равно не отрабатывает корректно DataBinding, то и фиг с ним. Так что от промежуточного класса избавились.
Файл с исходниками выложил пока на ifolder. Там есть пример, вроде даже работает. Мог конечно ошибиться где-нибудь с размерами, но вроде похоже на правду. Так как времени сейчас особо много нет, то подробно расписывать что там и как не буду, кому интересно пока предлагаю скачать и посмотреть. Там 18 килобайт.
Осталось 2 неприятные вещи.
1. Пока HeaderHeight и FooterHeight задается вручную. Для эксперимента пойдет, но надо будет все-таки сделать возможность вычислять высоту HederTemplate автоматом.
2. Не получилось придумать нормального места для PageNumber и PageCount. Подставлять их через шаблон вроде как идеологически не очень правильно (хотя и самое простое). А привязать к самому отчету с наскоку не получилось. Поэтому пока работает по следующему алгоритму: У DataContext-а отчета (который по идеологии DM-V-VM является просто обычным объектом) через Reflection ищутся свойства PageNumber и PageCount. И если нашлись - то им присваиваются соотв. значения. Ну а в самом отчете на них можно забиндится. В принципе работает, единственное неудобсвто, что надо заводить 2 дополнительных свойсва, которые будут слать NotifyPropertyChanged.
вторник, 23 декабря 2008 г.
суббота, 20 декабря 2008 г.
Небольшие дополнения и непонятки
Вчера, когда публиковал предыдущую заметку, наткнулся еще на одно описание решения аналогичной проблемы (http://pieceofsummer.livejournal.com/72317.html). Там почти тоже самое, но немного по другому. Кроме очевидных ItemTemplate и ItemsSource используются еще DataTemplateSelector и GroupStyles. Пока не очень понятно практическое применение, но может кому и потребуется. Плюс к тому за базовый класс выбран Section, что может быть когда-нибудь кому-нибудь и понадобится, но в моем случае пока не используется. В общем надо иметь в виду, может и пригодится.
Зато вроде как на первый взгляд используется аналогичный механизм, просто устанавливающий DataContext. Может это и есть правильный способ.
Но самое интересное, что там внутри DataTemplate используется напрямую Paragraph. А у меня так не получается хоть убей. Property 'VisualTree' does not support values of type 'Paragraph' и все...
Зато вроде как на первый взгляд используется аналогичный механизм, просто устанавливающий DataContext. Может это и есть правильный способ.
Но самое интересное, что там внутри DataTemplate используется напрямую Paragraph. А у меня так не получается хоть убей. Property 'VisualTree' does not support values of type 'Paragraph' и все...
пятница, 19 декабря 2008 г.
Отчеты и DataBinding
Когда начинал копаться в WPF все выглядело красиво и хорошо. И на экране все красиво (ну если не считать проблем с шрифтами по XP), и отчеты вроде можно делать, и печатать их через XPS. Но вот наконец дело дошло до реализации отчетов. Я конечно предполагал, что есть засады, но такой подлянки не ожиал. Оказывается DataBinding (собсвенно самая интересная особенность в WPF) не работает во Flow-документах. Т.е. на экран вывести красивый список указав шаблон и подсунув свой список объектов - пожалуйста, а вот напечатать с применение такого подхода - фиг вам. Везде на выбор предлагается 2 варианта
Итак, описание маленького эксперимента. Совсем маленького и простенького, поэтому просьба сильно не пинать.
Так как очевидно, что самое частое применение подхода с ItemsTemplate и ItemSource - это отобразить какие-нибудь табличные данные, то в эту сторону и смотрим. Подходящим кандидатом для надругательств является TableRowGroup. От него и будем плясать.
public IEnumerable ItemSource
Что это такое и код целиком пока приводить не буду. Вот разберусь как в блог исходники залить - тогда и качайте :)
Первая засада возникает в том, что у меня с первого раза не получилось в DataTemplate положить TableRow. Ругается, что "Property 'VisualTree' does not support values of type 'TableRow'". Что в общем странно. Пока обошел созданием наследника от FrameworkElement, у кототорого есть только одно свойство:
public TableRow Row
Из-за этого конечно получается довольно большой XAML, но для эксперимента пойдет.
Дальше собсвенно все просто. При установке любого из свойств (либо ItemsTemplate, либо ItemSource) просто перегенерируем заново содержимое TableRowGroup. Примерно следующим образом
На самом деле код скорее всего не идеолгически правильный, но на скорую руку не нашел как правильно разворачивать ItemsTemplate, а в исходники ItemsControls-а лезть тоже лениво.
Собсвенно этого достаточно. Теперь можно писать примерно вот так
Осталось выяснить несколько вопросов
- Генерить отчет в коде. Ага, сейчас. Последний раз я это делал в 94-м году на клиппере. А сейчас все-таки 21-й век, опять-таки избаловались всякими FastReport-ами и иже с ними.
- Генерить статический XAML по шаблону. Например как вот здесь: http://janrep.blog.codeplant.net/WPF-Multipage-Reports--Part-IV--Pagination.aspx. Уже конечно интереснее, но все равно попахивает прошлым веком :)
Итак, описание маленького эксперимента. Совсем маленького и простенького, поэтому просьба сильно не пинать.
Так как очевидно, что самое частое применение подхода с ItemsTemplate и ItemSource - это отобразить какие-нибудь табличные данные, то в эту сторону и смотрим. Подходящим кандидатом для надругательств является TableRowGroup. От него и будем плясать.
- Делаем наследника от TableRowGroup
- Приделываем к нему 2 Dependency-свойства
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>
Осталось выяснить несколько вопросов
- Разобраться почему TableRow не может быть элементом в DataTemplate.
- Идеологически правильно сделать Refresh, что-то мне подсказывает что биндинг надо по другому настраивать :)
- Разобраться как в блогах постить исходные коды, а то запарился вручную <pre> ставить и заменять больше-меньше на lt-gt.
- Разобраться куда бы положить архив с исходниками, если кому будет интересно. А думал его к блогу можно приделать, но либо не разобрался как, либо оно умеет только картинки и видео в блог вставлять. А заводить отдельный сайт лениво, потому как никогда не делал, надо думать какой лучше (на корпоративный вроде как некрасиво, на narod.ru стыдно :) ).
Предистория
Чукча не писатель, чукча читатель. Пробуем переориентироваться...
Всю долгую жизнь сидели, писали на Delphi. Но жизнь не стоит на месте, надо же куда-то развиваться. Как-то уже давно посмотрел что придумали умные люди на .NET. Когда был .NET 1.0 впечатление что в принципе тоже самое, но тормознее. NET 2.0 уже побыстрее. Потом придумали 3.5, в частности WPF. Посмотрели, с виду прикольно. На первый взгляд вроде как запихнули dfm в xml и радуются. Но при более подробном взгяде оказалось что все намного интереснее. В общем решили копать.
Как всегда сначала было: ура, форма в xml, можно динамически нагенерить XAML и радоваться. Потом присмотрелись, прочувсвовали DataBinding - и понравилось совсем.
В общем лениво расписывать как все интересно, перейдем к более содержательной части.
Всю долгую жизнь сидели, писали на Delphi. Но жизнь не стоит на месте, надо же куда-то развиваться. Как-то уже давно посмотрел что придумали умные люди на .NET. Когда был .NET 1.0 впечатление что в принципе тоже самое, но тормознее. NET 2.0 уже побыстрее. Потом придумали 3.5, в частности WPF. Посмотрели, с виду прикольно. На первый взгляд вроде как запихнули dfm в xml и радуются. Но при более подробном взгяде оказалось что все намного интереснее. В общем решили копать.
Как всегда сначала было: ура, форма в xml, можно динамически нагенерить XAML и радоваться. Потом присмотрелись, прочувсвовали DataBinding - и понравилось совсем.
В общем лениво расписывать как все интересно, перейдем к более содержательной части.
Подписаться на:
Комментарии (Atom)