Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование от Чертенка.ру Выпуск 12. Работа с Oracle в Delphi. Заключительный выпуск по ODAC.


Информационный Канал Subscribe.Ru

Доброго времени суток, уважаемые подписчики!

Сегодня будут рассмотрены остальные основные компоненты ODAC.

А для начала - интересная новость от IBM:

IBM выпустила бесплатную версию DB2
IBM начала предлагать бесплатную версию СУБД DB2 - DB2 Universal Database Express-C, которую можно использовать не более чем на двух двухпроцессорных серверах, имеющих до 4 Гбайт памяти. Продукт доступен в вариантах для Linux и Windows. Отличием от аналогичных предложений основных конкурентов IBM является отсутствие ограничений на количество одновременно подключенных к СУБД пользователей и на размер баз. Однако в DB2 Express-C отсутствуют некоторые из функций платной DB2 Express, включая модуль DB2 Warehouse Manager, механизм тиражирования источников данных формата Informix и адаптеры обмена данными DB2 Connect. Лицензия разрешает использовать Express-C для нужд предприятия и использовать в составе коммерческих программных продуктов. Продукт можно загрузить по этому адресу. До IBM бесплатные варианты своих СУБД также выпустили Oracle и Microsoft.

Если после выходных Вам лень работать, то ждем Вас на форуме, где можно малость отвлечься от работы и поболтать на отвлеченные темы:

Культура программирования или как НАДО (или как не надо) писать программы.

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

Как часто вы меняете работу ?

Голосование и обсуждение.
К сожалению, наблюдается тенденция, что для повышения зп и своего статуса приходится менять работу.
А для многих это стало способом быстрого увеличения зп и они буквально скачут с места на место.
Как часто меняете работу Вы ? С чем это связано и как по Вашему должно быть?

Обсудить

Какая польза от человечества на земле?

Собственно хочу узнать какую пользу природа хотела получить и что она получила в итоге, создав человечество на этом земном шаре? Высказывайте свои предположения!
Обсудить

В начале сегодняшнего выпуска хотелось бы остановится на объектах-полях. Объекты-поля не являются "собственностью" ODAC, а уже заложены в механизм доступа к БД TDataSet.

При выполнении запроса датасет автоматически создает в памяти поля-объекты и именно к ним происходит обращение при помоши FieldByName/Fields. Однако такие поля-объекты можно создавать самим, что дает определенные преимущества, например, можно настраивать формат отображения, заголовок поля, а также обращаться к значению поля как к обычному компоненту. Также можно создавать лукап-поля и калк-поля. Рассмотрим все перечисленные возможности.

Создать объекты-поля очень легко - для этого нужно вызвать редактор Fields Editor (одноименный пункт в контекстном меню датасета или редактор свойства Fields). В появившемся пустом списке
из контекстного меню нужно выбрать один из трех вариантов:

  • Add fields... - добавить одно или несколько плей (из предложенных в списке)
  • New field... - создать новое поле (тут можно создать калк- или лукап-поле)
  • Add all fields... - добавить все поля.
Внимание! обратите внимание, что если Ваш запрос выбирает скажем 10 полей, а Вы создали 4 объекта-поля, то и обращаться в коде будет возможно только к этим четерем полям. При попытке обратится к другим полям будет выдана ошибка, мол поле не найдено.

Чтобы не лить много воды (хоть я и Водолей :) ) рассмотрим создание полей на конкретном примере. Создаем новый проект, настраиваем подключение к Ораклу (TOraSession, см 7 выпуск), в TOraQuery пишем следующий запрос select owner, object_name, object_type, status from all_objects. Теперь открываем редактор полей и вызываем команду Add all fields. У вас должно получится так:

Свойства подробно расписаны в справочной системе и я останавливаться на них не буду, замечу, что назначение свойств понятно из названия. Обратите просто внимание на такие свойства, как DisplayFormat, DisplayLabel, ReadOnly, Required. Остановлюсь только на событии OnGetText. Это событие позволяет произвольно изменять визуальное содержимое поля. Например, если в поле Вы храните пол человека в виде M/F, то в этом событии можно сделать виртуальную подстановку на Муж/Жен:

procedure TForm1.OraQuery1STATUSGetText(Sender: TField; var Text: String;
  DisplayText: Boolean);
begin
  if Sender.AsString = 'F' then Text := 'Жен'
  else if Sender.AsString = 'M' then Text := 'Муж'
  else Text := 'Гемофродит'; // :)
end;

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

Теперь для доступа к полям можно написать OraQuery1STATUS.Value, хотя OraQuery1.FieldByName('status').asString никто не отменял :)

Очень часто Вы будите создавать калк- и лукап-поля. Для примера создадим калк-поле. Для этого выберем команду New field. В появившемся окне вводим имя поля, тип, и выбираем вид поля - Calculated:

Так как поле вычисляемое на стороне клиента, то Вы сами должны позаботится о его значении. Для этого у TDataSet есть событие onCalcFields:

procedure TForm1.OraQuery1CalcFields(DataSet: TDataSet);
begin
  OraQuery1cnt.Value := OraQuery1.RecordCount;
end;

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

Рассмотрим создание лукап-поля. Лукап-поля используются для подстановки значения указанного поля из другой таблицы вместо поля текущего запроса (организация связи основной запрос - справочники). Давайте в нашем примере добавим новое поле, которое будет показывать код владельца объекта базы. (Хотя это лучше сделать одним запросом). Для этого добавим еще один компонент TOraQuery и припишем там запрос select * from all_users. У OraQuery1 создаем новое поле:

Результат виден моментально.

Теперь вернемся к рассмотрению компонентов ODAC.

OraQuery может также выполнять хранимые процедуры/функции, а также анонимные блоки PL/SQL. Вот рабочий пример вызова хранимой функции:

function IsUniqueRNN(dm: TDMCommon; clid: Double; RNN: Double; cltype: string): Boolean;
begin
  if cltype = '1' then
    dm.OraSession.ExecSQL(
      'declare' + #13#10 +
      '  v_RESULT boolean;' + #13#10 +
      'begin' + #13#10 +
      '  v_RESULT := pf.PKG_CLIENTS.ISUNIQUERNN(:CLID$, :RNN$, :CLIENTTYPE$);' + #13#10 +
      '  :RESULT := sys.DIUTIL.BOOL_TO_INT(v_RESULT);' + #13#10 +
      'end;', [clid, rnn, '1'])
  else
    dm.OraSession.ExecSQL(
      'declare' + #13#10 +
      '  v_RESULT boolean;' + #13#10 +
      'begin' + #13#10 +
      '  v_RESULT := pf.PKG_CLIENTS.ISUNIQUERNN(:CLID$, :RNN$);' + #13#10 +
      '  :RESULT := sys.DIUTIL.BOOL_TO_INT(v_RESULT);' + #13#10 +
      'end;', [clid, rnn]);
  Result := dm.OraSession.ParamByName('Result').AsBoolean;
  if cltype = '1' then
  begin
    dm.OraSession.ExecSQL(
      'declare' + #13#10 +
      '  v_RESULT boolean;' + #13#10 +
      'begin' + #13#10 +
      '  v_RESULT := pf.PKG_CLIENTS.ISUNIQUERNN(:CLID$, :RNN$, :CLIENTTYPE$);' + #13#10 +
      '  :RESULT := sys.DIUTIL.BOOL_TO_INT(v_RESULT);' + #13#10 +
      'end;', [clid, rnn, '2']);
    Result := dm.OraSession.ParamByName('Result').AsBoolean and Result;
  end;
end;
Хотя здесь и используется компонент OraSession вместо OraQuery, но в этом нет ничего страшного, можно смело использовать OraQuery.

Компонент TSmartQuery

В прошлом выпуске мы научились создавать "живые" запросы (это такие запросы, которые поддерживают модификацию данных). Для этого нужно было заполнить соответствующими командами свойства SQLInsert/SQLUpdate/SQLDelete. Это все хорошо, если бы не было столь утомительным :) (даже с учетом того, что редактор помогает сгенерировать эти команды). SmartQuery расширяет возможности OraQuery и самостоятельно во время генерирует нужные команды модификации. Достаточно только заполнить свойства SQL, KeySequence, KeyFields - все остальное сделает компонент. В конце выпуска будет приведен рабочий пример.

SmartQuery поддерживает очень интересную возможность SmartRefresh (см. одноименное свойство). Суть этой техники состоит в следующем. Как вы наверное заметили при многопользовательской работе, если один клиент внес какие-либо изменения в таблицу, то все другие клиенты для отображения изменений должны переоткрыть нужные запросы. Это справедливо для всех СУБД и всех библиотек доступа к СУБД. Но ODAC и тут на высоте. Благодаря встроенному пакету Оракла dbms_pipe ODAC может давать сигналы другим клиентам, что были внесены изменения в данные и их нужно перечитать. Чтобы посмотреть SmartRefresh в действии, создайте новый проект, "натравите" TSmartQuery на какую-либо табличку (примерный запрос select t.*, t.rowid from mytable), включите SmartRefresh и подключите SmartQuery к гриду. Запустите программу несколько раз (или на разных компах) и внесите изменения на одном клиенте. Посмотрите, как ведут себя другие клиенты.

Компонент TOraSQL

Этот компонент аналогичен TOraQuery за небольшим исключением. Он предназначен для выполнения любой команды/процедуры/анонимного блока PL/SQL, кроме команды select. В результате этого этот компонент меньше весит и меньше расходует память.

Компонент TOraTable

Предназначен для работы с одной таблицей без написания какого-то бы ни было sql-кода, просто указываете имя таблицы. Является наследником TSmartQuery.

Компонент TOraStoredProc

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

Компонент TOraScript

Этот компонент предназначен для выполнения последовательности команд. Вы можете сказать, что последовательность команд можно оформить в виде PL/SQL кода и выполнить через другие компоненты, но в PL/SQL коде нельзя указывать команды DDL. Вот тут и приходит на помощь TOraScript. Каждая команда должна быть отделена от других символом ; или /, причем / должен начинаться с новой строки и любой блок PL/SQL должен заканчиваться символом /.

Компонент TOraPackage

Предназначен для инкапсуляции работы с пакетами. Хочу заметить, что с пакетами можно работать с помощью анонимного блока PL/SQL, а следовательно с помощью компонент TOraQuery, TOraSmartQuery, TOraSQL.

Компонент TOraLoader

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

  • триггеры не поддерживаются
  • проверочные ограничения не поддерживаются
  • ограничения справочной целостности не поддерживаются
  • кластерные таблицы не поддерживаются
  • пользовательские типы данных не поддерживаются

Для использования загрузчика Оракла нужно выполнить следующие шаги:

  1. указать имя таблицы в свойстве TableName
  2. настроить поля, в которые будут загружаться данные (свойство Columns)
  3. написать обработчик события OnGetColumnData или OnPutData
  4. вызвать метод Load для начала загрузки данных
Ниже приведены примеры обработчиков OnGetColumnData и OnPutData
procedure TfmMain.GetColumnData(Sender: TObject;
  Column: TDAColumn; Row: Integer; var Value: Variant;
  var EOF: Boolean);
begin
  if Row <= 1000 then begin
    case Column.Index of
      0: Value := Row;
      1: Value := Random(100);
      2: Value := Random*100;
      3: Value := 'abc01234567890123456789';
      4: Value := Date;
    else
      Value := Null;
    end;
  end
  else
    EOF := True;
end;
Где строка EOF := True; сигнализирует о том, что достигнут конец данных.
procedure TfmMain.PutData(Sender: TDALoader);
var
  Count: Integer;
  i: Integer;
begin
  Count := StrToInt(edRows.Text);
  for i := 1 to Count do begin
    Sender.PutColumnData(0, i, 1);
    Sender.PutColumnData(1, i, Random(100));
    Sender.PutColumnData(2, i, Random*100);
    Sender.PutColumnData(3, i, 'abc01234567890123456789');
    Sender.PutColumnData(4, i, Date);
  end;
end;

Компонент TOraErrorHandler

Этот компонент позволяет транслировать сообщения Oracle в понятные для пользователя сообщения. Трансляция текста ошибки проходит или в событии OnError или с помощью специальной таблицы, которую можно создать и редактировать в design-time с помощью самого компонента (дважды кликните на компоненте). Например, таблица может содержать такие данные:

Обработчик OnError может быть примерно таким:

procedure TdmCommon.OraErrorHandler1Error(Sender: TObject; E: Exception;
  ErrorCode: Integer; const ConstraintName: String; var Msg: String);
var
  s: string;
begin
  s := '';
  case ErrorCode of
    1017: s := 'Неправильно введено имя пользователя/пароль';
    12560: s := 'Неправильно введено имя пользователя/пароль или отсутствует связь с сервером';
    12154: s := 'Введено неверное имя сервера';
  end;
  Msg := s;
  if Msg = '' then
  begin
    s := E.Message;
    if (Pos('!*<', s) > 0) and (Pos('>*!', s) > 0) then
      Msg := Format('Поле ''%s'' должно содержать значение' , [Copy(s, Pos('!*<', s) + 3, Pos('>*!', s) - Pos('!*<', s) - 3)])
  end;
end;

Тут все должно быть понятно, кроме разве конструкции

    if (Pos('!*<', s) > 0) and (Pos('>*!', s) > 0) then
      Msg := Format('Поле ''%s'' должно содержать значение' , [Copy(s, Pos('!*<', s) + 3, Pos('>*!', s) - Pos('!*<', s) - 3)])
  end;

Если у поля установлен флаг Required, то поле обязательно должно иметь какое-либо значение. Если поле не будет содержать никакого значения (скажем пользователь не ввел его) то датасет перед отправкой данных на сервер выполнит проверку на наличие значения, и если поле не содержит значение, то будет выведено стандартное сообщение на английском языке (Field ... must have a value), что обычному пользователю не совсем понятно. Хочу обратить Ваше внимание, что эту ошибку генерирует не Оракл. А так как TOraErrorHandler обрабатывает все ошибки, а не только ошибки Оракла, то введя для отображаемого имени поля (TField.DisplayLabel) специальные маркеры, можно написать единый централизованный обработчик для таких ошибок, причем для каждого поля можно сделать свой текст ошибок.

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

Для начала была создана следующуя стуктура таблиц:

-- Create table
create table PERIODS_TAB
(
  ID    NUMBER not null,
  YEAR  NUMBER(4),
  MONTH NUMBER(2)
);
-- Create/Recreate primary, unique and foreign key constraints
alter table PERIODS_TAB
  add constraint PK_PERIODS primary key (ID);
-- Create/Recreate check constraints 
alter table PERIODS_TAB
  add constraint CHK_PERIODS_MONTH
  check (month between 1 and 12);
-- Create/Recreate indexes 
create unique index UN_PERIODS on PERIODS_TAB (YEAR, MONTH);
-- Grant/Revoke object privileges 
grant select, insert, delete on PERIODS_TAB to PF_USER;
-- Create sequence
create sequence PERIODS_SEQ
minvalue 1
maxvalue 999999999999999999999999999
start with 1
increment by 1
nocache;

create or replace trigger trg_periods_bi
  before insert on periods_tab  
  for each row
declare
  -- local variables here
begin
  if :new.id is null then
    getid('periods', :new.id);
  end if;
end trg_periods_bi;

-- Create table
create table PLAN_CITY_TAB
(
  ID           NUMBER not null,
  IDPERIOD     NUMBER not null,
  IDCITY       NUMBER not null,
  PLAN_DOG     NUMBER default 0,
  PLAN_REM     NUMBER default 0,
  MIN_PLAN_DOG NUMBER default 5,
  MIN_PLAN_REM NUMBER default 4
);
-- Create/Recreate primary, unique and foreign key constraints
alter table PLAN_CITY_TAB
  add constraint PK_PLAN_CITY primary key (ID);
alter table PLAN_CITY_TAB
  add constraint FK_PLAN_CITY_CITY foreign key (IDCITY)
  references DIVISIONS_TAB (ID);
alter table PLAN_CITY_TAB
  add constraint FK_PLAN_CITY_PERIOD foreign key (IDPERIOD)
  references PERIODS_TAB (ID) on delete cascade;
-- Create/Recreate indexes 
create index IDX_PLAN_CITY_CITY on PLAN_CITY_TAB (IDCITY)
;
create index IDX_PLAN_CITY_PETIOD on PLAN_CITY_TAB (IDPERIOD);
-- Grant/Revoke object privileges 
grant select, update on PLAN_CITY_TAB to PF_USER;

-- Create sequence
create sequence PLAN_CITY_SEQ
minvalue 1
maxvalue 999999999999999999999999999
start with 1
increment by 1
nocache;

create or replace trigger trg_plan_city_bi
  before insert on plan_city_tab  
  for each row
declare
  -- local variables here
begin
  if :new.id is null then
    getid('plan_city', :new.id);
  end if;
end trg_plan_city_bi;

create or replace trigger trg_periods_ai
  after insert on periods_tab  
  for each row
declare
  -- local variables here
begin
  insert into plan_city_tab(idperiod, idcity) 
    select :new.id, id from divisions_tab where isagp='0';
end trg_periods_ai;

Несколько замечаний по структуре:
1. На таблицу periods_tab не выданы гранты на изменение, так как нет абсолютно никакого смысла изменять период.
2. На таблицу PLAN_CITY_TAB не были выданы гранты на вставку и удаление, так как вставка и удаление происходит автоматически - добавление городов в триггере trg_periods_ai, а удаление - ограничением справочной целостности FK_PLAN_CITY_PERIOD.
3. В триггерах trg_plan_city_bi и trg_periods_bi сначала проверяется, установлено ли значение первичного ключа и если нет, то генерируется.
4. Значение месяца должно быть в интервале 1..12 (проверка CHK_PERIODS_MONTH)
Таким образом, вся бизнес-логика была реализована на стороне сервера. Клиент будет реализовывать только интерфейс пользователя (ввод и редактирование первичных данных).

В качестве интерфейса были взяты два DBGridEh (библиотека EhLib). В первом гриде будут отображаться плановые периоды, а во втором - планы по городам за выбранный период (классическая связь master/detail)
Настройки первого грида:

        object gPeriods: TDBGridEh
          Left = 2
          Top = 15
          Width = 245
          Height = 384
          Align = alClient
          DataSource = dsPeriods
          Columns = <
            item
              EditButtons = <>
              FieldName = 'YEAR'
              Footers = <>
              Title.Caption = 'Год'
            end
            item
              EditButtons = <>
              FieldName = 'MONTH'
              Footers = <>
              KeyList.Strings = (
                '1'
                '2'
                '3'
                '4'
                '5'
                '6'
                '7'
                '8'
                '9'
                '10'
                '11'
                '12')
              PickList.Strings = (
                'Январь'
                'Февраль'
                'Март'
                'Апрель'
                'Май'
                'Июнь'
                'Июль'
                'Август'
                'Сентябрь'
                'Октябрь'
                'Ноябрь'
                'Декабрь')
              Title.Caption = 'Месяц'
              Width = 113
            end>
        end
      end

Обратите внимание на второй столбец - в таблицу будет записываться значение из KeyList, а в гриде отображаться соответствующее значение из PickList.

Настройки второго грида:

        object gCity: TDBGridEh
          Left = 2
          Top = 15
          Width = 427
          Height = 384
          Align = alClient
          AllowedOperations = [alopUpdateEh]
          DataSource = dsPlanCity
          ShowHint = True
          UseMultiTitle = True
          Columns = <
            item
              EditButtons = <>
              FieldName = 'CITY'
              Footers = <>
              ToolTips = True
              Width = 133
            end
            item
              EditButtons = <>
              FieldName = 'PLAN_DOG'
              Footers = <>
            end
            item
              EditButtons = <>
              FieldName = 'PLAN_REM'
              Footers = <>
            end
            item
              EditButtons = <>
              FieldName = 'MIN_PLAN_DOG'
              Footers = <>
            end
            item
              EditButtons = <>
              FieldName = 'MIN_PLAN_REM'
              Footers = <>
            end>
        end

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

Настала очередь датасетов. С первым гридом ассоциирован датасет:

  object qPeriods: TSmartQuery
    KeyFields = 'ID'
    KeySequence = 'PF.PERIODS_SEQ'
    SequenceMode = smInsert
    Session = dmCommon.OraSession
    SQL.Strings = (
      'select t.*, t.rowid from pf.periods_tab t')
    FetchAll = True
    ReadOnly = True
    Options.ReturnParams = True
    Left = 32
    Top = 113
  end

Тут должно быть все понятно (если внимательно читали прошлые выпуски). Единственно хочу сказать, что сразу перевожу датасет в режим ReadOnly, чтобы пользователи бесконтрольно не стали вводить периоды.

Второй датасет (ассоциирован со втором гридом):

  object qPlanCity: TSmartQuery
    KeySequence = 'PF.PLAN_CITY_SEQ'
    SequenceMode = smInsert
    Session = dmCommon.OraSession
    SQL.Strings = (
      'select t.*, t.rowid from pf.plan_city_tab t')
    MasterFields = 'id'
    DetailFields = 'idperiod'
    MasterSource = dsPeriods
    FetchAll = True
    Left = 549
    Top = 141
    ParamData = <
      item
        DataType = ftUnknown
        Name = 'id'
      end>
    object qPlanCityID: TFloatField
      FieldName = 'ID'
      Required = True
    end
    object qPlanCityIDPERIOD: TFloatField
      FieldName = 'IDPERIOD'
      Required = True
    end
    object qPlanCityIDCITY: TFloatField
      FieldName = 'IDCITY'
      Required = True
    end
    object qPlanCityPLAN_DOG: TFloatField
      DisplayLabel = 'План|Договора'
      FieldName = 'PLAN_DOG'
    end
    object qPlanCityPLAN_REM: TFloatField
      DisplayLabel = 'План|Заявления'
      FieldName = 'PLAN_REM'
    end
    object qPlanCityMIN_PLAN_DOG: TFloatField
      DisplayLabel = 'Минимальный план|Договора'
      FieldName = 'MIN_PLAN_DOG'
    end
    object qPlanCityMIN_PLAN_REM: TFloatField
      DisplayLabel = 'Минимальный план|Заявления'
      FieldName = 'MIN_PLAN_REM'
    end
    object qPlanCityROWID: TStringField
      FieldName = 'ROWID'
      ReadOnly = True
      Size = 18
    end
    object qPlanCityCITY: TStringField
      DisplayLabel = 'Город'
      FieldKind = fkLookup
      FieldName = 'CITY'
      LookupDataSet = qAreas
      LookupKeyFields = 'ID'
      LookupResultField = 'NAME'
      KeyFields = 'IDCITY'
      Size = 128
      Lookup = True
    end
  end

В этом датасете было создано лукап-поле для отображения названия города по его коду.

Осталась самая малость - при открытии формы открыть датасеты:
qPeriods.Open;
qPlanCity.Open;
и сделать три кнопочки:
* добавить период
* удалить период
* сохранить изменения
вот их обработчики:

procedure TFrmPensionHandBook.actAddPeriodExecute(Sender: TObject);
begin
  qPeriods.ReadOnly := False; //Разрешаем редактировать датасет
  qPeriods.Append; // добавляем пустую запись, конкретные значения
  // юзер будет вводить непосредственно в гриде
end;
procedure TFrmPensionHandBook.actDelPeriodExecute(Sender: TObject);
begin
  if Application.MessageBox('Вы дейсвительно хотите удалить период и все плановые показатели?', 'Подтверждение', MB_YESNO) <> ID_YES then Exit;
  qPeriods.ReadOnly := False; //Разрешаем редактировать датасет
  qPeriods.Delete; // удаляем период, все города за этот период
  //удалятся автоматически сервером
  qPeriods.ReadOnly := True; //Запрещаем редактировать датасет
end;
procedure TFrmPensionHandBook.actPostPeriodExecute(Sender: TObject);
begin
  qPeriods.Post;  // Отсылаем изменения на сервер
  qPeriods.ReadOnly := True; //Запрещаем редактировать датасет
  qPlanCity.Refresh; // Обновляем планы по городам
end;

Ну и еще несколько обработчиков для управления кнопками

// Кнопка "сохранить изменения" доступна когда датасет не в режиме ReadOnly
procedure TFrmPensionHandBook.actPostPeriodUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled := not qPeriods.ReadOnly;
end;
// Кнопка "добавить период" доступна когда датасет в режиме ReadOnly
procedure TFrmPensionHandBook.actAddPeriodUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled := qPeriods.ReadOnly;
end;
// Кнопка "удалить период" доступна когда датасет в режиме ReadOnly и
// есть хотя бы одна запись
procedure TFrmPensionHandBook.actDelPeriodUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled := qPeriods.ReadOnly and not qPeriods.IsEmpty;
end;

Форма в работе:

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

Вы не забыли, что мы ждем Вас на форуме Чертенок.ру?

Ждем Ваших откликов на емайл 5781-author@subscribe.ru или subscr@chertenok.ru


Приглашаем авторов в рассылку!


С уважением,
координатор рассылки Алексей aka Gelios.

Наши координаты:

сайт - www.delphi.chertenok.ru
форум - www.forum.chertenok.ru
контактный email - 5781-author@subscribe.ru

Другие проекты:

www.travel.chertenok.ru - сайт о путешествиях!



Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.allofdelphi
Архив рассылки
Отписаться Вебом Почтой
Вспомнить пароль

В избранное