Здравствуйте, Kore Sar, Вы писали:
KS>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит.
Эта проблема давно решается совершенно тривиальным способом:
1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный:
— на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим.
— пересчет делаем на событие от таймера
2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, <Аноним>, Вы писали:
А>Не знаю, не знаю. Написать PostMessage() вместо Invalidate() и воткнуть одну проверку в обработчик (перед вызовом все того же Invalidate()) — по-моему, это самое простое решение. И ценность его как раз в этом.
Неет, нужно ещё сделать проверку перед отправкой PostMessage — было ли принято предыдущее.
Иначе есть сценарии, когда ты захламишь очередь сообщений своими дубликатами. Тормозить будет, конечно, меньше, чем с синхронным обновлением — но всё равно будет тормозить.
А таймер с самого начала не дублируется.
К тому же, таймером можно гибче регулировать отклик на изменения.
Если пользователь вводит информацию быстро, то и таймер, и умный постмесседж откладывают изменения до тех пор, пока не кончится ввод. Потом выполняется обработчик.
Если пользователь вводит медленно, то обработчик успевает выполниться в паузах между вводом.
Если же пользователь недостаточно быстрый, то при обработчик начнёт вклиниваться в ввод, и по факту получается синхронное выполнение, а пользователь ловит тормоза.
Так вот, с помощью таймера можно явно указывать, какой темп ввода считается медленным. И сделать это, исходя из удобства пользователя.
Скажем, ввести задержку в полсекунды.
И даже больше: в зависимости от того, с каким контролом сейчас работает пользователь.
Понятно, что натискивание чекбоксов — это редкие одиночные действия, между которыми, во-первых, есть естественная пауза, а во-вторых, хорошо бы как можно скорее показать результат изменений.
А ввод текста — напротив, это медленный, но непрерывный поток событий, который заканчивается или сменой фокуса, или большой паузой, когда пользователь наконец отвлёкся от печати.
Тут и секундная задержка полезна — на EN_CHANGE, ну а на EN_KILLFOCUS нужно реагировать побыстрее.
Ничего этого с постмесседжем нахаляву сделать невозможно.
Здравствуйте, <Аноним>, Вы писали:
А>Зачем так сложно? Есть халявный 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>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Аноним, Вы писали:
S>>Если я чего-то не понял, то изложи свою идею с PostMessage поподробнее.
А>Мне это решение показал один друг, KW, оптимизатор от бога. Я, вот именно, дальше потоков не придумал, а он сходу нашел решение с PostMessage(). Причем, оно у него работало. Детали я подзабыл, и могу переврать, но, по-моему, фишка была в следующем.
Тут фокус в том, чтобы так или иначе эмулировать idle loop.
Это можно сделать 3 способами
— вломиться в цикл прокачки сообщений (или воспользоваться готовыми решениями — например, MFC и отчасти WTL это умеют)
— посылать PostMessage и вручную управлять очередью сообщений
— использовать таймер — логика работы которого уже совпадает с тем, что нужно делать над сериями "WM_DELAYUPDATE"
Таймер предпочтительнее, т.к.
— это уже готовое решение; нет нужды ни потрошить очередь, ни реализовывать антидребезг с помощью флажков, ни писать собственный цикл прокачки (тем более, что он не везде доступен).
— можно гибко управлять сглаживанием реакции на изменения (вводя разные таймауты)
И это всё — в оконном потоке.
Использование дополнительного, рабочего потока — это дальнейшее улучшение. Безотносительно того, каким способом ловится ситуация trigger --> idle в оконном потоке.
Если производительности хватает, пусть будет всё в оконном потоке, не жалко.
У нас есть 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) Какие есть общие рекомендации на вот такой вот множественно зависимый интерфейс?
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Kore Sar, Вы писали:
KS>>Каждый подсчёт на панели 5 длится несколько секунд. Проблема в том, что панель 5 подсчитывается слишком часто и слишком много раз. Приложение ужасно тупит. S>Эта проблема давно решается совершенно тривиальным способом: S>1. Делаем пересчет не на OnChange, а по таймеру, через некоторое время после последнего OnChange. Алгоритм простой и понятный: S>- на каждый OnChange взводим таймер на, к примеру, 500мс. Если таймер уже взведен — перевзводим. S>- пересчет делаем на событие от таймера S>2. Сам пересчет выполняем в отдельном потоке, чтобы UI не тупил.
Здравствуйте, 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(), который позволяет обойтись без таймеров и потоков (принцип тот же). Правда, не знаю, как с ним дела в не-виндах.
Здравствуйте, 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.
Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым. При изменениях в UI выставляется флажок на прерывание текущего пересчета и запуск нового. Но в графических расчетах много характерных однородных циклов, так что текущий цикл по флажку прерывается технически просто и очень быстро. И конечно во время пересчета показывается хорошо видимая индикация, что, мол, думает программа.
Здравствуйте, goto, Вы писали:
G>Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым.
... а также состоит из грубой и точной фазы.
Например, грубая фаза просто перерисовывает скелет, а точная натягивает текстуры.
Или грубая фаза стретчит старую картинку под новый размер, а точная — рожает картинку с новым разрешением.
Так же и здесь: можно разбить пересчёт на несколько фаз.
В самом начале все зависимые контролы дисаблятся, а затем в несколько приёмов пересчитываются и энаблятся.
---
Чем чаще сталкиваюсь с такими потребностями в сценариях работы над окном, тем больше прихожу к мысли, что нужно как-то воплощать континюэйшены.
Вот недавно в журнале MSDN Journal, что ли, попалась статья о том, как удобно писать континюэйшены на C#2 / C#3.
Найду, опробую идеи на нативном C++, обязательно расскажу.
Суть в том, что есть окно и есть сценарии модальной работы над этим окном.
Размазывать сценарии по коду обработчиков сообщений окна — это большой головняк, особенно, когда сценариев несколько.
Перекуём баги на фичи!
Re[6]: Зависимости частей интерфейса.
От:
Аноним
Дата:
11.12.08 21:10
Оценка:
Здравствуйте, Кодт, Вы писали:
К>Таймер предпочтительнее, т.к. К>- это уже готовое решение; нет нужды ни потрошить очередь, ни реализовывать антидребезг с помощью флажков, ни писать собственный цикл прокачки (тем более, что он не везде доступен). К>- можно гибко управлять сглаживанием реакции на изменения (вводя разные таймауты) К>И это всё — в оконном потоке.
Не знаю, не знаю. Написать PostMessage() вместо Invalidate() и воткнуть одну проверку в обработчик (перед вызовом все того же Invalidate()) — по-моему, это самое простое решение. И ценность его как раз в этом.
Если в приложении идут расчеты, а не тормозит отрисовка, то это немного другая ситуация. И в ней надо использовать потоки безусловно (т.е. при любых раскладах).
К>Использование дополнительного, рабочего потока — это дальнейшее улучшение. Безотносительно того, каким способом ловится ситуация trigger --> idle в оконном потоке. К>Если производительности хватает, пусть будет всё в оконном потоке, не жалко.
Я не заток континюэйшенов, но тема интересная. А конкретно в графике наиболее затратно перемалывание большого кол-ва однородных объектов (пикселы, полигоны, лучи), и там, как мне кажется, все проще в этом отношении.
Я когда-то подсмотрел незатейливое решение в одном древнем софте для видеообработки, в его плагинной части. Там плагин возвращал сигнатуру, формируя ее обычно по своим входным параметрам. Хост-софт каждый раз сначала звал эту ф-ю плагина, и если сигнатура не изменилась, то счетная часть не вызывалась, а брался готовый предыдущий результат из кэша. Не удивлюсь, если сейчас по этому поводу есть "паттерн" .
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, goto, Вы писали:
G>>Добавлю немного. В графических софтах явно встречается ситуация, когда при каждом изменении пар-ра запускается пересчет, который может занимать заметное время, например, минуту. Решается тем, что счетный процесс делается прерываемым.
К>... а также состоит из грубой и точной фазы. К>Например, грубая фаза просто перерисовывает скелет, а точная натягивает текстуры. К>Или грубая фаза стретчит старую картинку под новый размер, а точная — рожает картинку с новым разрешением.
К>Так же и здесь: можно разбить пересчёт на несколько фаз. К>В самом начале все зависимые контролы дисаблятся, а затем в несколько приёмов пересчитываются и энаблятся.
К>--- К>Чем чаще сталкиваюсь с такими потребностями в сценариях работы над окном, тем больше прихожу к мысли, что нужно как-то воплощать континюэйшены. К>Вот недавно в журнале MSDN Journal, что ли, попалась статья о том, как удобно писать континюэйшены на C#2 / C#3. К>Найду, опробую идеи на нативном C++, обязательно расскажу.
К>Суть в том, что есть окно и есть сценарии модальной работы над этим окном. К>Размазывать сценарии по коду обработчиков сообщений окна — это большой головняк, особенно, когда сценариев несколько.
1) Не знаю, что означает в Вашем понимании "континюэйшен". Что это?
2) Про головняк — это Вы попали в десятку, как раз у меня тут головняк. Сценариев кулькуляции около 10-15 штук.
3) Ссылочку на статью можно?
Здравствуйте, Kore Sar, Вы писали:
KS>1) Не знаю, что означает в Вашем понимании "континюэйшен". Что это?
Континьюэйшн — это, на пальцах, "недовыполненная функция", которую можно дернуть еще раз и сказать "продолжись".
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, 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'ом. Так не оказалось бы, что это типовой случай).
Что касается дифференциации контролов — это вообще противоестественно. На практике решение применяется в следующих случаях:
Набор текста.
Изменение размеров окна.
Прокрутка.
Оптимизация типа той, что хотел автор ветки, когда запросов на отрисовку заведомо больше, чем нужно.
Прошу заметить, что в последнем случае хоть и используются разнотиповые контролы, с точки зрения дизайна нет смысла давать им приоритеты.
Здравствуйте, 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 и соотв. по концу оного. В некоторых ситуциях типа твоей
может значительно улучшить дело.