LINUX.ORG.RU

Методика запоминания предыдущего значения в QML

 


0

2

По причине отсутствия ответов вот в этой теме:

Не работает простая анимация через YAnimator в QML

...хочу разобраться в более общем вопросе. Вопрос звучит так:

Каким образом в декларативном языке QML можно организовать запоминание предыдущего значения свойства?

То есть, задача состоит в том, чтобы перед любым изменением свойства запомнить его значение в другое свойство. Проблема в том, что существующий механизм сигналов onИмяСвойстваChanged() испускает сигнал об изменении свойства уже после его изменения. И в этом обработчике невозможно получить предыдущее значение свойства чтобы его запомнить.

----- 8< -----

UPD: Небольшое пояснение. Имеем некий тип RibbonImage. У него есть два пользовательских свойства: targetDigit и previousDigits.

Код, который находится «выше», задает значение targetDigit путем установки свойства, вот так:

RibbonImage {
    targetDigit: timeString.charAt(5)
}
Этот код ничего не знает (и не должен знать) про предыдущее значение. Значит, запоминаться предыдущее значение targetDigit должно в самом типе (в нашем случае в RibbonImage).

Отследить изменение свойства targetDigit в коде RibbonImage мы можем с помощью обработчика onTargetDigitChanged(). Но внутри него невозможно сохранить предыдущее значение targetDigit в свойство previousDigits. А невозможно потому, что это targetDigit в момент вызова обработчика уже новое. И не из чего получить предыдущее значение, чтобы его запомнить в другом свойстве.

★★★★★

Последнее исправление: Xintrea (всего исправлений: 3)
Ответ на: комментарий от uralbash

Как машина состояний поможет, если все равно нет разницы где хранить значения - в свойствах типа или свойствах, предназначенных для машины состояний?

Похоже, что здесь вообще фундаментальное ограничение QML.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Так, походу понял. Все можно сделать через очередь. Очередь из двух ячеек. В первую ячейку кладется targetDigit. Из второй ячейки достается previousDigit.

Фишка в том, что первая ячейка очереди является «управляемым» хранилищем targetDigit. То есть, значение в ней сохраняется ручным способом, а не просто меняется внешним кодом.

Таким образом, в обработчике onTargetDigitChanged() мы будем иметь два значения targetDigit: абсолютно новое и предыдущее в первой ячейке, которое надо сдвинуть во вторую ячейку, и оно станет previousDigit.

* * *

Теперь вопрос: почему приходится использовать три сущности (targetDigit + две ячейки очереди) а не две (просто targetDigit + previousDigit) ?

Потому что обработчик onTargetDigitChanged() срабатывает _только_ в начале нашего логического цикла и не имеет возможности сработать в конце, когда по-хорошему и надо targetDigit запомнить в previousDigit. Поэтому приходится выполнять «то, что должно происходить в конце», в начале следующего цикла.

Здесь под логическим циклом я понимаю следующее:

- Установка нового значения
- Использование нового значения
- Запись нового значения как предыдущего

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

Поэтому приходится так извращаться.

Xintrea ★★★★★
() автор топика

Ты всё слишком усложняешь. В обработчике onChanged сохраняй новое значение в previousDigits. При первом изменении targetDigit у тебя не будет previousDigits, но в последующих у тебя будут и новое значение и предыдущее.

ox55ff ★★★★★
()
Ответ на: комментарий от ox55ff

Теперь подумай, каковы будут значения targetDigit и previousDigit после выполнения обработчика onChanged(). Они будут равны, ведь в обработчике ты пишешь previousDigit=targetDigit. По сути, для всего другого кода у тебя все время будет оказываться, что previousDigit=targetDigit.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

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

ox55ff ★★★★★
()
Ответ на: комментарий от Xintrea

Ты придумываешь сложности там, где их нет. Заведи массив из двух значений.

#include <stdio.h>

static int prev[2];

static void set_new_val(int val) {
  prev[0] = prev[1];
  prev[1] = val;
  printf("got new val: %d\n", val);
}

static int get_current_val(void) { return prev[1]; }
static int get_prev_val(void) { return prev[0]; }

static void print_state(void) {
  printf("prev: %3d, current: %3d\n", get_prev_val(), get_current_val());
}

int main(void) {
  print_state();
  set_new_val(10);
  print_state();
  set_new_val(11);
  print_state();
  set_new_val(20);
  print_state();
}
i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

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

Касаемо твоего примера: Вот ты сам написал сеттер. На начало выполнения сеттера у тебя новое значение в переменной val. С переменной val ты можешь сделать что угодно, например запомнить ее, предварительно сохранив предыдущее значение. А теперь представь, что переменной val нет, а новое значение само появляется в prev[1]. И уведомляют тебя об этом после того, как значение этой ячейки изменилось. Первая команда твоего сеттера (который теперь правильно называть обработчиком) делает prev[0] = prev[1]; И что получается? У тебя сразу копия самого нового значения. И никакого предыдущего значения не сохранилось.

Xintrea ★★★★★
() автор топика

Вообще все эти извращения с предыдущим значением нахрен не нужны для решаемой задачи. Анимация переключения состояний - это задача Transition, анимация изменения свойства - Behavior, они и задают значения from и to для NumberAnimation.

unC0Rr ★★★★★
()
Ответ на: комментарий от unC0Rr

Анимация переключения состояний - это задача Transition, анимация изменения свойства - Behavior, они и задают значения from и to для NumberAnimation.

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

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

В общем, вот такой наивный код работает как задумано:

Image {
    id: ribbon
    source: "qrc:/resource/pic/digits/ribbon.png"

    // Целевое число
    property int targetDigit: 0

    // Предыдущее число
    property int previousDigit: 0

    // Очередь из двух ячеек
    property int queue0: 0
    property int queue1: 0

    onTargetDigitChanged: {
        queue1=queue0; // Сначала сдвиг очереди
        queue0=targetDigit // Запоминание нового значения в начало очереди
        previousDigit=queue1; // Получение предыдущего значения из конца очереди

        console.log("Target digit: "+targetDigit+" , previous: "+previousDigit);
    }
}


Тут может кому-то прийти в голову, что есть запаздывание (ведь есть очередь на два элемента). На самом деле запаздывания нет, и previousDigit четко хранит предыдущее значение targetDigit. Почему так, объяснено вот в этом комментарии: Методика запоминания предыдущего значения в QML (комментарий)

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Вау! Сначала ты объясняешь мне, почему такой подход не работает, а потом пишешь, что это и есть решение! Браво!

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Вау! Сначала ты объясняешь мне, почему такой подход не работает, а потом пишешь, что это и есть решение!

Я это решение сделал еще вчера: Методика запоминания предыдущего значения в QML (комментарий) , просто не сопроводил его кодом потому что домой надо было ехать. А сегодня запульнул код.

У тебя есть сеттер. Я тебе показал, как твое решение трансформировалось бы в окружение без сеттера. Ведь ты написал решение на Си а не на QML.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Ведь ты написал решение на Си а не на QML.

А есть разница? Суть в любом случае одинакова.

У тебя есть сеттер. Я тебе показал, как твое решение трансформировалось бы в окружение без сеттера.

Нет, ты просто разводишь болтовню там, где это бессмысленно. Одно дело — писать какую-то документацию, которую всё равно никто читать не будет, а положит в коробку. Там нужно всеми способами добить до пяти тысяч страниц.

А тут — суть выражена в трёх строчках кода, причём строчки сами себя объясняют. Ты на каждую тривиальную операцию пишешь по две страницы пояснительного текста? Как в таком режиме вообще можно что-либо сделать?

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Представь, что у тебя есть класс с двумя закрытыми свойствами value и previousValue. Ты весь такой плюсовик, задаешь значения через сеттеры и проблем не знаешь. В сеттере setValue() ты написал первой строчкой previousValue=value, и у тебя все работает.

Тут тебе говорят: чувак, нет. Архитектура будет другой. Давай ты свойство value сделаешь открытым. И пусть его меняет внешний код. А вот о том что значение поменялось, класс будет узнавать через функцию обратного вызова onValueChanged() после того как значение поменялось. Ну, так нам захотелось.

Если ты мгновенно можешь сообразить, как при такой перестройке сохранить поведение строки previousValue=value, то я рад за тебя. Мне же понадобилось разложить задачу на атомарные операции, чтобы понять что без еще одной ячейки не обойтись. Плюс нужно формализовать какие данные в этой дополнительной ячейке должны лежать (хотя бы придумать этой ячейке понятное название, а для этого нужно понять ее назначение), и в какой момент данные туда должны попадать / или когда их можно читать.

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

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

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Почему при наличии сеттера можно обойтись двумя свойствами класса для хранения двух значений, а при наличии обработчика - требуется три свойства

Вообще-то в обоих случаях нужно только два поля. Само значение, которое меняется — не в счёт.

без нормального знания английского языка

Такое ощущение, что ты этим хвастаешься.

в детстве ты учил английский язык

Не учил :-( Кабы учил, было бы намного легче.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Вообще-то в обоих случаях нужно только два поля. Само значение, которое меняется — не в счёт.

Вообще-то в счет. Но ты можешь доказать обратное, показав код без сеттера с двумя значениями (на QML), который сможет работать правильно. Я не исключаю, что такая возможность есть, просто я о ней не знаю.

Не учил :-( Кабы учил, было бы намного легче.

А какой учил?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

доказать обратное, показав код без сеттера с двумя значениями

Ты правда не умеешь читать код или прикидываешься?

А какой учил?

Формально везде был английский, но по окончании ВУЗа я всё равно толком читать на нём не мог. Не говоря уже про школу.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Ты правда не умеешь читать код или прикидываешься?

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

Ты дал код на Си.

QML и Си - это разные вещи. Ты прикидываешься, что не видишь разницы между QML и Си?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Еще раз. Показать код без сеттера с двумя значениями на QML

Я даже не понимаю, что ты имеешь в виду под словом «сеттер» в текущем контексте. А ещё я никогда не использовал QML. Но, блин, мне проще решить, чем объяснить тебе решение. Какой-то ты нудный и многословный.

QML и Си - это разные вещи. Ты прикидываешься, что не видишь разницы между QML и Си?

Спасибо, кэп! Но я пока не вижу между ними серьёзной разницы.

i-rinat ★★★★★
()
Ответ на: комментарий от deep-purple

Кстати, делал между трёх — нужно было найти 0x00 0x00 0x03 в потоке байт. Самое тормозное место в коде получилось тогда. Но чинить было лень.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

А я тыкал палочкой и примеры куймэльные. И вообще говорят оно тормозное. Я б на месте ТС уже б на голом кутэ сделал и не парился про состояния. У него задача простая, как у сайто-фрилансеров — спрайт в координатах поперемещать.

У меня было интереснее — сайт такси и делал там машину выезжающую изза экрана к пункту меню к которому мышь подвели, резко тормозила, продавливаясь рессорами передка, а если ты по меню мышь быстро водишь то она за тобой гоняется, разворачиванется, а если отвел от меню то уезжает за экран. И все это на спрайтах — стили + установка классов из js по определенным условиям, т.е. набор классов как битовое состояние.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от deep-purple

И вообще говорят оно тормозное.

Оно мало того, что тормозное, оно ещё и до ужаса непопулярное.

По причине отсутствия ответов вот в этой теме

...

Я б на месте ТС уже б на голом кутэ сделал и не парился про состояния.

Возможно ТС делает на мобилку. А там с QtWidgets'ами вообще жвах.

EXL ★★★★★
()
Ответ на: комментарий от EXL

Возможно ТС делает на мобилку. А там с QtWidgets'ами вообще жвах.

Именно. А QtWidgets по сути только формально компилятся в мобильное представление, весьма кривовато, и оставляют все свое наследие десктопа. Это конечно прикольно, но выглядит неестественно. На мобиле без QtQuick никуда.

Xintrea ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.