UI, архитектурный вопрос
какое кол-во текст боксов?
В листбоксе от нуля до тысячи.
Текстбоксов штук десять.
ты пытаешься построить чисто событийно-ориентированную архитектуру, как я писал, вскольз, в соседнем треде - такая архитектура очень не надежная, т.к. если забыть какое-нибудь событие (пусть даже редкое) - то все разрушается как карточный домик.
Или ты считаешь, что порочна формулировка задачи, и УИ надо строить исходя из архитектуры, а не из соображений удобства для пользователя?
На самом деле мне не нужна "серебрянная пуля". Мне было бы достаточно просто централизации управляющего кода и отсутсвия экспоненциального роста сложности от количества уровней подчинённости.
А ещё немножко удивительно - неужели за 20 лет существования визуальных интерфейсов никто не придумал стандартного паттерна?
Использовать препроцессор?
---
...Я работаю...
я бы просто хранил последние значения текстбоксов, если они не изменились, то ничего бы и не делал.
Ты не понял проблемы. Зачем хранить последние значения текстбоксов? Они и так в них есть =)
Проблема на самом деле тривиальная: мне нужно узнать, что является причиной изменения индекса в листбоксе - действия юзера (внешнее по отношению к системе воздействие) или мои собственные действия (внутреннее воздействие). Фреймворк 1.1 не позволяет это сделать прямо, соотвественно на самом деле есть всего два варианта:
1) расковырять листбокс и вставить туда нужный код, отлавливающий либо внутренние, либо внешние воздействия, и сообщающий мне о результатах.
2) не трогать листбокс и отлавливать все внутренние воздействия.
2Контра: я не очень люблю кодогенерацию. Насколько я понимаю, есть три возможности:
1) настоящий препроцессор, который делает по моему файлу другой, который и компилится на самом деле. Это жутко неудобно, ИМХО, в частности потому, что вижуальник, кажется, совершенно для этого не приспособлен.
2) препроцессор, патчащий исходный файл. У меня один такой даже есть самописный на регексах, типа, использование выглядит так:
// FSMG:FASTCOLLECTION CollectionName<ElementType>
сюда он каждый раз вставляет код, сгенерённый по хедеру
// FSMG:END
Всё отлично, только немножко напрягает дополнительный мусор - эти самые комментарии
3) постпроцессор, патчащий дллку на основе метаданных. За 900 баксов можно купить один такой, называется как-то типа "XC#". Наверное, лучший вариант, правда не знаю что там с дебаггингом происходит. Ну и 900 баксов опять же =)
Я не в курсе последних виндовых извращений.
Насколько я понял, его хватило бы.
Я понимаю твои чувства.
Если б я был знаком только с cpp, я полностью бы их разделял.
---
...Я работаю...
Он находится где-то посередине между "нельзя давать сантехникам ни малейшей возможности написать unmaintable code" (c)Java и "Да пишите чо хотите нах, мы сами нифига не понимаем что происходит" (с)С++
---
...Я работаю антинаучным аферистом...
К сожалению (или к щастью) иногда от языка программирования требуется не только лёгкость написания, но и эффективность результата.
Кстати, как на твоём Любимом Языке Программирования решается такая проблема? Или словосочетание User Interface в отношении него звучит издевательством?
Есть, но мало. Чуть больше чем в жаве, наверное. Но макропроцессора нет, "потому что развращает" =)
а затем наклепать её поверх всего, чего надо, либо есть такое:
: later r> r> swap >r >r ;
Меня последнее время просто прёт от такого.
---
...Я работаю антинаучным аферистом...
Почему бы не сделать подобие функционалов?
Если всё inline, то скорость не потеряется.
---
...Я работаю антинаучным аферистом...
Что иль кто есть >r ? Доставание из стека, или из входного потока?
---
...Я работаю антинаучным аферистом...
Я не понимаю, к чему ты это сказал про функционалы. У меня нет достаточного опыта работы с ними, чтобы понять, как их можно применить к данной задаче. Если у тебя, в свою очередь, нет достаточного опыта работы с event-driven UI, то мы никогда не сможем извлечь что-нибудь полезное из этого диалога.
то это примерно так:
(define (wrapped handler)
(lambda ' (begin (set! flag 1) (handler) (set! flag 0
(define handler1 (wrapped (lambda ' ...
(define handler2 (wrapped (lambda ' ...
---
...Я работаю антинаучным аферистом...
В шарп 2.0 такое реализуемо через анонимные делегаты. Так что я успокаиваюсь и с оптимизмом смотрю в будущее (то есть в следующий проект).
Спасибо.
: wrapped flag on later flag off ;
: handler1 wrapped ... ;
: handler2 wrapped ... ;
---
“Skillfully written Forth code is like poetry...”
Не знаю, что означает "всё такое," но событийное управление делается просто.
По крайней мере, я не думаю, что сложнее, чем на самых приплюснутых сях.
---
...Я работаю антинаучным аферистом...
Я сталкивался с точно такой же проблемой, правда, текстовых полей и других кнопок у меня было существенно меньше. Ничего более умного, чем использовать флаг я так и не придумал. Однако, для уменьшения геморроя с флагом можно при входе в процедуры его увеличивать, а при выходе уменьшать. Этим решается проблема с сохранением флага в некоторых процедурах.
Неужели в XXI в. всё ещё нужно работать со всеми этими Mouse-, KeyEvent, selectedItem?
Храним те, которые были на момент последней синхронизации.
> Проблема на самом деле тривиальная: мне нужно узнать, что является причиной изменения индекса в листбоксе - действия юзера (внешнее по отношению к системе воздействие)
если значения textbox-ов не совпадают с последними заполненными - значит listbox менял пользователь.
т.е. в памяти храниться пара: значение listbox-а и значения textbox-ов
при событиях: currentListBox.Value[до события] == lastlistbox.Value && currentTextBox.Value != lastTextBox.Value, то положить значение в базу
лень думать, но может быть можно обойтись и без хранения старых значений
главное, чтобы у тебя было только два метода - которые что-то делают.
один метод должен синхронизировать форму с базой
другой метод должен синхронизировать базу с формой.
а события должны лишь всегда инициировать вызовы этих методов.
раз метода лишь два, то можно уже и флаги заводить, и еще чего-то, т.к. нет главной проблемы - роста кол-ва функционала от кол-ва событий.
Немного изменил паттерн от MS для работы с event-ами.
public event EventHandler SelectedIndexChanged;
private bool fireEvent = true;
protected void OnSelectedIndexChanged(EventArgs e)
{
if (!fireEvent) return;
if (SelectedIndexChanged != null)
SelectedIndexChanged(this, e);
}
private void ФункцияОбновленияИзКода
{
fireEvent = false;
try
{
// Обновляем содержимое листбокса
}
finally
{
fireEvent = true;
}
}
// Обработчик события твоего листбокса
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
OnSelectedIndexChanged(e);
}
И предложение мне нравится.
Это ужасно криво. Дико просто криво. А если у меня полностью переписывается содержимое листбокса - если юзер тыкнул в рефреш или где-то по пути случилась ошибка и ИИ приняло решение всё перегрузить? Таки заводить флажок "ни при каких условиях не пытаться сохранить старое значение", но использовать его не везде, а только в паре мест?
Только не на виндовой платформе.
Пожалуй, попробую сравнить си, форт и схему 4-го стандарта.
---
...Я работаю антинаучным аферистом...
Сталкивался с такой проблемой в Java. Решил следующим образом. За каждым элементом закреплен некий Listener (возможно, несколько штук). Пусть пришло событие об изменении элемента T1. Во время обработки этого события требуется изменить элемент T2. Перед изменением я отцепил от T2 его Listener-ы, сделал все изменения, потом прицепил Listener-ы обратно.
гать листбокс и отлавливать все внутренние воздействиАргумент sender не пробывал анализировать?
Он всегда один и тот же - листбокс.
При использовании data binding таких проблем кажется не возникает, по крайней мере в .Net 2.0.
Оставить комментарий
bleyman
Меня мучает уже довольно давно.Типа, винформы.
Есть листбокс со списком объектов. Ещё есть разнообразные текстбоксы, в которые выводится детальная информация о текущем выбранном объекте в листбоксе.
Хочется следующего поведения: можно изменить содержимое текстбоксов, затем выбрать другой айтем в листбоксе, после чего возможно выведется запрос на сохранение, при положительном ответе изменённые поля пропишутся в предыдущий выбранный айтем, который затем сохранится куда-нибудь типа БД.
Пикантность происходящему придают следующие две особенности:
1) в результате изменения полей может измениться имя айтема (под которым он фигурирует в листбоксе и, соответственно, позиция, поэтому придётся его оттуда удалять и вставлять заново.
2) а ещё на форме есть разные кнопки типа "apply" и "refresh", которые тоже изменяют содержимое листбокса.
Пикантность состоит в том, что если вешаться на listBox.SelectedIndexChanged, то необходимо как-то разделять вызовы, произошедшие в результате действий пользователя (когда нужно сохранять предыдущее значение) от вызовов, произошедших в результате программного изменения содержимого листбокса. В случае программных вызовов категорически ничего нельзя делать, потому что предыдущий айтем уже может быть сохранён (в случае нажатия на Апплай или его ни за что нельзя сохранять, и вообще он неопределён (в случае нажатия на Рефреш ну или на самом деле мы уже находимся в предыдущем обработчике SelectedIndexChanged. Плюс - во всех этих случаях производятся некие программные действия, направленные на сохранение/выставление правильного СелектедИндекса после изменения содержимого, и очень важно им не помешать.
Я вижу несколько вариантов:
1) вешаться не на SelectedIndexChanged, а на разные чисто юзерские эвенты типа MouseClick, MouseDown, KeyDown, KeyPress, возможно ещё WM_MOUSEWHEEL, и считать селектед индекс самому как-то. ИМХО это жуткий отстой, потому что я должен абсолютно точно повторить поведение листбокса, то есть продублировать код, и при любом его изменении в следующих версиях фреймворка мне придётся всё переделывать.
2) Выдрать код листбокса рефлектором, сделать свой контрол и выставлять Специальный Флажок если селектедИндексЧейнджед вызывается в результате действий юзера. Или даже поднимать специальный другой эвент. Во-первых ломает. Во-вторых, там же ещё и винапи как-то страшно завязано.
3) Выставлять Специальный Флажок ignoreUserInput в начале каждой функции, которая может потенциально привести к вызову SelectedIndexChanged и обнулять при выходе. А в SelectedIndexChanged, соотвественно, сразу выходить, если он выставлен.
Так я и сделал, потому что проще всего. Однако приходится тщательно следить за происходящим, держать в голове весь граф вызовов, оборачивать внутренность функций в try {..} finally {ignoreUserInput = false;}, в особо тяжёлых случаях запоминать на входе предыдущее значение ignoreUserInput и выставлять на выходе именно его, а не просто false.
Это какой-то ужас и кошмар.
Особенно когда появляется ещё какой-нибудь комбобокс, от которого на самом деле зависит содержимое всего остального (включая листбокс!) и с которым тоже происходят аналогичные пертурбации.
Как такую фигню делать правильно?