Толик Панков
hex_laden
............ .................. ................

October 2030
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

Толик Панков [userpic]
C#, Симпатичное окошко "О программе"



Можно сказать, по многочисленным просьбам зрителей. Итак, спрашивали, как устроено в наших программах окошко "О программе" с "эффектом титров". Т.е. список разработчиков и прочее медленно выплывает снизу, проползает по форме и исчезает наверху, часть всплывающих строк останавливается в центре формы, в конце выплывает слоган, так же останавливающийся на некоторое время в центре.

Смотреть анимацию


На самом деле, форма была устроена ужасно, написана совсем другими людьми для другого проекта, а у нас кочевала из программы в программу без особых изменений, строки добавлялись в 3 ArrayList в коде формы, в функции рисования были какие-то непонятные жестко заданные поправочные коэффициенты, подобранные на глаз, и т.д.
Решили немного поправить и все переделать. Не знаю, насколько получилось лучше, но понятнее и универсальней точно. Далее будет не столько код с пояснениями, сколько попытка показать, как мы рассуждали, переделывая форму.

Определение сущностей


Сначала надо определить с какими данными мы будем работать. В первую очередь, мы выводим набор строк, т.е. первая сущность это строка, у строки может быть задан шрифт и цвет текста, и, собственно, содержимое строки - это будут явные параметры сущности. В процессе добавился еще один, неявный параметр - номер сцены

Шрифт целесообразно выделить в отдельную сущность. Во-первых, все используемые шрифты желательно загрузить и сформировать до вывода строк, во-вторых, у шрифта куча своих параметров, и задавать их для каждой отдельной строки неудобно.

Параметры шрифта это FontFamily (Arial, Times New Roman и т.д.), размер и начертание или стиль (жирный, курсив, подчеркнутый, зачеркнутый, или все вместе). Для того, чтобы связать шрифт со строкой, пришлось добавить еще один параметр внутреннее имя, по которому программа будет узнавать, какой шрифт к какой строке применить.

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

Формат данных


Негоже задавать эту кучу параметров непосредственно в коде, поэтому пришлось подумать над форматом данных. Решено было хранить описание строк, шрифтов и сцен в виде простого текстового файла следующего формата:

...
команда;параметр1;параметр2;параметр3 [...]
команда;параметр1;параметр2;параметр3 [...]
команда;параметр1;параметр2;параметр3 [...]
...


Формат команд следующий (в квадратных скобках необязательные параметры):

Шрифт:
addfont; FontFamily; Размер (pt, float); [Стиль]; Внутреннее_имя
Например:
addfont; Arial;9;Bold;Group;
addfont; Microsoft Sans Serif;8;;Names;
addfont; Arial;14;Bold italic;Slogan;


Строка:
addstring; Строка; [Имя_шрифта]; [цвет_текста];
Например:
addstring;Make code, not war! C# like you.;Slogan;FF FF 00;

Сцена:
scene; [цвет_фона];[скорость_таймера_прорисовки ms];[пауза ms]
Например:
scene;20;3000;

Цвет текста задается в шестнадцатеричном формате через пробел(ы): R G B [Alpha]
Например:
FF FF 00 - желтый
FF 00 00 80 - красный с прозрачностью

Начертания (стили) шрифта перечисляются через пробел(ы), от регистра не зависят.

Структуры для хранения данных


Шрифты:
Генерируются при анализе "скрипта", и помещаются в приватный Dictionary<string, Font>, где строковый ключ - заданное "внутреннее имя" шрифта.

Строка:
Текст и параметры хранятся в специальной структуре:
private struct AboutString
{
    public string Text;
    public string FontName;
    public Color TextColor;
    public int SceneNumber;
}

А все строки в private List<AboutString> Strings

Сцена:
Для описания сцены так же создана структура:
private struct AboutScene
{
    public int PauseTimeout;
    public int StringsHeight;
    public int StringsCount;
    public Color BackColor;
    public int DrawTimeout;
}


И описания сцен так же хранятся в private List<AboutScene> Scenes

Заметки про парсинг


- последняя точка с запятой является необязательной
- пустые строки, или строки, содержащие только пробельные символы, пропускаются
- команды приводятся к нижнему регистру (т.е. они регистронезависимы)
- есть возможность оставить однострочный комментарий, начинающейся с ~ (тильды). Все, что после знака ~ считается комментарием и пропускается при анализе "скрипта".
- если первая (нулевая) сцена явно не определена, то создается сцена с параметрами по умолчанию

Команды, благо их немного, последовательно анализируются с помощью конструкции switch/case и передаются в функции добавления шрифта, строк, или сцены, где анализируются далее.
Параметры и команда разбираются с помощью string.split(';');

Некоторые вспомогательные функции для парсинга


Размер шрифта должен быть float, поэтому надо сделать функцию-обертку над Convert, так, как я писал об этом ранее:

private float ConvertToFloat(string Number)
{
    NumberFormatInfo format = new NumberFormatInfo();
    format.NumberDecimalSeparator = ".";
    return (float)Convert.ToDouble(Number, format);
}


Некоторые параметры передаются через пробел (значения RGBA цвета, или стили шрифта). Нужна функция, которая будет избавляться от дублирующих пробелов, поскольку лишний пробел поставить легко, а вот вылавливать его потом неприятно:

private string RemoveDupSpaces(string s)
{
    while (s.Contains("  "))
    {
        s = s.Replace("  ", "");
    }

    return s;
}


Заметки о реализации


Естественно, весь функционал не стоит реализовывать прямо на форме, потому вся работа вынесена в отдельный класс AboutDrawer, который парсит переданный скрипт и занимается отрисовкой строк на форме.
На форме строки отображаются в компоненте PictureBox, который передается в конструктор класса. Конструктор также устанавливает некоторые параметры по умолчанию.
Поскольку, класс все равно работает с компонентами формы, то спокойно подключаем пространство имен System.Windows.Forms, и пользуемся всеми его возможностями. Например, в классе создаются два таймера (Timer). Внутри обработчика события Tick одного из них, происходит отрисовка, а другой используется для эффекта паузы.
В классе созданы открытые свойства для установки цвета фона, цвета текста по умолчанию, и размера расстояния между строк.

Подробнее ознакомиться с кодом примера можно на Github:

Код на GitHub

Это репост с сайта http://tolik-punkoff.com
Оригинал: http://tolik-punkoff.com/2018/06/11/c-simpatichnoe-okoshko-o-programme/

Tags: ,
Comments

крутое окошко, ага.

Очередной номенант на премнию Тюринга?

А че, Трамп настолько подкосил американскую науку, что даже мы таки да?

(Anonymous)

Такой хуйней можно хвасться только учась в 3 классе школы. В 5 классе уже должно быть стыдно.

Так а никто не хвастается, просто симпатичное окошко.

отличное пособие по тому, как никогда не надо делать парзеры.

Подскажи как надо, переделаем.

для вашего случая задача вообще решается регулярками, которые наличествуют в стандартной библиотеке.

На мой взгляд, регулярки сложнее с точки зрения читабельности кода и для понимания вообще. Это как из пушки по воробьям... Регулярное выражение нужно там, где надо найти определенную структуру, спрятанную в плейнтексте. Или я ошибаюсь?

регулярные выражения нужны тогда, когда у тебя есть текст, который надо обработать, и который возможно обработать регулярными выражениями. вот ровно в этот момент они становятся нужны. а вот делать очень хуёвый ручной парзинг (а здесь он очень хуёвый) — не надо вообще никогда. это можно было бы оправдать, если бы парзилось руками для производительности — но нет, по производительности этот парзер отсасывает как три турбины.

Блин, регулярных выражений для относительно сложного случая я побаиваюсь. Попробую, конечно, сам, но ежили не влом, скинь какой-нибудь концепнуальный мануал про регулярные... Все равно, хотел давно их как-то подучить, а вот ты мне и повод подкинул.

а чего там мануалить-то: они простые как полено. сопоставление строки с регулярно повторяющимися подстроками — вот и всё. реально — это всё. остальное — просто синтаксический сахар для удобства записи.

Там скорее синтаксическая соль, я проще в египетских (кеми) иероглифах, даже без Краеугольного Камня разберусь, чем в регулярках, так что посвяти в таинство, пожалуйста.

да всё просто. ты досовые вилдкарды помнишь? те самые, которые '?' и '*'? так вот это оно (и да, ты пользовался регулярками каждый день кучу лет, оказывается — это совсем нестрашно ;-). разница лишь в том, что в вилдкардах всегда подразумевается any char, а в более продвинутых регулярках можно задавать образец для повторения.

а теперь от философии к технологии. все регулярные выражения — это просто запись фразы «повтори вот этот кусок некоторое количество раз». фактически, это обычная нотация «образец-операция». примеры:
'a*' — повторить 'a' от нуля до дохуя раз
'a?' — повторить 'a' ноль раз или один раз
'abc*def' — вот тут надо помнить, что «образец» может состоять из кучи элементов, и поэтому: повторить 'abc' ноль-дохуя, а за ним повторить 'def' ровно один раз.

как видишь, для «повторить ровно один раз» никакого спецсинтаксиса нет, но технически это всё та же операция повторения.

сопоставление с регуляркой обычно делается (логически) так, чтобы получилось как можно большее количество повторений первого «вариантивного» паттерна, потом на остатке — второго и так далее. это называется «жадные регулярки» — потому что они стараются по-максимуму «наповторяться».

фактически, ты на этом месте ты уже знаешь почти всё, что надо знать. осталась ещё одна операция: «альтернатива». что делать, если нам надо заебенить выбор: «или 'a', или 'b'»? тогда мы так и пишем: «a|b».

тут опять есть нюанс: '|' является символом-разделителем обзазцов (как и любой небуквенноцифровой символ). поэтому запрос: «начинается с 'a' или 'b', потом много 'fuck'» будет выглядеть так:
(a|b)fuck*

ура, мы узнали, что в регулярках можно использовать скобки для группировки — прямо как в математике или… программировании! а заодно почти все движки позволяют по номеру скобок извлечь то, что они реально отматчили. то есть, я могу спросить у движка: «какую точно строку отматчили первые скобки выражения?»

ну вот, этого уже почти достаточно, чтобы применять регулярки для парзинга регулярных же строк. остались мелочи синтаксиса типа «а как написать пробел?» обычно — вот так: «\s». старые добрые «искейпы», только немного расширеные. ах, да: искейп считается «буквоцифрой», и не является разделителем обзазцов.

вот с этой базы ты можешь без труда начать плясать самостоятельно — остался там всякий синтаксический сахар.

БЛЯБЛЯБЛЯ! Вот иди ты уже в пигидий в преподы, пожалуйста. "Даже я понял",попробовать потренероваться осталось.

в интернетах есть сайты, которые позволяют сразу вводить паттерн и строки, и рисуют результаты. там чистый жабоскрип, оно несложно — но обычно удобно для «набивания руки». синтаксис регулярок везде очень похожий — они все из одного корня растут.

я, конечно, кучу нюансов не упомянул — но это действительно фигня. в смысле — когда ты грок основу, то будет уже несложно обмазаться дополнительным сахаром. типа char ranges, repetition counts, etc.

ах, да, чтобы ты не искал: точка в регулярке обозначает «any char». ;-) а поскольку точка не буква, то «ab.*c» обозначает: «ab» один раз, любой символ любое количество раз, «c» один раз — по правилу разделения образцов.

и да, я спиздел, извини: искейп СЧИТАЕТСЯ буквоцифрой, и ЯВЛЯЕТСЯ разделителем. так же, как и точка, например.

а, и ещё нюанс, на который часто попадают. регулярка
(a)*
технически совершенно правильная, и отматчится правильно, но вот запрос «где там отматчились скобки» — смысла не имеет. потому что она разворачивается в бесконечную последовательность:
(a)(a)(a)(a)(a)…
все эти скобки имеют один и тот же «внутренний идентификатор», и бедный матчер просто не знает, какая из скобок тебя интересует. поэтому для подобных случаев делают так:
((a)*)
и вот тут уже первые скобки присутствуют в развёрнутой последовательности ровно один раз, поэтому о них можно спрашивать.

А еще заведи, наконец, вебмани или Киви. А то мы хотим тебе переслать, а не можем.

увы. я понимаю, что надо это мне, а проблемы у вас, но только btc. впрочем, говорят, что с киви делается перевод на приват. но btc в любом случае лучше — он ещё и анонимный.

Иначе не можем, Киви в ГОРФ не делает перевод в приват, потому что антисанкции. Так бы мы тебе, хоть потихоньку, но донатили, будь у тебя яндекс, киви, или вебмани, мы мимо бухгалтерии каждый день проходим, и че бы там тебе сотку не кинуть, а если парням сказать, то они все по сотке кидать могли бы. Тем более, на твоих советах весь завод работает.

ты не поверишь, но я тоже не в рф, так что не заводить какие-то рф-сервисы не только зашквар, но ещё и бесполезно, гыг.

придумаем, не сразу, но придумаем. Директор так и сказал - считать сотрудником, потому что твоя помощь в переходе серверов (промышленных) на Линукс, это уже круто! Кетмар, переезжай в Петрозаводск.

>переезжай в Петрозаводск
за что ты меня так ненавидишь? я же честно старался пояснить за регулярки попонятней! ;-)

Наоборот, любдю! Не, это вообще. Ты для Димона, для меня и для минимум трех проектов, сделал настолько дохуя просто подсказав! Ты не писал бизнес-план, ты не подбирал сотрудников, но благодаря тебе, я подумал, почему бы не скачать слакварь плюс халявный софт для бухгалтерии, плюс поставить пару схем для контроля, которые используют Тоталь?

спасибо, я рад, если помог. на корпорации (а также заводы и ты пы) мне похуй, я не с ними общаюсь, а с людьми. несмотря на мой hatred image, я действительно рад, когда кому-то помогает то, что я пишу.

Я стараюсь не на кого не работать, но иногда получается так, что таки да (либо они пиздят мой контент, но поскольку он свободный - а и хер с ним, воду в городе отключают теперь не на три месяца, а на неделю)

Тоталь, это в смысле исходники, написанные в корпорации Тоталь (нефтяная такая компания, друзья Хуйла еще)

И у меня квартира, а я жду как с тобой поделиться, потому что ты мой учитель, и, наверное, самый лучший. Прямо ААЗ из "Корпорации МИФ" (Если б ААЗ существовал ИРЛ, то мы бы с ним любое Хуйло свернули).

*мне заводить

Вон, от windows-серверов перешли на linux, заодно нашли софт для бетономешалок (я кроме как в охоте и бетоне понимаю только в литературе), заодно заменили техпроцесс, где раньше надо было четыре чувака, теперь линукс старается.

молодцы, чо. это я не в смысле «свысока по плечу похлопать», а просто радуюсь.