Зависимости частей интерфейса.
От: Kore Sar  
Дата: 09.12.08 19:06
Оценка:
У нас есть GUI приложение (WinForms .Net) — см. рисунок в конце сообщения.

Панель 1. В зависимости от того, какую из кнопок нажал пользователь, показываются те или иные контролы на панели 2.
Панель 2. В зависимости от того, что ввёл пользователь в контролы, показывается та или иная информация в панели 4.
Панель 4. В зависимости от того, какой из айтемов выбрал пользователь, показываются подсчитаные цифры в панели 5.
Панель 5. Цифры на ней подсчитываются каждый раз, когда что-либо поменялось в панелях 1, 2 и 4.
Панель 3. Цифры на ней подсчитываются каждый раз, когда что-либо поменялось на панелях 1 и 2.

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

Ведь стоит поменять что-то на панели 2, то сразу запускается инициация панели 4 и пересчет панели 5. Инициация панели 4 в свою очередь опять пересчитывает панель 5. В результате на каждый чих пользователя в любом контроле панелей 3 и 5 подсчитываются по 5-6 раз.

Как это всё закодировано
1) Создаётся окно и ВСЕ его элементы.
2) Подписываются ВСЕ обработчики на ВСЕ контролы на ВСЕХ панелях.
3) Вычитываются данные для панели 1 и программа ждёт нажатия пользователя, что теми или имыми данными заполнить панель 2, которая в свою очередь заполнит панели 3 и 4, а панель 4 заполнит в свою очередь панель 5.

Вопросы.
1) Как правильно закодить эти все отношения между панелями и контролами, чтобы считалось всё один раз, а не 5-6?
2) Какие есть общие рекомендации на вот такой вот множественно зависимый интерфейс?

Re: Зависимости частей интерфейса.
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.12.08 07:43
Оценка: 40 (4)
Здравствуйте, Kore Sar, Вы писали:

KS>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит.

Эта проблема давно решается совершенно тривиальным способом:
1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный:
— на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим.
— пересчет делаем на событие от таймера
2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Зависимости частей интерфейса.
От: Kore Sar  
Дата: 10.12.08 08:06
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Kore Sar, Вы писали:


KS>>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит.

S>Эта проблема давно решается совершенно тривиальным способом:
S>1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный:
S>- на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим.
S>- пересчет делаем на событие от таймера
S>2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.

Мда, всё гениальное — просто.
Сейчас попробую.
Re[2]: Зависимости частей интерфейса.
От: Kore Sar  
Дата: 10.12.08 14:28
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Kore Sar, Вы писали:


KS>>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит.

S>Эта проблема давно решается совершенно тривиальным способом:
S>1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный:
S>- на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим.
S>- пересчет делаем на событие от таймера
S>2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.

Ув. Sinclair.

Вы осчастливили меня, всю мою команду и заказчика в частности. Теперь у меня, вместо подвисания GUI, выводится слово "calculating" до тех пор, пока не завершится подсчёт. Код моего нового "мега-класса" расшариваю просто так, для потомков. C#.


using System;
using System.Timers;
using System.Collections.Generic;

namespace MyNamespace
{
    public class TimedEvent
    {
        private const int TimerInterval = 500;
        private Action function;
        private Timer timer = new Timer(TimerInterval) { AutoReset = false };
        private string updatingText = "calculating";

        public string UpdateingText
        {
            get { return updatingText; }
            set { updatingText = value; }
        }

        public IEnumerable<System.Windows.Forms.Control> UpdatingControls { get; set; }

        public TimedEvent(Action _function)
        {
            if (_function == null) throw new ArgumentException("Can not be null", "_function");
            function = _function;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        }

        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            function();
        }

        public void Call()
        {
            if (timer.Enabled)
            {
                timer.Stop();
                timer.Start();
            }
            else
            {
                if (UpdatingControls != null)
                {
                    foreach (var control in UpdatingControls)
                    {
                        control.Text = updatingText;
                    }
                }
                timer.Start();
            }
        }
    }
}



Использование следующее:

            TimedEvent timedUpdate = new TimedEvent(VeryLongCalculationsFunction_Timed)
            {
                UpdatingControls = new List<Control>() 
                { 
                    lblSomeInterger1, lblSomeInterger2, lblSomeInterger3, lblSomeInterger4, lblSomeInterger5
                }
            };



    private void VeryLongCalculationsFunction() // <- это функция, которую вызывают все подряд. Раньше она содержала все калькуляции.
    {
        timedUpdate.Call();
    }

    private void VeryLongCalculationsFunction_Timed()
    {
        // ... функция, которая считает много и долго всякое разное ...
    }
Re[2]: Зависимости частей интерфейса.
От: Аноним  
Дата: 10.12.08 15:06
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Kore Sar, Вы писали:


KS>>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит.

S>Эта проблема давно решается совершенно тривиальным способом:
S>1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный:
S>- на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим.
S>- пересчет делаем на событие от таймера
S>2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.

Зачем так сложно? Есть халявный PostMessage(), который позволяет обойтись без таймеров и потоков (принцип тот же). Правда, не знаю, как с ним дела в не-виндах.
Re[3]: Зависимости частей интерфейса.
От: Sinclair Россия https://github.com/evilguest/
Дата: 10.12.08 15:45
Оценка: +1
Здравствуйте, <Аноним>, Вы писали:

А>Зачем так сложно? Есть халявный PostMessage(), который позволяет обойтись без таймеров и потоков (принцип тот же). Правда, не знаю, как с ним дела в не-виндах.

Не понял про халявный PostMessage. Если имеется в виду предложение вместо синхронного пересчета на каждый OnChange посылать самому себе PostMessage, и пересчет делать в его обработчике, то ничего хорошего не получится.
Следим за руками:
1. Пользователь делает инпут (например, нажимает кнопку клавиатуры с фокусом в текстбоксе).
2. Дефолтный обработчик WM_CHAR в текстбоксе выдает нотификейшн EN_CHANGE, который транслируется фреймворком в OnChange евент.
3. Обработчик этого евента, допустим, ставит в собственную очередь специальный мессадж WM_DELAYEDUPATE и возвращается в MessagePump
4. Пользователь тем временем нажимает еще какую-то кнопку, что ставит в очередь еще один WM_CHAR
5. Но этот WM_CHAR стоит в очереди после WM_DELAYEDUPDATE
6. Поэтому Message Pump сначала вытащит из очереди WM_DELAYEDUPDATE и начнет его обработку. По условиям задачи, эта обработка занимает несколько секунд.
7. Все эти несколько секунд WM_CHAR ждет в очереди, что наблюдается пользователем как тормоза UI.
8. Когда пересчет наконец протупит, мы попадем на пункт 2, а за ним неизбежно пойдут 3-7.
Проблема — в том, что обработчик WM_DELAYEDUPDATE будет вызван ровно столько же раз, сколько и EN_CHANGE.
Таймер как раз решает проблему многократного вызова: есть гарантия, что пересчет будет инициироваться не более одного раза в интервал (например, 500мс), независимо от скорости пользовательского набора.
Второй поток решает проблему замораживания интерфейса на время пересчета: прерывать message pump на пять секунд — не очень хорошо (десять — уже предел допустимого).
В принципе, фоновый поток позволяет не использовать таймер. Но нужно быть аккуратным с передачей в него данных и возможностью экстренно прерывать расчет. Поэтому отложенный пересчет на первое время пойдет.

Если я чего-то не понял, то изложи свою идею с PostMessage поподробнее.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Зависимости частей интерфейса.
От: Кодт Россия  
Дата: 10.12.08 17:08
Оценка: +1
Здравствуйте, Sinclair, Вы писали:

S>2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.


На то пошло, можно и без оконного таймера — всё в этом отдельном потоке.
Пусть он сам и дребезг устраняет.
Перекуём баги на фичи!
Re[4]: Зависимости частей интерфейса.
От: Jack128  
Дата: 10.12.08 21:25
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, <Аноним>, Вы писали:


S>Проблема — в том, что обработчик WM_DELAYEDUPDATE будет вызван ровно столько же раз, сколько и EN_CHANGE.


Что мешает при обработке WM_DELAYEDUPDATE удалить из очереди сообщений остальные нотификации??
Re[4]: Зависимости частей интерфейса.
От: Аноним  
Дата: 11.12.08 01:17
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, <Аноним>, Вы писали:


А>>Зачем так сложно? Есть халявный PostMessage(), который позволяет обойтись без таймеров и потоков (принцип тот же). Правда, не знаю, как с ним дела в не-виндах.

S>Не понял про халявный PostMessage. Если имеется в виду предложение вместо синхронного пересчета на каждый OnChange посылать самому себе PostMessage, и пересчет делать в его обработчике, то ничего хорошего не получится.
S>Следим за руками:
S>1. Пользователь делает инпут (например, нажимает кнопку клавиатуры с фокусом в текстбоксе).
S>2. Дефолтный обработчик WM_CHAR в текстбоксе выдает нотификейшн EN_CHANGE, который транслируется фреймворком в OnChange евент.
S>3. Обработчик этого евента, допустим, ставит в собственную очередь специальный мессадж WM_DELAYEDUPATE и возвращается в MessagePump
S>4. Пользователь тем временем нажимает еще какую-то кнопку, что ставит в очередь еще один WM_CHAR
S>5. Но этот WM_CHAR стоит в очереди после WM_DELAYEDUPDATE
S>6. Поэтому Message Pump сначала вытащит из очереди WM_DELAYEDUPDATE и начнет его обработку. По условиям задачи, эта обработка занимает несколько секунд.
S>7. Все эти несколько секунд WM_CHAR ждет в очереди, что наблюдается пользователем как тормоза UI.
S>8. Когда пересчет наконец протупит, мы попадем на пункт 2, а за ним неизбежно пойдут 3-7.
S>Проблема — в том, что обработчик WM_DELAYEDUPDATE будет вызван ровно столько же раз, сколько и EN_CHANGE.
S>Таймер как раз решает проблему многократного вызова: есть гарантия, что пересчет будет инициироваться не более одного раза в интервал (например, 500мс), независимо от скорости пользовательского набора.
S>Второй поток решает проблему замораживания интерфейса на время пересчета: прерывать message pump на пять секунд — не очень хорошо (десять — уже предел допустимого).
S>В принципе, фоновый поток позволяет не использовать таймер. Но нужно быть аккуратным с передачей в него данных и возможностью экстренно прерывать расчет. Поэтому отложенный пересчет на первое время пойдет.

S>Если я чего-то не понял, то изложи свою идею с PostMessage поподробнее.


Мне это решение показал один друг, KW, оптимизатор от бога. Я, вот именно, дальше потоков не придумал, а он сходу нашел решение с PostMessage(). Причем, оно у него работало. Детали я подзабыл, и могу переврать, но, по-моему, фишка была в следующем.

Контроллер не инвалидейтил представление, а менял модель и делал PM. Псевдокод (для рассматриваемого случая с WM_CHAR примем, что контроллер и представление — одно и то же):

ViewController.OnChange(ChangeParam e)
{
    model.Change(e.[...]);
    // Было и приводило к туплению: this.Invalidate();
    ::PostMessage(this.Handle, WM_DELAYEDUPDATE);
}


А в представлении при получении WM_DELAYEDUPDATE логика должна быть такая: проверяем, последнее ли это сообщение в очереди, и если да — выполняем перерисовку. Если нет — вызываем ::PostMessage(this.Handle, WM_DELAYEDUPDATE); и выходим из обработчика. Тем самым, мы гарантируем, что рано или поздно (а именно — когда пользователь перестанет жать кнопки) WM_DELAYEDUPDATE окажется последним сообщением в очереди и его, наконец, можно будет обработать.

Таким образом,

S>6. Поэтому Message Pump сначала вытащит из очереди WM_DELAYEDUPDATE и начнет его обработку. По условиям задачи, эта обработка занимает несколько секунд.

S>7. Все эти несколько секунд WM_CHAR ждет в очереди, что наблюдается пользователем как тормоза UI.

не соответствует действительности. По условиям задачи отрисовка занимает несколько секунд, а не обработка. Обработка, если пропущена отрисовка, почти не занимает времени. А она будет пропущена до тех пор, пока в очереди не останется больше WM_CHAR, WM_LBUTTONDOWN и т.п. Дальше идет улучшательство: например, фильтруем сообщения, не влияющие на контроллер, типа WM_MOUSEMOVE, перед тем, как вызвать PM, выполняем вывод замещающей графики, типа "calculating" etc.
Re: Зависимости частей интерфейса.
От: goto Россия  
Дата: 11.12.08 16:27
Оценка:
Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым. При изменениях в UI выставляется флажок на прерывание текущего пересчета и запуск нового. Но в графических расчетах много характерных однородных циклов, так что текущий цикл по флажку прерывается технически просто и очень быстро. И конечно во время пересчета показывается хорошо видимая индикация, что, мол, думает программа.
Re[5]: Зависимости частей интерфейса.
От: Кодт Россия  
Дата: 11.12.08 19:41
Оценка: +1
Здравствуйте, Аноним, Вы писали:

S>>Если я чего-то не понял, то изложи свою идею с PostMessage поподробнее.


А>Мне это решение показал один друг, KW, оптимизатор от бога. Я, вот именно, дальше потоков не придумал, а он сходу нашел решение с PostMessage(). Причем, оно у него работало. Детали я подзабыл, и могу переврать, но, по-моему, фишка была в следующем.


Тут фокус в том, чтобы так или иначе эмулировать idle loop.
Это можно сделать 3 способами
— вломиться в цикл прокачки сообщений (или воспользоваться готовыми решениями — например, MFC и отчасти WTL это умеют)
— посылать PostMessage и вручную управлять очередью сообщений
— использовать таймер — логика работы которого уже совпадает с тем, что нужно делать над сериями "WM_DELAYUPDATE"

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

Использование дополнительного, рабочего потока — это дальнейшее улучшение. Безотносительно того, каким способом ловится ситуация trigger --> idle в оконном потоке.
Если производительности хватает, пусть будет всё в оконном потоке, не жалко.
Перекуём баги на фичи!
Re[2]: Зависимости частей интерфейса.
От: Кодт Россия  
Дата: 11.12.08 19:50
Оценка:
Здравствуйте, goto, Вы писали:

G>Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым.


... а также состоит из грубой и точной фазы.
Например, грубая фаза просто перерисовывает скелет, а точная натягивает текстуры.
Или грубая фаза стретчит старую картинку под новый размер, а точная — рожает картинку с новым разрешением.

Так же и здесь: можно разбить пересчёт на несколько фаз.
В самом начале все зависимые контролы дисаблятся, а затем в несколько приёмов пересчитываются и энаблятся.

---
Чем чаще сталкиваюсь с такими потребностями в сценариях работы над окном, тем больше прихожу к мысли, что нужно как-то воплощать континюэйшены.
Вот недавно в журнале MSDN Journal, что ли, попалась статья о том, как удобно писать континюэйшены на C#2 / C#3.
Найду, опробую идеи на нативном C++, обязательно расскажу.

Суть в том, что есть окно и есть сценарии модальной работы над этим окном.
Размазывать сценарии по коду обработчиков сообщений окна — это большой головняк, особенно, когда сценариев несколько.
Перекуём баги на фичи!
Re[6]: Зависимости частей интерфейса.
От: Аноним  
Дата: 11.12.08 21:10
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Таймер предпочтительнее, т.к.

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

Не знаю, не знаю. Написать PostMessage() вместо Invalidate() и воткнуть одну проверку в обработчик (перед вызовом все того же Invalidate()) — по-моему, это самое простое решение. И ценность его как раз в этом.

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

К>Использование дополнительного, рабочего потока — это дальнейшее улучшение. Безотносительно того, каким способом ловится ситуация trigger --> idle в оконном потоке.

К>Если производительности хватает, пусть будет всё в оконном потоке, не жалко.
Re[3]: Зависимости частей интерфейса.
От: goto Россия  
Дата: 12.12.08 00:25
Оценка:
Я не заток континюэйшенов, но тема интересная. А конкретно в графике наиболее затратно перемалывание большого кол-ва однородных объектов (пикселы, полигоны, лучи), и там, как мне кажется, все проще в этом отношении.

Я когда-то подсмотрел незатейливое решение в одном древнем софте для видеообработки, в его плагинной части. Там плагин возвращал сигнатуру, формируя ее обычно по своим входным параметрам. Хост-софт каждый раз сначала звал эту ф-ю плагина, и если сигнатура не изменилась, то счетная часть не вызывалась, а брался готовый предыдущий результат из кэша. Не удивлюсь, если сейчас по этому поводу есть "паттерн" .
Re[3]: Зависимости частей интерфейса.
От: Kore Sar  
Дата: 12.12.08 06:57
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, goto, Вы писали:


G>>Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым.


К>... а также состоит из грубой и точной фазы.

К>Например, грубая фаза просто перерисовывает скелет, а точная натягивает текстуры.
К>Или грубая фаза стретчит старую картинку под новый размер, а точная — рожает картинку с новым разрешением.

К>Так же и здесь: можно разбить пересчёт на несколько фаз.

К>В самом начале все зависимые контролы дисаблятся, а затем в несколько приёмов пересчитываются и энаблятся.

К>---

К>Чем чаще сталкиваюсь с такими потребностями в сценариях работы над окном, тем больше прихожу к мысли, что нужно как-то воплощать континюэйшены.
К>Вот недавно в журнале MSDN Journal, что ли, попалась статья о том, как удобно писать континюэйшены на C#2 / C#3.
К>Найду, опробую идеи на нативном C++, обязательно расскажу.

К>Суть в том, что есть окно и есть сценарии модальной работы над этим окном.

К>Размазывать сценарии по коду обработчиков сообщений окна — это большой головняк, особенно, когда сценариев несколько.

1) Не знаю, что означает в Вашем понимании "континюэйшен". Что это?
2) Про головняк — это Вы попали в десятку, как раз у меня тут головняк. Сценариев кулькуляции около 10-15 штук.
3) Ссылочку на статью можно?
Re[4]: Зависимости частей интерфейса.
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.12.08 07:30
Оценка:
Здравствуйте, Kore Sar, Вы писали:

KS>1) Не знаю, что означает в Вашем понимании "континюэйшен". Что это?

Континьюэйшн — это, на пальцах, "недовыполненная функция", которую можно дернуть еще раз и сказать "продолжись".
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Зависимости частей интерфейса.
От: Кодт Россия  
Дата: 12.12.08 18:58
Оценка: 4 (1)
Здравствуйте, <Аноним>, Вы писали:

А>Не знаю, не знаю. Написать PostMessage() вместо Invalidate() и воткнуть одну проверку в обработчик (перед вызовом все того же Invalidate()) — по-моему, это самое простое решение. И ценность его как раз в этом.


Неет, нужно ещё сделать проверку перед отправкой PostMessage — было ли принято предыдущее.
Иначе есть сценарии, когда ты захламишь очередь сообщений своими дубликатами. Тормозить будет, конечно, меньше, чем с синхронным обновлением — но всё равно будет тормозить.

А таймер с самого начала не дублируется.

К тому же, таймером можно гибче регулировать отклик на изменения.

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

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

И даже больше: в зависимости от того, с каким контролом сейчас работает пользователь.
Понятно, что натискивание чекбоксов — это редкие одиночные действия, между которыми, во-первых, есть естественная пауза, а во-вторых, хорошо бы как можно скорее показать результат изменений.
А ввод текста — напротив, это медленный, но непрерывный поток событий, который заканчивается или сменой фокуса, или большой паузой, когда пользователь наконец отвлёкся от печати.
Тут и секундная задержка полезна — на EN_CHANGE, ну а на EN_KILLFOCUS нужно реагировать побыстрее.

Ничего этого с постмесседжем нахаляву сделать невозможно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[4]: Зависимости частей интерфейса.
От: Кодт Россия  
Дата: 12.12.08 18:58
Оценка:
Здравствуйте, Kore Sar, Вы писали:

KS>1) Не знаю, что означает в Вашем понимании "континюэйшен". Что это?


Как сказал Синклер — недовыполненная функция.
Точнее, это вот что:

f1 : начальные параметры -> побочные эффекты + f2
f2 : очередное событие -> новые побочные эффекты + f3
и т.д.

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

В качестве событий могут быть или просто отсчёты времени (разбили вычисление на этапы), или поток ввода.


KS>2) Про головняк — это Вы попали в десятку, как раз у меня тут головняк. Сценариев кулькуляции около 10-15 штук.


При таком разнообразии — стоит задуматься о том, нет ли здесь общего подхода, дающего единственный сценарий?
А именно: нарисовать граф зависимостей, топологически отсортировать его...

Возможно, что таких графов несколько.
Простой пример: два чекбокса, которые работают как радиокнопки. ("Выберите любое или не выбирайте ничего").
Клик по первому энаблит/дисаблит второго. Клик по второму — энаблит/дисаблит первого.
Ровно два графа.

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


KS>3) Ссылочку на статью можно?


Бумажный журнал дома валяется. Посмотрю.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[8]: Зависимости частей интерфейса.
От: Аноним  
Дата: 14.12.08 23:25
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Неет, нужно ещё сделать проверку перед отправкой PostMessage — было ли принято предыдущее.

К>Иначе есть сценарии, когда ты захламишь очередь сообщений своими дубликатами. Тормозить будет, конечно, меньше, чем с синхронным обновлением — но всё равно будет тормозить.
К>А таймер с самого начала не дублируется.

Ты о чем? Они и должны уходить целыми пачками. А обработчик пропускает их, как в анекдоте, "до единого" — there must be only one. Тормозов на просер очереди не видел еще в 2004 году, когда впервые увидал фишку в действии. А тогда железо было насколько слабее?

К>К тому же, таймером можно гибче регулировать отклик на изменения.


К>Если пользователь вводит информацию быстро, то и таймер, и умный постмесседж откладывают изменения до тех пор, пока не кончится ввод. Потом выполняется обработчик.

К>Если пользователь вводит медленно, то обработчик успевает выполниться в паузах между вводом.
К>Если же пользователь недостаточно быстрый, то при обработчик начнёт вклиниваться в ввод, и по факту получается синхронное выполнение, а пользователь ловит тормоза.

Опять же, это теория. А на практике все весьма юзабильно. Почему? Потому, что отрисовка должна просто "слегка притормаживать". Если она жестко тормозит систему до последствий, описанных выше, оба варианта — с потоками-таймерами и PM — одинаково неприемлимы. Пользователь прекращает ввод и сидит ждет реакции... ждет.... ждет... Набрал пару слов... ждет реакции... ждет.... ждет... Варианты лечения:

  • Сделать отрисовку с LOD. То есть, использовать в PostMessage() сообщения не одного типа, а двух, трех, четырех и так далее. Это налагает следующее ограничение: прорисовка должна поддаваться алгоритмам типа interlacing'а. Число уровней зависит, опять же, от алгоритма.
  • Не пытаться рвать системе анус real time'ом, а честно пересмотреть концепцию и ввести операцию рендеринга (или процессинга — если тормоза связаны не с самой отрисовкой, а с просчетом дополнительных деталей для нее) в UI scope.
  • Заказать оптимизацию соответствующего алгоритма, как вариант — повешать реализацию на спецжелезо (как повешена обработка 3D на ускорители).

    По вкусу — смешать.

    "Гибкость" таймеров — это не только плюс, но и минус. Задав шаг слишком крупным, можно создать тормоза там, где их нет (на сверхбыстрой машине n+m-ого поколения). PM же действительно гибок — он "подстраивается" под машину.

    И личное: я вообще не перевариваю решения с заданными паузами. Например, когда программист не знает, как обработать event и ляпает второй поток с таймером, узнавая — как там, не пришла ли пора? В этом случае event handler, конечно, нерелевантен, но решение-то того же типа.

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

    К>Скажем, ввести задержку в полсекунды.

    К>И даже больше: в зависимости от того, с каким контролом сейчас работает пользователь.


    К>Понятно, что натискивание чекбоксов — это редкие одиночные действия, между которыми, во-первых, есть естественная пауза, а во-вторых, хорошо бы как можно скорее показать результат изменений.

    К>А ввод текста — напротив, это медленный, но непрерывный поток событий, который заканчивается или сменой фокуса, или большой паузой, когда пользователь наконец отвлёкся от печати.
    К>Тут и секундная задержка полезна — на EN_CHANGE, ну а на EN_KILLFOCUS нужно реагировать побыстрее.

    К>Ничего этого с постмесседжем нахаляву сделать невозможно.


    Поверь, это то, что у старых людей называлось "глюкалу полировать".

    Тот же самый KW по молодости сидел и точил движок для двухмерной отрисовки поля распределения параметра (с изолиниями, градиентиками и прочими делами). Машины тогда были даже еще слабее, чем в 2004 году . Сделал динамическую смену алгоритма интерполяции, отключение-включение сглаживаний и так далее. Свел все к одному интегральному параметру, который привязал к слайдеру "Качество отрисовки" от 0 до 100%. И что? Практика показала, что этот слайдер пользователи никогда не меняли. Название форума-то какое? Неюзабильно оказалось. Вот так и любая попытка "явно указывать, какой темп ввода считается медленным" обречена на провал. (Замечу в скобках: пользователи его движка на самом деле вообще плевать хотели на навороченную визуализацию — в отличие от расчетов, где предпочитали настраивать вообще все — интерполяцию, степень влияния и пр. Само собой, расчет шел на массивах данных, с progress bar'ом. Так не оказалось бы, что это типовой случай).

    Что касается дифференциации контролов — это вообще противоестественно. На практике решение применяется в следующих случаях:
  • Набор текста.
  • Изменение размеров окна.
  • Прокрутка.
  • Оптимизация типа той, что хотел автор ветки, когда запросов на отрисовку заведомо больше, чем нужно.

    Прошу заметить, что в последнем случае хоть и используются разнотиповые контролы, с точки зрения дизайна нет смысла давать им приоритеты.
  • Re: Зависимости частей интерфейса.
    От: c-smile Канада http://terrainformatica.com
    Дата: 15.12.08 00:17
    Оценка:
    Здравствуйте, Kore Sar, Вы писали:

    KS>У нас есть GUI приложение (WinForms .Net) — см. рисунок в конце сообщения.


    KS>Вопросы.

    KS>1) Как правильно закодить эти все отношения между панелями и контролами, чтобы считалось всё один раз, а не 5-6?
    KS>2) Какие есть общие рекомендации на вот такой вот множественно зависимый интерфейс?

    Не знаю применим ли опыт имплементации batch update в htmlayout для .NET но тем не менее...

    При обработке внешнего события типа скажем WM_LBUTTONUP
    создается(инициализируется) update queue.
    WM_LBUTTONUP в виде MOUSE_UP доставлется всем заинтресованным dom элементам (bubbling).
    Все вызовы мутирующих методов dom элементов в обработчиках этого события фактически
    помещают эти dom элементы в update queue. update queue не добавляет элемент если он уже
    заявлен или содержится внутри элемента требующего update.
    В качестве последнего действа при обработке WM_LBUTTONUP собственно и происходит
    обновление всех элементов и делается InvlaidateRect для всех update roots.

    Такой транзакционный метод работает достаточно хорошо и эффективно.
    Изменения появлются на экране в темпе вальса.

    Я думаю что тебе имеет смысл попробовать звать LockWindowUpdate() для своего окна
    в начале massive update и соотв. по концу оного. В некоторых ситуциях типа твоей
    может значительно улучшить дело.
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.