Майстри delphi, (документація, вихідні коди, скачати)

Застосування фреймів для написання "правильних" додатків

Не знаю як ви, а я свого часу часто зустрічався з такою ситуацією. При розробці досить великих проектів кількість форм з часом розросталося. І чим далі, тим гірше. Але навіть це не було великою проблемою до тих пір, поки я працював сам - все таки себе можна самодісциплінованим - змусити використовувати однакові назви форм, методів, змінних. Але після того, як я став працювати в команді, проблема стала на повен зріст - рефакторинг часто став зводитися до "переписати все", так як у кожного програміста своє розуміння "правильно написаного коду" Після деяких роздумів я вирішив створити якийсь "движок" , який полегшить написання немаленьких проектів. В основу цього движка я поставив такі принципи:
1. Всі без винятку об'єкти для роботи з базами даних повинні знаходиться в модулях даних, причому кількість об'єктів в базі даних не повинно перевищувати певний критичний межа (для мене - до 50 об'єктів) - далі стає складно орієнтуватися;
2. Всі операції по роботі з даними з БД повинні також описуватися в модулях даних, у відповідних події або ActionList;
3. У головній формі не повинно міститися коду по роботі з режимами, тільки виклик режиму і виклик абстрактних, загальних для всіх методів, які будуть перевизначатися в кожному відповідному режимі.
4. Інтерфейс всіх режимів повинен бути повністю единообразен.
5. Режим повинен мати "право" зміни головного вікна.
6. Режим не повинен знати про існування інших режимів і та інших форм взагалі, для режиму має бути доступні тільки головна форма і модулі даних.
7. Модулі даних не повинні знати про існування режимів.
8. Режими повинні створюватися динамічно, щоб не займати зайву пам'ять.

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

Крім того, код головної форми був перевантажений функціями в яких визначалося, який режим на даний момент завантажений, і відповідно викликався потрібний метод режиму.

Тому було вирішено відійти від "важкої спадщини" :) процедурного програмування і використовувати основні принципи ООП.

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

Як можна помітити, багато функцій базового фрейму повертають значення типу TFunctionResult. Ця структура визначена в модулі UnitConstTypes_etc, в який в майбутньому будуть додаватися інші типи, константи. Функції повертають прапор успішного завершення операції, а разі виникнення помилки - текст повідомлення про помилку.

Це, зрозуміло, шаблон. У моїх нинішніх додатках вищезазначених функцій більш ніж достатньо. Однак, якщо вам потрібно додати якийсь специфічний метод, в цьому зрозумілі не ніякої складності.

Крім того, в процедурі експорту даних викликається метод SaveToHTML поточного гріда фрейма. Цей метод визначений в модулі MyDBGrid.

Перейдемо до відображення фреймів на головній формі програми.

Головні форми додатків у мене мають приблизно такий вигляд: зліва - дерево меню, внизу лог додатки, зверху - панель інструментів, інший простір порожній, його займає панель, на якій будуть відображатися фрейми.

Спочатку створимо свій тип

Тепер потрібно створити правильно працюючу головну форму. У uses додамо використання модуля UnitFrameBase, в розділ public внесемо об'єкт MainFrame класу TFrameBase. Тепер потрібно написати функцію, яка буде коректно відображати потрібний фрейм при відкритті потрібного режиму.

Як же викликати цю функцію? Створимо додатково повідомлення - для виклику процедур фільтрації.

Param1 і Param2 використовуються для формування потрібних запитів в однотипних фреймах. Тепер потрібно написати обробник повідомлення FILTER_EVENT.

Перейдемо до відображення меню фреймів. Як джерело зберігання структури деревоподібного меню можна використовувати xml файл (це зручно, якщо програма не використовує БД), таблицю в використовуваної в додатку БД або ж зберігати структуру прямо в додатку (дуже зручно це робити в dxTreeList від DevExpress, однак ці компоненти платні) . Так як у всіх моїх додатках використовуються бази даних (в основному СУБД Oracle або Firebird), то я вибрав другий варіант. Створимо таблицю наступної структури

Крім того, для забезпечення цілісності дерева, з таблицею пов'язано кілька тригерів і обмежень. Повністю структуру таблиці і тексти обмежень ви можете подивитись в початковому тексті БД.

З цієї реляційної таблиці досить легко створити дерево за допомогою рекурсивної процедури, текст якої ви також можете подивитися в початковому тексті БД. Зауважимо, що принципи створення такої процедури взяті з книги "Світ InterBase".

Не вдаючись в деталі побудови дерева на сервері, Відзначимо що в клієнтському додатку можна побудувати дерево за один прохід по набору даних.

Процедура відображення дерева приведена нижче. Спочатку визначимо структуру елементів меню

Природно, що структура елемента меню збігається зі структурою таблиці в БД. Створимо меню наступною функцією

Оброблювач зміни елемента в меню буде мати наступний вигляд

Аналогічно і для інших. Крім того, в головній формі потрібно ще створити спільні елементи для фільтрації. Це можуть бути елементи для фільтрації по датах, по розрахунковим рахункам і т.д. Однак в обробнику зміни по цих елементах потрібно всього-лише послати повідомлення CX_FILTER. Як обробити отримане повідомлення, буде вирішувати конкретний фрейм.

Крім вишеперчісленних, в головній формі потрібно буде зареєструвати всі типи фреймів. Робиться це в секції initialization функцією RegisterClasses.

Тепер потрібно створити фрейми. Фрейм повинен успадковуватися від створеного вище абстрактного фрейму TFrameBase. Після цього потрібно перевизначити необхідні режими.

У найпростішому випадку потрібно перевизначити конструктор фрейму, до якому перевизначити опис фрейма, викликавши SetDesc і визначити поточний датасета фрейма, викликавши SetFrameCurrentDataSet. Якщо на фреймі є грід, то потрібно перевизначити функцію GetMainGrid, щоб вона повертала потрібний.

Таким чином для додавання нового режиму в найпростішому випадку потрібно прописати всього 9 рядків коду!

Однак його зміна (поліпшення) фрейму абсолютно не приведе до жодних правок головної форми. Функції роботи з даними викликають лише методи відповідних DataSet. У обробнику яких можуть викликатися додаткові модальні форми або діалоги.

Код тестового додатка, демонстріруещего описану роботу з кадрами находется тут.

Схожі статті