Конечные автоматы, как правило, слишком низкого уровня, чтобы помочь вам думать о пользовательском интерфейсе. Они делают хороший выбор реализации для инструментария пользовательского интерфейса, но в обычном приложении слишком много состояний и переходов, чтобы описать их вручную.
Мне нравится думать об интерфейсах с продолжениями. (Google это - термин достаточно конкретен, что вы получите много высококачественных хитов.)
Вместо того, чтобы мои приложения находились в различных состояниях, представленных флагами состояния и режимами, я использую продолжения для управления тем, что приложение делает дальше. Проще всего объяснить на примере. Скажем, вы хотите открыть диалоговое окно подтверждения перед отправкой электронного письма. Шаг 1 создает письмо. Шаг 2 получает подтверждение. Шаг 3 отправляет электронное письмо. Большинство инструментариев пользовательского интерфейса требуют, чтобы вы возвращали управление обратно в цикл обработки событий после каждого шага, что делает его действительно ужасным, если вы пытаетесь представить его с помощью конечного автомата. С продолжениями вы не думаете о шагах, которые навязывает вам инструментарий - это всего лишь один процесс создания и отправки электронного письма. Однако, когда процессу требуется подтверждение, вы фиксируете состояние вашего приложения в продолжении и передаете это продолжение кнопке OK в диалоговом окне подтверждения. Когда нажата OK, ваше приложение продолжает с того места, где оно было.
Продолжения относительно редки в языках программирования, но, к счастью, вы можете получить что-то вроде версии для бедного человека, используя замыкания. Возвращаясь к примеру отправки электронной почты, в тот момент, когда вам нужно получить подтверждение, вы пишете оставшуюся часть процесса как закрытие, а затем передаете это закрытие кнопке ОК. Замыкания похожи на анонимные вложенные подпрограммы, которые запоминают значения всех ваших локальных переменных при следующем вызове.
Надеюсь, это даст вам несколько новых направлений для размышлений. Я постараюсь вернуться позже с реальным кодом, чтобы показать вам, как он работает.
Обновление: вот полный пример с Qt в Ruby. Интересные части находятся в ConfirmationButton и MailButton. Я не эксперт по Qt или Ruby, поэтому буду признателен за любые улучшения, которые вы все можете предложить.
require 'Qt4'
class ConfirmationWindow < Qt::Widget
def initialize(question, to_do_next)
super()
label = Qt::Label.new(question)
ok = ConfirmationButton.new("OK")
ok.to_do_next = to_do_next
cancel = Qt::PushButton.new("Cancel")
Qt::Object::connect(ok, SIGNAL('clicked()'), ok, SLOT('confirmAction()'))
Qt::Object::connect(ok, SIGNAL('clicked()'), self, SLOT('close()'))
Qt::Object::connect(cancel, SIGNAL('clicked()'), self, SLOT('close()'))
box = Qt::HBoxLayout.new()
box.addWidget(label)
box.addWidget(ok)
box.addWidget(cancel)
setLayout(box)
end
end
class ConfirmationButton < Qt::PushButton
slots 'confirmAction()'
attr_accessor :to_do_next
def confirmAction()
@to_do_next.call()
end
end
class MailButton < Qt::PushButton
slots 'sendMail()'
def sendMail()
lucky = rand().to_s()
message = "hello world. here's your lucky number: " + lucky
do_next = lambda {
# Everything in this block will be delayed until the
# the confirmation button is clicked. All the local
# variables calculated earlier in this method will retain
# their values.
print "sending mail: " + message + "\n"
}
popup = ConfirmationWindow.new("Really send " + lucky + "?", do_next)
popup.show()
end
end
app = Qt::Application.new(ARGV)
window = Qt::Widget.new()
send_mail = MailButton.new("Send Mail")
quit = Qt::PushButton.new("Quit")
Qt::Object::connect(send_mail, SIGNAL('clicked()'), send_mail, SLOT('sendMail()'))
Qt::Object::connect(quit, SIGNAL('clicked()'), app, SLOT('quit()'))
box = Qt::VBoxLayout.new(window)
box.addWidget(send_mail)
box.addWidget(quit)
window.setLayout(box)
window.show()
app.exec()