Маленькие хитрости: автоматическое восстановление вида указателя курсора

    Добрый день, коллеги!

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

    Изменять вид курсора все равно придется самостоятельно, а вот восстанавливать вид курсора можно автоматически. Для этого послужит вот такой код:

    type
      ICursorSaver = interface
      end;
    
      TCursorSaver = class(TInterfacedObject, ICursorSaver)
      private
        FCursor: TCursor;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    implementation
    
    constructor TCursorSaver.Create;
    begin
      FCursor := Screen.Cursor;
    end;
    
    destructor TCursorSaver.Destroy;
    begin
      Screen.Cursor := FCursor;
      inherited;
    end;
    


    Далее в нужном месте кода объявляем переменную типа ICursorSaver и инициализируем ее.

    var
      saveCursor: ICursorSaver;
    begin
      saveCursor := TCursorSaver.Create;
      Screen.Cursor := crSQLWait;
    // здесь свой код, выполняющий обработку данных
    end;
    


    Как это работает? TInterfacedObject ведет учет ссылок на интерфейс, когда счетчик опускается до нуля — вызывается деструктор. В начале области видимости мы создаем объект и инициализируем им интерфейсную переменную, при этом захватывается текущий вид курсора. В конце области видимости интерфейсная переменная разрушается, интерфейс освобождается, деструктор возвращает вид курсора к первоначальному состоянию.

    Данный способ можно использовать для сохранения состояния не только курсора, но и состояния любых других объектов — только в этом случае нужно делать deep copy объекта.

    UPD: Коллеги romik и koreec предлагают устанавливать вид курсора прямо в конструкторе. Тогда конструктор будет выглядеть так:

    constructor TCursorSaver.Create(ACursor: TCursor = crHourGlass);
    begin
      FCursor := Screen.Cursor;
      Screen.Cursor := ACursor;
    end;
    
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 13

      +1
      Тогда уж можно переименовать TCursorSaver в TCursorChanger и передавать ему новый курсор в конструктор. Будет более SRPшно.
        +2
        А если курсор поменять нужно более чем на время жизненного цикла одной функции? К примеру, у меня асинхронная загрузка 1000 html документов, и курсор должен оставаться в виде crSomeWait, пока не придет последний ответ. Я так понимаю, с таким примером, ваш метод не пройдет?
          +1
          Если созданный интерфейс будет сохраняться на протяжении всей цепочки вызовов (передачей в виде параметра или сохранением в какое-то приватное поле класса, в рамках которого работает асинхронная загрузка) — то подойдет.
            0
            Ну тогда нужно будет явно удалять экземпляр TCursorSaver, а это уже не автоматически…
              0
              Почему? Я уже писал в статье, что у TInterfacedObject есть счетчик использований. Нужно, чтобы до конца всей цепочки вызовов где-то имелась хотя бы одна ссылка на интерфейс, а в конце цепочки вызовов эта ссылка разрушалась — вместе с TCursorSaver.
              Как вариант — можно инициализировать ICursorSaver перед вызовом цепочки, и передавать его в каждый блок загрузки. Тогда счетчик использований обнулится в конце последнего блока загрузки — и курсор вернется в нормальное состояние.
                0
                передавать его в каждый блок загрузки — беда беда. Так нельзя делать, это будет сложнее чем так не делать.

                В этом то и суть, что чтобы убрать из него последнюю ссылку — нужно что-то сделать, а если нужно что-то сделать — это уже не автоматически).
                  0
                  Не зная, как у вас ваша асинхронная загрузка реализована — сложно что-то подсказать. Но общее направление поиска возможного решения — удерживать созданный интерфейс используемым (т.е. с ненулевым счетчиком) до самого последнего блока выполнения. Как это сделать, и насколько это будет целесообразно — вам виднее.
                    0
                    Да нет у меня никакой асинхронной загрузки), я пытался указать на сложности при необходимости изменения курсора более чем на жизненный цикл одного метода. Конечно можно модернизировать ваш пример для чего угодно, но в этом случае уже легче будет самостоятельно возвращать курсор, имхо.
          +2
          Можно еще оптимизировать:
          Определить Create как
          constructor Create(ACursor: TCursor = crHourGlass);
          

          и добавить в него
          ScreenCursor := ACursor;
          

          Тогда код вызова станет на одну строку меньше.
            +3
            *чешет затылок*
            А я по-старинке делал что-то типа
            procedure busy(flag: boolean);
            begin
                if flag then
                    screen.cursor:=crSQLwait
                else
                    screen.cursor:=crNormal;
                PanelWait.visible:=flag
            end;
            

            Ну и перед тяжёлыми вычислениями вызывал busy(true), а по завершении — busy(false).
              0
              Я делал в JQuery стиле:
              procedure SetBusy(IsBusy: boolean = true); begin if IsBusy then screen.cursor:=crSQLwait else screen.cursor:=crNormal; end;
              Вызов
              SetBusy; // я занят, нихт троген! SetBusy(false); // свободен, яя арбайтен финиш
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Что это? А то гугл разленился, показывает только этот Ваш коммент…

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

              Самое читаемое