Я чрезвычайно доволен своим компаратором WebTest, который даже загрузился на http://store.yandex.ru (поищите WebTest или WarThunder), и даже пытается, несмотря на объявленную цену в $0,95( 29 руб.), обновиться совершенно бесплатно. К сожалению, пока обновление не проходит - "ошибка загрузки". Но я не теряю надежды.
А что, собственно, обновилось? Есть и фичи, есть и баги. Причём, баги, увы, не все мои. Это, так сказать, "системные фичи", которыми на текущий момент (XE5 Update5) "радует" нас Fire Monkey. Подробнее об этом потом, а сначала немного о том, что поменялось именно под влиянием эксплуатационного опыта.
1. Имена вновь созданных юнитов теперь не вычисляются в виде "Новый", "Новый(2)" и т.д. Я скопировал поведение MSWindows для имени новой папки, и на предварительных тестах оно мне нравилось. Но, когда пришёл массовый ввод, я запарился удалять эти буквы - они оказались только помехой. Теперь имя пустое, лишь с текстом-подсказкой. Если оставить имя пустым и ничего не вводить в числитель-знаменатель, то по Назад-кнопке юнит будет тихо удалён. В принципе, такое поведение и для старых юнитов вполне логично - если удалил имя и данные, то удалил всю запись. Но такое поведение не вполне очевидно, и кнопку "Удалить" я оставил.
2. Формат дробных чисел теперь - с тремя знаками после запятой. Я понадеялся на форматы по-умолчанию, которые идут с двумя знаками. Вначале я не особо напрягался, что данные статистики идут в простых единицах или в кило (К), т.е. с разницей в три порядка. Но потом кол-во два знака вместо трёх стало напрягать всё больше и больше, и я, ценой титанических усилий, изменил формат 2-х контролов и 2-3 места форматирования рейтинга. И стало счастье.
3. Я стал таки хранить числитель и знаменатель. Да, я не храню его в списке - там, как я и думал, это ни к чему. Но при массовом вводе старые значения оказались очень полезны. Они дают возможность быстро понять - вводили мы данные по результатам последних сражений только что, или вычисленный коэффициент является результатом предыдущих наблюдений.
Во-первых, я заметил, что при изменении статуса юнита не меняется звёздочка-индикатор в списке, пока не поменяется порядок сортировки. Мой грех. Исправил. Не стоило бы и говорить об этом, т.к. здесь ничего интересного. Но пусть будет для отчётности. А интересное - дальше.
Во-вторых (не по значимости и не по порядку решения) я как-то не сразу, но заметил необъяснимые странности выделения текущего элемента. Я долго не мог сообразить, в чём дело и что делать. Но теперь, кажется, проблемы решены. Вот тут я поделюсь подробностями.
Сначала всё работало нормально - нажатие Ентер, и контролы меняются фокусом. Но иногда вдруг механизм давал сбой. Не большая беда - в нужный контрол можно легко и просто взять и ткнуть пальцем. Но неприятно.
Я стал разбираться. Даже пробовал вместо KeyDn повесить событие на KeyUp. Иногда событие приходило сначала на один контрол, а потом сразу на второй, и в результате двойной смены хозяина фокус оставался на месте. Я стал обнулять Key. Но выяснилось, что ActiveControl иногда просто не соответствует реальному положению вещей! Т.е. Sender не только вовсе не равен ActiveControl но и уже равен тому контролу, в который я собираюсь передать фокус, т.е. внешне неактивному!
Что делать? QualityCentral? Я не останавливаю желающих улучшить FMX. Я даже призываю: Эй, смелые и грамотные люди, сделайте заявку! Но я пока нашёл работающую альтернативу:
Итак, всем удачи. Надеюсь, чтение сего поста было для вас хоть как-то полезно.
А что, собственно, обновилось? Есть и фичи, есть и баги. Причём, баги, увы, не все мои. Это, так сказать, "системные фичи", которыми на текущий момент (XE5 Update5) "радует" нас Fire Monkey. Подробнее об этом потом, а сначала немного о том, что поменялось именно под влиянием эксплуатационного опыта.
Фичи:
1. Имена вновь созданных юнитов теперь не вычисляются в виде "Новый", "Новый(2)" и т.д. Я скопировал поведение MSWindows для имени новой папки, и на предварительных тестах оно мне нравилось. Но, когда пришёл массовый ввод, я запарился удалять эти буквы - они оказались только помехой. Теперь имя пустое, лишь с текстом-подсказкой. Если оставить имя пустым и ничего не вводить в числитель-знаменатель, то по Назад-кнопке юнит будет тихо удалён. В принципе, такое поведение и для старых юнитов вполне логично - если удалил имя и данные, то удалил всю запись. Но такое поведение не вполне очевидно, и кнопку "Удалить" я оставил.
2. Формат дробных чисел теперь - с тремя знаками после запятой. Я понадеялся на форматы по-умолчанию, которые идут с двумя знаками. Вначале я не особо напрягался, что данные статистики идут в простых единицах или в кило (К), т.е. с разницей в три порядка. Но потом кол-во два знака вместо трёх стало напрягать всё больше и больше, и я, ценой титанических усилий, изменил формат 2-х контролов и 2-3 места форматирования рейтинга. И стало счастье.
3. Я стал таки хранить числитель и знаменатель. Да, я не храню его в списке - там, как я и думал, это ни к чему. Но при массовом вводе старые значения оказались очень полезны. Они дают возможность быстро понять - вводили мы данные по результатам последних сражений только что, или вычисленный коэффициент является результатом предыдущих наблюдений.
А теперь о багах.
Во-первых, я заметил, что при изменении статуса юнита не меняется звёздочка-индикатор в списке, пока не поменяется порядок сортировки. Мой грех. Исправил. Не стоило бы и говорить об этом, т.к. здесь ничего интересного. Но пусть будет для отчётности. А интересное - дальше.
Во-вторых (не по значимости и не по порядку решения) я как-то не сразу, но заметил необъяснимые странности выделения текущего элемента. Я долго не мог сообразить, в чём дело и что делать. Но теперь, кажется, проблемы решены. Вот тут я поделюсь подробностями.
Итак, я заполнял список процедурой Populate( const SelectItem: String = '' ), где заполнял отсортированный список (Sorted = True, OnCompare = ListBox1Compare). Параметры фильтра и сортировки я брал из текущих значений контролов, а для установления текущей строки в списке передавал в процедуру параметр.
... ListBox1.BeginUpdate; try ListBox1.Clear; ... for ... ... with TListBoxItem.Create( ListBox1 ) do begin Parent := ListBox1; ... TagString := ...; Text := ... ... end; //with end; //for ... finally ListBox1.EndUpdate; end; ... if not SelectItem.IsEmpty then for I := 0 to ListBox1.Count-1 do if SameStr( ListBox1.ItemByIndex( I ).TagString, SelectItem ) then begin ListBox1.ItemIndex := I; Break; end;Ну, что тут неправильного? Список отсортирован, и на EndUpdate вызывается ListBox1Compare. Осталось, вроде бы, отыскать после сортировки нужный ListBoxItem, и всё. Но в результате я постоянно получал по две(!) выделенных строки.
Решив, что дело в не вполне корректной отрисовке, я после некоторых экспериментов добавил перед ListBox1.ItemIndex := I строчку ListBox1.SetFocus; и, увидев стабильное одинарное выделение, успокоился. А зря.
Поработав некоторое время с программой, я обнаружил, что выделяется вовсе не та строка, которую я имел в виду. Т.е. когда я меняю рейтинг, то Ок. Но когда, скажем, программа стартует и пытается выйти на ту строку, которая была активна до предыдущего завершения, то получается нечто весьма забавное: в цикле поиска индекс искомого элемента равен его месту в неотсортированном списке, т.е. полученному при загрузке! И это после того, как был вызван алгоритм сортировки! А в момент появления на экране строки расположены уже в отсортированном порядке, и найденный ранее индекс соответствует уже другой строке!
Оказывается, сортируемые элементы списка в первый раз ещё не связаны с FMX-объектами, которыми они будут представлены на экране. Контекст контрола-списка создаётся в момент отрисовки. И при этом проводится повторная сортировка. И вот уже теперь элементы данных соответствуют объектам представления. Бред какой-то. Я не стал разбираться, кто виноват и как исправить FMX. Меня больше интересует вопрос "Что делать?".
Итак, результатом моих изысканий стало следующее решение: отказаться от TListBox.Sorted = True, и использовать ListBox1.Sort.
Этот метод требует делегата, который нужно определить вне класса. И работает он с парой TFmxObject'ов. А старое OnCompare - выкинуть? А оно даже не всю сортировку делало, а работало только для определённого вида сортировки. Стоит ещё сказать, что моё решение было экспериментальным, и переписывать всё подряд я не хотел. Поэтому я использовал protected функцию CompareItems, которая и раньше делала всю работу по предварительной сортировке и вызову события OnCompare.
И вот уже наконец я добрался до "в третьих". Здесь всё просто. Точнее - не просто, но быстро. Быстро нашёлся способ обхода проблемы. В конце концов, проблемы программиста не в том, что его средство программирования как-то неправильно работает, а в том, что неправильно работает его программа. Кто-то не согласен?
Вот у меня Числитель и Знаменатель - числа. Это значит, что у меня на экране устройства(скриншот здесь из Win32, но все понимают) цифровая клавиатура с цифрами, точкой и кнопками "Забой" и "Ввод". Я хочу использовать vkReturn(т.е. Ввод, <ВК>, Ентер, Пуск), чтобы перемещаться между числами. Более того, такое перемещение вызывает всякие события валидации и изменения контрола, что приводит к приятному вычислению показателя. Не сложно вроде бы:
Поработав некоторое время с программой, я обнаружил, что выделяется вовсе не та строка, которую я имел в виду. Т.е. когда я меняю рейтинг, то Ок. Но когда, скажем, программа стартует и пытается выйти на ту строку, которая была активна до предыдущего завершения, то получается нечто весьма забавное: в цикле поиска индекс искомого элемента равен его месту в неотсортированном списке, т.е. полученному при загрузке! И это после того, как был вызван алгоритм сортировки! А в момент появления на экране строки расположены уже в отсортированном порядке, и найденный ранее индекс соответствует уже другой строке!
Оказывается, сортируемые элементы списка в первый раз ещё не связаны с FMX-объектами, которыми они будут представлены на экране. Контекст контрола-списка создаётся в момент отрисовки. И при этом проводится повторная сортировка. И вот уже теперь элементы данных соответствуют объектам представления. Бред какой-то. Я не стал разбираться, кто виноват и как исправить FMX. Меня больше интересует вопрос "Что делать?".
Итак, результатом моих изысканий стало следующее решение: отказаться от TListBox.Sorted = True, и использовать ListBox1.Sort.
Этот метод требует делегата, который нужно определить вне класса. И работает он с парой TFmxObject'ов. А старое OnCompare - выкинуть? А оно даже не всю сортировку делало, а работало только для определённого вида сортировки. Стоит ещё сказать, что моё решение было экспериментальным, и переписывать всё подряд я не хотел. Поэтому я использовал protected функцию CompareItems, которая и раньше делала всю работу по предварительной сортировке и вызову события OnCompare.
procedure TForm2.ListBox1Compare(Item1, Item2: TListBoxItem; var Result: Integer); begin ... end; type MyLB = class ( TListBox ) // function CompareItems(const Item1, Item2: TListBoxItem): Integer; virtual; end; function ListBox1Compare(Left, Right: TFmxObject): Integer; var Item1: TListBoxItem absolute Left; Item2: TListBoxItem absolute Right; begin Result := MyLB( Form2.ListBox1 ).CompareItems( Item1, Item2 ); end; ... ListBox1.Sort( uMain.ListBox1Compare );Обращу внимание на совпадение имён делегата и события, которое по-вкусу мне и может быть не по-вкусу вам. Но более важно здесь то, что мне пришлось использовать переменную Form2. Вот это действительно не очень изящно. Мне кажется, это уже повод для размышлений по поводу языка-компилятора - почему мы не можем в качестве делегата использовать нечто более инкапсулированное. Но, тем не менее, решение такое, и оно работает. Сама Fire Monkey для доступа к экземпляру списка берёт парента у Left, что я, наверно, и буду использовать в дальнейшем, но это тоже не выглядит супер-пупер.
И вот уже наконец я добрался до "в третьих". Здесь всё просто. Точнее - не просто, но быстро. Быстро нашёлся способ обхода проблемы. В конце концов, проблемы программиста не в том, что его средство программирования как-то неправильно работает, а в том, что неправильно работает его программа. Кто-то не согласен?
Вот у меня Числитель и Знаменатель - числа. Это значит, что у меня на экране устройства(скриншот здесь из Win32, но все понимают) цифровая клавиатура с цифрами, точкой и кнопками "Забой" и "Ввод". Я хочу использовать vkReturn(т.е. Ввод, <ВК>, Ентер, Пуск), чтобы перемещаться между числами. Более того, такое перемещение вызывает всякие события валидации и изменения контрола, что приводит к приятному вычислению показателя. Не сложно вроде бы:
procedure TForm2.ItemDenominatorKeyDn(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if Key = vkReturn then begin if Sender = ItemNumerator then ActiveControl := ItemDenominator else ActiveControl := ItemNumerator; end; end
Сначала всё работало нормально - нажатие Ентер, и контролы меняются фокусом. Но иногда вдруг механизм давал сбой. Не большая беда - в нужный контрол можно легко и просто взять и ткнуть пальцем. Но неприятно.
Я стал разбираться. Даже пробовал вместо KeyDn повесить событие на KeyUp. Иногда событие приходило сначала на один контрол, а потом сразу на второй, и в результате двойной смены хозяина фокус оставался на месте. Я стал обнулять Key. Но выяснилось, что ActiveControl иногда просто не соответствует реальному положению вещей! Т.е. Sender не только вовсе не равен ActiveControl но и уже равен тому контролу, в который я собираюсь передать фокус, т.е. внешне неактивному!
Что делать? QualityCentral? Я не останавливаю желающих улучшить FMX. Я даже призываю: Эй, смелые и грамотные люди, сделайте заявку! Но я пока нашёл работающую альтернативу:
procedure TForm2.ItemDenominatorKeyDn(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if Key = vkReturn then begin if Sender = ItemNumerator then ItemDenominator.SetFocus // ActiveControl := ItemDenominator else ItemNumerator.SetFocus; // ActiveControl := ItemNumerator; Key := 0; end; end
Итак, всем удачи. Надеюсь, чтение сего поста было для вас хоть как-то полезно.
Комментариев нет:
Отправить комментарий