Почему OnEditError или OnPostError не обнаруживают недопустимую запись пользователя в TClientDataSet?

У меня есть набор данных в памяти, прикрепленный к TDBGrid через некоторый источник данных. Проблема в том, что AV (ошибка проверки) запускается всякий раз, когда пользователь вводит знак минус в числовое поле и нажимает ENTER или переходит к другой записи. Я не могу поймать AV ни из TDBGrid, ни из TClientDataSet (OnEditError, OnPostError).

Я здесь что-то не так делаю? Если нет, есть ли у кого-нибудь обходной путь?

Использование Delphi XE2 Enterprise, 4-е обновление; последняя версия MIDAS.DLL; целевая платформа: Win64.

==============================================================================

Пожалуйста, попробуйте следующее:

1- Создайте новое приложение / форму VCL и добавьте в форму объекты: TClientDataSet, TDBGrid и TDataSource. ClientDataSet не привязан к какой-либо базе данных (в памяти)

2- Добавьте постоянное числовое поле в Table1 (TClientDataSet).

3- Свяжите три вышеупомянутых объекта. Столбец постоянного поля должен появиться в TDBGrid.

4- В методе OnShow основной формы добавьте следующие строки:

Table1.Active: = False;

Table1.CreateDataSet;

Table1.Active: = True;

5- Запустите вашу программу и введите знак минус в столбце числового поля, затем нажмите Enter.

Проверьте AV, а затем попробуйте добавить обработчики событий OnEditError и / или OnPostError в Table1, чтобы поймать AV. Вы не можете прервать публикацию. Это означает, что AV произойдет, вы можете поймать его только с помощью обработчика Application.OnException. Это нехорошо. Вы должны уметь делать что-то на том уровне, на котором работает ваша программа, если ваше приложение содержит множество форм и подпрограмм, а не на более высоком уровне.

Надеюсь, я прояснил свой вопрос.


person Experience    schedule 06.06.2012    source источник
comment
Сложно сказать, потому что вы не разместили код. Если мы не знаем, что вы делаете, трудно понять, что вы делаете не так. :-)   -  person Ken White    schedule 06.06.2012
comment
Не устанавливайте Table1.Active := True CreateDataSet уже делает это (вы можете проверить значение сразу после вызова CreateDataSet). Даже если это не причина вашей проблемы, нет смысла снова делать его Активным! В лучшем случае это пустая трата времени.   -  person Disillusioned    schedule 07.06.2012
comment
Также я проверил, что вы делаете, и у вас НЕ антивирус. AV (нарушение доступа) - это очень специфическая ошибка. Не следует путать это с другими исключениями. В вашем случае EDatabaseError. Я дополню свой ответ объяснением.   -  person Disillusioned    schedule 07.06.2012
comment
Верно, Крейг, это не AV. Я имел ввиду исключение.   -  person Experience    schedule 07.06.2012


Ответы (1)


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

У меня есть неплохой опыт работы с TClientDataSet в Delphi 3 и 6. Я сомневаюсь, что все так сильно изменилось. Кроме того, я собираюсь больше сосредоточиться на методах, которые помогут вам двигаться в правильном направлении.

Во-первых, убедитесь, что вы компилируете с помощью Debug DCU, чтобы можно было получить точки останова в коде VCL. Не бойтесь читать и проходить через этот код - это отличный способ учиться. Кроме того, вы сможете увидеть ошибки Borland / CodeGear / Embarcadero во всей их уродливой красе.

Я предполагаю, что у вас уже включена функция Stop on Exceptions (поэтому вы знаете, что конкретно получаете ошибки AV).

Сделай свой тест. Когда вы получите свой AV, вы, вероятно, выйдете в DbClient.pas (или, возможно, в одном из модулей более низкого уровня).

Пройдите по стеку вызовов в поисках места, в котором вы находитесь в части try блока try..except, который либо поглощает исключение, либо замыкается на другой обработчик, а затем проглатывает его.

ПРИМЕЧАНИЕ. DbClient.pas изобилует несколькими экземплярами следующего плохого кода:

except //swallowing or squashing exceptions is 
end;   //very dangerous and should be avoided.

except
  //short-circuiting to Application.HandleException (usually informs the user)
  //but is not much better. The point is the rest of the program up the 
  //call-stack remains blissfully unaware that something went wrong.
  Application.HandleException(Self);
  Action := raAbort;
end;

Боковой комментарий: Просто потому, что это сделала Borland, не означает, что это хорошая практика. Вы и многие другие получили и будете обожжены этим.

Как только вы это найдете, вы на полпути. Теперь вы знаете, почему вам не сообщают об антивирусной программе.
Теперь где-нибудь в коде, который является частью этого стека вызовов, будет ключ к пониманию того, как вы должны обрабатывать ошибку. Вы также можете разместить точку останова немного раньше в коде VCL, чтобы можно было пройти через нее, чтобы увидеть последовательность событий, ведущих к исключению. Поскольку вы специально упомянули AV (нарушение прав доступа), ищите объекты, которые ссылаются на nil.

Один из примеров того, что может быть причиной проблемы, приведен в следующем фрагменте из D5 DbClient.pas.

    FOnReconcileError(DataSet, E, UpdateKind, Action);
  finally
    E.Free;
  end;
except
  Application.HandleException(Self);
  Action := raAbort;
end;

Из вышеизложенного видно, что в определенных ситуациях требуется реализовать обработчик событий OnReconcileError. Без этого dll не будет выполнять обратный вызов в приведенный выше фрагмент кода, и многие ошибки незаметно прячутся под ковер, заставляя вас чесать голову.

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

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

  • Рассмотрите возможность использования обработчика событий OnReconcileError.
  • Рассмотрите возможность реализации объекта UpdateProvider.
  • Вы упомянули, что используете его как набор данных в памяти, поэтому вы, вероятно, правильно используете CreateDataSet. Но если я правильно помню, у вас было 2 варианта указания полей, и некоторые из атрибутов более мелких деталей были немного придирчивыми.
  • Несвязанный совет: будьте осторожны с длиной ваших строковых полей, TClientDataSet использовался (и может все еще) заполнять память аналогично коротким строкам.

РЕДАКТИРОВАТЬ

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

procedure TIntegerField.SetAsString(const Value: string);
var
  E: Integer;
  L: Longint;
begin
  if Value = '' then Clear else
  begin
    Val(Value, L, E);
    if E <> 0 then DatabaseErrorFmt(SInvalidIntegerValue, [Value, DisplayName]);
    SetAsInteger(L);
  end;
end;

Выше по стеку вызовов вы получаете следующий метод, который подсказывает, как решить проблему.

procedure TField.SetEditText(const Value: string);
begin
  if Assigned(FOnSetText) then FOnSetText(Self, Value) else SetText(Value);
end;

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

Причина, по которой ваши попытки обработать ошибку в OnPost error не сработали, заключается в том, что вы не приближаетесь к попытке опубликовать свою запись. Вы получаете ошибку проверки поля, и место для ее обработки находится в поле.

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

ОДНАКО я бы не советовал это делать. Стандартное поведение вполне приемлемо! Пользователь получает сообщение об ошибке, объясняющее, что он сделал не так, и он получает возможность исправить это и повторить попытку. Если они передумают, они могут нажать Escape и отменить редактирование.

person Disillusioned    schedule 06.06.2012
comment
Хорошо, Крейг. Я думаю, что мне следует реализовать событие OnSetText, как вы сказали. Это должно сработать. Если я не буду обрабатывать это часто возникающее исключение записи со знаком минус, пользователь может раздражаться. Большое спасибо. - person Experience; 07.06.2012