Пользователь
0,1
рейтинг
4 июля 2012 в 17:13

Разработка → MVC-подход к разработке пользовательских интерфейсов в Delphi. Часть 1. Галочка tutorial


Не буду писать красивых предисловий, потому что статья не развлекательная, а скорее техническая. В ней я хочу обратиться к приемам программирования пользовательского интерфейса классических desktop-приложений Delphi в MVC-стиле. Это вводная статья из последующей серии.
Тех немногих, кто еще пользуется этой средой разработки, прошу под кат.


Под классическими приложениями я подразумеваю десктопные GUI-приложения для Windows на основе VCL. Про фреймворк FireMonkey, появившийся в новых версиях Delphi, пусть напишет статью кто-нибудь другой.
Пользовательские интерфейсы очень многообразны. И если в вебе их вообще полный зверинец, то в настольных приложениях все происходит более консервативно. Конечно, разработчики некоторых приложений (см. Skype, Mikogo, Office 2010) продолжают придумывать всяческие визуальные ухищрения, которые призваны еще более повысить удобство использования, для большинства из нас (людей старой закалки) все-таки привычнее стандартные Windows и VCL контролы, придуманные, наверное, еще во времена Windows 3.1:
— кнопка (TButton)
— галочка (TCheckBox)
— переключатель (радиокнопка, TRadioButton)
— однострочное поле ввода (TEdit)
— многострочное поле ввода (TMemo или TRichEdit)
— всяческие комбинированные контролы (TSpinEdit, TDateTimeEdit)
— многостраничные контролы (TPageControl, TTabControl)
— гриды
— панели, групбоксы, бевелы, шейпы, ImageBox'ы и т.д.

Основная задача при создании пользовательского интерфейса придумать такое представление внутренних данных программы с помощью вышеназванных элементов, чтобы пользователю было удобно с этими данными работать. Ну или с другой стороны, придумать такой набор элементов, с помощью которого пользователь мог бы сказать программе то, что программа от него должна услышать.
Проектирование интерфейса с точки зрения компоновки элементов это уже само по себе является довольно сложной и важной задачей. Интерфейс — это лицо и основной способ по взаимодействию пользователя с вашей программой (не считая ее снятия по Ctrl+Alt+Del :) ). Именно поэтому нужно всегда заниматься проектированием интерфейса итеративно, получая каждый раз фидбэк от юзеров, удобно ли им работать с вашей программой, не приходится ли по 200 раз кликать мышкой, заходить во вложенные окна (которые в последний момент оказываются еще и модальными), по 10 раз вводить одни и те же данные из-за того, что они не сохраняются при повторном входе в окно и т.д. и т.п. (я уверен, искушенный читатель может привести еще массу примеров, каким не должен быть пользовательский интерфейс :) ).

Говорят, что умные ребята за океаном, которые подходят к проектированию интерфейсов серьезно и обстоятельно, применяют и такие технологии, как отслеживание движений мыши при работе с программой (чтобы зря не возили туда обратно), подсчет кликов и даже слежение за направлением взгляда пользователя (если глаза начинают бегать в случайных направлениях, это уже должно настораживать).
Но в данной статье речь пойдет не об этих чудесах. Я позволю себе предположить, что вы уже придумали, как будет выглядеть то или иное окно. Вам лишь нужно как-то связать это представление с внутренними данными программы. И именно о способах этой связи я хотел бы поговорить.

Начнем с примитива. Галочка.

Предположим, у вас в какой-то окне есть галочка (TCheckBox), отражающая выбор одного из двух вариантов. Чтобы говорить не о сферических конях в вакууме, придадим ей какой-то смысл. Пусть это будет окно импорта каких-то данных из файлов определенного каталога в базу данных. Галочка будет отражать, нужно ли удалять импортированные файлы после завершения операции. Тогда так и назовем нашу галочку
cbNeedDeleteFiles: TCheckBox;

Кстати, давать префиксы именам контролов на основе типа контрола очень удобно. Например, cb для TCheckBox, rb для TRadioButton, bt для TButton. Если вы установите в Delphi пакет расширений cnPack, то при размещении очередного контрола на форме будет выскакивать окошечко с предложением сразу переименовать этот контрол в соответствии с вашими правилами. Это позволяет избежать засилья валяющихся на форме Button87, CheckBox32 и т.п.

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

if Something then
  cbNeedDeleteFiles.Checked := True;

if SomethingElse then
  cbNeedDeleteFiles.Checked := False;

На первый взгляд ничего плохого тут нет. Но как показывает многолетняя практика, это УЖАСНО. Это и называется жесткой завязкой кода на пользовательский интерфейс. Допустим, впоследствии вам придется заменить TCheckBox на две радиокнопки: «Удалить импортированные файлы» и «Не удалять импортированние файлы». В этом не очень много смысла, но вы можете это сделать для лучшей визуализации или в рамках рефакторинга перед добавлением третьего состояния данной настройки типа «Удалить файлы только при отсутствии ошибок импорта». И в этот момент вам придется в куче мест, где раньше вы обращались к cbNeedDeleteFiles.Checked вставить какой-то код по работе с RadioButton'ами.

Как этого избежать?

В сети давно и много трубят про MVC, MVP, MVVM. Будто это такие чудодейственные методики, следуя которым можно запрограммировать пользовательский интерфейс «правильно» и не иметь того гемора, который описан выше. На самом деле это лишь подходы, которые действительно помогают, но которые можно реализовать абсолютно по-разному в разных языках программирования и даже в одном языке. Т.е. это скорее советы, с какой стороны лучше подходить к программированию пользовательского интерфейса.
Если еще раз посмотреть на аббревиатуры, то видно, что во всех трех есть буквы M (Model, модель) и V (View, представление). Если говорить очень простым языком, то модель — это внутренние данные программы, а представление — внешние (пользовательский интерфейс). Возвращаясь к галочке, очевидно, что внутренним преставлением данной галочки является булево значение. Решение о том, в атрибуте какого класса должно храниться это значение принимается в каждом конкретном случае индивидуально. Например, это может быть класс TConfig, предоставляющий доступ к настройкам программы. Однако в простых случаях вполне достаточно создать соответствующий атрибут просто у класса формы:
TfmImport = class(TForm)
...
private
  ...
  FNeedDeleteFiles: Boolean;
public
  ...
  property NeedDeleteFiles: Boolean read FNeedDeleteFiles write SetNeedDeleteFiles;
end;


Далее необходимо связать состояние свойства NeedDeleteFiles с состоянием визуального компонента (TCheckBox'а) cbNeedDeleteFiles. Это удобно сделать через set метод свойства:

procedure TfmImport.SetNeedDeleteFiles(const Value: Boolean);
begin
  if FNeedDeleteFiles <> Value then
  begin
    FNeedDeleteFiles := Value;
    cbNeedDeleteFiles.Checked := FNeedDeleteFiles;    
  end;
end;


Зачем нужно условие FNeedDeleteFiles <> Value я поясню чуть позже. Главное, что теперь при присвоении значения свойству NeedDeleteFiles у нас будет автоматически выставляться галочка (это уже почти модель MVC — мы меняем значение элемента модели, а представление изменяется автоматически). Но это связь лишь в одну сторону — от внутренних данных к интерфейсу. Нужно еще добиться обратной связи — от представления (т.е. от галочки) к модели. Для этого в обработчике OnClick нашего чекбокса напишем такой код:

procedure TfmImport.cbNeedDeleteFilesClick(Sender: TObject);
begin
  NeedDeleteFiles := cbNeedDeleteFiles.Checked;
end;


Т.е. действие над предствлением (в данном случает щелчок по галочке) приведет модель в соответствие с текущим состянием представления. Однако модель никогда не доверяет представлению и поэтому вызовет повторное приведение состояния представления к состоянию модели (принудительно выставит cbNeedDeleteFiles.Checked := FNeedDeleteFiles. Ничего страшного при этом не произойдет. И мы даже дополнительно застраховались проверкой if FNeedDeleteFiles <> Value от ситуации, что визуальный контрол снова вызовет обработчик OnClick. На самом деле он этого не сделает, т.к. там стоит аналогичная проверка:

procedure TCustomCheckBox.SetChecked(Value: Boolean);
begin
  if Value then 
    State := cbChecked 
  else 
    State := cbUnchecked;
end;

procedure TCustomCheckBox.SetState(Value: TCheckBoxState);
begin
  if FState <> Value then
  begin
    FState := Value;
    ...
  end;
end;


Теперь у нас состояние свойства TfmImport.NeedDeleteFiles синхронизировано с состоянием галки cbNeedDeleteFiles.Checked в обе стороны. Во всех местах программы, где мы раньше обращались к cbNeedDeleteFiles.Checked теперь следует обращаться к свойству NeedDeleteFiles. Это позволяет нам полностью забыть о том, что представлением элемента NeedDeleteFiles является CheckBox. Вы даже не представляете, насколько это замечательно. Впоследствии мы можем заменить CheckBox на две радиокнопки или на что угодно и переписать нужно будет только set-метод SetNeedDeleteFiles (направление Model -> View) и обработчик, срабатывающий при изменении состояния представления, т.е. визульных компонентов (направление View -> Model).

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

procedure TfmImport.FormCreate(Sender: TObject);
begin
  FNeedDeleteFiles := False; // Намеренно присваиваю значение, отличающееся от того, которое хочу задать, чтобы сработал set-метод
  NeedDeleteFiles := True; // Тут сработает set-метод и синхронизирует GUI с присвоенным значением (выставит галочку)
end;


В следующей части статьи я постараюсь рассказать о более сложных случаях: работе в MVC-стиле со списками элементов (TListBox, TCheckListBox, TComboBox) и о подводных камнях при запоминании состояния визуальных элементов окна при его закрытии.

UPD. Добавлена вторая часть статьи
UPD. Добавлена третья часть статьи
Джон Смит @alan008
карма
59,0
рейтинг 0,1
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (117)

  • +2
    cbNeedDeleteFiles: TCheckBox; — плохое имя.

    cb = ComboBox.

    Я обычно использую префикс chk.
    • 0
      Для ComboBox я использую cbb :)
      • +1
        У меня бы в глазах слилось. В любом случае, главное — единый стандарт для всего кода проекта. А там хоть венгерскую нотацию колбась.
        • 0
          chb? Правда пунто постоянно меняет на «сри» ;)
          • +2
            Аналогично.
            Вообще Delphi стоит в исключениях в PuntoSwitcher
            • +2
              А почему вы не отключаете автоматику в PS вообще? По-моему, самый адекватный вариант использования PS — хоткей на переключение (Pause|Break, к примеру).
              • –1
                Я не отключаю банально потому, что не вбиваю руками имена компонентов в коде вообще — при расположении нового компонента выскакивает окошко уже упомянутого cnPack с предложением переименовать контрол, а для того что бы обратится нему из кода — просто копирую имя в design-window, всего-то выделить контрол и нажать хоткей (Ctrl + Alt + N, при наличии все того же cnPack)
              • 0
                Потому что я очень часто забываю переключать клавиатуру при наборе (особенно когда через слова приходится менять раскладку). А PS очень даже помогает. Отключаю его только в средах программрования ибо там он полный неадекват.
                • 0
                  Выше и написано, что если вы переведете его в режим ручного переключения, то не придется добавлять ваши IDE в список исключения. Просто будете по ситуации жмякать хоткей и получать корректный вариант (даже в коде).
          • 0
            Самый разумный вариант, встречается чаще всего. chb — TCheckBox, cmb — TComboBox
  • +3
    Спасибо. Продолжите, пожалуйста, набор статей. Интересно.
    • +1
      В таком случае вас может заинтересовать вот эта заметка. Фактически ее автор пишет о том же, но чуть по-другому.
  • –17
    оффтоп:
    А какими сейчас преимуществами обладает Delphi перед C#+VS?
    • –13
      Эх, зеленой травой воспоминаний о юнности…
      Имхо Делфя по сравнению с С++ Билдером — так себе обрезок.
      • +6
        Вспоминаю опыт перехода с Delphi 7 на C++ Builder 6, и мне совсем другое кажется. Может быть с тех пор многое изменилось, но…

        Очевидно, что Builder был производным от Delphi, там даже проброс VCL был реализован за счёт использования классов Delphi (поэтому классы VCL можно было создавать только через указатели). А уж если сравнивать стабильность, то Delphi тут был вне конкуренции (постоянно возникали ситуации, когда вместо компиляции приходилось использовать пересборку проекта, иначе выскакивала Access Violation при запуске). Так что я бы скорей назвал Builder обрезком Delphi (касательно IDE, а не языка).

        P.S. Уже много лет не программирую на Delphi и не пользуюсь C++ Builder'ом.
        • +1
          Мне эти Борланды перестали в кошмарах снится году так в 2005, только JBuilder еще пару лет преследовал(но это немного другое)
          Билдер понимал как паскаль, так и С. Половина исходников вообще из куликса(или как там этот линуксовый обрезок назвался)?
          И ниразу никаких проблем по ЕГО вине не встречал.
          Ну разве только очень большая сложность упаковать все в один exe(как умел делать Дельфи) без необходимости нести это VCL с собой в отдельных файлах.
          Возможно это просто связанно что 10 лет попрограмировав на Паскале я его стал люто ненавидеть когда на С перебрался( читай на работу устроился ).
    • +1
      Типа нативностью. Весьма сомнительное преимущество в общем-то.
      А недостатков — полно.
    • 0
      Ну, в принципе, по соотношению минусов к ответам становится ясно, что единственное преимущество — это отсутствие необходимости переучивать свои закостеневшие мозги.
  • +10
    Ну захардкодили вы некое примитивное подобие DependencyProperty. И задолбаетесь потом такую реализацию поддерживать. Потому что она не уменьшает связность как положено, а увеличивает.
    У вас получилась прямая зависимость из модели в отображение, что в рамках разговора о MVC/MVVM — ересь феерическая.

    Почему бы не показать нормальную реализацию через события или паттерн Observer?
    И с возможностью забиндиться на событие нескольким клиентам?

    И, еще, у меня есть некоторое подозрение, что в Delphi оно вроде было библиотечное, не? Могу ошибаться, не писал ничего на Delphi уже 7 лет. Как минимум Action`ы (важный элемент реализации MVVM) там точно были.

    Однако в простых случаях вполне достаточно создать соответствующий атрибут просто у класса формы

    Мы все еще говорим о MVC и разделении приложения на слабосвязанные слои? Жжете.
    • +1
      >>Мы все еще говорим о MVC и разделении приложения на слабосвязанные слои
      Я никакое разделение всего приложентия на слои не предлагал. Чтобы «разделать на слои» в вашем понимании, надо написать кучу межслойного кода, который эти слои между собой связывает. Это бы пригодилось, если бы вы программировали сами среду разработки (например, создавали бы свою предметно-ориентированную платформу, подобную 1С). Я же говорю о простых, примитивных случаях, которые можно реализовать в MVC-стиле, когда вам надо каждый день создавать по 5-10 новых диалоговых форм. Если не следовать подобным правилам, то количество говнокода быстро подавит любое желание вообще что-то делать.
      • +1
        Я никакое разделение всего приложентия на слои не предлагал.

        Вообще-то MVC и есть про разделение на слои. Внезапно, да.

        надо написать кучу межслойного кода


        А что вы делаете у себя, когда пишете обработчики событий? Внезапно пишете контроллер, который связывает модель и представление.

        Ну и ваша реализация зависимых свойств и есть одна из вариаций на тему «межслойного кода».

        Если не следовать подобным правилам, то количество говнокода быстро подавит любое желание вообще что-то делать.

        Вообще, есть такие правила — SOLID. И вот одно из них говорит, что прямые зависимости как раз и приводят к говнокоду.
    • 0
      Да да, не представляю как можно реализовать нормальный MVVM без банального DataBinding'а.
      На самом деле, всё что нужно для реализации этой инфраструктуры в Delphi есть — события, статические члены.
      Одно но — всего-то надо реализовать инфраструктуру а ля DependencyProperty и переписать весь VCL под неё.
      В FireMonkey, насколько я понял из обзоров, какую-то маленькую часть этого дела из WPF таки скопипастили.
  • +2
    У нас есть замечательное правило — писать софт так, чтобы в любой момент можно было отделть GUI и сделать, например, сетевой сервер. Таким образом, обращение к checkbox со стороны основого кода производится только в двух ситуациях — выгрузки параметров на форму (из класса OptionsGlobal, например) и чтение их обратно в «недра» программы.

    Работает на ура.

    Кстати, за подобный код я сильно ругаюсь:
    if Something then
      cbNeedDeleteFiles.Checked := True;
    


    на самом деле, он сводится к одной строке + коммент:
    // set the parameter we need
    cbNeedDeleteFiles.Checked:= Something;
    
    • +2
      С замечанием согласен, только обратите внимание, что ваш код не эквивалентен моему. Мой код при ложности условия не изменит текущее состояния галки, а ваш код сбросит ее в «не выставлено».
      • 0
        И появится UB! Круто блин!
      • 0
        Да, у вас логика там мудреная, но смысл, думаю, понятен. Если под if then булево выражение, но нет надобности его обрамлять в условный оператор, можно просто присвоить его результат .Checked.

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

        Не эквивалент, конечно, но в глаза бросилось)
    • +1
      У нас есть замечательное правило — писать софт так, чтобы в любой момент можно было отделть GUI

      Ну в этом же и есть смысл паттерна, который автор пытается показать.
      • –1
        Если приложение представляет какой-нибудь сервер приложений, который допускает как визуальное конфигурирование, так и конфигурирование через командную строку и/или ini-файл, то полностью согласен. Если же это обычное приложение для клиентской машины, зачем его код писать с задумкой под отделение GUI? Это уже ближе к YAGNI. Я говорю о способах не полного потенциального отделения, а разумного разделения и локализации обращений между GUI и внутренней логикой приложения
        • +3
          Когда у вас большая модель и интерфейс, который в разных окошках юзает пересекающиеся подмножества бизнес-процессов и соответственно модели, внезапно, такое разделение ОЧЕНЬ сильно удешевляет поддержку.

          p.s. я на всякий случай напомню, что поддержка кода (модификации) дороже его разработки, и про ситуацию под названием «технический долг».
        • +1
          Потому что в таком случае проще другую морду натягивать блин! Гуй и логика должны быть на разных уровнях если это не прога на три кнопочки или утилита на одно окошко (хотя для таких часто нужна еще консольная морда для того, чтобы из скриптов её юзать).
          • +1
            Гуй и логика, гуй и логика… Если у вас есть сложное окно, в котором присутствует какая-никакая логика — это все-таки гуй или все-таки логика? К бизнес-логике, как и к системной низкоуровневой логике такая логика вообще никакого отнешения не имеет. Она относится чисто к работе этого окна, а не к внутренней логике системы. Вы для этой оконной логики пишете отдельные классы?
            • +1
              Есть логика отображения. И да, для нее есть специальные классы — ModelView из MVVM.
      • +5
        retran, у вас богатый опыт, мудрые комментарии. Но ни одного поста. Напишите, пожалуйста, как правильно реализовать MVC на Delphi. Судя по этому топику — будет популярно.
        • 0
          Боюсь, retran не может, у него карма слишком снизилась. На самом деле попытки создать какой-то халявный (с точки зрения использования) механизм связи GUI и объектов предпринимались (см. например тут), а в Delphi XE2 даже появилась такая штуковина как LiveBindings (это тоже типа механизм MVVM, прямо от разработчиков Delphi, но это такая накрученная, бесполезная, неудобная жесть по сравнению с тем простейшим примитивом который описываю я)…
          • 0
            Воооо, а я чего говорю — есть реализация.

            Мне в детстве Дельфи казалась жутко накрученной, бесполезной и неудобной по сравнению с турбопаскалем. А вопрос на самом деле как всегда в компромиссе между стоимостями разработки, поддержки, тестирования и отладки.

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

            Простой в данном случае зканчивается где-то на 4-5 объекте предметной области, требующем хотя бы CRUD.
            • 0
              >Пока ваш проект простой
              Надеюсь, это был просто оборот речи, т.к. вы ничего не знаете о моих проектах. Проекты, в которых я участвую, состоят из тысяч модулей и сотен тысяч строк кода. И что-то пока не ощущается проблем из-за отсутствия в Delphi встроенных средств по связыванию данных с объектами.
              • 0
                Возможно не с чем сравнивать?

                Не боитесь, что после вас код будет какой-нибудь маньяк поддерживать?
                • 0
                  Знаю я про маньяка с бензопилой. Я как раз и пытаюсь до вас донести, что код этих проектов написан качественно и хорошо, механизмы связи данных с гуи реализованы собственным образом (на основе принципов, близких к тем, которые я сейчас начал излагать в статье). Я просто начал с мега-примитива. Мог бы начать сразу с Observer'а, легче вам стало от этого?
                  • +1
                    Проблема в том что ваш примитив нарушает кучу выстраданных кровью практик. И не имеет отношения к mvc.

                    Вы юнит-тесты на модели пишете?
              • 0
                Ну и без конкретных метрик типа цикломатической сложности, глубины наследования, количества дублирования кода, покрытия тестами итд ваши тысячи модулей никакого впечатления не производят. Говнокодерских проектов в энтерпрайзе большинство — специфика области и процессов в ней. А еще длительности этих проектов.

                Собственно, разгребанием и рефакторингом такого я достаточно долгое время занимался, получил хороший опыт, но больше не очень хочу ;)
          • 0
            ссылку дат не туда. надо смотретьтут
            • +1
              Ну, кстати, этот немец хорошее дело делает, биндинг к VirtualTreeView — отлично. Биндинг обычных объектов — тоже неплохо, и недавно в его блоге был пример реализации MVVC, так там вообще минимум кода для связи между интерфейсом и объектами.

              Я как раз пытаюсь собрать в одну кучу все хорошие проекты, которые повыходили начиная с D2010 и попробовать задействовать новые технологии по максимум. С ORM, биндингом и т.д… Ваша статья очень в тему, спасибо.

              С тем что мешать интерфейс и бизнес-логику в одну кучу не стоит — согласен. А вот насчёт реализации есть вопросы. С помощью того же DSharp есть возможность вообще не писать ни каких обработчиков для UI контолов — просто при создании формы в цикле обойти все контролы и привязать к нужному объекту. В чём профит от того, что мы поля объекта будем менять вручную, а не доверим это дело биндингу? Единственный плюс, который вижу — рефакторинг. При переименовании полей класса мы сразу получим ошибку компиляции, а в случае с биндингом — скорее всего нет. Но это решается написанием юнит-тестов… Зато насколько более чистым будет код, разве не так?
              • 0
                На тему ORM вот такой проект есть. Но у них там тоже все довольно сыро. И разработка слабо движется (опенсорс, чего с него взять).

                >В чём профит от того, что мы поля объекта будем менять вручную, а не доверим это дело биндингу?
                Да вобщем-то ни в чем, если вы этому биндингу действительно доверяете :).

                >Единственный плюс, который вижу — рефакторинг.
                Это ДАА. Если биндинги задаются текстом (а вобщем-то они так и задаются), то это жесть. Для нас компилятор — главный помощник в рефакторингах — он сразу говорит, где чего забыли переименовать, где тип параметра не совпал и т.д. и т.п. А тут вообще неупрявлямый проект получится.

                >насколько более чистым будет код
                Да вот не уверен я, что он станет супер чистым. Просто на смену одному коду (ручному связыванию) придет другой код (автоматическое связывание, биндинги).

                Но хочу еще раз акцентировать внимание на том, что зачастую пользовательский интерфейс представляет данные совсем по-другому, не в том виде, в каком они хранятся в слое логики. И в этом случае мне что-то не очень хочется писать еще промежуточные классы только затем, чтобы перегнать в них данные из слоя логики и привязать их к контролам, т.к. мне все равно придется еще программировать перегонку данных из этих промежуточных классов в классы слоя логики. Мне проще сразу запрограммировать «ручную привязку» классов логики к «совсем не похожему на эти классы» GUI.

                Но в целом вы все верно понимаете.
                • 0
                  да, я как раз выбираю между DORM и TMS Aurelius. И тот и другой — пока не идеальны имеют и свои преимущества и недостатки, но пользоваться можно и тем и другим. Что для меня достаточно важно, оба — это понятные в использовании фреймворки, в отличие от предыдущих попыток вроде TiOPF (который я банально не осилил или времени не хватило..)

                  у Aurelius в качестве одного из преимуществ — наличие виртуального датасета, который можно очень, очень легко «прибиндить» к любой коллекции вроде TObjectList. Т.е. буквально две строчки кода и получаете любимый грид в котором строки — объекты из списка, а колонки — поля этих объектов. Причём объекты любые, не только увязанные с БД. Если что-то подобное сделают для DORM — будет просто шикарно.

                  А для деревьев подойдёт DSharp, попробовал перевести одну небольшую утилиту на использование этой компоненты — код сократился существенно… Вообще количество кода для меня — критично. Если я буду каждый справочник рисовать вручную — то потрачу уйму времени, поэтому стараюсь или использовать формы наследники, или вообще создавать формы в run-time. Из серии «лучше день потерять, потом за 5 минут долететь» :)
                  • +1
                    Про
                    >каждый справочник рисовать вручную
                    и
                    >вообще создавать формы в run-time

                    Ну мы обычно имеем одну форму, которые умеет открывать (показывать) почти любой справочник. Для специализированных справочников, конечно, делаем отдельные.
                    • 0
                      Хмм… Тогда, честно, не очень понимаю, как способ, описанный в статье, можно использовать на такой форме… Создаёте обработчики нажатий и присваиваете их контролам в run-time?
        • 0
          Спасибо за комплимент, но ничего особого мой опыт из себя не представляет. А пост писать… С отрицательной кармой (да, я не люблю эппл и пхп) и после 7-ми лет без Дельфи?

          Ну, и, собственно, она весьма слабо будет отличаться от того же самого, но на дотнете. А про дотнет статей прилично.

        • 0
          Поддерживаю! Было бы очень интересно =)
    • +2
      // set the parameter we need
      cbNeedDeleteFiles.Checked:= Something;
      Напомнили, во время обучения в институте я, да и все сокурсники, любили снабжать код комментариями а-ля:

      A := B + C; // Складываем два числа
      writeln(A); // Выводим результат вычислений
      


      Это я к тому, что снабжать элементарные действия комменариями — только загаживать код.
  • –18
    А что, в Дельфийских фреймворках до сих пор нет MVC нормального встроенного? Нахрена эта некрофилия?
    • –10
      А нахрена вообще Дельфи? Закопайте!
      • –3
        Можно и так сказать, но здесь даже по дельфийским понятиям некрофилия, да ещё и в корне ошибочная. Какое-то жесточайшее ретроградство и нежелание пользоваться новыми инструментами.
      • –4
        • +12
          Ребят, я не понимаю. Когда ктото пишет на экзотике, о которой вы даже не слышали, это OK. А когда пишут на Delphi сразу же приходят люди, которые кричат «закопайте».

          Я успешно пишу на Delphi. Проблем не знаю, все работает. Код нативный, быстро выполняется. WinAPI работает, надстройки над ним тоже есть (для ленивых). Что еще надо?!
          • –12
            Эта экзотика всё-таки куда интереснее, чем окаменелости.
            • +4
              По логике в экзотике проблем в 100500 раз больше (хотябы изза новизны, нераспространенности, устаревания, etc.), чем в Delphi. В чем соль?
              • –4
                Решать эти проблемы может быть? В R&D вот сплошные проблемы как-бы!
          • –2
            Есть экзотика прогрессивная. Те фичи, которые там появляются, когда-нибудь станут мейнстримом. А есть медленно умирающая платформа с очень маленькими перспективами и полная legacy-решений. Отношение соответствующее, уж простите.
            • +3
              У меня нет проблем с Delphi «полного легаси решений». Может, объясните, зачем я должен бежать сломя голову в Java, например?
              • –3
                Более прогрессивные технологии (хотя про Java можно похоливорить, я больше верю в дотнет), более крупные проекты, большая зарплата, саморазвитие. Мало?
                • +4
                  Компилятор все равно генерирует нативный процессору код. WinAPI никуда не сдвинится. Что же там нового? Может быть, относительно Delphi, расскжите, если все новое вшивается в WinAPI?

                  ДонНет кстати, легко «вскрывается», сломать программу на .Net намного проще, чем нативный код. Думаю, вы в курсе. И про скорость работы тоже. Особенно в криптоалгоритмах, которые нам нужны на каждом шагу.

                  Большие проекты? DLL с экспортированными функциями свяжут любые языки вместе.

                  Я технический директор, не жалуюсь на зарпалату. Она не от языка зависит. Язык — инструмент. Им надо владеть чуть больше, чем полностью. Тогда и старым казаться не будет)
                  • 0
                    Давайте тогда посмотрим с позиции технического директора:
                    1. Если у вас есть некий старый проект, который тем не менее не ушел в технический долг и нормально поддерживается — то никакого смысла переходить нет.
                    2. Если вы начинаете новый проект, то:
                    2.1. Разработка на дотнете тупо дешевле и быстрее, чем на Delphi. Просто за счет более прогрессивного инструментария (рекомендую глянуть на WPF, поймете о чем я).
                    2.2. Вам нужны разработчики. Под дотнет хорошего разработчика найти СИЛЬНО проще.
                    2.3. Значительно более сильное коммьюнити, документация и так далее.
                    2.4. Поддержка от вендора. Майкрософт же.
                    2.5. Ну и перепрыгнуть с Delphi на дотнет не очень сложно, в силу очень похожей идеологии и общего архитектора.
                    • +4
                      Разработка однофигственна по времени, и тут и там можно таскать мышкой)

                      Если честно, в гробу я видал этих «профи дотнэта». Может их и больше, но концентрация грамотных разработчиков, которые знают, что за фрэймворком есть жизнь, мизерна. А эникеев от програмирования мне не надо.

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

                      Не сложно, я прекрасо знаю все это. Но нравится именно Delphi изза анлоподобного языка. никаких нечеловеческий ~Classname, в Class.Destroy. Профит, кстати, все очевидно.
                      • –5
                        Разбудите меня когда в Дельфях появятся аналоги WPF или QML
                        • +4
                          FireMonkey?
                    • 0
                      под пунктом 2, вы наверное имели ввиду «если вы собираете команду с нуля, то..»? Уже сложившимся девелоперским коллективам всё равно какой язык человек изучал раньше, главное чтобы голова была ясная, а остальному — научат.

                      2.1, это чем-то подтверждается или ваше субъективное мнение? Недостаточно ведь просто выбрать C# вместо делфи, чтобы этот пункт сработал. А своего инструментария, у любой успешной фирмы, должно быть достаточно.

                      2.2, мы берём народ из местного ВУЗа. Процесс перехода на/с дотнет — достаточно безболезнненный.

                      2.3. спорить не буду, может так, а может и нет.

                      2.4 вот тут полностью согласен.

                      2.5 Я наблюдал и обратный процесс. В фирме, где я руководил отделом до недавнего времени, были такие переходы, пусть и вынужденные. Из отдела разработки на c# — в отдел разработки на Delphi. Без потерь в зарплате, и, надеюсь, перспектив.
                      • 0
                        Ну новый проект — это в непрофильной компании часто организация отдела, а в бодишопах — сбор команды из пула разработчиков. Поэтому да, как правило — это сбор новой команды.

                        2.1 личный опыт и субъективная оценка тулсета и разработческого окружения (я имею в виду весь team system)

                        2.2 ну так то ж вчерашние студенты — это в любом случае болезненно. А вот если брать с опытом…

          • –3
            Нет, не понимаю. Дельфи был умирающим языком даже когда им занимался великий Борланд — этой компании не хватало ресурсов чтобы угнаться за непрерывно плодящимися технологиями под виндой. Была например такая штука — кажется BDE называлась — единственный способ подключать визуальные компоненты к базе данных. Так это BDE сыпалось прямо на глазах.
            Людей, готовых писать новые библиотеки под дельфи всё меньше. Вот сейчас выйдет Windows 8 и заказчик попросит вас прокачать вашу прогу до Metro — что вы будете делать? Не понимаю. Зачем сидеть на дельфе, когда есть VS и .NET? Оно же реально лучше! И нативнее, и библиотеки там лучше, и синтаксис ява-подобный, а значит с него легче перескочить на какой-нибудь модный андроид. Вся жизнь кипит — там. Даже чисто из интереса можно было бы перейти.
            Вообще не понимаю.
            • +3
              Я думаю, мне не о чем с вами говорить, если ".net нативнее и лучше". Секрет открою — есть delphi для VS. Дотнетовский язык тоже. И .net не нативный, он интерпретируемый.

              С metro что делать? Документацию читать, как он строится и работает. В любом случае, в Windows все всегда упирается в WinAPI, наборы DLL.
              • +1
                А зачем нужен Delphi, когда есть Фортран и Кобол? Все равно ведь все компилируется в машинные коды, не?
                • +2
                  IDE по душе, синтаксис хорош. И вот не встречал Кобол для Windows 7, например)
                  • –1
                    Ну вот на мой взгляд, Visual Studio сильно выигрывает у Delphi, а синтаксис C# гораздо «чище» и «эстетичней» ;)
                  • 0
                    И да — www.opencobol.org/
              • 0
                Если не о чем говорить то зачем эти два абзаца?
                Когда я говорил что .net нативнее — я имел в виду что он, а не winapi, является основной платформой для микрософта. Код, написанный под .net, подойдёт для pc, x-бокса и виндофонов. А сверх-нативная прога на дельфе может внезапно перестать работать под новой виндой, как это уже один раз было с турбопаскалем. И даже с супер-пупер-нативным турбоассемблером.
                • +1
                  Вы путаете программы DOS и Windows. Не только паскалевский код отваливался в старших версиях Windows, а любой. Это всязанно с тем, что для выполнения DOS программ используется прослойка, они не родные для Windows. Для 100% совместимости требуется полная виртуализация компьютера под управлением DOS, например, DOSbox — самый бескровный вариант.

                  P.S. Перешлите ваше сообщение разработчикам скайпа или тотал коммандера) не в обиду, но ваш коммент странный.
              • 0
                > И .net не нативный, он интерпретируемый.

                Минуточку. Это вы о чем, вообще?
              • 0
                Вот тут у вас промашка. Metro работает не под WinAPI а под WinRT, это своя подсистема окружения. Да, формат WinPE никуда не делся, но VCL уже никак задействовать не выйдет, хотя бы по тому, что в WinRT нет функции CreateWindow, так же как нет такого понятия HWND. WinRT сам содержит в себе весь VCL, диспетчеры и DataBinding'и. Вся подсистема — объектная, работает на интерфейсах, аналогах COM-интерфейсов.
                Единственное, что получится сделать в Delphi — натянуть Metro скин. Но создавать настоящие Metro приложения не выйдет.
                • +1
                  Ага, осталось только дождаться, когда корпоративный сегмент перейдет на Windows 8. А то до сих пор многие сидят на Windows XP.
                  • 0
                    Да, есть такое дело. Эх, если бы WinRT появился бы лет 5-10 назад — цены б ему не было, а теперь придется долго ждать пока процент Windows 8 среди потенциальных пользователей дойдет хотя бы 90. Отсутствие возможности «доустановить» WinRT хотя бы в Windows 7 или в Vist'е так же очень расстраивает. Тем не менее, возможности предоставляемые платформой звучат очень вкусно. Reference counted memory management, глубокая поддержка async, возможность писать хоть на XAML\C++, хоть на XAML\C#, хоть на HTML5\JavaScript, и бесшовная интеграция при этом…
        • +6
          Это так эпично — зайти в топик по Делфи и начать очередной холивар по этой IDE. Зачем это?
          • +3
            Не знаю) Это самый большой вопрос.
          • 0
            Ну холивар начал не я, я зашел в топик покритиковать реализацию паттерна ;)
            • +2
              Мотороллер не мой! Я просто разместил объяву!!! ;)

              Надо заканчивать флуд не по теме. Остальное — в личку или скайп. Тут вам не бар, чтобы ху**и мериться.
              • 0
                Согласен. Предлагаю вернуться к обсуждению темы статьи.
            • +2
              ой, да ладно вам… отсюда и прям сюда, конечно не вы :)
              • 0
                Это ответ-предупреждение на другой комментарий, посмотрите внимательнее ;)
                Я один раз так пошутил, а человек попробовал пошутить так же ;)
    • 0
      Лучше напишите, где он есть. И приведите пожалуйста критерии «нормальности» для шаблона MVC. Сколько кода нужно написать, чтобы мой класс отобразил свое состояние в визуальные элементы окна и синхронизировал бы при этом свое состояние с состоянием этих визуальных элементов.
      • 0
        MVVM — msdn.microsoft.com/ru-ru/magazine/dd419663.aspx

        Готовая реализация зависимых свойств, экшенов и так далее. Интерфейс с моделью связываются полностью декларативно.

        • 0
          Чудесно, чудесно. Если там для отображения простого поля нужно кучу кода наплодить, яуж боюсь себе представить когда начнется отображение списков и зависимых сущностей. А еще, вы себе не представляли такую ситуацию, когда в интерфейсе нужно показать данные в совсем другом виде, не в том, в котором они хранятся в базе данных или в каком-то вашем промежуточном слое? Т.е. нечего не к чему привязывать — интерфейс имеет свое представление, а к внутренним данным переход производится по щелчку, например, по кнопке «Сохранить.» (точнее там может быть не одна, а много конвертация, т.к. у нас же многослойная система ;) )
          • 0
            Там не куча кода ;)
            И обратите внимание, что там в примере не одно поле, а полноценный crud-интерфейс.
          • 0
            Если надо показать данные в каком-то специфичном виде — на помощь всегда придет DataTemplate или ControlTemplate. И всё это декларативно. Текста действительно больше (хотя это спорно), но получаемый взамен функционал окупает это с лихвой.
            Спорно по тому, что в Delphi DFM файл принято редактировать при помощи дизайнера, а не в текстовом виде, а в WPF обычно наоборот — в виде XAML.
      • 0
        • 0
          Видел, читал, отверг. При сколько угодно крупном проекте и при хоть мало мальски сложных окнах (а не 3 Edit'а, как в примере) заниматься таким извращением, как описывать то, что у них называется ICalculatorViewModel — это ж удолбаться можно :) Честно вам говорю :)
          • 0
            не спорю ) но на простых справочных формах — почему бы и нет? Впрочем я и там пока поостерегусь, с этой мыслью надо переспать. И не одну ночь :). У меня с ним была небольшая переписка и Стефан в письме сказал, что MVVM он постарается развивать и дальше, то что мы видим — только начало. Плюс в активной разработке — некий «порт» с Caliburn.Micro. Подробностей не знаю, не стал отвлекать человека… Так что как знать, может и XAML в Делфи протащит :)
  • +1
    Благодарю за хорошую техническую статью. MVC тут под сомнением, конечно, ну да и в топку тот MVC. Вы спрятали связь бизнес-логики с графическим интерфейсом, и все правильно сделали.
    • 0
      Я же не писал «реализация паттерна MVC в Delphi» :) Считайте название статьи заманухой. Это скорее просто вариант программирования GUI, навеянный шаблонами типа MVC
  • –1
    ИМХО, ужасно не
    if cbNeedDeleteFiles.Checked then ...


    а как раз
    if Something then
      cbNeedDeleteFiles.Checked := True;
    
    if SomethingElse then
      cbNeedDeleteFiles.Checked := False;


    Плюс объясните плиз, для чего введена переменная FNeedDeleteFiles, если можно написать геттер свойства напрямую из чекбокса? Раз уж по факту нет никакого разделения на слои? К чему это усложнение с назначением лишнего обработчика?

    Ну и про MVC вам уже написали выше. Гуйня (то есть View) должна быть отдельно и код
    if cbNeedDeleteFiles.Checked then
    вполне допустим в контроллере, например.

    Вы молодец, продвигаете хорошие мысли. Разделять данные и гуй — правильно. Только вот реализация прихрамывает:)
  • 0
    пока непонятно что за подход, но похоже на MVVM(вот только самой модели данных не видно), сейчас есть некий класс модели данных MV привязанный к отображению. Ибо в классическом MVC контроллер меняет модель, а она кидает ивент о том что данные поменялись.
    • 0
      Вам действительно так важно, это MVVM, классический MVC или неклассический MVC??? Это просто способ. Работа которого похожа на то, что происходит в MVC. Что бы вы к чему ни привязали, каких бы классов-контроллеров не наплодили, если у вас есть на форме, к примеру, тупое поле ввода (TEdit), то данные, которые пользователь будет в него вбивать, все равно сначала запишутся в его свойство Text, а уж только потом хитрые классы-контроллеры по событию OnChange этого TEdit'а могут передать это значение из поля класса TEdit в какое-то место модели. Модель потом конечно может кинуть и свой Event об изменении, если это хорошая, MVC-шная модель, но все равно сначала данные окажутся в визуальном компоненте, потом в модели и только потом снова в визуальном компоненте (возьмутся заново из модели по event'у изменения модели).
      Может в других языках/средах существуют полностью виртуальные GUI-классы, которые не хранят данные в самих себе, а при изменении данных сразу дергают контроллер, тогда конечно будет больше похоже на «классический» MVC.
      • 0
        ну да, данные получают из неких компонентов типа TEdit, и в этом нет ничего криминального. Это классический MVC — передача данных от пользователя в контроллер. Нам не важно где хранятся данные от пользователя, в компонентах ли, или как-то еще. Главное что мы их своевременно получаем. В классическом MVC контроллер говорит модели «изменить текущий индекс на N». Модель меняет внутри себя что-то и кидает ивент «бла-бла текущий индекс сменился». На ивент подписан view, который «бла-бла данные изменились, перерисовать там-то». В вашем подходе контроллер говорит «сменить индекс у модели», модель меняет индекс и сама что-то делает с гуем. Т.е. в модели хранится логика работы с гуем. и это как раз и есть M-V-VM. И важный момент завязки промежуточного слоя модели на вид.
        • –1
          Ну наконец-то нашелся мыслящий человек, который все правильно понял и объяснил (себе и всем остальным) вместо криков о «неклассическом» MVC. Подписку GUI на уведомления от объектов я покажу в 3-й части статьи.
        • 0
          >> и кидает ивент «бла-бла текущий индекс сменился»
          >> и сама что-то делает с гуем.

          Если исходит из мнения (кстати, канонического и достаточно установившегося), что event — это сообщение, ровно как и вызов метода — это тоже посыл сообщения, то в этом смысле что кидать event, что вызвать метод «что-то сделать с GUI» ничем друг от друга не отличаются — и в том, и в другом случае модель оповещает view об изменении в данных
          • 0
            Ага, я об этом тоже хотел сказать. Но понял, что Ravager ругался не на то, что надо посылать сообщение, а на то, что event на самом деле не знает, чей метод вызывается. Т.е. event — это метод, определяемый извне класса. Класс вызывает event, а кто там зарегистрировался обработчиком этого event'а ему неизвестно. В этом и состоит желаемая развязка. Как у TButton'а событие OnClick — мы можем повесить на него любой код, а TButton просто вызывает его при щелчке.
          • 0
            каким образом слои оповещаются об изменениях в модели(через систему сообщений или паттерном Observer) это без разницы. Главное, чтобы модель ничего не знала о том, кто на нее подписан. В случае же когда модель сама что-то делает с гуем — есть уже ручное управление гуем(читай pull model вместо push model). Есть такой способ «отделения мух от котлет» — MVP. в котором presenter управляет отображением(ну или раздает необходимые указания для перерисовки)
      • –1
        alan008, всё-таки надо признать, что это не MVC и ваша реализация не отражается сути шаблона и не решает проблем, которые призван решать этот шаблон.

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

        Я думаю, что через 5-6 проектов с этим подходом, вы придете к более правильной концепции, тогда хотелось бы увидеть продолжение на Хабре.
  • 0
  • 0
    В Delphi VCL на панели Standart есть такой компонент, как ActionList. Он ведь там неспроста?

    Это по сути и есть «модель» для примитивных контролов (кнопки, менюшки, флажки). Можно нескольким разным контролам задать один Action. Более того, в Action задается внешний вид контрола. Например, если поменять Caption у Action, то он изменится у всех контролов, к которым этот Action назначен.
    • 0
      Расскажите, пожалуйста, как правильно работать с ActionList? В моём понимании, использовать его в design-time скорее вредно, т.к. код становится сложно читаемым (привязка к объектам хранится на форме, и нужно переключаться на неё и смотреть подробности)
      • 0
        Я обычно создаю несколько ActionList'ов для разных разных видов операций. Держать все-все операции в одном списке как-то не очень удобно. Возможно, потому что я использую один обработчик действий на список, в котором проверяю какой Action сработал. Просто для однородных действий (над элементами таблицы, например) удобнее все действия обрабатывать в одной процедуре.

        Что касается читаемости — достаточно давать действиям внятные имена. Например, actAddUser, actDeleteUser. А на контролы смотреть не надо, один раз назначил контролу действие и дальше работаем только с действием.
  • 0
  • 0
    А как быть с вариантами переключения состояния checkbox не только по OnClick, но и по, например, хоткею spacebar при фокусе на галочке? Предрекаю предложение использовать OnChange, но тогда как отличить change программный от change'а пользовательского?
    • 0
      Если говорить конкретно про TCheckBox, то у него событие OnClick срабатывает и при клике мышкой, и при переключении галки пробелом на клавиатуре и при программном присвоении CheckBox.Checked := not CheckBox.Checked. Так что в данном случае ругать надо скорее всего название события :) Надо было его назвать не OnClick, а скорее OnChange.
      Хотя проблема, которую вы подметили, верная: у разных компонентов события могут срабатывать совершенно по-разному в зависимости от способа взаимодействия с компонентом (мышкой, клавиатурой, программно), поэтому нужно каждый раз проверять, действительно ли вы выбрали правильное событие и срабатывает ли оно, когда вам надо. Иногда (редко) приходится использовать сразу 2 события. Хотя обычное удается найти «правильное». Либо написать свой компонент, который работает так как вам надо.
      • 0
        Возможно, слишком иносказательно, но я пытался обратить внимание на другую проблему, которая существует как минимум в Delphi и .NET, а вообще и в других библиотеках графических компонентов. Это невозможность в обработчике события изменения какого-либо свойства определить, что привело к этому изменению: пользовательское действие или программное присваивание. Следовательно, не понятно, сообщать модели об этом изменении или это было обновление представления из нашего кода. Вы в простом примере галочки во избежание рекурсии («чего-то страшного») сравнили старое и новое значение. Но что делать, если модель более сложная, и простое сравнение невозможно, либо модель нужно оповещать об установке любого значения (например, для логирования)?
        • 0
          >>не понятно, сообщать модели об этом изменении или это было обновление представления из нашего кода
          Ваши опасения я понял, но не очень их разделяю :). В качественно написанных компонентах повторные присвоения одних и тех же значений _не должны_ приводить к повторным срабатываниям события. Это как бы такая аксиома, правило хорошего тона. Если же событие происходит в дело и не в дело (т.е. любом срабатывании setter'а свойства) — я считаю, что это плохо написанный компонент. Также я считаю довольно плохим стилем программирования необходимость в коде определять причину установки того или иного свойства компонента (происходит ли эта установка в результате воздействия пользователя или в результате программной установки в ответ на событие обновления модели). С другой стороны, модели действительно бывают разные и могут возникнуть ситуации, когда срабатывание события нам мешает, но тем не менее оно происходит. Для этих целей обычно имеются методы блокировки срабатывания событий (BeginUpdate..EndUpdate). Если таких методов нет у самого компонента, можно тупо у формы завести поле FUpdating: Integer и там, где нужно обновить модель без срабатывания обработчиков сделать:
          Inc(FUpdating); 
          try 
            // Обновляем модель
          finally 
            Dec(FUpdating); 
          end;
          

          а в обработчиках событий компонентов (таких как OnClick, OnChange) в самое начало вписать
          if FUpdating>0 then
            Exit;
          // ... код обработчика...
          

          Таким образом мы сможем заблокировать срабатывание нежелательных обработчиков там, где работаем с моделью.
        • 0
          В Delphi VCL у всех событий есть свойство Sender: TObject
          • 0
            Sender не говорит, произошёл ли вызов TCheckBox.OnClick в результате пробела или присваивания или клика мышкой… На самом деле эта проблема уменьшается, когда есть отдельные события на разные действия и общее на произошедший в результате действия изменение. В частности, когда OnClick — только про мышку, а про изменение Checked — OnChange. Но VCL он такой VCL…
            • 0
              Есть же отдельные события для мыши (OnMouse***) и клавиш (OnKey***). А клик — явление логическое, можно ведь и программно кликнуть (метод .Click ).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.