Перейти к содержанию
Fire Monkey от А до Я

[Android] [TFrame] Ошибка при динамическом удалении/создании фреймов


kvantum

Рекомендуемые сообщения

Здравствуйте ! 

 

Опираясь на тему "Архитектура приложения под Андроид на основе TFrame" был создан тестовый пример приложения на Андроид, в котором переключаемые фреймы создаются (удаляются) динамически. Суть ошибки: если в Frame1 ввести текст в строку и нажать кнопку перехода к Frame2 (правый верхний угол) - возникает ошибка. При уничтожении текущего фрейма клавиатура скрывается автоматически методом HideKeyboard : 

  if Assigned(fCurrentFrame) then begin
    HideKeyboard;
    fCurrentFrame.DisposeOf;
    fCurrentFrame:=nil;

    HideButtonsLayouts;
  end;

Прошу помочь в исправлении ошибки. Пример и скриншоты во вложении.

 

post-266-0-36114400-1421669229_thumb.png

post-266-0-20265200-1421669230_thumb.png

AndroidFramesTest.zip

Изменено пользователем Brovin Yaroslav
Отформатировано
Ссылка на комментарий

Как я понял проблема возникает если при удалении фрейма когда у одного из элементов ввода данных стоит фокус. 

Я решил эту проблему следующим способом.

 

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

 

При переключении на новый фрейм я вначале делаю очистку активного фокуса текущего фрейм (необходимо если используется переключение без очистки предыдущего фрейм) и очистку фокуса старого фрейм при помощи процедуры ControlsResetFocus, ее тело описано ниже

function TfmMain.ActiveFrame(AFrameClass: TFrameClass; ADisposePrevFrame: Boolean): TFrame;
var
  AFrame: TFrame;
  AIdx: Integer;
begin

  if FFrame <> nil then
  begin
    //Сбрасываем форкус элементов текущего активного фрейма
    ControlsResetFocus(FFrame);

    //Если новый фрейм отличается от текущего
    if FFrame.ClassType <> AFrameClass then
      //Ишем старые фреймы созданные от этого класса для очистки
      for AIdx := 0 to ComponentCount - 1 do
      begin
        if Components[AIdx].ClassType = AFrameClass then
        begin
          AFrame := TFrame(Components[AIdx]);

          //Сбрасываем форкус элементов старого фрейма
          ControlsResetFocus(AFrame);

          //Освобождаем старый фрейм из памяти
          AFrame.Parent := nil;
          AFrame.DisposeOf;
          AFrame := nil;

          Break;
        end;
      end;
  end;

  if ADisposePrevFrame then
  begin
    if FFrame <> nil then
    begin
      FFrame.DisposeOf;
      FFrame := nil;
    end;

    FFrame := AFrameClass.Create(Self);
    FFrame.Parent := Self;
    FFrame.Align := TAlignLayout.Client;

    Result := FFrame;
  end
  else
  begin
    Result := AFrameClass.Create(Self);
    Result.Parent := Self;
    Result.Align := TAlignLayout.Client;
  end;

  mvMenu.HideMaster;

  Application.ProcessMessages;
end;
unit uFunctions;

interface

uses
  FMX.Controls, FMX.Edit;

  procedure KeyboardHide;
  procedure KeyboardShow(AEdit: TEdit);

  procedure ControlsResetFocus(Control: TControl);

implementation

uses
  FMX.Platform, FMX.VirtualKeyboard;

procedure KeyboardHide;
var
  KeyboardService: IFMXVirtualKeyboardService;
begin
  // Запрашиваем сервис виртуальной клавиатуры
  if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService, IInterface(KeyboardService)) then
    KeyboardService.HideVirtualKeyboard;
end;

procedure KeyboardShow(AEdit: TEdit);
var
  KeyboardService: IFMXVirtualKeyboardService;
begin
  // Запрашиваем сервис виртуальной клавиатуры
  if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService, IInterface(KeyboardService)) then
    KeyboardService.ShowVirtualKeyboard(AEdit);
end;

procedure ControlsResetFocus(Control: TControl);

  procedure ResetFocus(SubControl: TControl);
  var
    AIdx: Integer;
    AControl: TControl;
  begin
    for AIdx := 0 to SubControl.ChildrenCount - 1 do
      if SubControl.Children.Items[AIdx] is TControl then
      begin
        AControl := SubControl.Children.Items[AIdx] as TControl;
        AControl.ResetFocus;

        if AControl.ChildrenCount > 0 then
           ResetFocus(AControl);
      end;

    SubControl.ResetFocus;
  end;

begin
  KeyboardHide;
  ResetFocus(Control);
end;

end.

Ниже приведен код очистки фреймов "Настройка" и "Обновление конфигурации" из памяти 

 

Настройка

procedure TfrOptions.SaveClick(Sender: TObject);
begin
  with fmMain do
  begin
    Options.UrlServer := edUrlServer.Text;
    Options.Save;

    ControlsResetFocus(Self);
    Self.Parent := nil;
    Self.DisposeOf;
  end;
end;

Обновление конфигурации 

procedure TfrLogin.StartUpdateCfgClick(Sender: TObject);
var
  AFrame: TfrUpdateCfg;
  AStatus: TfrUpdateCfgStatus;
begin
  with dmMainModule, fmMain do
  begin
    AStatus := ucsEndNormal;

    if InetConnectState then
    begin
      AFrame := TfrUpdateCfg(ActiveFrame(TfrUpdateCfg, False));
      AStatus := AFrame.Start;

      ControlsResetFocus(AFrame);
      AFrame.Parent := nil;
      AFrame.DisposeOf;
      AFrame := nil;
    end;

....
Ссылка на комментарий
  • Модераторы

 

Вот, давно уже отрапортовал об этом поведении http://qc.embarcadero.com/wc/qcmain.aspx?d=127108

Embarcadero сообщило о каком-нибудь решении ?

 

Нет, к сожалению, решения пока нет, иначе оно было бы по ссылке или в Update 1 для XE7, может в XE8…

Ссылка на комментарий

Подправил твою программу

 procedure ControlsResetFocus(Control: TControl); procedure ResetFocus(SubControl: TControl); var AIdx: Integer; AControl: TControl; begin for AIdx := 0 to SubControl.ChildrenCount - 1 do if SubControl.Children.Items[AIdx] is TControl then begin AControl := SubControl.Children.Items[AIdx] as TControl; AControl.ResetFocus; if AControl.ChildrenCount > 0 then ResetFocus(AControl); end; SubControl.ResetFocus; end; begin KeyboardHide; if Control <> nil then ResetFocus(Control); end; 

добавил проверку на nil в процедуре ControlsResetFocus

 if Control <> nil then ResetFocus(Control);

Хотя, все равно через раз появляется ошибка

Нужно ждать фикс

Ссылка на комментарий

Нужно ждать фикс

Ну ... фикс ! Когда он будет ? Нужно ведь сейчас писать... Что если заменить TFrame формой TForm, что думаешь ? Вопрос задан здесь: http://fire-monkey.ru/topic/914-android-tform-ili-tframe/

Изменено пользователем Andrey Yefimov
поправил ссылку
Ссылка на комментарий

Мне по любому нужно эту ошибку обойти каким нибудь способом. Если я решу эту проблему сразу отпишусь

Проверь у себя

я определил константу FrameOrder которая отвечает за порядок переключения фреймов

const
  FrameOrder: array[0..2] of TFrameClass = (TframeRegUser, TframeSetUser, TframeAuthUser);

На кнопки далее и назад

procedure TfrmMain.SpeedButton1Click(Sender: TObject);
begin
  SetFrame(IfThen(fCurrentFrameIndex = 0, 2, fCurrentFrameIndex - 1));
end;

procedure TfrmMain.SpeedButton2Click(Sender: TObject);
begin
  SetFrame(IfThen(fCurrentFrameIndex = 2, 0, fCurrentFrameIndex + 1));
end;

Изменил SetFrame, добавил пример получения данных с фрейм по одному имени стиля, то есть в хедер main формы передаю значение с lable фрейма

procedure TfrmMain.SetFrame(AFrameIndex: Integer);
var
  ALbl: TLabel;
begin
  fCurrentFrameIndex := AFrameIndex;

  ALbl := TLabel(ActiveFrame(FrameOrder[fCurrentFrameIndex], true).FindStyleResource('description'));

  if ALbl <> nil then
    lbHeaderText.Text := ALbl.Text;
end;

изменил ControlsResetFocus, убрал скрытие клавиатуры

procedure ControlsResetFocus(Control: TControl);

  procedure ResetFocus(SubControl: TControl);
  var
    AIdx: Integer;
    AControl: TControl;
  begin
    for AIdx := 0 to SubControl.ChildrenCount - 1 do
      if SubControl.Children.Items[AIdx] is TControl then
      begin
        AControl := SubControl.Children.Items[AIdx] as TControl;
        AControl.ResetFocus;

        if AControl.ChildrenCount > 0 then
           ResetFocus(AControl);
      end;

    SubControl.ResetFocus;
  end;

begin
  ResetFocus(Control);
end;

AndroidFramesTest.zip

Ссылка на комментарий

Добрый день!

Судя по всему - проблема достаточно серьезная и исправить ее нелегко. Для примера - можно убрать сообщение об ошибке "Access violation..." для этого достаточно указать модификатор [weak] переменной vActiveCaret в модуле FMX.Types.

было

var
  vKBTimer: TTimer = nil;
  vActiveCaret: TCustomCaret = nil;
  vOldDisplayed: boolean = False;
  vShowVKProc: TShowVirtualKeyboard = nil;
 
стало
var
  vKBTimer: TTimer = nil;
  [weak] vActiveCaret: TCustomCaret = nil;
  vOldDisplayed: boolean = False;
  vShowVKProc: TShowVirtualKeyboard = nil;

В ней залипает объект TCaret при уничтожении фрейма. Но далее происходят другие чудеса, причем, под iOS и Android чудеса разные.

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

Ссылка на комментарий

платформа пока много на что неспособна. и это печально :(

боюсь если в хе8-9 ничего конкретно не исправят, придется менять fmx, а вместе с ним и Delphi, на что-то проверенное и надежное...

а то как-то поднадоело искать баги, править исходники и ждать "манны небесной"...

Ссылка на комментарий

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...