Конечные автоматы обычно слишком низкоуровневые, чтобы помочь вам задуматься о пользовательском интерфейсе. Они представляют собой хороший вариант реализации для набора инструментов пользовательского интерфейса, но существует слишком много состояний и переходов, которые нужно описать в обычном приложении, чтобы вы могли описать их вручную.
Мне нравится думать о пользовательских интерфейсах с продолжениями. (Погуглите это - термин достаточно конкретен, чтобы вы получили много качественных обращений.)
Вместо того, чтобы мои приложения находились в различных состояниях, представленных флагами состояния и режимами, я использую продолжения, чтобы контролировать, что приложение будет делать дальше. Проще всего объяснить на примере. Предположим, вы хотите открыть диалоговое окно подтверждения перед отправкой электронного письма. Шаг 1 создает электронное письмо. Шаг 2 получает подтверждение. Шаг 3 отправляет электронное письмо. Большинство наборов инструментов пользовательского интерфейса требует, чтобы вы передавали управление обратно в цикл событий после каждого шага, что делает это действительно уродливым, если вы пытаетесь представить его с помощью конечного автомата. С продолжениями вы не думаете о шагах, которые навязывает вам инструментарий - это всего лишь один процесс создания и отправки электронного письма. Однако, когда процессу требуется подтверждение, вы фиксируете состояние своего приложения в продолжении и передаете это продолжение кнопке ОК в диалоговом окне подтверждения. При нажатии ОК ваше приложение продолжает работу с того места, где оно было.
Продолжение относительно редко встречается в языках программирования, но, к счастью, с помощью замыканий можно получить что-то вроде версии для бедняков. Возвращаясь к примеру отправки электронной почты, в тот момент, когда вам нужно получить подтверждение, вы пишете остальную часть процесса как закрытие, а затем передаете это закрытие кнопке ОК. Замыкания похожи на анонимные вложенные подпрограммы, которые запоминают значения всех ваших локальных переменных при их следующем вызове.
Надеюсь, это даст вам новые направления для размышлений. Я постараюсь вернуться позже с реальным кодом, чтобы показать вам, как это работает.
Обновление: вот полный пример 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()
person
Ken Fox
schedule
27.02.2009