tag:blogger.com,1999:blog-39461611107556386942024-03-13T20:40:15.418-07:00ViValutionВ программисты, что ли податься...anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.comBlogger16125tag:blogger.com,1999:blog-3946161110755638694.post-86050786531620368932010-05-05T12:30:00.001-07:002010-05-05T14:04:32.976-07:00Coloroid: Silverlight Profiling by Example<div>Привет, друзья!
<br />
<br />
Предлагаю вам игру: <a href="http://dl.dropbox.com/u/5313583/ViValution/ColoroidTestPage.html" target="_blank">Coloroid</a>. Суть проста: у вас есть летающий мячик,
отбивающий блоки. Выпустите мячик за пределы сцены - и вы проиграли. Звучит знакомо? Что же, уточнения. Размер летающего мячика - один
пиксел. Размер блока - один пиксел. Если вам удается поймать отбитый осколок -
он так же превращается в мячик. <br />
<br />
<div style='text-align:center'>
<img alt="Coloroid Start"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuAygwbbxX5oS12qFNXkW0yCTL9B8YqfYbHCbK-a1uglpL_tkQQAW-5ggTNVUWssVnjvFqwZkR-NXuSrzOJIaN7gC62-m6JR1jVF22_ipHX4q1_HdyiQsCKA0hmawSnCeMKPDoEvn7ByYz/s1600/Start.jpg" />
<br /></div>
<br />
Через некоторое время у вас в арсенале несколько тысяч мячиков, а
число оставшейся работы уменьшается с завидной скоростью:<br />
<br />
<div style='text-align:center'>
<img alt="lookma"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBgVR4krHRFBZVBUAoJkSqWEgQTEaDy-t-vLWRCtaIyUIiXGDRu_ryn85D_OmsAk23aEG8Np1gnKQmRi9zjM0gmL23TdQnhwfPD2-iansliEw-PudaNx382Vt0VdSDlBKpXaQcyqg-E0rx/s1600/Interim.jpg" /><br />
</div>
<br />
В игре есть 100 000 пикселей, которые предстоит разбить. В худшие времена, когда
картинка почти вся разобрана по пикселям, а по сцене носятся десятки тысяч
мячиков, на моем двухгигагерцевом пеньке FPS не опускается ниже 20. Как же
достичь такой скорости? <br />
<br />
<h2>Профилирование Silverlight приложений</h2><br />
<br />
<div>Профилировать Silverlight приложения можно стандартными средствами Visual Studio 2010.
Проблема лишь в том, что эти стандартные средства не имеют стандартных кнопочек,
чтобы их запустить из IDE. Вместо этого - вооружимся консолью Visual Studio:</div><br />
<div style='text-align:center'>
<img alt="LaunchTools" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1G1eMWPU4KIrtEAfchHcVA6bLoe8wv1C2KCjzi3jqETJ8Ao5gyv5LgMmqutxA1IrwgQ8z5SL6c-3firRoMJM7RGxlhKcSJ4FOeYikV-dW6ymQNSa8J5TkKsNG47mzKNoB795P8t3MiRh0/s1600/LaunchTools.jpg" /></div>
<br /><br />
<div>
Последовательность действий такова. Запускаем консоль VS 2010
(от имени администратора под Vista и выше), и выполняем
команды:<br />
<pre>
cd {папка, где находятся скомпилированные Silverlihgt, (не Web!) исходники}
VSPerfClrEnv /sampleon
"%ProgramFiles%\Internet Explorer\iexplore.exe" {Полный путь к тестoвой страничке SL приложения}
VSPerfCmd /start:sample /output:{Имя Файла с результатами профилирования} /attach:{PID процесса iexplore.exe}
{Играемся с приложением}
VSPerfCmd /detach
VSPerfCmd /shutdown
VSPerfClrEnv /off
</pre>
Все, что находится в фигурных скобках, вы заполняете сами и без скобок. В моем
случае эта последовательность выглядела так:<br />
<br />
<pre>
cd c:\Projects\Coloroid\Coloroid\Bin\Debug
VSPerfClrEnv /sampleon
"%ProgramFiles%\Internet Explorer\iexplore.exe" C:\Projects\Coloroid\Coloroid\Bin\Debug\TestPage.html
VSPerfCmd /start:sample /output:ProfilingName /attach:8816
Играемся с приложением
VSPerfCmd /detach
VSPerfCmd /shutdown
VSPerfClrEnv /off
</pre>
Тройка важных моментов. <br />
<br /><b>
Момент 1.</b> Запускать профайлер нужно именно из
той папки, где находятся
скомпилированные файлы сильверлайта. Иначе вы не увидите имена функций в файле
профайлера:<br />
<br />
</div>
<div style='text-align: center'>
<img alt="Launch foloder is important"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU8jsjNUtbcG4_91iVY8KS5FeakCQq_bRGoG9QmqLn6I4CRCOROIkHCf3rt1UUBVVZ7p7e2aH3_7KXYshzb4oW-kP44Xs2jWZ9_JO4xeCX51V6XAk2rXaK8uV-fE7QSyccifYrPIR4pHq2/s1600/FolderPath.jpg" /><br />
</div><br />
<div>
<b>
Момент 2.</b>
Если вы запускаете приложение под IE8+, то вы должны указать идентификатор
именно того процесса, в котором запущен Silverlight. Иначе вы будете
профилировать код процесса-контейнера. Определить, какой именно процесс выполняет
Silverlight - легко. Откройте диспетчер задач и поработайте со своим приложением.
Кто загрузит процессор - тот и будет нашим кандидатом.<br />
<br />
<b>Момент 3.</b> Как определить идентификатор процесса? - В диспетчере задач зайдите в
Вид-> Выбрать столбцы...</div><br /><br />
<div style='text-align:center'>
<img alt="Pid"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfJz_5cIN_c9v75fVDZO3gmX2tuvnpSvDlhYtLdMjzG9u3TtER-yWNz2QeURvdPS1MIcDsT8Lfj_GzzNVopQ7S6P0DwpKnY4Oy_HT9XsHkIQO5fasbN85bORC-ZeDf2vY8cAA1U_tODgZB/s1600/pid.jpg" /></div>
<br />
<div>
Если все сделано верно, то на выходе мы получим
.vsp файл, который по умолчанию не ассоциирован с 2010-й студией, но легко
открывается в ней. Вот, что получилось у меня:<br />
</div><br />
<div style="text-align:center">
<img alt="Don't use DP's when do intensive calculations"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggpxogrKmfMsACnBxRfAmUg84aHbbpNuf0xuYn_PqGTzSjA2pIvgjgIg1BWcwPpGgOhmp2K6RN1vGiiLbE2hi16AetAhPGDQev1D1hFQxtvdN5Xt8dPaiOEMp9OvgdRCGJJIBfaT1xDEfr/s1600/Surprise.jpg" /></div><br />
<div>
Сюрприз!
Функция, проверяющая столкнулся ли мячик с доской внизу, занимала львиную долю
вычислений. В чем же дело? Дело в том, что мы часто обращаемся к Dependency
Properties прямоугольника (<em>Width</em> и <em>Height</em>). Это самая
распространенная причина медлительности Silverlight/WPF приложений: использование dependency properties в
интенсивных вычислениях. Решается простым кешированием значений перед выполнением
алгоритма:
<br />
</div>
<pre class="brush: csharp">
/* Где-то в классе Bar */
/* .. */
/// <summary>
/// Creates a new instance of <see cref="Bar"/>.
/// </summary>
/// <param name="barVisual">Visual presentation of the bar.</param>
public Bar(Rectangle barVisual)
{
_barVisual = barVisual;
_barVisual.Width = GameSettings.BarWidth;
_barWidth = GameSettings.BarWidth;
_barHeight = _barVisual.Height;
_barTop = GameSettings.SceneHeight - 50;
Canvas.SetTop(_barVisual, _barTop);
}
/// <summary>
/// Checks whether point with given coordinates hits the bar.
/// </summary>
public bool Hit(int x, int y)
{
// Using _barVisual.Width & _barVisual.Heihgt was a bottleneck.
// since they are dependency properties...
//return x > _barLeft && x < _barVisual.Width + _barLeft &&
// y >= _barTop && y < _barTop + _barVisual.Height;
return x > _barLeft && x < _barLeft + _barWidth &&
y >= _barTop && y < _barTop + _barHeight;
}
</pre><br />
<div>Такое простое изменение позволило увеличить скорость в два раза.</div>
<h2>Спасибо!</h2><br />
Вдохновение к этой игре я нашел на сайте <a href="http://wonderfl.net/">
wonderfl.net</a> - потрясающий сайт, где собирается много талантливых
Flash-разработчиков. Мне нравится копировать оттуда flash работы. Не потому, что
я тешу себя фразой Пикассо: "<em>Хорошие
художники копируют, великие - воруют</em>". Нет. Просто таким образом ты не только
сравниваешь две технологии, но и неизбежно учишь новое. Желаю вам, друзья,
легкого профилирования, быстрых программ и неиссякаемого вдохновения!<br />
<br />
PS:
Исходники к игре я выложил под MS-PL
<a href="http://dl.dropbox.com/u/5313583/ViValution/Coloroid%20-%20Src.zip">здесь</a>.
Вдруг вам будет интересно :).<br />
</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com2tag:blogger.com,1999:blog-3946161110755638694.post-91628747055736794972010-04-25T02:20:00.000-07:002010-04-25T05:31:02.460-07:00Chess Url: Немного шахматных задачПривет :)!<br/>
<br/>
За неделю собралось немного красивых и сложных задачек (спасибо Андрею Котенко :)).<br/>
<br/>
<h2>Задача 1</h2><br/>
Белые начинают и дают мат в <b>два</b> хода.<br/>
<img src='http://chessurl.appspot.com/?NA8,NA7,RA4,BC6,BC5,QG7,RH8,KH2/KB8,RB2,PD3,BF8,RF3,BG8,PG2,NG1,QH7,PH3'/><br/>
<br/>
<h2>Задача 2</h2><br/>
Опять начинают белые и дают мат в опять <b>два</b> хода.<br/>
<img src='http://chessurl.appspot.com/?PB2,RE7,NF4,BF1,NG3,KH6,RH5,QH3/BA8,NA3,BB8,NB5,PC7,RC3,PD7,KD4'/>
<br/><br/>
<h2>Задача 3</h2><br/>
Наверное, вы уже начали догадываться :)... Начинают белые и дают мат в <b>два</b> хода.<br/>
<img src='http://chessurl.appspot.com/?RA4,BD4,QE5,BE4,RF8,NG8,PG3,PG2,PH4,KH2/QA6,RB5,PB3,BD2,BD1,RF1,KG4,NH1'/>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com2tag:blogger.com,1999:blog-3946161110755638694.post-72499105582684243852010-04-20T14:05:00.000-07:002010-04-20T15:54:03.276-07:00Chess Url (Чешурл) - Easy Chess Puzzles Sharing<div>
Привет, Друзья!<br />
<br />
Недавно стал играть в шахматы с Другом. Занятие это настолько
увлекательное, что начал решать шахматные задачки. Но вот беда - решаю я их на
смартфоне (ну нету у меня ифона, нету), а поделиться интересной задачкой
хочется. Как быть?<br />
<br />
Как всегда,
пошел в Google, в поисках подходящего сервиса, а там этих
сервисов... что Кот наплакал. То, что удалось найти имело фантастически убогий
интерфейс и потрясающее неудобство использования. Вобщем, почему бы не сделать свой сервис? - Сделал. О нем и речь в
сегодняшней статье.</div><br />
<h2>Знакомьтесь - Chess Url</h2><br />
<div><a href="http://chessurl.appspot.com/" target="_blank">Чешурл</a> позволяет расставить фигуры на доске и получить URL к картинке,
которая соответствует расстановке. Например, в следующем примере у белых есть
прекрасный ход. Какой?</div><br />
<div style='text-align:center'>
<a name='puzzle'><img src='http://chessurl.appspot.com/?RA8,PA7,KB4,PC5,PF6/RA6,PB5,PC6,PD5,KF7' alt='White to Move' /></a><br />
<i>Ход белых</i>
</div><br />
<div>Эта картинка имеет следующий URL: </div><br />
<div style='text-align:center'>
<i>http://chessurl.appspot.com/?RA8,PA7,KB4,PC5,PF6/RA6,PB5,PC6,PD5,KF7</i>
</div><br />
<div>До слеша идут белые фигуры (<i>RA8,PA7,KB4,PC5,PF6</i>), после слеша - черные
(<i>RA6,PB5,PC6,PD5,KF7</i>). Фигура начинается с имени (K - King, Q - Queen, R
- Rook (ладья), B - Bishop, N - Knight (конь), P - Pawn). Следующие два символа
- координаты фигуры на доске.</div><br />
<h2>Как делался Chess Url?</h2><br />
<div>
Программа состоит из двух частей: дизайнера и просмотрщика. Дизайнер написан на
Silverlight 4.0, просмотрщик на... Python в
<a href="http://code.google.com/appengine/" target="_blank">Google App Engine</a>.
Суть дизайнера - дать пользователю интерактивность при создании шахматной
комбинации. Просмотрщик отвечает за генерацию картинки по указанным параметрам
строки запроса.<br />
<br />
Исходники дизайнера (Silverlight части) можно скачать
<a href="http://dl.dropbox.com/u/5313583/ViValution/ChessUrl-Src.zip">здесь</a>.
Я решил открыть их под Microsoft Public License. Что касается исходников
просмотрщика - позвольте оставить их за кулисами... Дело в том, что мои познания в
Python настолько глубоки, что написанный код стыдно показывать <img src='http://dl.dropbox.com/u/5313583/Stuff/blush.gif' alt='blush' style="border:0px"/>.
Отсутствие Python-исходников попробую компенсировать, поделившись полученным
опытом.</div><br />
<div>
<ul>
<li>
<div><b>Откуда картинки?</b> - Изображения шахматных фигур я нашел на Википедии, в
формате SVG.
<br />
<br />
</div>
</li>
<li><div><b>Как конвертировать SVG в XAML?</b> - Нужно скачать последнюю версию
<a href="http://www.inkscape.org/" target="_blank">Inkscape</a>. Редактор
позволяет экспортировать SVG в XAML, и является абсолютно бесплатным.
Единственный минус, у меня некорректно обработалась одна трансформация в SVG
файле. Пришлось дотачивать напильником, но процесс этот безболезненный, т.к.
форматы очень похожи.<br />
<br />
</div>
</li>
<li><div><b>Как происходит отрисовка картинки по строке запроса?</b> - В состав
Google App Engine входит
<a href="http://code.google.com/appengine/docs/python/images/" target="_blank">Images API</a>,
который позволяет производить трансформации над картинками. Однако тут не без
сюрпризов. Когда объединяешь .PNG файлы с прозрачностью, App Engine игнорирует
прозрачность, заливая все белым цветом (как на эмуляторе сервера, так и на
сервере). Решилась проблема использованием .GIF файлов. Благо
<a href="http://www.getpaint.net/" target="_blank">Paint.NET</a> легко конвертирует файлы.<br />
<br />
</div></li>
<li><div><b>В чем разрабатывалось Python приложение?</b> - Мне очень понравилась
<a href="http://www.aptana.org/" target="_blank">Aptana</a>. Это IDE, с открытым
исходным кодом. Для Python устанавливается плагин, и вы получаете хороший user
experience, имея полноценный intellisense в Python. Кстати, этим же IDE я
пользуюсь для создания JavaScript приложений (прости меня Студия, но ты и рядом
не стояло).<br />
<br />
</div></li>
<li><div><b>Сколько времени ушло на разработку?</b> - около 4-х часов. Но я молчу
о поиске изображений и их конвертировании. Так же промолчу и о поисках багов и их
"конвертировании" :). Суммарных усилий хватило на два выходных дня.</div></li>
</ul></div><br />
<div>Самым приятным сюрпризом был App Engine. Во-первых, очень либеральные квоты
(один гиг трафика на вход и один на выход... в день). Во-вторых, простота
программирования. Я заметил, что много потребляю ресурсов на конвертирование
изображений (а это тоже входит в квоту: 2 592 000 трансформаций в день). Решение
в три
строчки кода: прикрутил
<a href="http://code.google.com/appengine/docs/python/memcache/usingmemcache.html" target="_blank">
Memcache</a>. Теперь число трансформаций упало в разы.</div> <br />
<h2>Что вы думаете?</h2><br />
<div>Буду очень рад услышать ваше мнение о <a href="http://chessurl.appspot.com/">сервисе</a>,
<a href="http://dl.dropbox.com/u/5313583/ViValution/ChessUrl-Src.zip">коде</a> и <a href='#puzzle'>шахматной задаче</a>
сверху :). Если у вас есть свои интересные задачки - пишите, вставляйте в блог,
пользуйтесь на здоровье :). Надеюсь, вам понравится.<br />
<br />
Спасибо за ваше внимание! Отличного программирования :)!</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com6tag:blogger.com,1999:blog-3946161110755638694.post-50198548518121266162010-03-08T05:41:00.000-08:002010-03-08T06:36:50.056-08:00Pro Debugging: А вы умеете?<div>
Привет, Друзья!
<br />
<br />
Сегодня мы отвлечемся от Silverlight'a и пройдемся по самому популярному
инструменту .NET разработчика: по <b>отладчику Visual Studio</b>, в контексте
языка C#. Прежде чем вы
подумаете, что я окончательно спятил, если собираюсь рассказывать очевидные
вещи, позвольте возразить. Мы, разработчики ПО, склонны останавливаться на
выученном, после того, как <i>думаем</i>, что знаний достаточно для выполнения работы.
В то же время, копнув глубже, можно найти более эффективные способоы
решения проблемы. Думаю, это одно из отличительных свойств настоящих Специалистов
- желание всегда копать глубже, чем большинство, не
останавливаясь на достигнутом.
<br />
<br />
Итак, приятного вам "копания". Да не испугает вас размер скроллбара - тут
картинок больше, чем текста :).</div>
<h2>Содержание</h2><br />
<div>
<a name='content'></a>
Для вашего удобства, вот список всех полезностей, рассмотренных в этом посте:
<ul>
<li>
<a href='#MakeObjId'>Наблюдение за экземплярами классов или Make Object ID</a></li>
<li>
<a href='#WatchReadability'>Улучшаем читабельность в Watch window.</a></li>
<li>
<a href='#CatchThemAll'>Поймать все исключения? - Легко!</a></li>
<li>
<a href='#exception'>Исключение, ты кто? - $exception.</a></li>
<li>
<a href='#FullStop'>Debugger.Break(), или Стоп машина!</a></li>
<li>
<a href='#ThreadsOutOfTheShadow'>Поток, тебя как зовут?</a></li>
<li>
<a href='#LoveIsAll'>Позвольте к вам прикрепиться: Attach to Process.</a></li>
<li>
<a href="#DesignTimeDebugging">Отладка без отладки: Design Time Debugging.</a></li>
<li>
<a href='#Conditional'>Хочу остановиться здесь, если только...</a></li>
<li>
<a href='#TracePoints'>Вывод информации, без вывода информации: Trace Points.</a></li>
<li>
<a href='#CantFixIt'>Когда очень плохо: Отладка .NET framework'a.</a></li>
<li>
<a href='#LaunchDebug'>Отладка запуска сервисов/приложений.</a></li>
<li>
<a href='#SilverdeBug'>Отладка Silverlight приложений.</a></li>
<li>
<a href='#SheIsBrilliant'>Знайте свой инструмент, или блог Сары Форд.</a></li>
</ul>
</div><br />
<a name='MakeObjId'><h2>Наблюдение за экземплярами классов или Make Object ID</h2></a>
<br />
<div>Когда вы работаете с несколькими экземплярами класса, как можно отличить один от
другого? Если это неуправляемый код, можно посмотреть на адрес объекта. Если вы
в управляемом коде, то можно попросить GetHashCode() у объекта, и надеятся, что
его реализация не подкачает. В отладчике есть более простой способ: правый клик
на объекте в окне Watch/Locals/Autos -> Make Object ID.
<br />
<br />
<img alt="Context menu: Make Object Id"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUCSsXDZOyjU4O-j16WJMP7rWAuRyw-nGCyw0yGc_v4_Dl1WhOxz5VOgEiLmDv-zMNFgEs66gZaElW-PHCGi-_3JEjCDHKnf8peyAAhyEYrnt1q9vY1QZZX8z_iIF4NkP_PBcJTZTVMXhe/s1600/Debug-Make-ObjectId-CtxMenu.jpg" /><br />
<br />
После этого объекту присваивается уникальная метка {1#}, {2#}, и т.д. Метка
дается объекту на все время жизни, и именно так объект будет появляться во всех
окошках, в том числе, в Immediate Window, и даже в тултипах.
<br />
<br />
1. Делаем Object ID:<br />
<br />
<img alt="Make object id" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiec2AYMM699s3MkDdDoyVYU4wFItJMWS7x8rvJIRuJLlu0b_qPgVLI6Pprd4p9ADw74oL1kzhq5mrO1iCNGVpz_w6U-GUkO65jfmZh782BkM8c2p5UnwIg2g2YXnBv_1wfswFpFPoZLXXS/s1600/Debug-Make-ObjectId-Demo.jpg"
/><br />
<br />
2. Объект получает идентификатор:<br />
<br />
<img alt="Got Id" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-hcMeRdEMH9EjT3Hve7CToq5acx4tj1Qd2qdw4vrHQJNdkT3w0t2Og0bgJOJnagWKmgNM0mHmjTtiLbFF-wSGBf9csOX5sSBQ4Db_ZpuRjjmYgdjQ9hbBXW5zbAuA2RqZ4sTlfWiSRJQ9/s1600/Debug-Make-ObjectId-Demo1.jpg"
/><br />
<br />
3. Теперь всюду, где встречается "меченный" объект, студия даст нам знать об этом:<br />
<br />
<img alt="Id everywhere" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHGn6g8yWg71yc0SbqtlfZS6jkj4EP2N57jETQvbxp6EFhC_HxmlDNVLHJOsH3jsNxPnaHjOk_ld_ls1R9CEGYxpW4kVinoBv13JBb71RtglpxOOX3j_Z_60WBH4o4dnqEyB3sI9_9IVzl/s1600/Debug-Make-ObjectId-Demo2.jpg"
style="width: 619px; height: 623px" /><br />
<br />
Что еще более интересно, так это возможность использовать метку для ссылки
на объект. Т.е. можно написать в окне Watch <i>1#</i> и получить мгновенный
доступ к содержимому меченного объекта. Также метку можно использовать в
<a href='#Conditional'>условных (conditional) точках останова</a>. Например, условие <i>this == 1#</i> остановит выполнение программы только если это наш меченный объект.<br />
</div>
<br />
<div><a href='#content'>К оглавлению</a></div>
<a name='WatchReadability'>
<h2>Улучшаем читабельность в Watch window</h2></a>
<br />
<div>По умолчанию, значение объекта в окне Watch - имя его типа. Хочешь больше -
раскрывай дерево. Но ведь для списков мы видим число элементов в списке. Значит,
должен быть способ повысить читабельность нашего класса Customer?<br />
<br />
<img alt="Default Watch Value" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7NCU8cMEMhgEFQA5ym5QH63JMFbC499-9x7k4T9Vq8exAtEYh4HesLbl8Wre1ZLPbXq9iylm7HTAfxLu0sEfdKSF4nnR97n3usXqfVZ_V5K-3OGV1vvvP1PqXP8DFko7G_QnUR6JX5Z7B/s1600/Debug-Make-Watch-Demo1.jpg"
/><br />
<br />
<i>ToString()</i> - подумаете вы, и будете практически правы. Можно перекрыть метод
<i>ToString()</i> и выводить отладочную информацию, которая будет показана в Watch.
Но <i>ToString()</i> не всегда является лучшим выбором. Например, WPF использует его
для вывода элементов без определенного DataTemplate на UI. Более удобным методом
будет использование атрибута
<b>DebuggerDisplay</b>.<br />
<br />
<pre class="brush: csharp">
[System.Diagnostics.DebuggerDisplay("Customer: {Name}")]
public class Customer
{
public string Name { get; set; }
// ...
} </pre><br />
Перекомпилируем приложение, и под отладчиком получаем:<br />
<br />
<img alt="I Can Read This" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXNZMhyphenhyphenrD0rhGK-gLvttvoWbG77ivoVykiydTndLccEeLcuXp4z6mxNw3zrP14JDVci4-enyQQGRYtDYCGLpgn9EMlcOPiCYKmCxYnU1ivLguSpXGaHmhaFEnXjs6jOBgoiQwFntE0z-W8/s1600/Debug-Make-Watch-Demo2.jpg"
/><br />
<br />
Когда определены и атрибут DebuggerDisplay и ToString(), отладчик использует атрибут.
Однако будьте аккуратны, в некоторых случаях вы можете получить интересное
насекомое, зарегистрированное под именем
<a href="http://rsdn.ru/Forum/Info/FAQ.humor.bugspedia.aspx">Гейзенбаг</a>.<br />
</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='CatchThemAll'><h2>Поймать все исключения? - Легко!</h2></a><br />
<div>Отладчик может останавливаться при возникновении любого исключения. Даже на обработанных исключениях. И на исключениях в недрах библиотек. И даже
на обработанных исключениях в недрах библиотек. Особенно полезным это может
быть, когда причина бага, который вы пытаетесь поймать, кроется в "придушенном"
исключении:</div><br />
<pre class="brush: csharp">
try
{
DoSomethingBuggy();
}
catch
{
// Sorry.
}
</pre>
<div>
Все, что нужно для остановки, это зайти в меню <i>Debug -> Exceptions...</i> и
выбрать на чем именно вам интересно было бы остановиться:<br />
<br />
<img alt="Catch them all" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi12SJgkKRK2ft-FOWywDhjdd3siHBEA3gsj9cJYzrLBN_8hR5u-06ExxnrjVophtuB0vJcugi2z12ZbSp6_PQJ3vXI1OEAP2JTPVyuZMCqht_NZueSksDmAHO_Upl6-fmDeI_s6VSy12Ru/s1600/Debug-CatchThemAll.jpg"
/><br />
<br />
Теперь при возникновении любого из выбранных исключений, во время отладки,
отладчик любезно остановит выполнение программы и перенесет вас к проблемному
месту.</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='exception'>
<h2>Исключение, ты кто? - $exception.</h2></a><br />
<div>В предыдущем примере, когда мы останавливались на всех исключениях, вы могли
встретить код следующего типа:</div>
<pre class="brush: csharp">
try
{
// Do something
} catch
{
// Catch
}
</pre>
<div>Как теперь быть, если вы хотите посмотреть на исключение в окне Watch? Экземпляр исключения не указан в блоке catch,
что написать? Конечно, можно добавить
переменную исключения, перекомпилировать код, и посмотреть на красавца. Но ни в
коем случае не делайте так. Просто введите в окно Watch строку <b>$exception</b>:<br />
<br />
<img alt="No Name" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJOCkEhyphenhyphenIl_ypcafPu8lttsI9dFYFVjVl4OEAogIBDqQc0KQBkqzMyq6S-LmxBJsKve9NxvgoJmkCw6vwxyH50spDF7qG2H7m8JMkCqFIJEsmRQvK3URp_bDpa3eEtl5lMtreNtdZKzj_I/s1600/Debug-ExceptionNoName.JPG"
/><br />
<br />
PS: Какие еще псеводпеременные поддерживает отладчик? Для C# мне известна только
<i>$user</i>, которая показывает текущего пользователя. Для неуправляемого кода
<a href="http://msdn.microsoft.com/en-us/library/ms164891.aspx">их чуть-чуть
больше</a>.<br />
<br />
</div>
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='FullStop'><h2>Debugger.Break(), или Стоп машина!</h2></a><br />
<div>Чтобы мгновенно провалиться в отладчик, даже когда программа запущена не в
режиме отладке, и даже когда она запущена в Release версии, достаточно вызвать
<a href="http://msdn.microsoft.com/en-us/library/system.diagnostics.debugger.break.aspx">Debugger.Break()</a>. </div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='ThreadsOutOfTheShadow'><h2>Поток, тебя как зовут?</h2></a><br />
<div>Когда создаете новый поток, не поленитесь дать ему имя (<a
href="http://msdn.microsoft.com/en-us/library/system.threading.thread.name.aspx">Thread.Name</a>).
Так будет легче отличать потоки друг от друга во время отладки: на скриншоте
ниже Happy thread получил имя.<br />
<br />
<img alt="Thread Name" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghuv3HxZR37PkgGuK0PFI5AnohXNUSGoRNzSt0gqbs-F1KjORqDR-Tmh6zi3SM2O1xu4C3rrrUl7uzAXsefatI-5gqxkb1sHevtG1pxCwvWHlOQpxSn0pCrvJsICJjcj0oAvztK2w0Zn4l/s1600/Debug-OutOfTheShadow.jpg"
/><br />
</div>
<br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='LoveIsAll'><h2>Позвольте к вам прикрепиться: Attach to Process.</h2></a><br />
<div>Расставили все брякпоинты, прошли первые десять шагов багорепа, и вдруг
вспомнили, что программа запущена не под отладчиком? Ничего страшного. Выбираете
в меню <i>Debug -> Attach to Proces</i> или просто Alt + D, P.<br />
<br />
<img alt="Attach to process" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2-ZaEsHJhLdgwbJAgDqLu38gQxHeVvATyaqzC3Jh0CVTnAfeV965pR40wcOecdGkxpr8MZoBpLYzDqS70l73TATCwAtYKkww_N_FvJd4BkK75AnFmj-Ibzcpgx2da0nkhbf-tE9YVWL_V/s1600/Debug-AttachToProcess.jpg"
style="width: 853px; height: 584px" /><br />
<br />
После этого, выбираем процесс, который хотим отладить, и жмем Attach. Если все
загруженные символы соответсвуют прикрепленному процессу, вы попадаете в мир
полноценной отладки.</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='DesignTimeDebugging'><h2>Отладка без отладки: Design Time Debugging.</h2></a><br />
<div>Immediate Window (Ctrl + Alt + I, или Debug -> Windows -> Immediate) позволяет
выполнить метод даже если программа не запущена. А если еще поставить точку
останова в методе, то мы попадем в отладчик:<br />
<br />
<img alt="Design Time Debugging" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigp0-_k8_8HOxhLP5vooCzXZ2pD3zpGgySmko2ziTEOXgFPPtr9V5M75-Xpba97ukn53kTCkQDdEDY3xqtS4u8m_LLEHFJQAfYEMCbkYKZEo3n3_iS26aKhGEE77TSQW6f8zOxcOnEay23/s1600/Debug-DesignTime.jpg"
style="width: 620px; height: 616px" /><br />
<br />
Для того, чтобы отладить функцию <i>Sum()</i> достаточно ввести в Immediate
Window'e выражение <i>?Sum(x, y)</i>, и нажать Enter. Эта фича называется "Отладка в
режиме дизайна", и работает во всех редакциях студии, кроме Express'a.</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='Conditional'><h2>Хочу остановиться здесь, если только...</h2></a><br />
<div>Не можете понять как эта переменная становится null'ом? Или как этот счетчик
выходит за пределы? Установите условный breakpoint. Для этого нужно
добавить точку останова и тыкунть правой кнопкой мыши по ней. В контекстном меню
выбираем "Condition...":<br />
<br />
<img alt="Conditional Context" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx9hBC7C0AjkTk2TVqui9EHdAp3_Fqj_birQpJUFS6Zzl9jddkMKT3LUUxdnxCqXL4IN-B30DxSU9wXTd2U-VfkhhgOnF0qxc5KiSda55m3JDq64dKlR_eY1G4eOOa9nfhI8sdpraurVuf/s1600/Debug-Conditional-Ctx.jpg"
style="width: 619px; height: 617px" /><br />
<br />
И указываем при каких условиях должна сработать точка останова:<br />
<br />
<img alt="Debug Conditional Set" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCYNsrze_WUaPaMR4qziFOIkAF0qQEJJd6tV_DacgWS-fL2coT9iNB354PuJi8TwH4Ydgn_3wdEGzRIlxivPaP_uM91WJsRsPWn0g3EfTwdnW5YZSzNEJ8p14S5pR4gv8P-NrmzjWyGkI3/s1600/Debug-Conditional-Set.jpg"
style="width: 504px; height: 239px" /><br />
<br />
В следующий раз, когда вы войдете в функцию, и <i>x</i> будет меньше
сработает точка останова.
<br />
<br />
Совместно с <a href='#MakeObjId'>метками объектов</a>
условные точки останова представляют отличное подспорье, когда вы пытаетесь
отладить конкретный экземпляр коллекции. Если вы хотите, чтобы точка останова
срабатывала только для данного элемента коллекции достаточно пометить элемент
коллекции и использовать его метку в условии. Например условие <b>1# == this</b> остановит выполнение только в
случае если текущей объект обладает меткой <b>1#</b>.</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='TracePoints'><h2>Вывод информации, без вывода информации: Trace Points.</h2></a><br />
<div>Приходилось ли вам когда-нибудь писать следующий код для упрощения отладки?</div>
<pre class="brush: csharp">
private static int Sum(int x, int y) int y)
{
Trace.WriteLine(string.Format("Enetring function Sum(). X = {0}; Y = {1}; Time = {2}", x, y, DateTime.Now));
// ...
}
</pre>
<div>Да? - Поздравляю, вы делали ненужную работу. Я тоже так делал :).
Ведь это очень полезно, особенно когда ищешь баги, связанные с фокусом ввода
- переключение в отладчик влечет за собой потерю фокуса, и усложняет отладку. А
так - написал и смотришь в Output Window, что там куда приходит. Но есть способ
сделать это легче.
<br />
<br />
Устанавливаем breakpoint, кликаем по нему правой кнопкой, выбираем "When Hit..." и в
появившемся окошке пишем, что бы мы хотели увидеть:<br />
<br />
<img alt="Trace points" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAl9WIcND-lELSPc-HE2ZUbh8j2nwXGz40t1dV430AsuKeZ_lCUsZJ0dCzsjFA6TF9Z3BILScs3uFLB-WTdWJQK7cH1kuU8goGaRuXdvhPKk4pj6ayULPxhKinZkyOsjRZOfwjlvHYF-gv/s1600/Debug-TracePoints.jpg"
style="width: 504px; height: 422px" /><br />
<br />
Когда отладчик войдет в эту функцию, в окошке Output появится следующее
сообщение:</div>
<pre>
Entering ConsoleApplication1.Program.Sum(int, int); X == 0; Y = 1; Time = {19.02.2010 8:30:34}<br />
</pre>
<div>Как видите, можно выводить не только значения переменных, но и значения вызовов
функций или выражений. Безусловный плюс - не нужно перекомпилировать приложение,
если хотите изменить что-либо.<br />
<br />
PS: Это не значит, что <i>Trace.WriteLine()</i> становится вовсе бесполезным. Он все
так же бесценен для сопровождения приложений.</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='CantFixIt'><h2>Когда очень плохо: Отладка .NET framework'a.</h2></a>
<br />
<div>Я люблю копаться в framework'e. Эта страсть к археологии позволяет не только
лучше понять работу Библиотеки но и понять почему вызов этого метода не привел к
ожидаемым результатам. Для отладки .NET Framework вам нужны символы, флажок и
желание поискать на форумах, почему имея первые два пункта вы до сих пор не
можете отлаживать исходники .NET'a.
<br />
<br />
В самом простом случае, достаточно установить флажок в настройках студии Enable
.NET Framework source stepping (Tools -> Options -> Debugging -> General):<br />
<br />
<img alt="Enable .NET Source Code" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEU1WDHRAIdma9d-MQQ4kIpbfmILw49olmX2gAuNE85QZuTf5escXUa2rbHaaIxGa8pVzr-sjaG4Jdjx6m09DSM79wD0H2N78zYCuuaeBWVYfRGbT3S7Jo0anHUEb1V1Kw5tmcmjzz-MpK/s1600/Debug-FrameworkSource.jpg"
style="width: 757px; height: 438px" /></div>
<br />
Я также вЫключил флаг Requre source files to exactly match the original version.
Это увеличивает шансы на успешный вход в исходники .NET, в случае если
загруженные символы по какой-то прчине отличаются от загруженной библиотеки. <br />
<br />
Далее соглашайтесь на все предложения студии. Запускайте программу в дебаге, и
попробуйте войти внутрь вызова системного метода. Если в Output окне вы видите
сообщение "Step into: Stepping over method without symbols '...'":<br />
<br />
<img alt="Step into failed" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixLSlczbhKVv2e2DgFc7yMkiyd7q2a17eB8z2kiQ_xNH1C_NtbxdOPP54n5SLz9ojOM5yP5ThnYC31j9KgtsbmFjExlQYrKn3z3tTixPHEnhiwafXf16aHNPUD6HD0OgOHI09LgyCmLTxc/s1600/Debug-StepIntoOver.jpg"
style="width: 431px; height: 131px" /><br />
<br />
Значит нужно проверить, что символы загружены. Идем в Call Stack и смотрим на
методы без символов (серые):<br />
<br />
<img alt="Load Symbols" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG8-8NL7faXBCiJ6ohwcm_Njpil4mMdK1vtY7sGjPM113b5pCRlJGN0MHfWCndJK4typIphKgzNmDb2FJhGvY7wpNH4bkZ7i92yJ444uUAdCqZwmpHlMc7glYzTZda9YOBvidugezn65m7/s1600/Debug-LoadSymbols.jpg"
style="width: 528px; height: 233px" /><br />
<br />
Тыкаем правой кнопкой по любому из них и выбираем Load Symbols From -> Microsoft
symbols server. После этого студия начинает скачивать символы:<br />
<br />
<img alt="Load symbols progress" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC0O-JIl4AC1DLWqRks0PmqRn4DiE8LoWq1dVmum8mBLjbQpc1MtGd41sWy47XfBaRkrQbvEW93NQ4o3KV4Jc6B1Wetfxkhiy89KvBQqZpl25l_Djyljl-hwiSFLWMY8Q54XwNoSvCaL4I/s1600/Debug-LoadSymbols-Progress.jpg"
style="width: 545px; height: 89px" /><br />
<br />
Загрузить и проверить статус дополнительных символов можно так же из окна
Modules (Debug -> Windows -> Modules). Итак, символы загружены, пробуем войти в
функцию еще раз:<br />
<br />
<img alt="Hurray" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeGWIEWx1yaiZJbiiGc3nsulI7ffwY2LXPuAjXWJGL9DUeAEBoJP6mmr_JR5BeFpfiXypLskuygEiFizRJ-u_jstq0EBwcT_WEzRL-a_b7dW9C8xbsaZFnmsvvoyb5UDa3sr3HnxDwtwIs/s1600/Debug-LoadSymbolsSuccess.jpg"
style="width: 620px; height: 616px" /><br />
<br />
Что самое приятное - можно прочитать комментарии разработчиков библиотеки,
которые нельзя найти ни в одном Reflector'e и MSDN'e.
<br />
<br />
Напоследок хочу предупредить, что не для всех сборок .NET Framework
Microsoft позволяет отлаживать исходники. Смотрите в Output Window.<br />
<br />
<div><a href='#content'>К оглавлению</a></div>
<a name='LaunchDebug'><h2>Отладка запуска сервисов/приложений.</h2></a>
<br />
<div>Иногда нужно отладить код запуска сервисов или приложений, которые
стартует другой процесс (например, installer). Нажать F5 для запуска сервиса вы
не можете, а прикрепиться к процессу - уже слишком поздно. Как быть?
<br />
<br />
А быть надо с редактором реестра. Идем в раздел
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options" и добавляем новый подраздел, с названием процесса, который
будем отлаживать (e.g. explorer.exe). Затем, в этом разделе создаем
строковый параметр с ключом debugger и значением - указывающм на отладчик:<br />
<br />
<img alt="HP Health Monitor" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihlil9MxNL2bJKUzZUkJdj3UehByleJ3oXeHFYl7rbfXipUG7rnzNB8N3rfVI5dfHC0hMmz8zCjAm3ggrkrRGYTotpHcy9bAvccFfUGpvbnpodr3Un8gP2rBd9i5asr-2vOg1diK7YObC3/s1600/Debug-AutoLaunch-Regedit.jpg"
style="width: 784px; height: 428px" /><br />
<br />
На картинке выше я решил отладить сервис HP Health Check (не потому, что у моей
хапешки проблемы со здоровьем, тьфу-тьфу-тьфу, просто ради примера). Имя сервиса
<b>hphc_service.exe</b> - именно так я назвал подраздел в Image File Execution
Options. <b>VSJitDebugger</b> - это процесс запускающий отладчик Visual Studio.
Теперь, если кто-то запустит процесс мы получим окошко предлагающее отладку:<br />
<br />
<img alt="Debug it" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrwIHPJ9ifD3msYFjdAUICxzCQhhGv4j_NWEofOKse0TGalisyH2nGhb9AUWgS1JoWU1Shhj1l6NITGDA-0LSMv159hOogkmfs346tn4br0cpwu3uejZGHxu5egK840oIYmvLx4JshYWRb/s1600/Debug-AutoLaunch.jpg"
style="width: 408px; height: 441px" /><br />
<br />
Кстати 1. Этот подход помогает не только отладить приложения, но и выследить
какой засранец (извините) запускает исполняемые файлы вирусов... <br />
<br />
Кстати 2. Если вы когда-нибудь устанавливали
<a href="http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx">Process Explorer</a> от
SysInternals, и заменяли им стандартный диспетчер задач, то теперь вы знаете как
он подменяет запуск taskmgr.exe :).</div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='SilverdeBug'><h2>Отладка Silverlight приложений.</h2></a>
<br />
<div>Мой основной браузер - Google Chrome, но разработку я веду под Firefox. Как же
отлаживать Silverlight в таком случае? Один способ - это в настройках веб
проекта указать каким браузером вы хотите запустить сайт, но я предпочитаю
просто <a href='#LoveIsAll'>прикрепляться к процессу</a> Firefox.
<br />
<br />
В четвертом Silverlight появилась удобная возможность отлаживать Out of Browser
приложения. Достаточно указать в настройках Silverlight проекта какое
именно приложение нужно отладить:<br />
<br />
<img alt="OOB Silverlight Debugging" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiZ8CC77CBYFY9E1wh9U5Se75SSG36COUP7v_6se1phT-RaoKs2GghVH8BpVcH3zrcxR6D3lt1uMYUIgnaT_2ZPdvtkN4FoU3gjORze31KLNs7nBOc0trGVq8TBhhTS5ZGx3PXi7TLIqXO/s1600/Debug-OOB-Silver.jpg"
style="width: 763px; height: 551px" /></div><br />
<div><a href='#content'>К оглавлению</a></div>
<br /><a name='SheIsBrilliant'><h2>Знайте свой инструмент, или блог Сары Форд.</h2></a><br />
<div>Sara Ford - Program Manager CodePlex, автор книги
<a href="http://www.amazon.com/Microsoft%25C2%25AE-Visual-Studio%25C2%25AE-Tips-PRO-Developer/dp/0735626405/">MS Visual Studio Tips</a>,
очень жизнерадостная и веселая женщина. Она ведет потрясающий
<a href="http://blogs.msdn.com/saraford/">блог</a>, огромная часть советов из
которого и составила книгу. Если вы думаете, что отлично знаете Visual Studio -
посетите ее блог, чтобы понять, насколько глубоко заблуждаетесь :).</div>
<div><br /><a href='#content'>К оглавлению</a></div>
<h2>Спасибо!</h2><br />
<div> Друзья,
спасибо вам, что прочитали или просто пролистали до этого места. Надеюсь, вы
нашли что-нибудь новое для себя. Буду очень рад, если вы поделитесь своими
открытиями, которые не вошли в пост. Напоследок, хотелось бы упомянуть слова мудреца: If debugging is the process of
removing bugs, then programming must be the process of putting them in...
Отличного настроения и программирования :)!</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com4tag:blogger.com,1999:blog-3946161110755638694.post-91379729307110764902009-12-19T02:56:00.000-08:002010-03-12T11:53:11.152-08:00SilverNotes: Drag'n'Drop + Print + RightClick<div>Привет, Друзья!<br />
<br />
Сегодняшний пост посвящен сразу трем новинкам Silvelright 4.0: <a href='#printing'>Printing</a>, <a href='#drag'>Drag'n'Drop</a> и <a href='#RightClick'>Right Click</a>.<br />
<br />
Надо отдать должное разработчикам Silverlight: они сделали настолько простой API для рассматриваемых новинок,
что их описание влезло бы в два-три твита (интересно, твит - это уже стандартизированная
единица размера информации :)? ).<br />
<br />
В сумме эта тройка позволила мне за пару часов написать маленькое, но полезное приложение: SliverNotes (<a href="http://code.google.com/p/silvernotes/source/browse/#svn/trunk">исходники</a>,
<b><a href="http://dl.dropbox.com/u/5313583/ViValution/SilverNotesTestPage.html">демо</a></b>
- для демо нужен
<a href="http://silverlight.net/getstarted/silverlight-4-beta/#tools">4-й
сильверлайт</a>).
Эта программка позволяет сделать из обычного листка формата A4 карманную книжечку на 8 министраниц. Формат и содержание
каждой странички определяется пользователем. Например, это может быть список покупок, подготовленный
любимой женой забывчевому мужу, или же каждая страница может быть нотоносцем для талантливого музыканта,
или... все что захотите. Вот пример распечатанной карманной книжки:
<br /><br />
<div style='text-align:center'>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCJsLlZXQvJmIRhu6P3TICYTJi8BT4-N7ky1CMAuQgKwlEX8pfU6oMiH4zIvolruTW1ySShhaG1yDmfR5tqOt8Dj92bSrSVvIdCE_wsSvEUcjrwIK4KyVluR4u3SRzveOsgxNJjQh_OeCf/s1600-h/1.JPG"><img style="cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCJsLlZXQvJmIRhu6P3TICYTJi8BT4-N7ky1CMAuQgKwlEX8pfU6oMiH4zIvolruTW1ySShhaG1yDmfR5tqOt8Dj92bSrSVvIdCE_wsSvEUcjrwIK4KyVluR4u3SRzveOsgxNJjQh_OeCf/s320/1.JPG" border="0" alt=""id="Img1" /></a>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiol4LUGijVE0CYFUZ6Fo8E5u7D6Yg4l1CEHrKqUBeyQxOFhNU7D16X-vtVeUtzTtNEvYmTXT4Op5iQZDmEnyzhRfjLryVxXyBtL6Af2PvLoA4zvgVQftckGc98gQAyp4jC54Gbbm_KakRj/s1600-h/2.JPG">
<img style="cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiol4LUGijVE0CYFUZ6Fo8E5u7D6Yg4l1CEHrKqUBeyQxOFhNU7D16X-vtVeUtzTtNEvYmTXT4Op5iQZDmEnyzhRfjLryVxXyBtL6Af2PvLoA4zvgVQftckGc98gQAyp4jC54Gbbm_KakRj/s320/2.JPG" border="0" alt=""id="BLOGGER_PHOTO_ID_5416899972072820354" /></a>
</div>
<br /><br />
Итак к делу! (Иначе я рискую сделать введение самой длинной и сложной частью этого поста :)...)
</div>
<a name='printing'><h2>Печать</h2></a><br/>
<div>
Все, что нужно от разработчика для вывода содержимого на печать, это вызов функции
<i>Print()</i> у
объекта типа PrintDocument:<br />
<pre class="brush: csharp">
// PrintDocument - тот, кто нам нужен для печати.
var doc = new PrintDocument { DocumentName = "Notes" };
doc.PrintPage += (o, e) =>
{
e.PageVisual = uie;
e.HasMorePages = false;
};
doc.Print();
</pre>
</div>
<div>
В обработчике события <i>PrintPage</i> мы говорим Silverlight'у что именно
нужно напечатать (<i>e.PageVisual = uie</i>). Здесь <i>uie</i> - это объект типа
UIElement. Если бы мы хотели распечатать больше, чем одну страницу, то свойство
<i>e.HasMorePages</i> нужно установить в <i>true</i>.
</div><br />
<div>
<h4>Чуть-чуть деталей</h4>
<ul>
<li>Если элемент, который вы выводите на печать "обрезается" родительским контейнером, он будет обрезанным и на
бумаге. Чтобы это обойти, просто измените родительский контейнер, например, на
<i>Canvas</i>.</li>
<li>Если вы применяете трансформации прямо к элементу, который служит PageVisual'ом,
то они не будут применены на печати. Чтобы это обойти, устанавливайте
трансформации ниже по дереву: вместо самого PageVisual'a, трансформируйте его
первого ребенка (жуть-то какая :) ).</li>
<li>PageVisual не обязательно должен находиться в Visual Tree. Это может быть
просто объект в памяти.</li>
<li>Инициировать печать можно только по запросу пользователя (например, в
обработчике Click'a). Эй, а где еще его можно инициировать-то, умник? - Ну, у
меня было желание стартовать печать в событии <i>Loaded</i>, но
Silverlight пожелал мне удачи в виде приветливого Exception'a. Даже если обработчик Loaded - это лямбда в обработчике Click, вы получите
исключение...</li>
</ul>
</div>
<br />
<a name='drag'><h2>Drag'n'Drop</h2></a><br/>
<div>
Эта новинка позволяет пользователям перетаскивать в плагин [пока только] файлы
из операционной системы. Стоит ли говорить, на какой уровень это повышает UX
(user experience)? . Фантазия - ваш предел. В SilverNotes вы можете перетащить
на пустую страничку картинку, тем самым сделав свой шаблон для печати:</div><br />
<div>
<div style='text-align:center'>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxyJZ5b9v7WpH0nmvEwsy-yWxD7N8ErAuS1ZSKC28AI0sh99D1Ac9QbA_6Q7709phJU262Yeu4tPwRDNnh5tIFigzySJ3Yh8eqdWUdJkgi0KpcPlrVd6ArXl8Kv2IRXnfNEcAsJ3t6eZop/s1600-h/drag1.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxyJZ5b9v7WpH0nmvEwsy-yWxD7N8ErAuS1ZSKC28AI0sh99D1Ac9QbA_6Q7709phJU262Yeu4tPwRDNnh5tIFigzySJ3Yh8eqdWUdJkgi0KpcPlrVd6ArXl8Kv2IRXnfNEcAsJ3t6eZop/s320/drag1.jpg" border="0" alt=""id="Img4" /></a><br />
</div>
<b>1.</b>
Слева - окошко Windows Explorer'a. Я схватил фотографию леса из моего родного
города Лебедина, и тащу ее на SilverNotes. Превью фото делает Explorer.<br />
<br />
<div style='text-align:center'>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKWAwfDiKb1uQsb3dhuWnTVEt8LRdM5QXgGVrF4C9nlBms2FmBdvDTMIE8GsmJMJ5MYRSbWCGnBx8k0Z0aG54ScMG__zGyshrBH_6jM2ikKYCOXCF03xGjoJj0m86mjthTh3WbXVaYQ8SC/s1600-h/drag2.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKWAwfDiKb1uQsb3dhuWnTVEt8LRdM5QXgGVrF4C9nlBms2FmBdvDTMIE8GsmJMJ5MYRSbWCGnBx8k0Z0aG54ScMG__zGyshrBH_6jM2ikKYCOXCF03xGjoJj0m86mjthTh3WbXVaYQ8SC/s320/drag2.jpg" border="0" alt=""id="Img3" /></a>
</div><b>2.</b>
Теперь указатель мышки находится над пустой страничкой SilverNote's. Я замечаю это в событии DragEnter, и начинаю анимировать цвет фона, как бы говоря: "Давай, бросай!"<br />
<div style='text-align:center'>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFtH0GKea8o6IKmcVXJaLLudzs72wI3vY5VsDmrR5cMjogfCjRxh2ECeUl32PdAsOfL8r35MWfO_uClwN6QfNfBPieQHZYt-pJSSj6W2K0iX-8-kFXv08xjA3kjFPRWP_7mvqS850Jl63z/s1600-h/darg3.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 180px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFtH0GKea8o6IKmcVXJaLLudzs72wI3vY5VsDmrR5cMjogfCjRxh2ECeUl32PdAsOfL8r35MWfO_uClwN6QfNfBPieQHZYt-pJSSj6W2K0iX-8-kFXv08xjA3kjFPRWP_7mvqS850Jl63z/s320/darg3.jpg" border="0" alt=""id="Img2" /></a>
</div>
<b>3.</b> Ну и, наконец, я отпускаю левую кнопку, и фото оказывается в Silverlight приложении...
</div><br />
<div>
Для того, чтобы разрешить бросать файл на UIElement, достаточно установить
свойство <i>AllowDrop="True"</i>. После этого у UIElement'a будут срабатывать
обработчики событий <i>DragEnter</i>,<i> DragLeave</i>, <i>DragOver</i> и <i>Drop</i>. Вот пример кода,
который считывает брошенную картинку на UIElement с установленным AllowDrop'ом:</div><br />
<pre class="brush: csharp">
private void DoDrop(object sender, DragEventArgs e)
{
if (e.Data == null)
{
return; // И почему мы так не любим nulls?
}
// Куда именно это мы бросаем?
Point dropPosition = e.GetPosition(_cnvDropTarget);
// Ну-ка, что нам тут подбросили?
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
foreach (var file in files)
{
using (var stream = file.OpenRead())
{
// Обрабатываем файл:
ProcessFileContent(stream, dropPosition);
}
}
}
</pre>
<div>
<h4>Чуть-чуть деталей</h4>
<ul>
<li>Не пытайтесь получить доступ к данным во время событий DragEnter/DragLeave.
Только разочарование и SecurityException ждут вас. Правильное место для
считывания данных - обработчик события Drop.</li>
<li>
Не пытайтесь перетаскивать файл на Silverlight приложение, с установленным
windowless режимом. Также проблематичным будет использование Drag'n'Drop под
Маками (<a href="http://msdn.microsoft.com/en-us/library/ee670998(VS.96).aspx">см.
детали</a>).</li>
</ul>
</div><br />
<a name='RightClick'><h2>ПКМ ака RightClick</h2></a>
<br/>
<div>
О поддержке правого клика в сильверлайт приложениях раньше приходилось только
мечтать (или извращаться с html'ем поверх сильверлайта). Теперь же, все что
нужно сделать - это подписаться на событие <i>MouseRightButtonDown</i> (или <i>
MouseRightButtonUp</i>). Если вы хотите показать свое контекстное меню по
правому клику, то нужно... написать свое контекстное меню и показать его. Что?!
В сильверлайте нет стандартной реализации класса ContextMenu? - Да, покамест нет. Но не удивлюсь,
если с выходом четверки (не беты) оно там появится. Пока же Microsoft предлагает
пользоваться классом Popup, и в него встраивать контекстное содержимое.
<br />
<br />
Для демонстрации на IT Jam'e мы
воспользовались кодом
<a href="http://www.jebishop.com/2009/11/18/implementing-a-contextmenu-in-silverlight-4-beta/">
Jesse Bishop'a</a>, и чуть-чуть изменили его, сделав более
похожим на стандартное WPF меню:<br />
<br />
<div style='text-align:center'><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguf0VoofvrRoDGilthIarFyF9qDE3-ieo-2dX_YJ1EIPeNPDNZrnpKlQ5H0pDMC6ycvOvvUYATfrF5pDRxuVx7aL4rzXrROKLBoHwKz7FvpvcdlEzNY-NB6Crw_-xJ6w9fliK9cqXURpSC/s1600-h/ContextMenu.jpg"><img style="cursor:pointer; cursor:hand;width: 320px; height: 282px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguf0VoofvrRoDGilthIarFyF9qDE3-ieo-2dX_YJ1EIPeNPDNZrnpKlQ5H0pDMC6ycvOvvUYATfrF5pDRxuVx7aL4rzXrROKLBoHwKz7FvpvcdlEzNY-NB6Crw_-xJ6w9fliK9cqXURpSC/s320/ContextMenu.jpg" border="0" alt=""id="Img5" /></a>
</div>
<br /><br />
В SilverNotes контекстное меню для Grid'a устанавливает следующий код:<br />
<br />
<pre class='brush: xml'>
<Grid x:Name="LayoutRoot" Background="Transparent">
<menu:ContextMenu.ContextMenu>
<menu:ContextMenu>
<menu:MenuItem Click="OnDeleteClick">
<menu:MenuItem.Content>
<StackPanel Orientation="Horizontal">
<Image Margin="0, 0, 8, 0" Source="/SilverNotes;Component/Resources/delete.png" Width="16" Height="16"/>
<TextBlock Text="Delete"/>
</StackPanel>
</menu:MenuItem.Content>
</menu:MenuItem>
</menu:ContextMenu>
</menu:ContextMenu.ContextMenu>
<!-- Содержимое Grid. -->
</Grid>
</pre>
<br />
Вы также можете пользоваться и улучшать код проекта, скачав <a href="http://code.google.com/p/silvernotes/source/browse/#svn/trunk">исходники SilverNotes</a>.</div>
<div>
<h4>Чуть-чуть деталей</h4>
<ul>
<li>Чтобы полностью скрыть стандартное меню сильверлайта, не забудьте
установить <i>e.Handled = true</i>, в обработчике <i>MouseRightButtonDown</i>. </li>
<li>Событие <i>MouseRightButtonUp</i> не прийдет к вам, до тех пор пока вы не установите
<i>e.Handled = true</i> в <i>MouseRightButtonDown</i>.</li>
</ul>
</div>
<h2>Итак</h2><br />
<div>
Все три новинки наглядно показаны в SilverNotes. Я выложил этот проект на
<a href="http://code.google.com/p/silvernotes/">Google
Code</a>. Если у вас есть желание усовершенствовать проект, дописать шаблоны для
печати - добро пожаловать :). Кстати, создавать свои собственные шаблоны в
SilverNotes так же просто, как создавать обычный UserControl на Silverlight'e.
Шаблоны загружаются при помощи MEF (Managed Extensibility Framework), который
так же входит в поставку четвертого Silverlight. Но об этом чуть-чуть попозже :). <br />
<br />
Отличного настроения и прекрасного программирования в предновогодние морозы :)!</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com0tag:blogger.com,1999:blog-3946161110755638694.post-13147627335976142182009-12-07T12:38:00.000-08:002010-03-12T12:17:17.845-08:00Webcam FunПривет, Друзья!<br /><br />
<div>
На всеукраинской сходке айтишников <a href="http://it-jam.ciklum.net/">IT Jam</a>, нам с <a href="http://dev.net.ua/blogs/sergeylutay/default.aspx">Сергеем Лутаем</a>
посчастливилось делать доклад по новинкам четвертого Сильверлайта. Пока
обрабатывается видео с презентации, выкладываю код, который мы использовали для
демонстрации работы с веб камерой. Кроме кода, мы посмотрим на основы работы с
камерой и на несколько веселых эффектов, которые можно сделать при помощи
пиксельных шейдеров.</div><br />
<div>
Исходники к этому посту можно скачать
<a href="http://dl.dropbox.com/u/5313583/ViValution/01.CamDemo.zip">здесь</a>. <b>Живое демо</b>
находится <a href="http://dl.dropbox.com/u/5313583/ViValution/WebCamTestPage.html">здесь</a>.
Для запуска демо, вам, кроме камеры, понадобится установить
<a href="http://silverlight.net/getstarted/silverlight-4-beta/#tools">
Silverlight 4 Beta 1</a>. Рантайм есть только для разработчиков, но ведь это
про вас, верно :)?</div>
<h2>Основы</h2><br/>
<div>
Для старта нам понадобится... камера. Чтобы получить доступ к камере из
Silverlight'a, достаточно спросить у пользователя разрешения, и получив оное,
начать видеозахват:</div><br />
<pre class="brush: csharp">
private CaptureSource _captureSource;
private CaptureSource CaptureSource
{
get { return _captureSource ?? (_captureSource = new CaptureSource()); }
}
private void StartCapture()
{
// Можно мне попользоваться вашей камерой?
if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
{
// Урра! Начинаем захват:
CaptureSource.Start();
var brush = new VideoBrush();
brush.SetSource(CaptureSource);
VideoBrush = brush; // К этому свойству можно привязать любой Brush: <Border Background="{Binding VideoBrush}" .../>
}
}
</pre>
<div>
Видео является обычной кисточкой (Brush), а значит, мы можем
разукрасить выходом с камеры любой элемент, который можно разукрасить :).
Пожалуй, это и все. Просто, не так ли?</div><br />
<h2>Не Основы</h2><br/>
<div>
К "продвинутым" темам можно отнести сохранение рисунка с камеры. Тут есть два
варианта: воспользоваться функцией
<i>CaptureSource.AsyncCaptureImage()</i> или сделать снимок UIElement'a, как обычно,
при помощи WriteableBitmap'a: <i>new WriteableBitmap(uie, null)</i>. Разница в
том, что WriteableBitmap фотографирует содержимое со всеми эффектами, а
CaptureSource выдаст именно то, что есть на выходе камеры, без трансформаций,
эффектов и прочего.</div><br />
<div>
Если есть несколько камер (скорее всего, у вас, как и у меня, их нет, но мало
ли :)?), то можно всегда выбрать с какой камеры вести захват: <i>
CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices()</i>;</div><br />
<div>
Для того, чтобы сохранить WriteableBitmap в Jpeg формат нужно написать свой
энкодер, который будет последоватльно, байт за байтом, запаковывать цвета в
формат Jpeg. Для этого, конечно, нужно сначала достать спецификацию Jpeg'a,
изучить ее, и заимплементить... Шутка :). Все уже написано до нас: библиотека
<a href="http://code.google.com/p/fjcore/">FJCore</a> позволяет легко сохранять
jpeg'и. Для просмотра примера использования, загляните в исходники к этому
посту.</div><br />
<h2>Спецэффекты</h2><br/>
<div>
Использование спецэффектов радует больше всего. В реальном времени увидеть себя
с необычной стороны, и я говорю не о спине, - оказывается очень увлектально :)! О создании
собственных эффектов на шейдерах, я
<a href="http://vivalution.blogspot.com/2009/04/magic-shaders-genie-effect.html">
уже писал</a>. А на презентации мы показали четыре эффекта:</div><br />
<table>
<tbody>
<tr><td><img style="cursor:pointer; margin:4px; width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix0WGfpjLQtEyzSneJNTQdmNTRJWrvtUCFfP2Is4NZ948kWKZ5w7cT6qmDiY_HwZyzaVnAaPAmGcfFZjS2BuCBrk6Czb1-ee0dftAyB-KSzwFCsUn4pDytbRpMmpOiyuYGtUpfOQgNLC9-/s320/mic.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5412640670034237106" /></td>
<td valign='top'>
Эффект находит границы на рисунке и закрашивает их черным цветом. Все остальное
выводится белым. На этой фотке знакомые могут узнать
<a href="http://dev.net.ua/blogs/mikechaliy/">Мишу Чалого</a> :). </td></tr>
<tr><td><img style="cursor:pointer;margin:4px; width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSNZjwtSZS8IevPTsZ3GNbmVvkKpmJ99THFWlBncjWcU2RcfbzZvbe2QRMlhghBtE_nIyMg0v_aJ-ksgAvq2iSh-KC84JBoufxp9adcTrZ4adogJUefULSrlNnmjUCS_NOqEJ3EE1kMgkb/s320/Alien.jpg" border="0" alt=""id="Img1" /></td>
<td valign='top'>С этой фотки
из паралельной вселенной дружественный привет землянам шлет головастый и
чертовски умный двойник автора этого блога :).</td></tr>
<tr><td><img style="cursor:pointer; margin: 4px;width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQkmDmX0A-AC1O3bJ5JY1DogFFSQp2ofvW4AofuFZqXhUzKxsweQe_3NSFThFCpW6LPf1auaiOgsnWdbLyFWN0lUo25em1cxKiSYE4sEM_zma43AcyEohujJ9j8s-4u2z8N6G97LRxxtxn/s320/Lord.jpg" border="0" alt=""id="Img2" /></td>
<td valign="top">Этот эффект-страшилка просто инвертирует цвета. Как-то так выглядел мир по ту сторону кольца, когда Фродо надевал его :).</td></tr>
<tr><td><img style="cursor:pointer; margin:4px; width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVx_lNvgBngEAhT0Dk1Sq1I6z-ZSTqsLZR1YQEqFU8fT2OgfpqynPkjqIHXpUV9TJynMn4G20fIkRsr3LldvZkuISg84ycsUZKVbXVwSub-uVPJJ3CDsUpIt_W7CgA7pIcQBXWtXS_2LZF/s320/BillInHand.jpg" border="0" alt=""id="Img3" /></td><td valign="top">И, наконец, последний эффект,
"скрытая фича Sivlerlight 4.0", он же
знакомый нам по передачам типа "Прогноз погоды": подмена вывода. Как только цвет
точки становится ярче определенного порога, вместа оригинала выводится точка из
другого сэмплера (читай - brush'a). Здесь я направил включенный фонарик в
ладошку, открывая секретный кусочек лица Билла.<br />
<br />
Конечно, можно написать шейдер, который будет подменять вывод в зависимости от
разных условий, например, на синий цвет выводить Брина и Пейджа, на зеленый -
Стива, а на очень белый (или черный, в зависимости от того, на чьей вы стороне)
- Билла. </td></tr>
</tbody></table>
<h2>Выводы</h2><br/>
<div>
Пользоваться камерой в Silverlight 4.0 очень легко. Все операции с камерой
не требуют участия сервер-сайда. Можно легко сохранять изображения.</div><br />
<div>
Для тех, кто любит просто читать с конца, еще раз привожу ссылку на
<a href="http://dl.dropbox.com/u/5313583/ViValution/01.CamDemo.zip">исходники</a> и
<a href="http://dl.dropbox.com/u/5313583/ViValution/WebCamTestPage.html">демо</a>. Не
забудьте про рантайм:
<a href="http://silverlight.net/getstarted/silverlight-4-beta/#tools">
Silverlight 4 Beta 1</a>. Отличного программирования и прекрасного настроения!</div><br />
<div>
ЗЫЖ спасибо всем, кто пришел к нам на презентацию. И, конечно, всем тем, кто
поставил за нас "наляпочки" на доске голосования :). Нам было очень приятно!
</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com3tag:blogger.com,1999:blog-3946161110755638694.post-81104265775220260912009-07-08T09:13:00.000-07:002009-07-08T09:21:55.898-07:00July 10: See the LightПривет :)!<br/>
<br/>
Немножко пафосное название, но из домена букв не выбросишь. <a href="http://www.seethelight.com/" target="_blank">http://www.seethelight.com/</a> анонсирует выпуск третьего сильверлайта и бленда уже 10-го июля. Согласно <a href="http://www.robzelt.com/blog/2009/07/07/Silverlight+3+And+Expression+3+Launch.aspx" target="_blank">MSDN Flash'у</a>, виртуальный запуск состоится именно там.<br/>
<br/>
Ждем-ждем тяпницы!anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com0tag:blogger.com,1999:blog-3946161110755638694.post-81380808937863791762009-06-10T12:06:00.000-07:002009-06-10T13:55:16.418-07:00Silverlight vs WPF<div>Привет!</div></br></br>
<div>
Silverlight, обычно, называют подмножеством WPF и не зря. Стили программирвания
на WPF и на Silverlight очень похожи друг на друга: (XAML + Code Behind) * .NET. Но за
этим сходством кроется опасность. Например, программируя на WPF'e, можно
ожидать, что Silverlight так же поддерживает создание своих собственных
расширений разметки (markup extension). А он, козявка, и не думает. Таких тонких
различий, компания Wintellect насобирала на 70 страниц в документе
<a href="http://wpfslguidance.codeplex.com/" target="_blank">Guidance on Differences Between WPF
and Silverlight</a>. Помимо различий/сходств парни дают советы, как обойти
некоторые ограничения "подмножества".<br />
<br />
Надеюсь, в скором времени число страниц в документе уменьшится, как и различий
между Silverlight и WPF :).<br />
<br />
Отличного программирования, друзья!<br />
<br />
PS: XAML читается как "замл" или "зэмл", а не "икс-эй-эм-эл" и не "кзамл".
Кстати, изначально аббревиатура означала Extensible Avalon Markup Language, а не
Extensible Application Markup Language.</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com0tag:blogger.com,1999:blog-3946161110755638694.post-85239632107802388072009-05-17T11:49:00.000-07:002009-05-17T11:58:02.700-07:00TechTalks: Sackoverflow<div>Привет!
</div><br/>
<div>К своему огромному стыду только что узнал, что <a href="http://stackoverflow.com" target="_blank">stackoverflow.com</a> был создан Джоэлом Спольки (киньте в меня чем-то, за темноту). </div><br/>
<div>О том, почему сайт пользуется такой популярностью, Джоэл рассказывает на TeckTalk'e в Google. Очень рекомендую:</div><br/><div>
<object width="560" height="340"><param name="movie" value="http://www.youtube.com/v/NWHfY_lvKIQ&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/NWHfY_lvKIQ&hl=en&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object>
</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com1tag:blogger.com,1999:blog-3946161110755638694.post-42065077401773488092009-04-29T10:16:00.000-07:002010-03-12T12:24:47.182-08:00Magic Shaders: Genie Effect<div>Привет!
<br />
<br />
Мой коллега, счастливый обладатель MacBook'a, показал мне
сногсшибательный эффект сворачивающегося окошка в Mac OS'e: Genie effect (эффект
джина). Вот как он выглядит:<br />
<br />
<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/jq3mGowFR_Y&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/jq3mGowFR_Y&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object>
<br />
<br />
Тогда я загорелся идеей сделать аналогичный эффект в Silverlight приложении, но не знал
как :).
Последнюю неделю я провел за изучением пиксельных шейдеров и к огромной
моей радости обнаружил ключ к разгадке. В скором времени родилась библиотека,
реализующая Genie-эффект,
как в WPF'e, так и в Silverlight'e. <span style="font-size:large"><b>Посмотреть</b> живую <b>демку</b> эффекта
можно <a href="http://dl.dropbox.com/u/5313583/ViValution/SLGenieDemo.htm" target="_blank">здесь</a></span>
(нужен 3-й сильверлайт:
<a href="http://silverlight.net/getstarted/silverlight3/default.aspx" target="_blank">link</a>).
В этом посте мы:<br />
<ul>
<li>
Посмотрим как использовать Genie-эффект у себя в коде.
</li>
<li>
Посмотрим как создаются собственные эффекты на базе пиксельных шейдеров.</li>
<li>
Разберем алгоритм Genie-эффекта</li>
</ul>
Ссылка на исходники к статье находится в конце поста. Исходники распространяются под LGPL лицензией.<br />
<br />
<b>NB:</b> Apple Inc. является обладателем
<a href="http://www.appleinsider.com/articles/04/10/06/apple_receives_patent_for_genie_dock_effect.html" target="_blank">патента</a> на этот эффект в США. Надеюсь, я
не нарушаю их прав, описывая в исключительно учебных целях один из возможных
вариантов создания подобного эффекта на WPF/Silverlight технологиях. </div>
<br />
<h2>GenieLib в действии</h2>
<br />
<div>
GenieLib - это моя библиотека, реализующая описанный эффект Джина
(исходники - в конце поста). Для того, чтобы создать Genie-эффект в вашем
приложении достаточно добавить ссылку на сборку GenieLib и определить два
UIElement'a: один будет представлять Джина, а второй - Лампу (место, куда
прячется Джин).
<br />
<br />
Вот, как это выглядит в коде:<br />
<br />
<b>XAML:</b><br />
<pre class="brush: xml">
<Button Click="ToggleGenie"
x:Name="btnLamp"
Content="Lamp"/>
<Image x:Name="imgGenie" Source="...">
<Image.RenderTransform>
<TranslateTransform />
</Image.RenderTransform>
</Image>
</pre>
<br />
<b>C#</b><br />
<pre class="brush: csharp">
private void ToggleGenie()
{
GenieManager.IsGenieOut = !GenieManager.IsGenieOut;
}
private Magic GenieManager
{
get
{
if (_genieManager == null)
{
_genieManager = new Magic(btnLamp, imgGenie);
_genieManager.Expanding +=
(o, e) =>
{
imgGenie.Visibility = Visibility.Visible;
};
_genieManager.Collapsed +=
(o, e) =>
{
imgGenie.Visibility = Visibility.Collapsed;
};
}
return _genieManager;
}
}
</pre>
<br />
Как видите, мы определили два UIElement'a в xaml-файле, и по нажатию на кнопку,
которая по совместительству подрабатывает волшебной лампой, мы обращаемся к
объекту типа Magic, инвертируя текущее состояние Джина. Не смотря на название,
класс Магии не делает ничего таинственного. В его обязанности входит построение
анимации перехода Джина в Лампу и из нее. Класс не накладывает никаких
ограничений на представителей Лампы и Джина. Но для правильной работы он ожидает
от Джина наличия одного RenderTransform'a: TranslateTransform. Именно этот
трансформ позволяет Джину "летать" в пространстве по направлению к лампе и от
нее. Если это требование не соблюдается, Джин будет уменьшаться в рамках
собственной области, не приближаясь к лампе (картинка слева):
<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoHgYeqnVfn8eksLQ2Jb6KHwbWQEc1cGfg9H5UDnBFKmSvUHTgJfwD7724WwFCL14SwwS9fDP5gZWmPpxhG_Zt9LcYgKCWpPYQONTRS1tVRFy8fXOCtREZd063FB_dXnijt2N3ujE1fyHX/s1600-h/TranslateDiff.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 164px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoHgYeqnVfn8eksLQ2Jb6KHwbWQEc1cGfg9H5UDnBFKmSvUHTgJfwD7724WwFCL14SwwS9fDP5gZWmPpxhG_Zt9LcYgKCWpPYQONTRS1tVRFy8fXOCtREZd063FB_dXnijt2N3ujE1fyHX/s320/TranslateDiff.JPG" border="0" alt=""id="Img1" /></a>
<br />
<br />
Размер и позиция Лампы относительно Джина играют важную роль. Позиция определяет
в какую сторону будет лететь Джин при сворачивании, а размер Лампы (ширина или
высота, в зависимости от направления сворачивания) указывает Джину насколько
сильно нужно сузиться, чтобы "влезть" в горлышко Лампы :).<br />
<br />
Помимо этого класс Magic уведомляет клиентов при помощи событий о
специфических состояниях Джина (собирается спрятаться в лампу, спрятался,
собирается выйти из лампы и вышел из лампы). Клиенты могут реагировать на
события должным образом. В нашем случае, мы убираем Джина, устанавливая его
свойство Visibility в Visibility.Collapsed, когда Джин спрятался в Лампу, и
показываем его, когда Джин собирается наружу. Забегая вперед, скажу, что эффект,
который реализует уменьшение Джина ничего не знает о HitTesting'e, поэтому мы
должны позаботиться об этом самостоятельно. Иными словами, если бы мы не меняли
видимость Джина, пользователь все так же мог бы кликнуть мышью по пустой области
и попасть на невидимую кнопку спрятавшегося Джина.<br />
<br />
Библиотека также поддерживает несколько "продвинутых" сценариев, но я не хочу
утомлять вас, дорогой читатель, этими подробностями. Любопытные всегда смогут
найти детали в коде, который я старался тщательно документировать и писать
предельно ясно :). Дальше мы переходим к промежуточному шагу: создание эффектов
при помощи пиксельных шейдеров.</div>
<br /><br />
<h2>Создание эффектов при помощи шейдеров</h2>
<br />
<div>
Третий Silverlight ввел поддержку эффектов, которые сыздавна были доступны
разработчикам WPF-приложений. Теперь мы можем добавить любому
UIElement'у эффект тени (DropShadowEffect), или сделать его размытым
(BlurEffect). Но как быть, если мы хотим создать свой собственный эффект,
например, полностью убирающий красную составляющую из UIElement'a к которому он
прикреплен? На помощь приходит класс ShaderEffect, который также был доступен
WPF разработчикам с выходом WPF 3.5 SP1.<br />
<br />
Суть ShaderEffect'a проста:
взять визуальное представление элемента и прогнать его через пиксельный шейдер.
Мое наивное определение пиксельного шейдера: это функция, возвращающая цвет точки в заданной
координате. Правда, функция эта описывается не на C#, а на языке шейдеров, и
исполняется обычно не на CPU а на <b>G</b>PU. Мы пользуемся
С-подобным языком от Microsfot'a: HLSL (High Level Shaders Language -
высокоуровневым языком шейдеров) для описания шейдера. В Silvelright 3 Beta 1 сам шейдер выполняется
центральным процессором, не смотря на введенную поддержку GPU. В релиз версию
парни из Майкрософт обещают включить поддержку выполнения шейдеров видео картой.<br />
<br />
Алгоритм создания ShaderEffect'a:<br />
<br />
1. Создаем файл с названием RemoveRed.fx. В файле определяем функцию шейдера:<br />
<pre class="brush:csharp">
float r : register(C0);
sampler2D implicitInputSampler : register(S0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 c = tex2D( implicitInputSampler, uv );
c.r = r;
return c;
} </pre>
Здесь мы определили две глобальные переменные (<i>r</i> типа float;
<i>implicitInputSampler</i> типа sampler2D) и одну функцию main(). На входе
функция принимает переменную типа float2 - двумерный вектор, представляющий
координаты точки, для которой требуется вычислить цвет. Выходом из функции
служит переменная типа float4 - вектор с четырьмя измерениями, описывающий цвет
точки (r, g, b, a). Значения координаты точки меняется в пределах от 0 до 1.
Точка с координатами (0,0) - верхний левый угол, (1, 1) - нижний правый.
Аналогичным диапазоном измеряется и составляющие цвета: от нуля до единицы.<br />
<br />
Переменная типа sampler2D предоставляет механизм доступа к цветам элемента, к
которому мы применяем эффект. Для того чтобы узнать какого цвета точка, скажем в
правом верхнем углу достаточно вызвать встроенную функцию
tex2D(implicitInputSampler, (1, 0)), которая вернет нам вектор со значениями
цвета: (r, g, b, a). В примере выше, мы просто получаем
истинный цвет точки в своей собственной позиции и изменяем красную составляющую
на значение глобальной переменной <i>r</i>. Так, если значение <i>r</i> будет равно
нулю мы получим следующее преобразование:<br />
<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqaBktgFl_XYdDSxFbtZ9pkLcR7KfZnrXDyOrQ38NZ72yKLIqzUDY6G_HBcWcAFrYitnEGEohIUuFTDtNy7MtFOz_47W6RvKdgnPX-IPNgAqU5yE4QtXDsI41uvq12sBaaFWCXzimWMKJ8/s1600-h/RemoveRed.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 94px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqaBktgFl_XYdDSxFbtZ9pkLcR7KfZnrXDyOrQ38NZ72yKLIqzUDY6G_HBcWcAFrYitnEGEohIUuFTDtNy7MtFOz_47W6RvKdgnPX-IPNgAqU5yE4QtXDsI41uvq12sBaaFWCXzimWMKJ8/s320/RemoveRed.JPG" border="0" alt=""id="Img4" /></a><br />
Значения глобальных переменных мы можем установить извне шейдера. Как именно -
описано ниже. На этом мы закончим обзор HLSL и двинемся дальше, но пытливый
читатель всегда найдет много полезной и интересной информации в
<a href="http://msdn.microsoft.com/en-us/library/bb509561(VS.85).aspx" target="_blank">разделах
MSDN'a</a>, посвященных языку программирования шейдеров.
<br />
<br />
2. Далее, мы компилируем эффект при помощи утилиты
<a href="http://msdn.microsoft.com/en-us/library/bb232919(VS.85).aspx" target="_blank">fxc.exe</a> из
<a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=24a541d6-0486-4453-8641-1eee9e21b282&displaylang=en" target="_blank">DirectX SDK</a>..
Просто запускаем компилятор из командной строки:<br />
<br />
<pre>
fxc.exe /T ps_2_0 /E main /Fo RemoveRed.ps RemoveRed.fx
</pre>Здесь мы явно указываем компилятору использовать вторую модель пиксельных
шейдеров (/T ps_2_0), в качестве входа в программу шейдера служит функция main
(/E main), и, ожидаемым результатом компиляции файла RemoveRed.fx является
объектный файл RemoveRed.ps (/Fo RemoveRed.ps RemoveRed.fx). Если у вас нет
желания устанавливать дополнительно ПО, всегда можно воспользоваться
<a href="http://www.voxpeeps.com/slpixelshadercompiler/" target="_blank">онлайн компилятором</a>:
<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFSHkWes9nU0mYN_994rf_nwxSRqYoujZj-TtGfo-J7HlC4I2Cw_CnXCufIndSetS0NHyllJ-iu2bXd5rxWHHUiSqSsd2hvrsD7sb3RLjbg3PDO0gTW1gxaeIh16DTfvI2XszX-x4ynrRz/s1600-h/OnlineCompiler.JPG">
<img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 202px;"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFSHkWes9nU0mYN_994rf_nwxSRqYoujZj-TtGfo-J7HlC4I2Cw_CnXCufIndSetS0NHyllJ-iu2bXd5rxWHHUiSqSsd2hvrsD7sb3RLjbg3PDO0gTW1gxaeIh16DTfvI2XszX-x4ynrRz/s320/OnlineCompiler.JPG"
border="0" alt=""id="Img2" align="middle" /></a> 3. Добавляем скомпилированный файл шейдеров в наш проект. Устанавливаем его
Build Action в Resource:<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbNHYFkICwGQUeeV1-Ijtkra9AN0vMVzI0JaWm1sE16K-N7J0BG9Xj5rWl7hc_PEptRGmZTD5yFz1oSujHLlAltNbLwJDSm2nfgyF804-IAI9ik1iaX5wuVn1_n98MS_9PyyV-YEYgJj8v/s1600-h/ResourceBuild.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 185px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbNHYFkICwGQUeeV1-Ijtkra9AN0vMVzI0JaWm1sE16K-N7J0BG9Xj5rWl7hc_PEptRGmZTD5yFz1oSujHLlAltNbLwJDSm2nfgyF804-IAI9ik1iaX5wuVn1_n98MS_9PyyV-YEYgJj8v/s320/ResourceBuild.JPG" border="0" alt=""id="Img3" /></a>
<br />
4.Теперь создаем C# класс, который будет представлять наш шейдер в качестве
эффекта:<br />
<br />
<pre class="brush: csharp">
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;
namespace SilverShaderExample
{
public class RemoveRedEffect : ShaderEffect
{
public static DependencyProperty InputProperty =
RegisterPixelShaderSamplerProperty("Input",
typeof(RemoveRedEffect), 0);
public static DependencyProperty RProperty =
DependencyProperty.Register("R",
typeof(double),
typeof(RemoveRedEffect),
new PropertyMetadata(new double(), PixelShaderConstantCallback(0))
);
public RemoveRedEffect()
{
var u = new Uri(@"SilverShaderExample;Component/RemoveRed.ps", UriKind.Relative);
PixelShader = new PixelShader { UriSource = u };
// Инициализируем свойства, чтобы они получили верные начальные значения.
UpdateShaderValue(InputProperty);
UpdateShaderValue(RProperty);
}
public virtual Brush Input
{
get
{
return ((Brush)(GetValue(InputProperty)));
}
set
{
SetValue(InputProperty, value);
}
}
public virtual double R
{
get
{
return ((double)(GetValue(RProperty)));
}
set
{
SetValue(RProperty, value);
}
}
}
}
</pre>
<br />
Здесь основной акцент стоит сделать на конструкторе класса и на регистрации Dependency свойств.
<br />
<br />
Давайте на секунду обратимся к .fx файлу. Помните наши глобальные переменные в
шейдере? Так вот, при помощи Dependency-свойств, именно здесь происходит связывание
переменных шейдера со свойствами managed-объекта. Сэмплер регистрируется при
помощи метода <i>RegisterPixelShaderSamplerProperty()</i>,
а переменная R использует <i>PixelShaderConstantCallback</i>() в качестве
метода, реагирующего на изменения.
<br />
<br />
В конструкторе класса мы создаем ссылку на скомпилированный шейдер. Стоит быть
очень внимательным в формировании этой ссылки. Например, если вы укажете <i>Uri("RemoveRed.ps",
UriKind.Relative)</i> вместо <i>Uri(@"SilverShaderExample;Component/RemoveRed.ps", UriKind.Relative)</i>,
то сильверлайт упадет с далеко не очевидным объяснением исключения. Затем мы создаем новый экземпляр класса
PixelShader, который представляет собой управляемую обертку над нашим шейдером.<br />
<br />
Поскольку переменные шейдера и managed-объекта связываются при помощи
dependency-свойств, мы можем делать с переменными шейдерами все тоже, что и с
обычными dependency-свойствами (анимировать, привязывать к данным, использовать
в триггерах и т.д.).<br />
<br />
Дальше дело за малым: применить эффект можно к любому UIElement'у. Так,
следующий код, определяющий Grid с красным фоном будет отображен как черный
прямоугольник, благодаря нашему эффекту:<br />
<br />
<img style="display:block; margin:0px auto 10px; text-align:center;"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdoCQWSVzdYgWQM10dZV2cUP-mMuxHKgShTANcrVoMnxAZMW9FKi0pxyGWKWyA0hbjlKixnOl4QzoPsC2JQLwFzVPKKFByS-qepReRyFGuPHOhG7ryRwJzp5jStB98PKdQsF8HHPmszeVX/s1600/RemoveRed.JPG"
border="0" alt="RemoveRed" /><br />
В Silverlight 3.0 beta 1 можно применить только один эффект к одному элементу.
Но элемент всегда можно вложить в несколько, скажем, Border'ов, и применить по
одному из эффектов к каждому Border'у :).<br />
<br />
Завершая наш обзор шейдерных эффектов, привожу ссылку на инструмент, значительно
упрощающий работу: <a href="http://shazzam-tool.com/publish.htm" target="_blank">Shazzam Tool</a>
(click-once installer), <a href="http://shazzam-tool.com/" target="_blank">Shazzam Help</a>
(Silverlight-based help). Shazzam позволяет отредактировать HLSL код:<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC1yEKRr4TxUFBohErGt4Ai2SK_Jnnrjqm9sRTZducrEN4WcKbTXYvAgLSKJs5Fed7zjkdHZKfyLStKqP4a-bjivSXq04PD26kI9nXe8je7bl7mpDO6_pHrTpSF8sG-o7NTV0mFi0AepUV/s1600-h/Shazzam1.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 189px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgC1yEKRr4TxUFBohErGt4Ai2SK_Jnnrjqm9sRTZducrEN4WcKbTXYvAgLSKJs5Fed7zjkdHZKfyLStKqP4a-bjivSXq04PD26kI9nXe8je7bl7mpDO6_pHrTpSF8sG-o7NTV0mFi0AepUV/s320/Shazzam1.JPG" border="0" alt=""id="Img7" /></a>
<br />
<br />
Скомпилировать
HLSL-код, посмотреть на результат и на сгенерированный C#- или VB-код для
эффекта, не отходя от кассы (WYSIWYG, по-нашему):
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK8yMQkkFq_Rzb8U7JDKP4vDI1p7p1F5H1wT36E4PJvhXLdfmPwNWWL3xJZL5M0JIa-0SCAtW7xrkvQXsLD99E4w0-wySx5IdHoRNRQKmtjShyphenhyphenMfndf_QF4etqqCAZqnnxmLUzhJPNmujT/s1600-h/Shazzam2.JPG"
onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}">
<img id="Img6" alt="" border="0"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK8yMQkkFq_Rzb8U7JDKP4vDI1p7p1F5H1wT36E4PJvhXLdfmPwNWWL3xJZL5M0JIa-0SCAtW7xrkvQXsLD99E4w0-wySx5IdHoRNRQKmtjShyphenhyphenMfndf_QF4etqqCAZqnnxmLUzhJPNmujT/s320/Shazzam2.JPG"
style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 189px;" /></a><br />
Более того, если эффект содержит входящие параметры, их можно модифицировать
прямо из Shazzam'a:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjONmzKm9HnWK94tg5cX4IdCKovfeYM50oerENMk9TjI42uyWxX3PssEMbWaQwDtq6vxKT32qnITI88dVukFTbGysjtTrQJAEtphhODEJ3yo1MdJiwWpbJNjHeEqzibm53fSq_TAFDABBDH/s1600-h/Shazzam3.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 189px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjONmzKm9HnWK94tg5cX4IdCKovfeYM50oerENMk9TjI42uyWxX3PssEMbWaQwDtq6vxKT32qnITI88dVukFTbGysjtTrQJAEtphhODEJ3yo1MdJiwWpbJNjHeEqzibm53fSq_TAFDABBDH/s320/Shazzam3.JPG" border="0" alt=""id="Img5" /></a>
<br />
Вобщем, must-have, если вы серьезно настроены на создание своих эффектов :).</div><br />
<h2>Алгоритм Genie</h2><br /><div>Здесь мы посмотрим как заставить Джина
"свернуться" вниз. Сворачивание вверх, влево и вправо получается аналогично.<br />
<br />
В основе эффекта лежит простая идея: за время <i>t</i> нужно свернуть картинку в
точку (а точнее, в отрезок). При <i>t = 0</i> картинка полностью развернута, а при <i>t = 1</i> - полностью свернута. Как вы помните, функция пиксельного шейдера
возвращает значение цвета точки. Т.е.нам нужно определить цвет заданной точки, с
учетом текущего времени <i>t</i>. Это приводит нас к следующему коду:<br /><br />
<pre class="brush:csharp">
float t : register(c0); // Время
sampler2D implicitInput : register(s0); // сэмплер
float4 main(float2 uv : TEXCOORD) : COLOR
{
// [.. вычисление x1, y1 x2, y2 ..]
if (uv.x < x1 || uv.x > x2 || uv.y < y1 || uv.y >y2)
{
return float4(0, 0, 0, 0);
}
float2 pos = float2((uv.x - x1)/(x2 - x1), (uv.y - y1)/(y2 - y1));
return tex2D(implicitInput, pos);
}
</pre>
<br />
Мы проверяем, или выходит текущая точка <i>uv</i> за пределы области (x1,
y1, x2, y2). Если да - возвращаем прозрачный цвет (r = 0, g = 0, b = 0, a = 0),
если же нет, то мы должны вернуть такой цвет, чтобы
создался эффект "сжатия". Для этого мы отображаем ограничивающую нас
область на всю картинку, т.е. (x1, x2) -> (0, 1), и (y1, y2) -> (0, 1). Что дает
нам необходимые координаты: <i>x = (uv.x - x1)/(x2
- x1)</i>, <i>y = (uv.y - y1)/(y2 - y1)</i>.<br />
<br />
Осталось посчитать <i>x1, x2, y1, y2</i>. Начальные значения переменных
устанавливаются таким образом, чтобы вся картинка была видимой: <i>x1 = 0, y1 =
0, x2 = 1, y2 = 1</i>.Теперь мы вводим зависимость этих переменных от времени.
Если, например, <i>y1</i> будет увеличиваться пропорционально времени <i>t</i>: <i>y1 =
t</i>,
мы получим сжатие по вертикали:<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqQNspKynFwzmGstzHqzmlNrF23XJhykPg5JasetvRBUs8Xj8XXlyLTNcXh8EQifawhPcLajfWXxrRL2vb2cElaKyPPyRP2_Ij01yArhGyhOEHHd6bmv9HvVRqv7SMitgZMILcWVVIqzmk/s1600-h/SrinkSample.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 201px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqQNspKynFwzmGstzHqzmlNrF23XJhykPg5JasetvRBUs8Xj8XXlyLTNcXh8EQifawhPcLajfWXxrRL2vb2cElaKyPPyRP2_Ij01yArhGyhOEHHd6bmv9HvVRqv7SMitgZMILcWVVIqzmk/s320/SrinkSample.JPG" border="0" alt=""id="Img8" /></a><br />
Устанавливая более сложные зависимости, мы получаем
Genie-эффект. Эффект состоит из двух фаз. Фаза сжатия по оси Х длится 40%
времени:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjP9EaLxlkay9QEXS3V9_81y5V5NgvstDqxxZ_1c4RdB2gbEj0hF3HC9CqYuCfB3p5gb4XT2HhsS-wcZFetZbT48kjaMQo7NHakk4CrhkR4068B-N_caXI2IdrAPvo8fjR7OmQfRdM9R9m/s1600-h/ShrinkX.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 217px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjP9EaLxlkay9QEXS3V9_81y5V5NgvstDqxxZ_1c4RdB2gbEj0hF3HC9CqYuCfB3p5gb4XT2HhsS-wcZFetZbT48kjaMQo7NHakk4CrhkR4068B-N_caXI2IdrAPvo8fjR7OmQfRdM9R9m/s320/ShrinkX.JPG" border="0" alt=""id="Img9" /></a>
<br />
Фаза сжатия по оси Y (оставшиеся 60%):<br />
<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoyf0AQqFTTKJw9fNTuEc72aOClV7-BU6LKvl5q9rsw16IdI-RsfedvPja6LUBARIxFbtCZ9WCUOgDblWC35Zc4xFq8IfORjPeFBrMhswcFewpZMAqLrtK58UZlQIErfme107gsczWCTxP/s1600-h/ShrinkY.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 217px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoyf0AQqFTTKJw9fNTuEc72aOClV7-BU6LKvl5q9rsw16IdI-RsfedvPja6LUBARIxFbtCZ9WCUOgDblWC35Zc4xFq8IfORjPeFBrMhswcFewpZMAqLrtK58UZlQIErfme107gsczWCTxP/s320/ShrinkY.JPG" border="0" alt=""id="Img10" /></a>Для
вычисления <i>x1</i> и <i>x2</i> я воспользовался кубическими
<a href="http://ru.wikipedia.org/wiki/Кривая_Безье" target="_blank">кривыми Бизье</a>. Одна
кривая определяется четырьмя опорными точками P0, P1, P2 и P3. Кривая начинается
в P0 и заканчивается в P3. Внутренние точки P1 и P2 управляют формой кривой и
определяют начальный и конечный векторы касательных. Другими словами, кривая
Бизье третьего порядка начинается в точке P0 направляясь к точке P1 и
заканчивается в P3, подходя к ней со стороны точки P2:<br />
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WUq-NblChsLsTjfITuOO1lkIT_0m5q_OpfA6YTr5E5DTVorBctZNQX69LvDm_OAUnneUrcAKLpT3UUKY1V4l8R01xR0CzKrz421zMj16-_Hq1IXUs7PPHVbAeb0v3VkrtrruyWfUjqfo/s1600-h/Bezier.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 129px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0WUq-NblChsLsTjfITuOO1lkIT_0m5q_OpfA6YTr5E5DTVorBctZNQX69LvDm_OAUnneUrcAKLpT3UUKY1V4l8R01xR0CzKrz421zMj16-_Hq1IXUs7PPHVbAeb0v3VkrtrruyWfUjqfo/s320/Bezier.JPG" border="0" alt=""id="Img11" /></a>
<br />
Для нас началом кривой всегда является <i>P0 = 0</i>, а конец зависит от времени
<i>t</i>, центра лампы и ширины ее "горлышка". С учетом двухфазового
подхода к эффекту, мы получаем следующий HLSL код:
<pre class="brush:csharp">
float t : register(c0); // Время
float lampPosition : register(c1); // Центр Лампы
float lampWidth: register(c2); // Ширина Горлышка Лампы
sampler2D implicitInput : register(s0); // Сэмплер
// Параметрическая запись кубической кривой Бизье
// честно стибрена с http://ru.wikipedia.org/wiki/Кривая_Безье
float BezierInterpolate(float4 cp, float t)
{
float b0 = pow(1-t, 3);
float b1 = 3*t*pow(1-t,2);
float b2 = 3*t*t*(1-t);
float b3 = pow(t, 3);
return b0*cp[0] + b1*cp[1] + b2*cp[2] + b3*cp[3];
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
float x1=0, x2=1, y1=0, y2 = 1;
float4 lp, rp; // Опорные точки левой и правой кривых Бизье
// Фаза сжатия по оси Y
if (t > 0.4)
{
lp[3] = lampPosition - lampWidth/2;
rp[3] = lampPosition + lampWidth/2;
y1 = (5*t - 2)/3.0;
}
else // Сжимаемся по оси X.
{
lp[3] = t*(lampPosition - lampWidth/2)/0.4;
rp[3] = 1 + t*(lampPosition + lampWidth/2 - 1.0)/0.4;
y1 = 0;
}
// Подсчитываем значения осташихся опорных точек
// кривых Бизье.
lp[2] = 0.90*lp[3];
lp[1] = 0.25*lp[3];
lp[0] = 0;
rp[2] = 1.25*rp[3];
rp[1] = 1-(rp[3] - rp[2] - lampWidth/2);
rp[0] = 1;
lp = saturate(lp);
rp = saturate(rp);
// Вычисляем значение границ по оси X.
x1 = BezierInterpolate(lp, uv.y);
x2 = BezierInterpolate(rp, uv.y);
if (uv.x < x1 || uv.x > x2 || uv.y < y1 || uv.y >y2)
{
return float4(0, 0, 0, 0);
}
float2 pos = float2((uv.x - x1)/(x2 - x1), (uv.y - y1)/(y2 - y1));
return tex2D(implicitInput, pos);
}
</pre><br />
Единственной темной лошадкой остался вызов HLSL-функции saturate(),
применительно к опорным точкам кривых Бизье. Суть этого метода в отбрасывании
точек за границей отрезка [0, 1]:<br />
<pre>
function saturate(x)
{
if (x < 0) return 0;
if (x > 1) return 1;
return x;
}
</pre><br />
Очень удобно,
для проверки выхода за допустимые границы.<br />
<br />
Вот, собственно, и вся магия :).</div><br /><br />
<h2>Что Дальше?</h2><br />
<div>
Итак, вы дочитали до самого конца. Спасибо, мне очень приятно :)! Напоследок
могу предложить пару ресурсов, продолжающих тему эффектов и шейдеров:<br />
<br />
<ul>
<li><a href="http://www.codeplex.com/wpffx" target="_blank">WPFFX</a> - коллекция HLSL эффектов на
codeplex.com.</li>
<li><a href="http://wiki.truevision3d.com/tutorialsarticlesandexamples/programming_hlsl_shaders" target="_blank">Shaders and HLSL Programming</a>
- введение в HLSL на на английском.</li>
</ul>
Код к статье можно скачать <a href="http://dl.dropbox.com/u/5313583/ViValution/GenieDemo.zip">
здесь</a>. Буду рад вашим комментариям.<br />
<br />
Отличного настроения :)!</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com8tag:blogger.com,1999:blog-3946161110755638694.post-9839151758316091482009-04-07T05:19:00.000-07:002009-04-07T05:55:31.030-07:00UX PatternsПривет!
<br/><br/>
Наткнулся на очень интересное <a href='http://quince.infragistics.com/'>собрание UX Pattern'ов</a> (user experience patterns), от Infragistics. Сайт написан на Silverlight'e, что не может не радовать. Содержит детальное описание для каждого паттерна (в чем проблема, какое решение, в каком контексте используется, примеры). Можно так же смотреть связанные паттерны.
<br/><br/>
Вот несколько скринов (кликабельно):
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvjCtA6vIUUsM8h8kiZ6hzvVqxImFzl8k94KMHVESGj6mLCYLbU6smi_3gVOfKJeFkCj0jmjYpwghhcYAW6n-QzyccUKwh2kUGXapN59EZYFGC2FHwO0Z4URK0HHK7uFPTabINuAR7e8kz/s1600-h/Quince3.PNG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 258px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvjCtA6vIUUsM8h8kiZ6hzvVqxImFzl8k94KMHVESGj6mLCYLbU6smi_3gVOfKJeFkCj0jmjYpwghhcYAW6n-QzyccUKwh2kUGXapN59EZYFGC2FHwO0Z4URK0HHK7uFPTabINuAR7e8kz/s320/Quince3.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5321932262787960850" /></a>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIAiEImbzfdXh2SL1X8Su0Dhxy1rvBJS5HOoYAGFLxUBNFAHAMPyErgnmFJMZFIZlArtrbZAXCP7w3pFagYkeL6Dcr7ipvxCN5iIVYlcJZNQ339sY4ti2uVKrmaKu_dky_vkxhO5f2JbG0/s1600-h/Quince2.PNG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 258px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIAiEImbzfdXh2SL1X8Su0Dhxy1rvBJS5HOoYAGFLxUBNFAHAMPyErgnmFJMZFIZlArtrbZAXCP7w3pFagYkeL6Dcr7ipvxCN5iIVYlcJZNQ339sY4ti2uVKrmaKu_dky_vkxhO5f2JbG0/s320/Quince2.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5321932264050899810" /></a>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiQnAINwFqoSN_o1NuvTKPs8mRXOnWy36-3-5r6IqegmEDWAprykCwpTwarHO_UkS_YEoihyD6vgD1Cv1QK1aPv8SrQhwDetFbTXn-pI1BjY5j9qIkkTmF23-h-wlWKarFHXl6IVgBb4zx/s1600-h/Quince1.PNG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 258px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiQnAINwFqoSN_o1NuvTKPs8mRXOnWy36-3-5r6IqegmEDWAprykCwpTwarHO_UkS_YEoihyD6vgD1Cv1QK1aPv8SrQhwDetFbTXn-pI1BjY5j9qIkkTmF23-h-wlWKarFHXl6IVgBb4zx/s320/Quince1.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5321932259292384450" /></a>
<br/>
<br/>
Более того, можно предлагать свои находки. Молодцы, Infragistics. Молодцы!anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com5tag:blogger.com,1999:blog-3946161110755638694.post-13807611155259930152009-04-06T13:21:00.000-07:002010-03-12T12:28:58.422-08:00Triggers + Actions = FunПривет, друзья!<br />
<br />
Сегодня мы посмотрим на часть API MS Expression Blend 3.0 <b>триггеры</b> и <span class="Apple-style-span" style="font-weight: bold;">действия</span> (Triggers & Actions): что это такое, как их создавать и использовать. На закуску мы разработаем пару полезных действий: обновление стиля элемента по действию пользователя, и реализацию Command'a в Silverlight приложении, что сделает наш MVVM еще более эмвевеэмнее :).<br />
<h2>Что и как?</h2><br />
<div>
Проще всего представить себе эту парочку одним предложением:
<blockquote>Когда произойдет <i>А</i> сделай <i>Б</i>.</blockquote>Здесь А - это <span class="Apple-style-span" style="font-weight: bold;">триггер</span>, Б - <span class="Apple-style-span" style="font-weight: bold;">действие</span>. Например:<br />
<ul>
<li>Когда пользователь нажмет эту кнопку (trigger), показать MessageBox (action).
</li><li>Когда страница загрузится (trigger), установить фокус в это поле ввода (action).
</li><li>Когда пропадет сетевое подключение (trigger), перейти в автономный режим (action).</li><li>Когда рак на горе свистнет (trigger), получить прибавку к зарплате (action).
</li></ul></div>
<h3>Триггеры</h3><br />
<div>Очень похожи на триггеры в WPF, но с б<i><b>o</b></i>льшей долей свободы. Если раньше мы были ограничены триггерами, реагирующими на изменения dependency properties или на изменения в привязанных (data bound) свойств, то теперь триггером может быть что угодно (см. список выше).<br />
<br />
У каждого триггера есть коллекция действий, которые должны произойти в момент события. Все, что нужно для написания собственного триггера, это определить наступление того самого события и проинформировать все свои действия о том, что пора выполниться.
<br />
<br />
Запутанно? Давайте посмотрим на пример. В подтверждение широкого разнообразия природы триггеров, мы напишем триггер, который выполняет действия,
"когда рак на горе свистнет" :). Вот чудо-код:</div>
<div>
<pre class="brush: csharp;">
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Expression.Interactivity;
namespace TriggersActions
{
public class CrabWhistle : TriggerBase<UIElement>
{
private readonly Random _rand = new Random();
private Dispatcher _dispatcher;
protected override void OnAttached()
{
_dispatcher = Dispatcher;
new Timer(CrabLifeCycle, null, 100, 100);
}
private void CrabLifeCycle(object state)
{
if (_rand.Next(20) == 10)
{
_dispatcher.BeginInvoke(() => InvokeActions(null));
}
}
}
}
</pre>
</div><div>Чтобы стать триггером, мы унаследовались от TriggerBase<UIElement>. Generic-параметр сужает круг элементов, к которым может быть прикреплен наш триггер до UIElement'a. Как и с поведениями (Behaviors), наш триггер будет уведомлен о прикреплении к UIElement'у, вызовом метода <span class="Apple-style-span" style="font-style: italic;">OnAttached()</span>. Здесь-то мы и начинаем следить за раком: когда же он свистнет? Мы запустили таймер и там доверились генератору случайных чисел. Не самый достоверный показатель события "рак свистит", но достаточный для примера :).<br />
<br />
Когда наступает "тот самый" момент, мы вызываем метод
<span class="Apple-style-span" style="font-style: italic;">InvokeActions()</span>, который просто проходится по коллекции действий и заставляет их выполниться. Коллекция действий всегда доступна разработчику триггеров по свойству
<span class="Apple-style-span" style="font-style: italic;">Actions</span>.<br />
<br />
Из триггера мы так же можем обратиться к объекту, к которому прикреплен сам триггер (свойство
<span class="Apple-style-span" style="font-style: italic;">AssociatedObject</span>). Значит, мы можем подписаться и на события объекта и реагировать вызовом действий. Например, добавили триггер к кнопке и слушаем ее событие
<span class="Apple-style-span" style="font-style: italic;">Click</span>. В обработчике события вызываем
<span class="Apple-style-span" style="font-style: italic;">InvokeActions()</span>.
<br />
<br />
Итак, мы посмотрели на триггеры: объекты, инициирующие действия, по каким-то условиям. Но что же представляют собой действия?<br />
</div>
<h3>Действия</h3><br /><div>
<br />
Действия - это объекты типа <span class="Apple-style-span" style="font-style: italic;">TriggerAction</span>, реализующие метод <span class="Apple-style-span" style="font-style: italic;">Invoke()</span>. Именно этот метод вызывается методом <span class="Apple-style-span" style="font-style: italic;">InvokeActions()</span>, когда срабатывает триггер. "Позвольте, - скажете вы, - это же привязывает действия к триггерам". И будете абсолютно правы: действия имеют смысл лишь в контексте триггера. Давайте посмотрим на код действия, поднимающего нашу зарплату :) :</div><div>
<pre class="brush: csharp;">
using System;
using System.Windows.Controls;
using Microsoft.Expression.Interactivity;
namespace TriggersActions
{
public class IncreaseSalary : TargetedTriggerAction<TextBlock>
{
protected override void Invoke(object param)
{
Target.Text = string.Format("Зарплата поднята: {0}", DateTime.Now);
}
}
}
</pre></div><div>Здесь мы унаследовались от потомка <span class="Apple-style-span" style="font-style: italic;">TriggerAction</span>, а именно от <span class="Apple-style-span" style="font-style: italic;">TargetedTriggerAction<TextBlock></span> - разновидность действия из API Blend'a, позволяющая указать <span class="Apple-style-span" style="font-style: italic;">на ком</span> будет выполнятся действие. Мы выбрали <span class="Apple-style-span" style="">TextBlock</span>, чтобы обрадовать пользователя о повышении зарплаты. Целевой <span class="Apple-style-span" style="">TextBlock</span> будет определен в xaml.
<br />
<br />
Стоит отметить, что действия могут быть исключены из обработки, установкой свойства
<span class="Apple-style-span" style="font-style: italic;">IsEnabled</span> в
<span class="Apple-style-span" style="font-style: italic;">false</span>. Например, действие
<span class="Apple-style-span" style="font-style: italic;">IncreaseSalary</span> может устанавливать это значение в
<span class="Apple-style-span" style="font-style: italic;">false</span>, если на дворе случится какой-нибудь кризис. Но, поскольку это не наш случай, у нашего действия значение свойства
<span class="Apple-style-span" style="font-style: italic;">IsEnabled</span> оставлено по умолчанию:
<span class="Apple-style-span" style="font-style: italic;">true</span>.<br />
<br />
Поскольку действия имеют смысл только с триггерами, давайте соберем все вместе и посмотрим, на реализацию "<span class="Apple-style-span" style="font-style: italic;">Поднять зарплату, когда рак на горе свистнет</span>".<br /> </div>
<h3>Триггеры + Действия</h3><br />
<div>После компиляции в Blend'e сильверлайт-проекта с определенными выше классами
триггера и действия, на вкладке Behaviors мы найдем только
<span class="Apple-style-span" style="font-style: italic;">IncreaseSalary</span>:</div><br />
<br />
<a href="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary.PNG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}">
<img border="0" alt="" src="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary.PNG"
style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 258px;"/>
</a><div>
<br />
Перетащив это действие на единственный TextBlock, определенный в нашем xaml-файле, мы получим следующую картину:<br />
<br />
</div><div><a href="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary1.PNG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}">
<img border="0" alt="" src="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary1.PNG"
style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 258px;"/>
</a>
</div><div>
<br />
Expression Blend создал для нас триггер типа EventTrigger (поставляемый вместе с API), и поместил наше действие в качестве действий триггера:<br />
</div>
<pre class="brush: xml;">
<TextBlock VerticalAlignment="Top" Text="TexBlock1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ta:IncreaseSalary/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</pre><div>EventTrigger - весьма полезная штуковина, позволяющая при наступлении CLR события выполнять actions. Но нам-то нужно не простое CLR-событие, а событие "<span class="Apple-style-span" style="font-style: italic;">рак свистнул</span>". С интерфейсом Blend'a менять тип триггера одно удовольствие:
<br />
<br />1. В Objects and Timeline под нашим TextBlock'ом выбираем
<span class="Apple-style-span" style="font-style: italic;">IncreaseSalary</span>.<br />2. На вкладке свойств (Properties) тыцькаем New напротив TriggerType.
<br />
3. В появившемся окошке выбираем наш триггер
<span class="Apple-style-span" style="font-style: italic;">CrabWhistle<br />
</span> <br />
<a href="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary2.PNG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}">
<img border="0" alt="" src="http://dl.dropbox.com/u/5313583/ViValution/ActionsSalary2.PNG"
style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 258px;"/>
</a>
<br />
<br />
После всех проделанных операций мы получим следующую xaml-разметку:<br />
<pre class="brush: xml;"><UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity" xmlns:local="clr-namespace:TriggersActions"
x:Class="TriggersActions.MainControl"
Width="400" Height="400">
<Grid x:Name="LayoutRoot">
<TextBlock VerticalAlignment="Top" Text="TextBlock" TextWrapping="Wrap">
<i:Interaction.Triggers>
<local:CrabWhistle>
<local:IncreaseSalary/>
</local:CrabWhistle>
</i:Interaction.Triggers>
</TextBlock>
</Grid>
</UserControl>
</pre>
<br />
Запускаем приложение, и после первого же свистка рака наша зарплата повышается. Свисти, дружок, свисти :)!
<br />Стоит обратить внимание на то, что мы можем указать
<span class="Apple-style-span" style="font-style: italic;">IncreaseSalary</span> выбрать другой TextBlock для сообщения добрых вестей. Достаточно установить ему свойство
<span class="Apple-style-span" style="font-style: italic;">TargetName</span>. Например, следующий код будет так же прекрасно работать:<br />
</div>
<pre class="brush: xml;"><Grid x:Name="LayoutRoot">
<i:Interaction.Triggers>
<local:CrabWhistle>
<local:IncreaseSalary TargetName="txtInfo"/>
</local:CrabWhistle>
</i:Interaction.Triggers>
<TextBlock Name="txtInfo" Text="TextBlock"/>
</Grid>
</pre>
<div>Единственный элемент, на который мы не обратили до сих пор внимание - это Attached Property под названием <span class="Apple-style-span" style="font-style: italic;">Interaction.Triggers</span>. Суть работы этого свойства ничем не отличается от сути работы аналогичного свойства для поведений <span class="Apple-style-span" style="font-style: italic;">Interaction.Behaviors</span> (см. описание в
<a href="http://vivalution.blogspot.com/2009/03/behaviors.html">прошлом посте</a>).<br />
<br />
Прежде чем мы перейдем к созданию интересных действий, давайте подведем итоги:<br />
<br />
1. Прикрепленное свойство
<span class="Apple-style-span" style="font-style: italic;">Interaction.Triggers</span> позволяет определить коллекцию триггеров для UIElement'a (точнее: для любого DependencyObject'a).<br />
2. Триггер содержит коллекцию действий, которые выполняются вызовом метода
<span class="Apple-style-span" style="font-style: italic;">InvokeActions()</span>.<br />
3. Каждое действие реализует метод
<span class="Apple-style-span" style="font-style: italic;">Invoke()</span>, вызываемый при срабатывании триггера.</div>
<h2>Действие: обновление стиля.</h2><br />
<div>Очень просто в реализации:</div>
<pre class="brush: csharp">
using System.Windows;
using Microsoft.Expression.Interactivity;
namespace TriggersActions
{
public class SetStyleAction : TargetedTriggerAction<FrameworkElement>
{
public static readonly DependencyProperty StyleProperty =
DependencyProperty.Register("Style",
typeof(Style),
typeof(SetStyleAction),
new PropertyMetadata(null));
public Style Style
{
get
{
return (Style)GetValue(StyleProperty);
}
set
{
SetValue(StyleProperty, value);
}
}
protected override void Invoke(object parameter)
{
if (Target != null)
{
Target.SetValue(FrameworkElement.StyleProperty, Style);
}
}
}
}
</pre>
<div>
Как видите, мы определили свойство <i>Style</i>, и в момент срабатывания
триггера просто устанавливаем значение стиля FrameworkElement'у. Так, следующий
код, при наведении мышкой установит стиль Canvas'у, превращая цвет фона в
зеленый:</div>
<pre class="brush: xml">
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
xmlns:ta="clr-namespace:TriggersActions"
x:Class="TriggersActions.MainControl"
Width="400" Height="400">
<UserControl.Resources>
<Style x:Key="CanvasStyle" TargetType="Canvas">
<Setter Property="Background" Value="Green"/>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Canvas Width="200" Height="200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ta:SetStyleAction Style="{StaticResource CanvasStyle}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="Hover me"/>
</Canvas>
</Grid>
</UserControl>
</pre>
<div>Просто, не правда ли?</div>
<h2>Действие: вызов команд ViewModel'a.</h2><br />
<div>В сборке Microsoft.Expression.Interactivity есть класс, под названием
InvokeCommandAction. Но это действие предназначено для работы исключительно в
контексте поведения (Behavior). Во время вызова действия происходит поиск
свойства типа ICommand с именем CommandName у поведения, к которому прикрепелен
наш InvokeCommandAction. Мягко говоря, не совсем то, что нам нужно.<br />
<br />
Мы хотим создать действие, которое будет выполнятся в рамках триггера (не
поведения), и будет запускать команду с указанным именем, определенную в
DataContext'e. Т.е создать аналог следующего WPF кода:</div>
<pre class="brush: xml">
<Button Command={Binding DoSomethingCommand} />
</pre>
<div>Приступим!<br />
<br />
<b>Код C#:</b></div>
<pre class="brush: csharp">
using System.Windows;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
namespace TriggersActions
{
public class InvokeCommandAction : TriggerAction<FrameworkElement>
{
public static readonly DependencyProperty CommandNameProperty=
DependencyProperty.Register("CommandName", typeof(string), typeof(InvokeCommandAction),
new PropertyMetadata(DependencyProperty.UnsetValue));
public string CommandName
{
get { return (string)GetValue(CommandNameProperty); }
set { SetValue(CommandNameProperty, value); }
}
protected override void Invoke(object parameter)
{
var command = ResolveCommand();
if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
private ICommand ResolveCommand()
{
var dc = AssociatedObject.GetValue(FrameworkElement.DataContextProperty);
return (dc.GetType()).GetProperty(CommandName).GetValue(dc, null) as ICommand;
}
}
}
</pre>
<div>Мы выставляем наружу строковое свойство <i>CommandName</i>, и в
методе <i>Invoke()</i> пытаемся найти соответствующую этому имени команду. Для
поиска команды мы берем DataContext объекта, к которому прикреплен наш
TriggerAction, и через reflection пытаемся получить значение свойства с нужным
именем.
<br />
<br />
Но почему бы просто не определить свойство типа ICommand и использовать
привычный {Binding SomeCommand}? Дело в классе Binding. Для того, чтобы значению
свойства присвоить Binding-выражение, наличия одного лишь DependencyProperty
недостаточно в Silverlight'e. Необходимо чтобы объект, в котором определено это
dependency-свойство был типа FrameworkElement (именно такие ограничения
накладывает Silverlight на использование Binding'a). А TriggerActoin является
потомком DependencyObject'a. Ну да не беда. Чем горевать (или радоваться) об
отсутствии множественного наследования в .NET, мы взяли на вооружение
Reflection. </div>
<h3>Пример</h3><br />
<div>Давайте посмотрим на InvokeCommandAction в действии. Мы создадим приложение,
показывающее текущее время по требованию пользователя. ViewModel имеет два
свойства: команда для обновления текущего времени, и свойство, возвращающее
текущее время:<br />
</div>
<pre class="brush: csharp;">
using System;
using System.ComponentModel;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Input;
namespace TriggersActions
{
public class MainControlViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand UpdateTimeCommand { get; private set; }
public string Time { get { return DateTime.Now.ToString(); } }
public MainControlViewModel()
{
UpdateTimeCommand = new ActionCommand(() => InvokePropertyChanged("Time"));
}
private void InvokePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
</pre>
<div>Здесь класс ActionCommand - это реализация
<a href="http://msdn.microsoft.com/en-us/library/cc707894.aspx">
DelegateCommand'a</a>, из
<a href="http://msdn.microsoft.com/en-us/library/dd458807.aspx">Composite
Application Guidance for WPF</a>. Самим создавать класс не придется: в сборке
Microsoft.Expression.Interactivity он уже определен :). Правда, он не позволяет
переопределить метод <i>CanExecute()</i>, но вы всегда можете написать свой
собственный ActionCommand - не велика беда.
<br />
<br />
XAML выглядит так:</div>
<pre class="brush: xml;">
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
xmlns:ta="clr-namespace:TriggersActions"
x:Class="TriggersActions.MainControl"
Width="400" Height="400">
<UserControl.Resources>
<ta:MainControlViewModel x:Key="vm"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource vm}" >
<Canvas>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ta:InvokeCommandAction CommandName="UpdateTimeCommand"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="textBlock"
Canvas.Left="31" Canvas.Top="46"
Text="{Binding Time}"
TextWrapping="Wrap"/>
</Canvas>
</Grid>
</UserControl>
</pre>
<div>Теперь каждый раз, при наведении мышки на текст мы получим самое свежее время.
Слава MVVM'у :)!</div>
<h2>Итак</h2><br />
<div>Для тех, кто пролистал весь этот пост, в поисках выводов :). Мы посмотрели на
работу триггеров и действий. Написали никому не нужный триггер "свистящий рак",
который свистел по законам рандомайзера. К сожалению, поместить рака на гору мне
не удалось - очень много кода не способствовало бы пониманию концепции. Затем мы
натравили на каждый свисток рака действие, повышающее зарплату пользователя
(сложно назвать это действие бесполезным :)). Ну и самый конец статьи, возможно
будет интересен приверженцам MVVM-подхода в Silverlight'e: Мы реализовали
типичный
<a href="http://msdn.microsoft.com/en-us/library/cc707894.aspx">
DelegateCommand</a>.<br />
<br />
С нетерпением буду ждать ваших комментариев и пожеланий :)!
<br />
<br />
Отличного настроения и программирования, друзья!<br />
<br />
<b>Код к статье</b>: <a href="http://dl.dropbox.com/u/5313583/ViValution/TriggersActions.zip">
здесь</a>.</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com2tag:blogger.com,1999:blog-3946161110755638694.post-3843826661660380632009-03-29T07:56:00.000-07:002009-03-31T13:54:30.071-07:00Behaviors: Как это работает?В <a href="http://vivalution.blogspot.com/2009/03/behaviors-blend-wow.html">прошлом</a> посте я рассказал о том, что такое поведения, и показал простенький пример, реализующий поведение "перетягивабельности" UIElement'a. Здесь же мы посмотрим, что заставляет поведения работать.
Для того, чтобы вы комфортно себя чувствовали при чтении поста, от вас, дорогой читатель, ожидается
понимание модели свойств, представленной в WPF/SL:
<a href="http://msdn.microsoft.com/en-us/library/ms749011.aspx">Attached Properties</a> и
<a href="http://msdn.microsoft.com/en-us/library/ms752914.aspx">Dependency Properties</a>. Если же для вас эти названия в новинку - не отчаивайтесь,
в скором времени мы пройдемся и по этим товарищам, разметая туман загадочности
метлой желания и разума :).<br /> <br />
<h2>Поехали!</h2><br />
<div>Давайте посмотрим, на пример поведения, из
<a href="http://vivalution.blogspot.com/2009/03/behaviors-blend-wow.html">предыдущего</a> сообщения:</div><br/>
<pre class="brush: xml">
<Rectangle Fill="Green" Width="40" Height="40" x:Name="rct">
<i:Interaction.Behaviors>
<local:DragBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
</pre>
<div>Класс <i>Interaction</i> определяет присоединенное свойство (Attached Property) типа
<i>BehaviorCollection</i>:<br /><br />
<pre class="brush: csharp">
BehaviorsProperty = DependencyProperty.RegisterAttached(
"Behaviors",
typeof (BehaviorCollection),
typeof (Interaction),
new PropertyMetadata(new PropertyChangedCallback(Interaction.OnBehaviorsChanged))
);
</pre>
Когда мы прикрепляем к некоторому объекту это свойство, в рамках нашего класса-сервиса <i>
Interaction</i>, вызывается метод <i>OnBehaviorsChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)</i>. Первым параметром этого метода
будет объект, к которому мы присоединили attached property, а во втором мы
найдем список определенных поведений. (В нашем примере: <i>obj</i> ==
прямоугольник <i>rct</i>, а args.NewValue == коллекция, с одним элементом <i>
DragBehavior</i>). Имея эти два козыря на
руках, класс <i>Interaction</i>, в методе <i>OnBehaviorsChanged()</i> проходится по коллекции
поведений, и у каждого из поведений вызывает метод <i>Attach()</i>, передавая объект, к
которому был присоединен attached property:<br /><br />
<pre class="brush: csharp">
//... Где-то в OnBehaviorsChanged()
foreach (Behavior behavior in behaviorsCollection)
{
behavior.Attach(obj);
}
</pre>
Дальше остается дело за малым: базовый класс <i>Behavior</i> устанавливает свое
свойство <i>AssociatedObject</i> в переданный ему <i>obj</i>, и вызывает виртуальный метод
<i>OnAttached()</i>, чтобы известить своих наследников о произошедшем соединении :).
Именно этот метод мы использовали в примере из прошлого поста, чтобы подписаться
на события от мышки.<br /> <br />
<pre class="brush: csharp">
public class DragBehavior : Behavior<UIElement>
{
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
}
// ...
}
</pre>
<br/>
Последний штрих: мы не можем напрямую унаследоваться от класса <i>Behavior</i> (у
него internal конструктор). Вместо этого нам предлагают воспользоваться
обобщенным классом <i>Behavior<T></i>, где T - <i>DependencyObject</i>.
<br />
<br />
Почему на <i>T</i> накладывается ограничение - быть <i>DependencyObject</i>'ом понятно:
attached property можно прикрепить только к <i>DependencyObject</i>'у. Но
почему же мы не можем напрямую унаследоваться от Behavior? Зачем делать
конструктор внутренним? - Для нашего же блага :).
<br />
<br />
Например, мы захотим написать поведение применимое только к панелям. Вместо
того, чтобы ходить каждый раз после крэша в Graphic Design Department, и
объяснять дизайнерам: "Пожалуйста, не перетаскивайте поведение с названием
OnlyForPanelsBehavior, на кнопки - это сломает систему", мы просто наследуемся
от <i>Behavior<Panel></i>, и Blend не даст перетянуть поведение на кого-то, отличного
от Panel'a :). Удобно, не так ли?</div>
<br />
<h2>Что дальше?</h2><br />
<div>Айда писать классные, потрясающие и просто захватывающие поведения :)!
Поделиться своим произведением всегда можно на сайте
<a href="http://gallery.expression.microsoft.com/">Microsoft Expression Gallery</a>.
<br />
<br />
К сожалению, мой обзор поведений остается неполным без Trigger'ов и Action'ов. В
ближайшем будущем (на следующей неделе) я собираюсь избавиться от этого пробела
:).<br />
<br />
Отличного программирования, друзья! </div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com1tag:blogger.com,1999:blog-3946161110755638694.post-44175037684287765282009-03-25T23:10:00.000-07:002010-03-12T12:32:53.908-08:00Behaviors + Blend = WOW!<h2>Что такое Behavior?</h2>
<br/>
<div>На Mix 09 парни из Microsfot показывали 3-й Expression Blend, который уже можно <a href="http://www.microsoft.com/expression/try-it/blendpreview.aspx">скачать</a> и установить (ставится на ура рядом со вторым блендом). В новом бленде появилась возможность создавать Behaviors - поведения. Сложно сказать конкретно, что такое "поведение", т.к. оно может быть чем угодно :). Поведения наделяют объект каким-либо свойством. Например, на <a href="http://videos.visitmix.com/MIX09/C27M">презентации</a> длиною в 18 минут, <a href="http://blois.us/blog/">Pete Blois</a> дает возможность кнопкам:</div><div><ul><li>Соответствовать законам физики: при запуске приложения кнопки начинают падать.
</li><li>Обладать свойством перетаскивабельности: можно потянуть кнопку мышкой. А т.к. кнопка еще и действует по законам физики, вы получите некоторое последействие, когда отпустите мышку. Кнопка поедет чуточку дальше :).</li><li>Обладать магнетизмом: кнопка начинает притягивать другие объекты :).</li></ul><div>Поведения работают как в Silverlight 3.0, так и в WPF. В Preview версии Blend'a нет стандартных поведений:</div>
<div>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8t1aUx2YyE8x6fepcs-t9PiNQHcVgXsrK_h4uZM8AANBgDZo3jyGc7Qa6UXkVXkUZ3A6DZcUoW4PPWNoTwtPogv6FJ6bUHG3sXaUrwv8vVHdthY3t8gqIpNu6lm6cAmoggfZ7BwBa6s-C/s1600-h/NoBehaviors.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 281px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8t1aUx2YyE8x6fepcs-t9PiNQHcVgXsrK_h4uZM8AANBgDZo3jyGc7Qa6UXkVXkUZ3A6DZcUoW4PPWNoTwtPogv6FJ6bUHG3sXaUrwv8vVHdthY3t8gqIpNu6lm6cAmoggfZ7BwBa6s-C/s320/NoBehaviors.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5317382851638227954" /></a>
</div><div>но, на сайте <a href="http://gallery.expression.microsoft.com/">Microsoft Expression Community Gallery</a>, уже можно <a href="http://gallery.expression.microsoft.com/MIXBehaviorPack">скачать</a> те поведения, что использовал Pete в своей презентации :).</div><div>
</div><h2>Как создать свое поведение?</h2><div>Здесь приводится алгоритм создания своего поведения на примере DragBehavior'a, наделяющего объект свойством перетягивабельности :).</div>
<div>
</div><div>1. Для начала стоит поставить <a href="http://www.microsoft.com/expression/try-it/blendpreview.aspx">третий бленд</a> :). </div>
<div>2. Создаем новый Silverlight Project (с равным успехом, можно создавать WPF проект, но пример мы сделаем на Silverlight'e).</div>
<div>3. В References нашего Silverlight приложения добавляем сборку "<span class="Apple-style-span" style="font-style: italic;">Microsoft.Expression.Interactivity.dll</span>" (по умолчанию, она находится в папке "<span class="Apple-style-span" style="font-style: italic;">C:\Program Files\Microsoft Expression\Blend 3 Preview\Libraries</span>") :</div>
<div>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrrwEohD4_-G9NQTM65wcPV_eLAzcgIR3KLkajGtGWgZnmiSitjFW72ptKWcoJSUK8bnpO2M-PAZQPn26AT5zBg4hGsW4G9QjtG__3fMFfymV1oARS7BAS6OVVnWMeAdlt9QEOiJsMEZA4/s1600-h/Ref.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 267px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrrwEohD4_-G9NQTM65wcPV_eLAzcgIR3KLkajGtGWgZnmiSitjFW72ptKWcoJSUK8bnpO2M-PAZQPn26AT5zBg4hGsW4G9QjtG__3fMFfymV1oARS7BAS6OVVnWMeAdlt9QEOiJsMEZA4/s320/Ref.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5317394545737641874" /></a>
</div>
<div>4. Теперь нам нужно определить новый класс поведения. Поскольку я ленивый, сделаю это прямо в файле с Code behind'ом для моего Entry Point'a в приложение. После редактирования, он выглядит так:
</div><div>
<pre class="brush: csharp">
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Expression.Interactivity;
namespace SilverlightApplication1
{
public partial class MainControl : UserControl
{
public MainControl()
{
// Required to initialize variables
InitializeComponent();
}
}
public class DragBehavior : Behavior<UIElement>
{
private bool _isDragging = false;
private Point _prevPoint;
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
}
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_isDragging = false;
AssociatedObject.ReleaseMouseCapture();
}
void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (!_isDragging)
{
return;
}
var currentPoint = e.GetPosition(null);
var left = Canvas.GetLeft(AssociatedObject) +
currentPoint.X - _prevPoint.X;
var top = Canvas.GetTop(AssociatedObject) +
currentPoint.Y - _prevPoint.Y;
Canvas.SetLeft(AssociatedObject, left);
Canvas.SetTop(AssociatedObject, top);
_prevPoint = currentPoint;
}
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDragging = true;
_prevPoint = e.GetPosition(null);
AssociatedObject.CaptureMouse();
}
}
}
</pre>
</div>
<div> Как видите, новое поведение наследуется от Generic-класса "<span class="Apple-style-span" style="font-style: italic;">Behavior</span>". В качестве параметра шаблона мы передаем <span class="Apple-style-span" style="font-style: italic;">UIElement</span>, именно к такого типа элементам применимо наше поведение. Единственное ограничение на параметр шаблона: он обязательно должен быть DependencyObject'ом (Почему? - Читайте в следующем посте)</div><div>
</div><div>Мы также перекрыли вызов метода "<span class="Apple-style-span" style="font-style: italic;">OnAttached()</span>", что произойдет, когда наше поведение будет присоединено к UIElement'у. Сам UIElement доступен через свойство "<span class="Apple-style-span" style="font-style: italic;">AssociatedObject</span>". Имея в распоряжении полный доступ к элементу, мы подписываемся на его события мышки, и при зажатой левой кнопке меняем координаты Canvas.Left, Canvas.Top. Конечно, это накладывает ограничения: чтобы пример заработал, нужно положить UIElement в Canvas. </div>
<div>5. Скомпилируем проект, и пойдем в Asset Library. На вкладке Behaviors мы найдем наше поведение:</div>
<div>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_KNWAWsRgGHlfWa8TAh2l5MG2DBJPHQQ0-dgSARj-zie4XdJxU33xexMzkzaeZXOu9OSg42zoiUUR8xMX5kl2rwo51Y0Wi08GsO2FvGLcxNtqwWeBV5yzMwSfj2oBdglj4x_B7aOCitaA/s1600-h/AssetsLib.JPG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 281px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_KNWAWsRgGHlfWa8TAh2l5MG2DBJPHQQ0-dgSARj-zie4XdJxU33xexMzkzaeZXOu9OSg42zoiUUR8xMX5kl2rwo51Y0Wi08GsO2FvGLcxNtqwWeBV5yzMwSfj2oBdglj4x_B7aOCitaA/s320/AssetsLib.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5317395202013496130" /></a>
</div><div>
</div><div>6. Теперь, имея следующую разметку нашего Root Visual'a:</div><div>
<pre class="brush: xml">
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication1.MainControl"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="White">
<Canvas>
<Rectangle Fill="Green" Width="40" Height="40"/>
</Canvas>
</Grid>
</UserControl>
</pre>
</div><div>Мы можем перетащить DragBehavior на Rectangle. После этого, наш Rectangle получает... присоединенное DependencyProperty: Interaction.Behaviors:</div><div>
<pre class="brush: xml">
<Rectangle Fill="Green" Width="40" Height="40">
<i:Interaction.Behaviors>
<local:DragBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
</pre>
</div><div>7. <a href="http://dl.dropbox.com/u/5313583/ViValution/behaviorTest.html">Запустив приложение,</a> мы сможем возькать мышкой прямоугольник.</div><div>
</div>
<h2>Как работают поведения?</h2><div>
</div><div>Об этом читайте в следующем посте :). </div></div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com0tag:blogger.com,1999:blog-3946161110755638694.post-44082779426973675102009-03-24T14:16:00.000-07:002009-04-03T07:45:03.428-07:00Хорошего работника узнают по инструментуПословица честно <strike>стибрена</strike> позаимствована из книги Брукса <a href="http://ru.wikipedia.org/wiki/%D0%9C%D0%B8%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%87%D0%B5%D0%BB%D0%BE%D0%B2%D0%B5%D0%BA%D0%BE-%D0%BC%D0%B5%D1%81%D1%8F%D1%86">"МЧМ"</a> :). Однако, чем больше работаешь с кодом, тем лучше понимаешь важность выбора хороших инструментов. Хочу поделиться инструментарием, который может пригодится в повседневной жизни WPF-разработчика, и которым активно пользуюсь сам.
Если какой-либо инструмент был незаслуженно пропущен, пожалуйста, дайте знать. Я непременно обновлю пост и буду бесконечно благодарен :).
<br/>
<h2>Visual Studio 2008</h2>
<br/>
Не шучу. Многие ли из нас могут похвастаться доскональным знанием всех возможностей этого шедевра? Этот инструмент не раз удивлял меня. Предлагаю несколько находок, полезных в мире WPF/Silverlight:
<br/><br/>
<span style="font-weight: bold;">1. Debug Designer.</span> Как часто доводилось вам видеть следующую картину
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYiYQas0fUKzrZWBuR0SyonPqS-HlIlMkHBD414u1BT51oLVW_GJFSkBONKaPNOBEPuu_S4kiVKbXY7j9GDe4L876Dr6GXandgaJuIxwj210ImL0muQuQlMrkios5jrCCTd3UJh5zudJiK/s1600-h/Designer_Fail.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 264px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYiYQas0fUKzrZWBuR0SyonPqS-HlIlMkHBD414u1BT51oLVW_GJFSkBONKaPNOBEPuu_S4kiVKbXY7j9GDe4L876Dr6GXandgaJuIxwj210ImL0muQuQlMrkios5jrCCTd3UJh5zudJiK/s320/Designer_Fail.JPG" alt="" id="BLOGGER_PHOTO_ID_5316874147704763746" border="0" /></a>
Мне, увы, чаще, чем хотелось бы. Быстро найти проблему помогает... второй экземпляр студии. Запускаем еще одну студию. Присоединяемся к процессу первой студии (Tools -> Attach to Process...):
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpFdgOz4zwts4N8mCJiCAsMlEhw5O9WC-A2h7U-pv9RiDh0vK9t7DYKYLqBOAX3WyQCZfYpLRf_3drq3eelsG7Umajhh0NlpptldjgfqY73yt7NH4_DcW99pv_buqGD1QWNAWWmf-HjnMr/s1600-h/AttachStudio.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 224px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpFdgOz4zwts4N8mCJiCAsMlEhw5O9WC-A2h7U-pv9RiDh0vK9t7DYKYLqBOAX3WyQCZfYpLRf_3drq3eelsG7Umajhh0NlpptldjgfqY73yt7NH4_DcW99pv_buqGD1QWNAWWmf-HjnMr/s320/AttachStudio.JPG" alt="" id="BLOGGER_PHOTO_ID_5316874371828543842" border="0" /></a>
<br/>
И жмем в первой студии (с обломавшимся дизайнером) кнопочку Reload Designer. Второй экземпляр студии быстро (ну или не очень - зависит от вашего бортового CPU :)) останавливается на ошибке:
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMgcAm9BZh328QAxxmCNRRvc2X9q1gJDAAXdHhyphenhyphenGWmjrYivliIiEXx9j7yG8tWZUgGPExpX9tlfESaX2fjSBKubpNJCrpZAFaqJTGvkeawFy2zsipjXl8bU8DUGVOLJ96udHLlzFYqO3-7/s1600-h/Fail_Found.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 260px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMgcAm9BZh328QAxxmCNRRvc2X9q1gJDAAXdHhyphenhyphenGWmjrYivliIiEXx9j7yG8tWZUgGPExpX9tlfESaX2fjSBKubpNJCrpZAFaqJTGvkeawFy2zsipjXl8bU8DUGVOLJ96udHLlzFYqO3-7/s320/Fail_Found.JPG" alt="" id="BLOGGER_PHOTO_ID_5316874576603228066" border="0" /></a>
<br/>
Ага! В моем случае, причиной ошибки был <span style="font-style: italic;">{StaticResource ...}</span>: он был определен в App.xaml, и во время выполнения приложение не падало . Изменив определение на <span style="font-style: italic;">{DynamicResource ...}</span>, я получил работающий дизайнер и приложение (как и прежде):
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKSYaz87FdTpQddVI8SW9JUub6DpuKzQEmq5Fkkxk8uzrwqZldx_0JWW_UiiRLe3F_2tePHRRawW4hOB7WKa_b-ntaHZG21WPeRc0OXXq2drRiRV3ysE2r9WYfABT2eCXltqg-j7xAkqme/s1600-h/Desinger_Resolved.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 264px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKSYaz87FdTpQddVI8SW9JUub6DpuKzQEmq5Fkkxk8uzrwqZldx_0JWW_UiiRLe3F_2tePHRRawW4hOB7WKa_b-ntaHZG21WPeRc0OXXq2drRiRV3ysE2r9WYfABT2eCXltqg-j7xAkqme/s320/Desinger_Resolved.JPG" alt="" id="BLOGGER_PHOTO_ID_5316874830631529106" border="0" /></a>
<br/>
Спасибо, студия. Кстати, этот же трюк сработает и с дизайнером Expression Blend: просто присоединитесь к процессу Blend'a.
<br/>
<br/>
<span style="font-weight: bold;">2. Debug .NET Code.</span> Иногда просто лень читать документацию, и хочется посмотреть как это сделано внутри. Reflector - первый инструмент который приходит на ум, но он, к сожалению, не показывает комментарии к исходникам (во всяком случае, пока :) ). Студия и здесь может оказаться полезной. Идем в настройки, и включаем Enable .NET Framework Source Stepping:
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ_qdoEoBY8QmCsfnXJUC3d6v89gTVxWqh-ifBZWrtYMrhEtkllvXDaLgB6Uo5rpywnkDs4YPUBEloJmScbMj06QznOkx4b8nSp_3idTIINggxfkrQTmnbJlbBqnTKKtlaHmW1p9Ku-gM8/s1600-h/SourceStepping.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 190px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ_qdoEoBY8QmCsfnXJUC3d6v89gTVxWqh-ifBZWrtYMrhEtkllvXDaLgB6Uo5rpywnkDs4YPUBEloJmScbMj06QznOkx4b8nSp_3idTIINggxfkrQTmnbJlbBqnTKKtlaHmW1p9Ku-gM8/s320/SourceStepping.JPG" alt="" id="BLOGGER_PHOTO_ID_5316879199740113826" border="0" /></a>
<br/>
После вашего согласия на изменение некоторых зависимых настроек и Microsfot сервера символов, прийдется немножко подождать, пока студия выкачает необходимые файлы.
<br/><br/>
Например, мы хотим посмотреть, почему наш TextBox не получает фокус ввода при вызове <span style="font-style: italic;">txtName.Focus()</span>. Устанавливаем бряк поинт на вызов, и жмем F11:
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaUWE8joNP8_uKD2-cq3ZGWx2uRP38MyPWktDMTRsxnaQhDcLyLIOPa03ypopC4IYtHU1AdaLAyZVL-NPX5aFa2pYdiuG0xMFV9rHanhPwAwL_OV4UpZkesO8VCaSX7F1n6YOZj-gcGWnA/s1600-h/BreakSet.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 264px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaUWE8joNP8_uKD2-cq3ZGWx2uRP38MyPWktDMTRsxnaQhDcLyLIOPa03ypopC4IYtHU1AdaLAyZVL-NPX5aFa2pYdiuG0xMFV9rHanhPwAwL_OV4UpZkesO8VCaSX7F1n6YOZj-gcGWnA/s320/BreakSet.JPG" alt="" id="BLOGGER_PHOTO_ID_5316879501616158322" border="0" /></a>
<br/>
Пройдя последовательно по вызовам, мы можем прочитать все необходимые условия для передачи фокуса:
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzY7KFXITaWhtgklsmy5rXLfn9aeZjtIc6HZyesHWD9Prgoem7H6zgxh1iqdcicpx6Lgyok5o7wemUvcSU0zU7kOUHaGA4zouemya9ObxCLLtaZF_0kqMYR1upYiMtRl8T2JLGZHueWOCd/s1600-h/IsFocusable.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 264px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzY7KFXITaWhtgklsmy5rXLfn9aeZjtIc6HZyesHWD9Prgoem7H6zgxh1iqdcicpx6Lgyok5o7wemUvcSU0zU7kOUHaGA4zouemya9ObxCLLtaZF_0kqMYR1upYiMtRl8T2JLGZHueWOCd/s320/IsFocusable.JPG" alt="" id="BLOGGER_PHOTO_ID_5316879742822690914" border="0" /></a>
<br/>
<span style="font-weight: bold;">3. Visual Guidelines.</span> Недавно выкачал исохдники хромого, и привлекло описание <a href="http://dev.chromium.org/developers/how-tos/visualstudio-tricks">Visual Studio tricks</a> (особенно Column Limit). Достаточно задать "скрытый" строковый параметр в реестре по пути
<pre style="font-family: courier new,monospace; margin-left: 40px;">[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Text Editor]
"Guides"="RGB(128,0,0) 80"
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNVyZStJM9Q-4_BXM7kK_7mGtBJdcEoLA5rZSSjwaJuMuXa-xiBYeU7xWCZ2IbqEc3kBzkvCR4IrsoEjGCYa7ct5YaFrUptbduJGE47JT0NvYFNxLXjVMKDrJ8TcOivapfrFydEZsvwG2d/s1600-h/Regedit.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 101px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNVyZStJM9Q-4_BXM7kK_7mGtBJdcEoLA5rZSSjwaJuMuXa-xiBYeU7xWCZ2IbqEc3kBzkvCR4IrsoEjGCYa7ct5YaFrUptbduJGE47JT0NvYFNxLXjVMKDrJ8TcOivapfrFydEZsvwG2d/s320/Regedit.JPG" alt="" id="BLOGGER_PHOTO_ID_5316887729899722322" border="0" /></a>
</pre>
<br/>
И мы получаем от редактора полоску на 80-м символе (после рестарта студии):
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjytnPzfFWtA7FSEXybqqbkEAKZ_QVxRBI6eeNGNGN2mwxO7_lnBHK6OYrryiwczqoEHqX8nSugt811uWqFRbBeyCYFOhzmnwZ-ID9b8OL-RotXAbuJzX2TRO3Q5592zw7-BJHICYFBJkrm/s1600-h/VisualGuide.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjytnPzfFWtA7FSEXybqqbkEAKZ_QVxRBI6eeNGNGN2mwxO7_lnBHK6OYrryiwczqoEHqX8nSugt811uWqFRbBeyCYFOhzmnwZ-ID9b8OL-RotXAbuJzX2TRO3Q5592zw7-BJHICYFBJkrm/s320/VisualGuide.JPG" alt="" id="BLOGGER_PHOTO_ID_5316888239081008418" border="0" /></a>
<br/>
Какой от этого может быть прок? Давайте отойдем на секунду от кода и подумаем вот о чем. Текст в книгах и журналах набирается по колонкам, ширина которых обычно меньше ширины самой страницы. Зачем это делается? Да затем, что узкая колонка сокращает охватываемое взглядом пространство при движении глаз вперед-назад - чтение становится проще, когда глаза меньше работают. Чтение упрощается, когда то, что мы читаем, и то, что собираемся прочесть, находится в поле нашего зрения. Именно эту мысль высказывают авторы 32-й главы "Код в развитии" книги <a href="http://www.piter.com/book.phtml?978591180603">Идеальный код</a>, <a href="http://www.google.com.ua/search?q=Laura+Wingerd">Лаура Уингерд</a> и <a href="http://www.google.com.ua/search?&q=Christopher+Seiwald">Кристофер Сейвалд</a>.
<br/><br/>
Но у этого подхода есть еще один плюс: стараясь придерживаться правила 80-столбцов, мы можем просматривать два файла в студии бок-о-бок, не испытывая дискомфорта. А это бывает очень полезно, когда разрабатываешь приложения в стиле MVVM, или хотя бы ASP.NET + Code Behind. Только взгляните, насколько упрощается написание кода, без необходимости постоянного преключения контекстов (для включения этого режима, тыцьните правой кнопкой мышки по имени файла в редакторе и выберите New Vertical Tab Group, в контекстном меню):
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwJmEznsqAC2KclVauqQLqs4V5qa2AzEZ6CEo4gI5V0HT8de4Y-Q876LHihfuaJ0cVwJcWhcmijlO_zGZCOooKOH3a53MCpCOfvHWeGs-AB-c5vVZdP81dV7hHYu9j1sD_7HLoH8sU5e_z/s1600-h/SideBySide.JPG"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 144px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwJmEznsqAC2KclVauqQLqs4V5qa2AzEZ6CEo4gI5V0HT8de4Y-Q876LHihfuaJ0cVwJcWhcmijlO_zGZCOooKOH3a53MCpCOfvHWeGs-AB-c5vVZdP81dV7hHYu9j1sD_7HLoH8sU5e_z/s320/SideBySide.JPG" alt="" id="BLOGGER_PHOTO_ID_5316889523564381938" border="0" /></a>
Кстати, эта подсказка от Хромого привела к супер-пупер блогу Сары Форд: <a href="http://blogs.msdn.com/saraford/default.aspx">MS VS Tips</a>. Сара работала SDET, в VS Core Team, на протяжении пяти лет. И, у нее также описывался механизм гайдлайнов.
<br/><br/>
Покамест по студии все. Двинем дальше :).
<br/><br/>
<h2>Snoop + Silverlight Spy</h2>
<br/>
Ну почему? Почему здесь показывается этот серый квадратик? А кто это у меня обработал событие?
<br/><br/>
Знакомо? В таких ситуациях очень полезными оказываются наши очередные резиденты: <a href="http://blois.us/Snoop/">Snoop</a>, для мира WPF, и <a href="http://silverlightspy.com/silverlightspy/download-silverlight-spy/">Silverlight Spy</a>, для... Silverlight'a конечно :).
<br/><br/>
<span style="font-weight: bold;">Snoop</span>. Не очень представляю, как жил без этой утилиты. Может найти WPF процесс, внедрится в него и показать все внутренности. Просто просмотрите скриншоты на сайте <a href="http://blois.us/Snoop/">Snoop'a</a>, если мои доводы не убедительны :). Вот как Снуп разложил Blend по полочкам, т.е. по z-index'у:
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://blois.us/Snoop/pics/imgA.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 717px; height: 687px;" src="http://blois.us/Snoop/pics/imgA.jpg" alt="" border="0" /></a>
<br/>
<span style="font-weight: bold;">Silverligth Spy</span>. Может практически все, что может Snoop, только для Сильверлайта :). Если скажете, где лежит рефлектор, покажет и исохдники приложения. Если вы - сильверлайт разработчик, просто пройдитесь по <a href="http://silverlightspy.com/silverlightspy/introduction/">списку возможностей</a>.
<br/><br/>
<h2>KaXaml</h2>
<br/>
<a href="http://www.kaxaml.com/">KaXaml</a> - это очень легкий редактор замла. Удобен для тестирования разметки, да и вообще - удобен :) :
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.kaxaml.com/images/screenshot_small.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 595px; height: 427px;" src="http://www.kaxaml.com/images/screenshot_small.jpg" alt="" border="0" /></a>
<br/>
<h2>Baml Viewer</h2>
<br/>
<a href="http://www.codeplex.com/reflectoraddins/Wiki/View.aspx?title=BamlViewer&referringTitle=Home">Baml Viewer</a> - это add-in для Reflector'a. Позволяет декомпилировать .baml и смотреть чистый .xaml :).
<br/><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://i3.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=reflectoraddins&DownloadId=8110"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 544px; height: 212px;" src="http://i3.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=reflectoraddins&DownloadId=8110" alt="" border="0" /></a>
<br/>
Так, имея на руках Snoop, Reflector и Baml Viewer можно посмотреть исходники Blend'a (для изучения, конечно же :)).
<br/><br/>
<h2>Выводы?</h2>
<br/>
Позвольте не делать никаких выводов :). Я не включал в обзор сам Expression Blend, т.к. он заслуживает отдельного поста. Некоторые находят удобными так же <a href="http://karlshifflett.wordpress.com/xaml-power-toys/">Xaml Power Toys</a>, но мне как-то они не приглянулись. Хотя возможность редактировать Grid, изменяя порядок столбцов, и генерировать ViewModel по модели - это, конечно же, достойные плюсы.
<br/>
А чем вы пользуетесь? Было ли что-то полезно/интересно из предложенного списка? Не молчите, пожалуйста, дайте знать :). Буду очень признателен!
<br/>
Отличного настроения и программирования!anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com1tag:blogger.com,1999:blog-3946161110755638694.post-4964378425837414192009-03-21T13:49:00.000-07:002009-03-23T06:02:48.698-07:00Silverlight: MVVMПривет, друзья!<br/><br/>
<div>
<div>Вчера, в пятницу 20-го марта, нам вместе с <a href="http://dev.net.ua/blogs/sergeylutay/default.aspx">Сергеем Лутаем</a> и <a href="http://merle-amber.blogspot.com/">Сашей Кондуфоровым</a> посчастливилось выступать перед харьковским сообществом .NET программистов. Саша повел <a href="http://merle-amber.blogspot.com/2009/03/aspnet-mvc.html">доклад </a>за ASP.NET MVC, мы же с Сергеем, пролили немножко света в мир сильверлайта :). Спустя пару дней, выкладываем в онлайн то, что было в оффлайне :). В этой статье, я пишу про MVC в мире WPF/Silverlight (ака MVVM), а у <a href='http://dev.net.ua/blogs/sergeylutay/archive/2009/03/23/7995.aspx'>Сережи</a> вы найдете рассказ об архитектуре самой технологии.</div>
<br/><h2>MVVM</h2><br/>
<img style="float:right; margin:0 0 10px 10px; width: 148px; height: 285px;" src="http://upload.wikimedia.org/wikipedia/commons/c/c2/ADN_static.png" border="0" alt="Evolution" />
<div>Откуда берутся паттерны? Что делает одни шаблоны проектирования популярными а другие канут в Лету? Почему Лета пишется с большой буквы? Ой. Куда-то занесло не туда :). </div><br/><div>
</div><div>Не берусь полностью ответить на все вопросы, но, есть подозрение, что популярным паттерн делает та же сила, которая дала мне возможность писать этот текст, а вам читать его - <span class="Apple-style-span" style="font-weight: bold;">эволюция</span>.</div><div>
</div><br/><div>Чарльз Дарвин сказал:</div><div><blockquote>"<span class="Apple-style-span" style="font-style: italic;">Из всех видов выживают не самые сильные, и даже не самые умные, а лишь те, кто лучше других приспосабливаются к изменениям</span>".</blockquote>Когда думаешь о его словах в контексте проектирования, первым приходит в голову <span class="Apple-style-span" style="font-style: italic;">MVC</span>. Подумать только, этот паттерн шагает в ногу с разработчиками вот уже 31-й год! Сколько же технологий повидал этот долгожитель?</div>
<br/>
<h3>MVC/MVP</h3>
<br/>
<div>Нет-нет, я не планирую пускаться в пространные рассуждения обо всем и сразу. Дайте руку, дорогой читатель, и мы лишь помчимся по самым верхам со скоростью звука (т.е. со скоростью проговаривания текста про себя :)).</div><br/><div>
</div><div><a href="http://c2.com/cgi/wiki?ModelViewController">MVC</a>: простейший способ понять MVC: Model == Данные, View == Показанные данные, Controller == Связь между Данными и Показанными данными. Если придумаете проще - дайте знать :).</div><br/><div>
</div><div><span class="Apple-style-span" style="font-weight: bold;">MVP</span>:</div><div>1. <span class="Apple-style-span" style="font-style: italic;"><span class="Apple-style-span" style="font-weight: bold;">M</span>ost <span class="Apple-style-span" style="font-weight: bold;">V</span>aluable <span class="Apple-style-span" style="font-weight: bold;">P</span>rofessional</span>. Не наш случай :).</div><div>2. <span class="Apple-style-span" style="font-style: italic;"><span class="Apple-style-span" style="font-weight: bold;">M</span>odel <span class="Apple-style-span" style="font-weight: bold;">V</span>iew <span class="Apple-style-span" style="font-weight: bold;">P</span>resenter</span>. Model и View остаются без изменений, Controller превращается в Presenter'a. Паттерн неплохо приспосабливается в мире WinForms и ASP.NET'a. </div><div>
</div><br/><div>
<img style="width: 300px; height: 171px;"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1YZS1E-Y340nmWVSNtJ7hz2VWR-chM1NszxREv8B7F57AGaK34hwwLu35a5BjRvkPpGZs36HFvsF-7mPPFtclUm1Ozul4t8nsnvYY5YYYA9fUxIQQahC8f_QwrFqDEoKY_aYTf_sbwOuf/s320/mvp3.gif" border="0" alt=""
id="BLOGGER_PHOTO_ID_5315943382737697442" />
</div><br/>
Здесь View изображен в виде интерфейса. Слой представления в приложении (будь то ASP.NET, WinForms или еще что-то), обязуется реализовать этот интерфейс. Presenter же общается с конкретной имплементацией View через интерфейс, ничего не подозревая о самой имплементации.</div><div>
</div><br/><div>Прекрасный пример реализации MVP есть на <a href="http://www.codeproject.com/KB/architecture/ModelViewPresenter.aspx">codeproject.com</a>. Слямзил оттуда лишь кусочек кода, для пущей наглядности. Легче всего разбираться с новым кодом при помощи... юнит тестов:</div><div>
<pre class="brush: csharp">
[TestFixture]
public class CurrentTimePresenterTests
{
[Test]
public void TestInitView()
{
MockCurrentTimeView view = new MockCurrentTimeView();
CurrentTimePresenter presenter = new CurrentTimePresenter(view);
presenter.InitView();
Assert.IsTrue(view.CurrentTime > DateTime.MinValue);
}
private class MockCurrentTimeView : ICurrentTimeView
{
public DateTime CurrentTime
{
set { currentTime = value; }
// This getter won't be required by ICurrentTimeView,
// but it allows us to unit test its value.
get { return currentTime; }
}
private DateTime currentTime = DateTime.MinValue;
}
}
</pre>
</div><br/><div>Как видно, конструктор Presenter'a получает экземпляр мока в качестве View, и после вызова метода Init() ожидает от мока определенного состояния. Интерфейс ICurrentTimeView и класс Presenter'a выглядят следующим образом:
</div><div>
<pre class="brush: csharp">
public interface ICurrentTimeView
{
DateTime CurrentTime { set; }
}
public class CurrentTimePresenter
{
public CurrentTimePresenter(ICurrentTimeView view)
{
if (view == null)
throw new ArgumentNullException("view may not be null");
this.view = view;
}
public void InitView()
{
view.CurrentTime = DateTime.Now;
}
private ICurrentTimeView view;
}
</pre>
</div><div>Теперь мы можем легко сменить View, например на ASP.NET старницу. На страницу поместим Label с именем lblCurrentTime, и:</div><div>
<pre class="brush: csharp">
public partial class ShowMeTheTime : Page, ICurrentTimeView
{
protected void Page_Load(object sender, EventArgs e)
{
CurrentTimePresenter presenter = new CurrentTimePresenter(this);
presenter.InitView();
}
public DateTime CurrentTime
{
set { lblCurrentTime.Text = value.ToString(); }
}
}
</pre>
</div><div>Что же мы получили? - Отличное покрытие тестами, четкое разграничение обязанностей, моральное удовлетворение :).</div><div>
</div>
<br/>
<h3>Model-View-ViewModel (aka MVVM)</h3>
<br/>
<div>
</div><div>MVP - может прекрасно жить и в мире WPF/Silverlight, но благодаря отличительным особенностям технологий, жизнь программиста может быть еще проще. Большую часть забот по связыванию данных с их отображением берут на себя Binding-средства. Обо всей мощи Binding'a в WPF/Silverlight'e я собираюсь написать в ближайшем будущем, пока же достаточно будет показать маленький пример.</div><br/><div>
</div><div>Как вы знаете, структура приложения, написанного на WPF/SL определяется иерархической хмл-разметкой:</div><div>
<pre class="brush: xml">
<UserControl.Resources>
<vm:User x:Key="UserInfo"/>
</UserControl.Resources>
<Grid DataContext="{StaticResource UserInfo}">
<Border>
<TextBox Text="{Binding Name}"/>
</Border>
</Grid>
</pre>
</div><div>Здесь мы создали один Grid, положили внутрь него Border а внутрь последнего положили TextBox. Наибольший интерес для нас представляет свойство DataContext (которое Grid получил в наследство от FrameworkElement'a), и выражение Binding. Как только мы установили DataContext, все потомки Grid'a имеют к нему доступ, вне зависимости от уровня вложенности. Запись <span class="Apple-style-span" style="font-style: italic;">Text="{Binding Name}"</span> означает: возьми то, что находится в DataContext'e, найди у него свойство <span class="Apple-style-span" style="font-style: italic;">Name</span>, и сохрани значение этого свойства в <span class="Apple-style-span" style="font-style: italic;">Text</span>. Более того, механизм Binding'a будет наблюдать за изменениями как источника данных так и потребителя. Как только изменится первый - второй будет обновлен. И наоборот: если пользователь введет данные в <span class="Apple-style-span" style="font-style: italic;">TextBox </span>это вызовет обновления свойства <span class="Apple-style-span" style="font-style: italic;">Name</span>, у объекта лежащего в <span class="Apple-style-span" style="font-style: italic;">DataContext'e </span>(а в нашем случае это объект класса <span class="Apple-style-span" style="font-style: italic;">User</span>).</div>
<br/><div>Теперь, имея представление о связывании данных и представления в мире WPF/SL, давайте посмотрим на MVC. </div><div>
<div>
<img style="margin:0 0 10px 10px;width: 320px; height: 212px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlU2vE2pRQEHcNkfMY9ELw65gRAeoWFcfycDTgob_Uh9E0pLgMKAtK8x3EbF6BNa5uiCzFMkzMfC4QNlI65cQPDx5b3ySl6Wwl7Ju2Jcb8RxIP6mM_FIZHM-wlMGzN-vk4WhxG63kxqamZ/s320/MVVM.PNG" border="0" alt="" id="BLOGGER_PHOTO_ID_5316068106675431794" />
</div>
<br/>
</div><div>Все, что нужно View, для отображения данных - это правильный "DataContext" и правильные свойства в этом DataContext'e. Т.е. нужен посредник, который возьмет только необходимые для View данные из модели, и сделает их доступными через открытые свойства (ака properties). Этот посредник и получил название <span class="Apple-style-span" style="font-style: italic;">ViewModel</span>. ViewModel - это адаптер модели для View. Кто не верит, что это адаптер, взгляните на UML определение адаптера :) :</div><div>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAH_c03bc7cSbPao12bZST1qS-ut-dSoC3s3kubeIk9Nbi_UmbygqtWdpCGCn774AktzMjrvBWRCkeFVXQ0O5AWWPeUFK2d46HkZ0SQjAJOMasjKhV3Im_Qxc4SSscp40a8MwqcqGf8ec9/s1600-h/adapter.PNG"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 186px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAH_c03bc7cSbPao12bZST1qS-ut-dSoC3s3kubeIk9Nbi_UmbygqtWdpCGCn774AktzMjrvBWRCkeFVXQ0O5AWWPeUFK2d46HkZ0SQjAJOMasjKhV3Im_Qxc4SSscp40a8MwqcqGf8ec9/s320/adapter.PNG" border="0" alt="" id="BLOGGER_PHOTO_ID_5316072502788597266" /></a>
</div><div>Ну хорошо, а как быть, если нам нужно отреагировать на нажатие кнопки? В мире WPF'a широко используется интерфейс ICommand, для обработки подобных действий. Достаточно выставить у ViewModel'a наружу свойство типа ICommand, и привязать его к свойству <span class="Apple-style-span" style="font-style: italic;">Command</span> у Button'a. Когда будет нажата кнопка произойдет вызов метода <span class="Apple-style-span" style="font-style: italic;">Execute()</span>, где вы можете обновить состояние модели или ViewModel'a. Кстати, реализовывать свой ICommand, обычно не приходится. В стандартной поставке доступны несколько готовых реализаций, но в самом ViewModel'e оказывается очень удобным использовать <a href="http://msdn.microsoft.com/en-us/library/cc707894.aspx">DelegateCommand</a>.</div><div>
</div><br/><div>Сильверлайт так же имеет интерфейс ICommand, но на этом его поддержа коммандной модели заканчивается ("<span class="Apple-style-span" style="font-style: italic;">Silverlight 2 does not implement a commanding system. This interface is in place only for interoperability and compatibility reasons</span>." (c) <a href="http://msdn.microsoft.com/en-us/library/system.windows.input.icommand(VS.95).aspx">MSDN</a>). Что же, не беда. Всегда можно воспользоваться обработчиком события Click, и вызвать соответствующий метод у ViewModel'a.</div><div>
</div><br/>
<h3>Silver Dictionary</h3>
<br/><div>Пока мы готовились к презентации, мы написали небольшое Silverlight-приложение в MVVM стиле:</div><br/><div>
<a href="http://anvaka.googlepages.com/SilverDictionaryTestPage.html"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 197px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ZaQ4HkOwbH5k7I0W8bHxHhmbpHcH8y-i478II6PpxfCQWSCBaa4Fk-0ULUDeJIEJAhbhMde9r2_uHApSjAWzsXasGr4LMHKjkbzdlJo9X6c6Fd_CGojHz8KEoO5aPN6noIzJY5LvWH1N/s320/silverdict.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5316091366734835362" /></a>Суть приложения: переводчик. Выбираем с какого языка будем переводить на какой, и, через Google, осуществляем перевод. По исходному и по переведенному словам осуществляем поиск картинок (пользуясь сервисами Google). </div><div>
</div><br/><div>Исходные коды к проекту всегда доступны на <a href="http://code.google.com/p/silverdictionary/source/checkout">Google Code</a>. Загружайте/модифицируйте/изучайте код в свое удовольствие. А здесь же, мы пока пройдемся по самым основам приложения:
</div><br/>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"
href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2MqiBqghgBD_blEF9T4y4dSa7bQJ1hfCRSR96Rw5LG8V3S14-f9Ky4Le6z_G1r8YCffAtMvAyFz9V15tCBoEKJIrlw3nr5grkFQfurPls5sMLXMQuhokh2k1W_s8WkfgCI8ZkoXWBrkTW/s1600-h/silverdict-solution.JPG">
<img style="margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 235px; height: 320px;"
src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2MqiBqghgBD_blEF9T4y4dSa7bQJ1hfCRSR96Rw5LG8V3S14-f9Ky4Le6z_G1r8YCffAtMvAyFz9V15tCBoEKJIrlw3nr5grkFQfurPls5sMLXMQuhokh2k1W_s8WkfgCI8ZkoXWBrkTW/s320/silverdict-solution.JPG"
border="0" alt="" id="BLOGGER_PHOTO_ID_5316094730280548034" /></a><br/><br/>
В папке Model мы определяем интерфейс взаимодействия с сервисами Google (переводчик и поисковик картинок). Интерфейсы модели имеют простой вид: есть методы которые стартуют поиск картинок/перевод, и есть события которые вызываются по завершению поиска/перевода.<div>
</div><br/><div>В папке ViewModel собраны классы отвечающие за преобразование модели к виду, удобному для View. Например, SilverDictionaryViewModel содержит открытое свойство Languages, к которому привязываются списки с исходным и целевым языками перевода.</div><div>
</div><br/><div>Ну и наконец, xaml-файлы содержат разметку приложения и Binding выражения. DataContext мы устанавливаем при событии Page.Loaded: </div><div>
<pre class="brush: csharp">
void Page_Loaded(object sender, RoutedEventArgs e)
{
_silverDictionaryViewModel.Init();
DataContext = _silverDictionaryViewModel;
}
</pre>
</div><div>А вот как выглядит обработчик нажатия кнопки Translate:</div><div>
</div><div>Xaml:</div>
<div>
<pre class="brush: xml">
<Button Content="Translate"
Margin="5, 0, 5, 0"
Grid.Column="0"
Click="TranslateButtonClick"
IsEnabled="{Binding IsValid}"/>
</pre>
</div>
<div>Code Behind:</div><div>
<pre class="brush: csharp">
private void TranslateButtonClick(object sender, RoutedEventArgs e)
{
DoTranslate();
}
private void DoTranslate()
{
_silverDictionaryViewModel.Translate();
}
</pre></div>
<br/>
<h3>Конец... Или только начало?</h3><br/>
<div>
</div><div>Ну что же, дорогой читатель, на этом, наше путешествие по паттернам-приспособленцам подходит к концу. Спасибо, что вы дочитали до самого конца. Надеюсь, вам было интересно :). До скорых встреч и отличного программирования!</div><div>
</div><br/><div>ЗЫЖ <a href="http://ru.wikipedia.org/wiki/Лета">Лета</a> - это река забвения, в древнегреческой мифологии. Потому и пишут с большой буквы.</div>anvakahttp://www.blogger.com/profile/05286105082927535514noreply@blogger.com2