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

Fedor K

Пользователи
  • Постов

    55
  • Зарегистрирован

  • Посещение

  • Победитель дней

    17

Сообщения, опубликованные Fedor K

  1. Из приведенного выше кода ничего вручную удалять не нужно, кроме самого TListBoxItem, как и выполняется в примере выше (listbox1.Items.Delete(indexclick) или Form1.listbox1.Items.Delete(0)) . Все создаваемые дополнительные контролы (TGridPanelLayout, TRectangle, TText) создаются здесь с AOwner = TListBoxItem + задается Parent, который мы и удаляем, а при удалении родительского все дочерние удаляются вместе с ним. Можете убедиться в этом проверив утечку памяти: 

    ReportMemoryLeaksOnShutdown := True;

    Sashar333 не используйте никогда .Name в качестве хранилища или идентификатора, это плохая практика. Для этих целей отлично подходят тэги (TagString или TagFloat), которые имеют все TFmxObject.

     

  2. 6 часов назад, elxanders сказал:

    Речь не о TListBox (с ним как раз проблем нет) а о TComboBox - который выпадающий список.

    Опять столкнулся с этой же проблемой - заполняю список называниями папок, он вместо Black вводит Чёрный, т.к. на соседней форме есть такая Label с Autotranslate.

    Пожалуйста, внимательно посмотрите пример выше: cbbFiles: TComboBox;

    TComboBox содержит внутри себя TComboListBox с элементами списка, которые у вас автопереводятся. При помощи предоставленного выше примера автоперевод отключается. Если у Вас множество TComboBox, которым нужно запретить перевод, то можно воспользоваться таким способом:

    //Создаем helper для TComboBox
    type
      TComboBoxHelper = class helper for TComboBox
      public
        procedure SetAutoTranslate(AEnabled: Boolean = false);
      end;
    ...
    implementation
    ...
    { TComboBoxHelper }
    //Согласно примеру выше
    procedure TComboBoxHelper.SetAutoTranslate(AEnabled: Boolean);
    var
      i, count : integer;
    begin
      count := Self.Count - 1;
      for i := 0 to count do
        Self.ListBox.ListItems[i].AutoTranslate := AEnabled;
    end;
    ...
    
        //Пример использования helper в Вашем коде
        <Ваш TComboBox 1>.SetAutoTranslate;
        ...
        <Ваш TComboBox N>.SetAutoTranslate;

     

  3. В 16.12.2014 в 18:27, SVTX сказал:

    В юните FMX.WebBrowser.Android.pas есть метод TWebBrowserListener.onReceivedSslError.

    По документации Андроид в нем можно обработать данную ошибку. Но команда proceed; не дает никакого результата((

    Причина такого поведения довольно простая - java библиотеки FMX часто передают в Delphi только уведомление о событии, без непосредственного влияния на процесс. Открываем библиотеку fmx.jar и смотрим код класса WebClient (package com.embarcadero.firemonkey.webbrowser;):

      public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
      {
        super.onReceivedSslError(view, handler, error);// обработка ошибки происходит здесь по дефолту
        if (this.mListener != null) {
          this.mListener.onReceivedSslError(view, handler, error);//передача в Delphi лишь уведомления, что событие произошло
        }
      }

    Одно из решений*:

    1. Заменяем** код на:

      public void onReceivedSslError(WebView paramWebView, SslErrorHandler paramSslErrorHandler, SslError paramSslError)
      {
        if (this.mListener != null) {
          this.mListener.onReceivedSslError(paramWebView, paramSslErrorHandler, paramSslError);//Delphi обработчик
        } else {
          super.onReceivedSslError(paramWebView, paramSslErrorHandler, paramSslError);//обработчик по умолчанию
        }
      }

    После манипуляций получаем свою версию библиотеки, мой пример здесь (Delphi Berlin).

    2. Подключаем библиотеку к проекту (скриншот ниже).

    3. Копируем unit FMX.WebBrowser.Android в папку своего проекта и меняем следующие строки:

    procedure TAndroidWebBrowserService.TWebBrowserListener.onReceivedSslError(
      P1: JWebView; P2: JSslErrorHandler; P3: JSslError);
    begin
      P2.proceed;//добавляем разрешение для истекших сертификатов
      FWBService.FailLoadingWithError;//стандартный обработчик OnDidFailLoadWithError
    end;

    *Если вариант изменения кода java библиотек совсем не подходит, можно реализовать все средствами Delphi. Для этого создаем свою реализация класса WebViewClient (или WebClient) и задаем его для JWebBrowser:

    //все тот же unit FMX.WebBrowser.Android;
    procedure TAndroidWebBrowserService.InitUIThread;
    var
      lClient : TWebBrowserCLientFix;//наш класс
    begin
      FJWebBrowser := TJWebBrowser.JavaClass.init(TAndroidHelper.Activity);
      FJWebBrowser.getSettings.setJavaScriptEnabled(True);
    
      lClient := TWebBrowserCLientFix.Create(Self);
      FJWebBrowser.setWebViewClient(lClient);//меняем на наш класс
      FListener := TWebBrowserListener.Create(Self);
      lClient.SetWebViewListener(FListener);
      //комментируем 
      //FJWebBrowser.SetWebViewListener(FListener);

    **Замена кода происходит по тому же сценарию, как и создание своих собственных классов на java. Если кому потребуется помощь - пишите, я помогу ответами и примером.

    12.PNG

  4. 2 часа назад, ENERGY сказал:

    Я так понял сообщения можно удалить только все (CancelAll) - т.е. юзер тапнул на одно сообщение, прочитал и затем программа очистила все остальные.. Вот в чем проблема. Странно почему нет нормального механизма..

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

    localBuilder.setAutoCancel(true);

    В библиотеке fmx.jar этот код отсутствует (package com.embarcadero.rtl.notifications, public class NotificationPublisher). У меня есть перекомпиленная библиотека для Seattle. Изменение библиотек выполняется таким же образом, как и создание своих всем известным методов через .bat файл.

  5. ENERGY Для получения всех активных уведомлений в Android существует метод:

    getActiveNotifications
    
    added in API level 23
    StatusBarNotification[] getActiveNotifications ()

    FMX (Berlin и ниже точно, Tokyo не смотрел) не предоставляет доступ к этому методу, в исходниках (Androidapi.JNI.App.pas) он закомментирован. Возможно при использовании своего wrapper для класса JNotificationManager или отсюда можно получить доступ к требуемому функционалу.

  6. Golovanyukтакое случается, когда кабель не достаточно хорошего качества/расшатан разъем/кастомная прошивка + недостаточно прав на телефоне. Если приложение первый раз запускается - то проблема не в настройках IDE.

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

    1. Подключите телефон по USB
    2. Перейдите в папку platform-tools, лежит в Android SDK. У меня например такой путь: F:\18.0\PlatformSDKs\android-sdk-windows\platform-tools.
    3. Откройте окно команд (зажимаете "Shift" + правая кнопка мыши -> "Открыть окно команд"). 
    4. Выполните "adb devices" - отобразиться список подключенных устройств.
    5. Выполните "adb shell su 0 setenforce 0" - на телефоне может появится окно запроса прав.
  7.  Ingalime этой настройкой нельзя управлять программно по политики безопасности системы. Из приложения программно можно лишь проверить эту галочку и предложить пользователю открыть эти настройки. Есть 2 решения:

    1. (самый простой способ) не отправлять уведомление с сервера. Т.к. у Вас устройства хранятся в таблице, то необходимо просто добавить туда новый столбец (например "PushEnabled") и изменить sql запрос, которым выбираете токены устройств с БД. PHP код в этом случае не затрагивается.
    2. (если править сервер невозможно или у вас нет доступа) запретить уведомления в BroadcastReceiver, который их обрабатывает и непосредственно создает уведомление в статус баре (приложение свернуто/закрыто) или передает обработку в код FMX (приложение открыто).

    2-ой способ является не тривиальным, т.к. требуется изменить стандартную библиотеку FMX cloud-messaging.jar, либо написать свой собственный BroadcastReceiver и его использовать в проекте + придется использовать SharedPreferences, чтобы хранить флаг состояния уведомлений для BroadcastReceiver.

    Я рекомендую воспользоваться первым способом. Опишите логику "включения/отключения" пушей, по каким правилам и где изменяется эта настройка, чтобы легче было Вам помочь.

  8. Дело в том, что TComboBox лишь контейнер, вам нужно обращаться именно к списку элементов в ListBox. Чтобы запретить перевод можно поступить так:

    var
      i, count : integer;
    begin
      count := cbbFiles.Count - 1;
      for i := 0 to count do
        cbbFiles.ListBox.ListItems[i].AutoTranslate := False;
    end;

     

  9. У стандартного TListView уже все есть:

     

    //устанавливаем режим редактирования у списка либо в редакторе свойств выставляем
    ListView1.EditMode := True;
    //...
    //Получаем список всех выбранных элементов
    ListView1.Items.CheckedIndexes(true);
    //обращаться к свойству текущего элемента так:
      ListView1.Items.SetChecked(const Index: Integer; const Value: Boolean);
      ListView1.Items.GetChecked(const Index: Integer): Boolean;

     

    1.PNG

  10. Для таких таблиц лучше ее рисовать на ListView. Пока не видел достойных гридов для Андроид.

    Пример реализации:

    Спойлер
    
    //Обрабатываем событие отрисовки
    procedure TBaseView.lvBaseUpdateObjects(const Sender: TObject;
      const AItem: TListViewItem);
    var
        item : TListViewItem;
    begin
        item :=  AItem;
        //предотвращаем повторную прорисовку
        if item.Tag = TAG_UPDATE then
            exit;
        item.Tag := TAG_UPDATE;
        case item.Purpose of
            //рисуем заголовок
            TListItemPurpose.Header :
            begin
                item.UpdateHeader;
            end;
            //рисуем строку
            TListItemPurpose.None :
            begin
                item.UpdateItem(BookCashier[item.Index - 1].Values);
            end;
        end;
    end;

     

    
    //для моей задачи было достаточно задать размеры константами, т.к. экран был задан заранее.
    const
        RH = 25;
        CW = 66.5;
        TAG_UPDATE = 10;
    //UpdateHeader аналогичен этому
    procedure TListViewItemHelper.UpdateItem(const AValues: TArray<string>);
    var
        i, count : integer;
    begin
        count := Length(AValues) - 1;
        for i := 0 to count do
        begin
            CreateText(0 + i, 0, 1, 1, AValues[i]);
            CreateLine(0 + i, 0, 1, TLineType.ltV);
        end;
    end;
    
    function TListViewItemHelper.CreateLine(ACol, ARow, ARowCount: integer;
      ALineType: TLineType): TListItemObject;
    var
        lineItem :  TListItemImage;
    begin
        lineItem :=  TListItemImage.Create(self);
        lineItem.PlaceOffset.X := CW - 1 + ACol * CW;
        lineItem.PlaceOffset.Y := 0 + ARow * RH;
        case ALineType of
            ltH:
            begin
                lineItem.Height := 1;
                lineItem.Width := CW * ARowCount;
            end;
            ltV:
            begin
                lineItem.Height := ARowCount * RH;
                lineItem.Width := 1;
            end;
        end;
        lineItem.Visible := true;
        //bitmap - просто черный фон, рисуем сетку)
        lineItem.Bitmap := self.Bitmap;
        lineItem.ScalingMode := TImageScalingMode.Stretch;
        lineItem.Opacity := 0.3;
        Result := lineItem;
    end;
    
    function TListViewItemHelper.CreateText(ACol, ARow, AColCount,
      ARowCount: integer; AText: string): TListItemObject;
    var
        textItem : TListItemText;
    begin
        textItem :=  TListItemText.Create(self);
        textItem.PlaceOffset.X := 0 + ACol * CW;
        textItem.PlaceOffset.Y := 0 + ARow * RH;
        textItem.Width := CW * AColCount - 1;
        textItem.Visible := true;
        textItem.Text := AText;
        textItem.Height := RH * ARowCount - 1;
        textItem.TextAlign := TTextAlign.Center;
        textItem.TextVertAlign := TTextAlign.Center;
        textItem.WordWrap := True;
        textItem.Trimming := TTextTrimming.Character;
        textItem.Opacity := 1;
        textItem.TextColor := TAlphaColorRec.Black;
        textItem.Font.Family := 'Arial';
        textItem.Font.Size := 11;
        Result := textItem;
    end;

     

     

  11. 12 часов назад, Pax Beach сказал:

    Вот с какой стати модуль будет доступен, если приложение и сервис — это разные проекты. 

    Да, Вы правы, у приложения и сервиса общий только Context, обмен инфой возможен только стандартными способами Android.

  12. 2 минуты назад, Pax Beach сказал:

    Спасибо, полезные примеры.

    А как реализовать функцию «поделиться». Например, из своего приложения хочу поделиться фотографией в вотцап?

     

    1. TMS iCL

    2. DPF- смотрите в сторону TDPFQLPreviewController

    3. Ручками, что-то вроде такого (документация apple):

    var
      controller : UIDocumentInteractionController;
      URL: NSURL;
      {$ENDIF}
      path : string;
      temp : Boolean;
    begin
        path := ExtractFilePath(ParamStr(0)) + '/Sample.pdf';
      {$IFDEF IOS}
      URL := TNSUrl.Wrap(TNSUrl.OCClass.fileURLWithPath(StrToNSStr(path)));
      controller := TUIDocumentInteractionController.Wrap(
      TUIDocumentInteractionController.OCClass.interactionControllerWithURL(URL));
      TNSUrl.Wrap(TNSUrl.OCClass.fileURLWithPath(StrToNSStr(path)));
    
      temp := controller.presentOpenInMenuFromRect(
          WindowHandleToPlatform(self.Handle).View.frame,
          WindowHandleToPlatform(self.Handle).View,true);
      {$ENDIF}
    end;

     

  13. 3 часа назад, Сергей Сергеев сказал:

    LargeHeap не помогает, Lifecicle Restart ит после нажатия кнопки home..

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

    могу ли я в сервис положить свой  DataModuel в котором есть sqlconnection на сервер, который собственно и держит сессию на нем, несколько TclientDataSet, MemTable и TDSPProviderConnection ,

     и как это связывать с TListView, TEdit на форме, котороя получается "перерождается" постоянно.. 

     

    1. Возможно я где-то пропустил в теме, но зачем держать соединение в сервисе ?

    2. Связать можно как и обычно, из вашего приложения доступен DM сервиса, на котором может находится TMemData.

    3.Опишите пожалуйста более подробнее задачи, которые перед Вами стоят, тогда ответы будут более продуктивны. Данная тема скорей всего архитектурный вопрос, а не вопрос потребления памяти.

  14. Один из вариантов открытия одного приложения из другого - это использование URL schemes (Ярослав давал выше ссылку).  

    Из приложение А: 

    //отправка
    uses
    	Macapi.Helpers,
    	FMX.Helpers.iOS;
    
        //делаем намерение открыть URL (по сути тоже самое Intent в Android)
        SharedApplication.openURL(StrToNSUrl(Url));

    Приложение Б (подробнее, хотя немного устарело):

        //подписываемся на события
        if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(aFMXApplicationEventService)) then
          aFMXApplicationEventService.SetApplicationEventHandler(HandleAppEvent);
    
    //обработчик
    function HandleAppEvent(AAppEvent: TApplicationEvent;
      AContext: TObject): Boolean;
    var
        lURL : string;
    begin
      case AAppEvent of
        TApplicationEvent.OpenURL :
        begin
            lURL := (AContext as TiOSOpenApplicationContext).URL;
            //реализуем логику
        end;

    Как передать файлы я вижу несколько вариантов:

    - использовать буфер обмена (класс UIPasteboard - думаю не сложно его использовать в FMX, я им пользовался только в Xamarin, на FMX возможно (нужно проверить) можно использовать FMX.Platform.IFMXClipboardService). Перед открытием URL в "А" - сохраняем картинку/файл в буфер обмена, в "Б" при открытии считываем.

    - передавать в URL путь к файлу (это возможно только, если и "А" и "Б" являются приложением одной группы). пример с Object-C.

  15. 36 минут назад, Pax Beach сказал:

    Что-то я совсем нить рассуждений потерял. О каком "том" событии идет речь? В компоненте TfgApplicationEvents нет такого события.

    Вот задача: Из приложения "Папа" запустить приложение "Дочка", при этом сообщив "Помой посуду!". И чтобы приложение "Дочка" сообщила "Хорошо, папа" и открыло форму "Мойка посуды". ))) Как ее решить на разных платформах?

     

    Для Android достаточно в приложении "Дочка" обработать стартовый Intent:

    uses
      System.StartUpCopy,
      Androidapi.Helpers,
      Androidapi.JNI.GraphicsContentViewText;
    ...
    var
        Intent: JIntent;
        lStartIndex : Integer = -1;
    begin
        Application.Initialize;
        Intent := TAndroidHelper.Activity.getIntent;
        if (Intent <> nil) and (Intent.getExtras <> nil) and Intent.getExtras.containsKey(StringToJString('START_FORM')) then
        begin
            lStartIndex := Intent.getExtras.getInt(StringToJString('START_FORM'));
        end;
        //создание нужной формы в зависимости от lStartIndex

    Для мобильных платформ я бы советовал использовать TFrame, вместо TForm, оперирую лишь одной главной формой. Много форм оставьте для VCL.

  16. При реализации  OAuth 2.0 при помощи TWebBrowser главное отследить Redirect. В случае успешной авторизации в URL будет содержаться необходимый токен. Посмотрите в сторону использования вот этих форм (находятся в ..\source\data\rest):

      {$IFDEF MSWINDOWS}
          REST.Authenticator.OAuth.WebForm.Win
      {$ELSE}
          REST.Authenticator.OAuth.WebForm.FMX
      {$ENDIF}

    У них есть событие:

      property OnAfterRedirect: TOAuth2WebFormRedirectEvent read FOnAfterRedirect write FOnAfterRedirect;
    
      TOAuth2WebFormRedirectEvent = procedure(const AURL: string; var DoCloseWebView : boolean) of object;

    Вот обработчик:

    procedure TframeAutch.AfterRedirect(const AURL: string;
      var DoCloseWebView: boolean);
    var i:integer;
        Str: string;
        Params: TStringList;
    begin
      i := pos('#access_token=',AURL);
      if (i>0) then
        begin
          Str := AURL;
          Delete(Str,1,i);
          Params:=TStringList.Create;
          try
            Params.Delimiter:='&';
            Params.DelimitedText := Str;
            token := Params.Values['access_token'];
    		DoCloseWebView := True;
          finally
            Params.Free;
          end;
        end;
    end;

    **Код выше из XE7, сейчас мб что-то изменилось. Использовал такой подход для авторизации в vk, facebook, google+.

    ***Если нужно, могу сделать демку, но это только на выходных.

  17. В 08.09.2016 в 20:11, Kitty сказал:

    Видела приложение, которое автоматом публиковало в фейсбук картинку и комментарий.

    Выглядело так: человек идет в МакДональдс. Запускает приложение и приложение автоматом постит в фейсбук предопределенную внутри приложения картинку и комментарий типа "Ура я иду в МакДональдст!", при этом пользователь не предпринимает никаких доп. усилий.

    Как такое можно реализовать? :)

    Общая схема такая:

    1. Регистрация своего приложения в API соцсетей (facebookvk).

    2. Реализуете в своем приложении авторизацию через OAuth 2.0:

    - можно использовать легкий вариант через WebBrowser, т.о. образом сразу поддерживая Android/IOS.

    - можно сделать красиво, используя "Intent" и "startActivityForResult".

    - можно использовать SDK соцсетей, но это совсем другая история...

    3. После регистрации сохраняем токен, при помощи которого мы можем через API автоматически постить сообщения. Учитываем здесь время действия токена, периодически его обновляя.

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

        //получаем список пакетов, которые могут обработать ваше намерение
    	tempList := TAndroidHelper.Activity.getPackageManager.
            queryIntentActivities(Intent);
    	//стартуем только в случае наличия пакета
        if tempList.size > 0 then
            TAndroidHelper.Activity.startActivity(Intent);

    2. При отправке сторонним приложениям файлов (изображения в частности) всегда рекомендуется создавать копию изображения:

    а) с УНИКАЛЬНЫМ  именем - необходимо для разрешения проблем с кешированием в других приложениях. Например Facebook кеширует по имени и при повторном вызове не обновляет;

    б) сохранять нужно в CacheDir, т.к. другим приложениям может попросту не быть доступа к файлу вашего приложения (использование Uri обязательно).

    3. Т.к. у подобных приложений шарингом занимаются отдельные активности, то не забудьте про флаги NewTask | ClearTask у намерения.

    п.с. Не забудьте потом периодически удалять свой кеш.

  19. Необходимо использовать 

    TJContext.JavaClass.NOTIFICATION_SERVICE

    вместо

    TJActivity.JavaClass.NOTIFICATION_SERVICE

    Для получения менеджера лучше использовать контекст (данный код работает на версии 4.4 и 5+):

    function GetNotificationService: JNotificationManager;
    var
      NotificationServiceNative: JObject;
    begin
      NotificationServiceNative := TAndroidHelper.Context.getSystemService(TJContext.JavaClass.NOTIFICATION_SERVICE);
      Result := TJNotificationManager.Wrap((NotificationServiceNative as ILocalObject).GetObjectID);
    end;

     

×
×
  • Создать...