Форум по программированию Delphi Sources

 



Вернуться   Форум по программированию Delphi Sources > Все о Delphi > Базы данных
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #16  
Старый 04.09.2013, 07:58
Аватар для Freeman
Freeman Freeman вне форума
Человек-компилятор
 
Регистрация: 05.10.2012
Адрес: Санкт-Петербург
Сообщения: 575
Версия Delphi: 6
Репутация: выкл
По умолчанию

Книг не знаю, к сожалению. Могу только посоветовать поискать темы с аналогичными вопросами по проектированию на этом и других форумах, где не посылают в Google, а дают конкретные ответы. Благодаря нашей теме теперь хоть понятно, как это выглядит.
Ответить с цитированием
  #17  
Старый 08.10.2013, 18:51
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Я продолжил поиски по теме:


Процедура вызова окна, где заполняются данные о документе.
Код:
procedure TfMain.barbutNewTicketClick(Sender: TObject);
begin
  Tickets.Insert;
  fNewTicket.ShowModal;
end;

Три процедуры для заполнение подчинённых данных.
1. Insert в Detail таблицу -> Items.Insert;
2. Post в Detail таблицу (есть обработчик BeforePost, в котором проверяется адекватность введённых данных)
3. Cancel изменений в Detail таблице ->Items.Cancel;

Далее код для сохранения документа:
Код:
procedure TfNewTicket.butSaveClick(Sender: TObject);
begin
  // ... некое взаимодействие с пользователем

  fMain.Tickets.Post; // вносим изменения в билет
  fMain.Tickets.UpdateBatch; // отправляем изменения в БД (тем самым получаем ID записи, на которой сейчас стоит курсор (последняя добавленная)

  fMain.Items.First; // обработка всех добавленных записей в Detail таблице
  while not fMain.Items.Eof do
  begin
    fMain.Items.Edit;
    fMain.Items.FieldByName('IDTicket').AsInteger :=
      fMain.Tickets.FieldByName('ID').AsInteger;
    fMain.Items.Post;
    fMain.Items.Next; // Next вызывает метод Post? я не нашёл в реализации явного вызова, но судя по тому, что строка сохраняется делаю вывод что вызывается
  end;

  fMain.Items.UpdateBatch; // фиксируем изменения в Items.
end;

В итоге всё супер работает. Только пришлось отказаться от:
Код:
Items.MasterSource := dsTickets;
Items.MasterFields := ID;
Items.IndexFieldNames := IDTicket;
что приводит к тому, что нужно добавлять второй ADOTable для Items, для дальнейшего отображения данных уже на других формах (например на форме, редактирования конкретного билета)

И пришлось добавить:
Код:
procedure TfMain.ItemsAfterOpen(DataSet: TDataSet);
begin
  if not Items.Filtered then
  begin
    Items.FilterGroup := fgAffectedRecords;
    Items.Filtered := true;
  end;
end;
Собственно фильтрация по "несохранённым" в БД записям из Detail таблицы.

Что скажете?

Последний раз редактировалось Uniq!, 08.10.2013 в 19:03.
Ответить с цитированием
  #18  
Старый 09.10.2013, 02:16
Аватар для Freeman
Freeman Freeman вне форума
Человек-компилятор
 
Регистрация: 05.10.2012
Адрес: Санкт-Петербург
Сообщения: 575
Версия Delphi: 6
Репутация: выкл
По умолчанию

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

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

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

По вопросу из комментария: TDataSet.Next вроде бы вызывает CheckBrowseMode, которая и делает Post или Cancel в зависимости от наличия или отсутствия изменений.
__________________
Не стоит путать форумы с богадельнями. © Bargest
Ответить с цитированием
Этот пользователь сказал Спасибо Freeman за это полезное сообщение:
Mrak (11.10.2013)
  #19  
Старый 09.10.2013, 17:20
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Т.е. вот всё, что начинается с
Код:
  fMain.
необходимо переделать? Я, значит, не до конца осмыслил. Думал, что управление запрещено только визуальными компонентами из других форм.
Тогда возникает вопрос: как правильно организовывать доступ к ADOConnection ADOTable и DataSource со всех форм? (с учётом того, что DataModule тоже будет являться глобальной и его использование ничего не изменит)

Само решение не является идеальным. Работает как часы, даже с учётом того, что сервер отваливается время от времени, из-за "временных работ".

Сейчас перешёл на стадию синхронизации оффлайн и онлайн режимов, но это уже другая история.
Ответить с цитированием
  #20  
Старый 09.10.2013, 18:16
Аватар для Freeman
Freeman Freeman вне форума
Человек-компилятор
 
Регистрация: 05.10.2012
Адрес: Санкт-Петербург
Сообщения: 575
Версия Delphi: 6
Репутация: выкл
По умолчанию

Цитата:
Сообщение от Uniq!
как правильно организовывать доступ к ADOConnection ADOTable и DataSource со всех форм?
Это вопрос дискуссионный, рекомендации зависят от архитектуры приложения. Полагаю, что с момента первого удаленного консультирования оно сильно изменилось, и я теперь совершенно потерял контекст.

Если тебе удастся описать архитектуру на неком общем уровне, попробую дать совет.
__________________
Не стоит путать форумы с богадельнями. © Bargest
Ответить с цитированием
  #21  
Старый 09.10.2013, 23:36
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Как это сейчас: индекс f относится к формам. ВашКеп
fMain - общая таблица всех документов. В таблице только самая важная информация для быстрого взгляда и оценки этих документов. на ней же Connection(ADOConnection) Clients,Items,Tickets(ADOTable)
fNewTicket - Вызывается по нажатию кнопки с fMain. Тут три кнопки для заполнения данных (см. выше) и конечная кнопка bTicketSave по которой вызывается fMain.Tickets.UpdateBatch;
fSplash - обсудили и сделали уже. Splash-скрин для отображения загрузки данных из БД.
fSetClient - форма, которая вызывается для присоединения Человека к документу (кнопка на форме fNewTicket) На форме таблица Паспортных данных и быстрый поиск (фильтрация средствами cxGrid)
fAddClient - форма вызывается для добавления в справочник нового клиента (кнопка вызова на форме fSetClient)
fSettings - форма с настройками соединения с БД и базовые вещи, относящиеся к нумерации документов и всякая сторонняя информация.

Последний раз редактировалось Uniq!, 09.10.2013 в 23:42.
Ответить с цитированием
  #22  
Старый 10.10.2013, 20:42
Аватар для Freeman
Freeman Freeman вне форума
Человек-компилятор
 
Регистрация: 05.10.2012
Адрес: Санкт-Петербург
Сообщения: 575
Версия Delphi: 6
Репутация: выкл
По умолчанию

Фух, вроде форум наконец-то заработал, а я понял, что писать, чтобы не получилась отписка.

Цитата:
Сообщение от Uniq!
Как это сейчас: индекс f относится к формам.
На самом деле префикс "F" -- признак приватных полей классов. Так и у Borland, и у остальных нормальных людей. А формы чаще всего называются с постфиксом "Form". Это так, к слову.

Цитата:
Сообщение от Uniq!
fNewTicket - Вызывается по нажатию кнопки с fMain. Тут три кнопки для заполнения данных (см. выше) и конечная кнопка bTicketSave
Во, вот тут нарушение декомпозиции.

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

Диалог во всех случаях -- самый обычный, с кнопками "OK" и "Отмена". Нашкодил пользователь в диалоге, нажал "OK", -- действие диалога зафиксировалось (вызвался метод Post), а если нашкодил и нажал "Отмену" -- действие отменилось (вызвался метод Cancel). "Действие диалогом" хорошо абстрагируется и обычно реализуется в диалоге-предке, от которого наследуются диалоги конкретных действий.

Само собой, панелей инструментов в таком диалоге нет, а если есть -- то только для деталей, повторяющих шаблон "форма с данными -- действие диалогом".

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

Я не совсем в курсах насчет "паттернов" (шаблонов проектирования) и их терминологии, поэтому может оказаться, что описанное тут "действие диалогом" по-умному называется как-то по-другому. Если узнаешь, отпишись.
__________________
Не стоит путать форумы с богадельнями. © Bargest
Ответить с цитированием
  #23  
Старый 14.10.2013, 16:06
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Очень хочется разобраться до конца.
На главной форме:
Код:
procedure TfMain.barbutNewTicketClick(Sender: TObject);
begin
  Tickets.Insert;
end;
Вызывать Диалог, в котором будут редактироваться данные нужно в AfterInsert?
Код:
procedure TfMain.TicketsAfterInsert(DataSet: TDataSet);
begin
  fNewTicket.ShowModal;
end;
Открывается форма. На ней cxGrid в котором отображаются данные из подчинённой таблицы и куча DB компонент, которые связанны с fMain.Tickets (этого тоже не должно быть?). Три кнопки "Добавить Сохранить Отмена", которые относятся к Данным из Detail-таблицы (для неё дополнительный диалог я реализовывать не стал... не за чем)
ADOTable который содержит в себе информацию из Detail таблицы я перенёс на форму диалога. (по-моему это неправильно) Но если оставить её на FormMain то получится, что мне нужно будет вызывать Insert Post Cancel для этой таблицы из диалогового окна. (Как правильно?)

Далее как вы и посоветовали: Cancel & Ok;
OK:
Код:
procedure TfNewTicket.butSaveClick(Sender: TObject);
begin
  fMain.Tickets.Post;
end;
Опять управление из Диалоговой формы. Далее идёт BeforePost, в которой куча всевозможных проверок данных и несколько машинных пересчётов.
Далее AfterPost
Код:
procedure TfMain.TicketsAfterPost(DataSet: TDataSet);
begin
  Tickets.UpdateBatch;
  Items.First;
  while not Items.Eof do
  begin
    Items.Edit;
    Items.FieldByName('IDTicket').AsInteger := Tickets.FieldByName('ID')
      .AsInteger;
    Items.Next;
  end;
  Items.UpdateBatch;
end;

Я слабо, очень слабо представляю как избавиться от глобальной переменной fMain в коде из Формы-Диалога.

Про индекс F, я помню Просто важнее сейчас исправить основные ошибки.
Ответить с цитированием
  #24  
Старый 14.10.2013, 16:55
Аватар для Mrak
Mrak Mrak вне форума
Местный
 
Регистрация: 26.01.2013
Адрес: МО
Сообщения: 438
Версия Delphi: XE2
Репутация: 17
По умолчанию

Очень интересная и актуальная тема
Подскажите, не понимаю, здесь
Код:
procedure TfNewTicket.butSaveClick(Sender: TObject);
begin
  // ... некое взаимодействие с пользователем
 
  fMain.Tickets.Post; // вносим изменения в билет
  fMain.Tickets.UpdateBatch; // отправляем изменения в БД (тем самым получаем ID записи, на которой сейчас стоит курсор (последняя добавленная)
 
  fMain.Items.First; // обработка всех добавленных записей в Detail таблице
  while not fMain.Items.Eof do
  begin
    fMain.Items.Edit;
    fMain.Items.FieldByName('IDTicket').AsInteger :=
      fMain.Tickets.FieldByName('ID').AsInteger;
    fMain.Items.Post;
    fMain.Items.Next; // Next вызывает метод Post? я не нашёл в реализации явного вызова, но судя по тому, что строка сохраняется делаю вывод что вызывается
  end;
 
  fMain.Items.UpdateBatch; // фиксируем изменения в Items.
end;
вы всем записям в Items присваиваете номер последней записи из Tickets?

И еще небольшой вопросик для Freeman, использую DataModule и обращаюсь к Query на нем как через глобальную переменную
Код:
DataModule.ADOQuery1.SQL.Text:=... 
Получается, что у меня тоже "косяк"?

Спасибо
__________________
Я за здоровый экстрим!
Спасибо за "спасибо")
Ответить с цитированием
  #25  
Старый 14.10.2013, 17:17
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Идея такая:
1) Сначала отправить на сервер запись из Master таблицы.
2) Она получит свой ID. (Его можно было бы и руками самому проставить, но как оказалось это "костыль")
3) Далее считываю этот ID (да, он последний т.к. используется Insert и курсор прыгает именно на последнюю запись)
и присваиваю его всем подчинённым (Detail) записям. И только после этого отправляю их на сервер.

Последний раз редактировалось Uniq!, 14.10.2013 в 17:22.
Ответить с цитированием
  #26  
Старый 14.10.2013, 17:23
Аватар для Mrak
Mrak Mrak вне форума
Местный
 
Регистрация: 26.01.2013
Адрес: МО
Сообщения: 438
Версия Delphi: XE2
Репутация: 17
По умолчанию

Цитата:
Сообщение от Uniq!
Идея такая:
1) сначала отправить на сервер запись из Master таблицы.
2) Она получит свой ID. (Его можно было бы и руками самому проставить, но как оказалось это "костыль")
3) Далее считываю этот ID (да, он последний т.к. используется Insert и курсор прыгает именно на последнюю запись)
и присваиваю его всем подчинённым (Detail) записям. И только после этого отправляю их на сервер.
отлично, так примерно и представил
у меня же сделано все на основе LAST_INSERT_ID, когда заморочился с номерами заказов, спросил на форуме MySQL, посоветовали так...
Сейчас понимаю, что не очень правильно. Была идея засунуть два запроса в одну транзакцию, пока не увидел как у вас
__________________
Я за здоровый экстрим!
Спасибо за "спасибо")
Ответить с цитированием
  #27  
Старый 14.10.2013, 18:17
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

Моё тоже не идеально. Но LAST_INSERT_ID - слишком стрёмный вариант. Я от него отказался сразу же.

для Freeman прилепил два файла с GUI.
fMain(Главная форма) и fNewTicket(диалог)

С диалогом ещё дорабатываю. Жду, когда на фирме поставят devExpress c dxWizard.
Можно будет оформление документа организовать подобно установке любой программы (поэтапное заполнение)
Изображения
Тип файла: png fMainPNG.png (113.2 Кбайт, 10 просмотров)
Тип файла: png fNewTicketPNG.png (20.8 Кбайт, 9 просмотров)
Ответить с цитированием
  #28  
Старый 15.10.2013, 13:29
Аватар для Freeman
Freeman Freeman вне форума
Человек-компилятор
 
Регистрация: 05.10.2012
Адрес: Санкт-Петербург
Сообщения: 575
Версия Delphi: 6
Репутация: выкл
По умолчанию

Цитата:
Сообщение от Uniq!
Вызывать Диалог, в котором будут редактироваться данные нужно в AfterInsert?
Не-а, не угадал.

Весь смысл деления программы на интерактивные и пакетные потоки -- в том, чтобы не перемешивать "видимый" и "невидимый" код. Событие AfterInsert -- неинтерактивное, помещать в него вызов диалога -- нарушение декомпозиции. "Действие диалогом" должно выглядеть примерно так:

Код:
type
  TDataSetDialog = class(TForm)
    ...
    EditingSource: TDataSource; // назначен всем DB-компонентам
                                // и как MasterDataSet деталям
    ...
  public
    function Execute(DataSet: TDataSet): Boolean;
  end;

function TDataSetDialg.Execute(DataSet: TDataSet): Boolean;
begin
  EditingSource.DataSet := DataSet;
  Result := ShowModal = mrOK;
  if Result then
    DataSet.Post  // тут срабатывает AfterPost, делающий UpdateBatch деталей
  else
    DataSet.Cancel;
end;

// вызов
begin
  Tickets.Insert; // или Tickets.Edit
  with TTicketsDialog.Create do
  try
    Execute(Tickets);
  finally
    Free;
  end;
end;
Комментарий в коде подсказывает правильное использование AfterPost -- невизуальное событие для невизуальных действий.

Если редактирование деталей или доступ к справочникам требует какой-то дополнительной инициализации, вызов Post или Edit можно внести прямо в Execute, передавая ей вторым параметром команду:
Код:
type
  TDataSetCommand = (cmInsert, cmEdit);

  TDataSetDialog = class(TForm)
  public
    function Execute(DataSet: TDataSet; Command: TDataSetCommand): Boolean;
  end;

function TDataSetDialg.Execute(DataSet: TDataSet; Command: TDataSetCommand): Boolean;
begin
  EditingSource.DataSet := DataSet;
  // куча дополнительных инициализаций, в том числе через виртуальный метод в потомках
  if Command = cmInsert then
    DataSet.Insert
  else
    DataSet.Edit;
  ...
end;

Цитата:
Сообщение от Mrak
И еще небольшой вопросик для Freeman, использую DataModule и обращаюсь к Query на нем как через глобальную переменную
Не существует универсального ответа на все случаи. Нужно изучать архитектуру конкретного приложения и постановку задачи.

Цитата:
Сообщение от Uniq!
для Freeman прилепил два файла с GUI.
fMain(Главная форма) и fNewTicket(диалог)
Ну да, ну да, какой на фиг диалог? Такой дизайн может иметь боковая док-панель или фрейм внутри диалога, но не сам диалог. Диалог -- обязательно с диалоговыми кнопками.
__________________
Не стоит путать форумы с богадельнями. © Bargest
Ответить с цитированием
Эти 2 пользователя(ей) сказали Спасибо Freeman за это полезное сообщение:
Mrak (16.10.2013), Uniq! (15.10.2013)
  #29  
Старый 15.10.2013, 15:04
Аватар для Uniq!
Uniq! Uniq! вне форума
Местный
 
Регистрация: 29.09.2010
Сообщения: 536
Версия Delphi: Delphi XE3
Репутация: 374
По умолчанию

До меня вчера это всё дошло.) Но я боялся, что это слишком сложный вариант. А меня в институте приучили, что чем проще, тем лучше. И ещё должно быть правильно.

1) Значит я создаю форму - диалог. (исключаю её из автосоздаваемых) ВОТ это лишнее телодвижение почему-то не надо удалять.
2) Дальше на главной форме создаю этот диалог и вызываю его метод Execute передавая туда Tickets, оууу ес, который лежит в данный момент на главной форме (скажем нет глобальным переменным!)
3) Profit.

Осталось качественно задизайнить мой диалог. Погуглил. Нужно было определиться в понятиях. Вот диалог, который я представлял себе, а вот пример диалога, который должен был представлять.
Я вот только с этим никогда не сталкивался: присвоение с проверкой на равенство какому-то значению.
Код:
Result := ShowModal = mrOK;
В каких свойствах нужно порыться у cxButton1, чтоб на стала mbOk. И могла возвращать значение mrOK. Тоже самое с Cancel;
Ответ: в свойствах кнопки есть property: ModalResult.

Freeman, это реально круто. Остаётся только один вопрос. Где вы вычитали, что нужно делать именно так? Хотя подсознательно я понимаю, что это основные идеи любого ОО-языка.

Это оказывается шаблонизированная структура

Последний раз редактировалось Uniq!, 16.10.2013 в 11:55.
Ответить с цитированием
Этот пользователь сказал Спасибо Uniq! за это полезное сообщение:
Mrak (17.10.2013)
  #30  
Старый 16.10.2013, 11:20
Аватар для Mrak
Mrak Mrak вне форума
Местный
 
Регистрация: 26.01.2013
Адрес: МО
Сообщения: 438
Версия Delphi: XE2
Репутация: 17
По умолчанию

Цитата:
Сообщение от Uniq!
Freeman, это реально круто. Остаётся только один вопрос. Где вы вычитали, что нужно делать именно так?
Присоединяюсь, а то, не покидает ощущение, что у меня быдлокод везде
__________________
Я за здоровый экстрим!
Спасибо за "спасибо")
Ответить с цитированием
Ответ



Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 08:09.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources", 2004-2019

ВКонтакте   Facebook   Twitter