×
Namespaces

Variants
Actions

Archived:Framework básico para criação de interfaces gráficas

From Nokia Developer Wiki
Jump to: navigation, search

Archived.pngAquivado: Este artigo foi arquivado, pois o conteúdo não é mais considerado relevante para se criar soluções comerciais atuais. Se você achar que este artigo ainda é importante, inclua o template {{ForArchiveReview|escreva a sua justificativa}}.

All PySymbian articles have been archived. PySymbian is no longer maintained by Nokia and is not guaranteed to work on more recent Symbian devices. It is not possible to submit apps to Nokia Store.

Article Metadata

Exemplo de código
Código fonte: Media:MBA ui demo.zip

Artigo
Criado por marcelobarrosalmeida em 28 Jan 2009
Última alteração feita por hamishwillee em 07 May 2013

Contents

Introdução

A API para se escrever aplicativos para dispositivos Symbian com Python é bem objetiva. Uma vez que você tenha decidido que tipo de corpo (body) a sua aplicação irá ter (canvas, listbox ou text), você precisa definir o seu menu principal, o título da aplicação e a função que será chamada na saída do programa (exit handler). A interface gráfica é baseada em eventos e existe um mecanismo especial que usa um semáforo para controlar o fechamento do aplicativo. Ao criar o aplicativo, o usuário obtém o semáforo e passa a esperar por uma sinalização nele, por parte de eventos provenientes da interface gráfica. Ao ser sinalizado, a aplicação deve então ser fechada. A interface gráfica também permite o uso de abas, sendo que cada uma delas deve ter o seu próprio corpo e uma rotina para chavear entre eles deve ser criada.

Entretanto, quando se tem uma aplicação composta por várias janelas (diálogos), é necessário gerenciar todas as trocas de contexto entre corpo, menu e função de saída, uma vez que cada janela terá que lidar com estes elementos. É isto que irá fornecer ao usuário a impressão de que ele está numa aplicação com múltiplos diálogos. Alguns problemas aparecem ao lidar com esta situação:

  • a estratégia de saída, baseada em semáforos, deve ser única para toda a aplicação.
  • não podem existir erros na troca de contexto de diálogos, isto é, o conjunto corpo+menu+função de saída deve ser mudado consistentemente
  • uma estratégia unificada para bloqueio da interface gráfica pode ser necessária em operações com tempos de esperar maior. Por exemplo, ao se baixar um arquivo, pode-se desejar desabilitar todas as opções do menu. Caso contrário, eventos indesejados podem ser gerados pela ação do usuário.


Para unificar este processo, três classes adicionais são sugeridas neste artigo. A primeira, chamada de Window, é responsável por manipular o conteúdo da interface gráfica, como menu, corpo e função de saída, mudando-os consistentemente entre os vários diálogos. Além disso, ela pode também fazer o travamento/destravamento da interface gráfica, quando necessário. A segunda classe é chamada de Application e representa a aplicação em execução. Ela é responsável pela manipulação do semáforo de encerramento. Somente uma instância de Application pode existir para cada programa. Finalmente, a terceira classe é denominada de Dialog. Como o seu nome sugere, ela é responsável pelo gerenciamento dos vários diálogos existentes na sua aplicação.

Application e Dialog são classes derivadas de Window e herdam a habilidade de manipular o conteúdo mostrado (menu, corpo e função de saída). No entanto, cada uma tem a sua própria forma de finalização, isto é, finalização do aplicativo ou somente de um diálogo em execução.

Vamos ao código

O comportamento descrito na seção anterior é implementado pelo script window.py, a seguir, onde Window, Application e Dialog são definidos.

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from appuifw import *
import e32
import key_codes
 
__all__ = [ "Application", "Dialog" ]
 
class Window(object):
""" This class is responsible for holding UI contents like menu, body
and exit handler and exchanging properly all UI elements for
the derived classes. Moreover, it may lock/unlock the UI when necessary.
"""

__ui_lock = False
 
def __init__(self, title, body, menu = None, exit_handler = None):
""" Creates a new window given a title, body and optional
menu and exit handler.
"""

 
self.title = title
self.body = body
 
if menu is None:
menu = [(u"Exit", self.close_app)]
 
if exit_handler is None:
exit_handler = self.close_app
 
self.menu = menu
self.exit_handler = exit_handler
 
def set_title(self,title):
" Sets the current application title "
app.title = self.title = title
 
def get_title(self):
" Returns the current application title "
return self.title
 
def bind(self, key, cbk):
" Bind a key to the body. A callback must be provided."
self.body.bind(key,cbk)
 
def refresh(self):
" Update the application itens (menu, body and exit handler) "
app.title = self.title
app.menu = self.menu
app.body = self.body
app.exit_key_handler = self.exit_handler
 
def run(self):
" Show the dialog/application "
self.refresh()
 
def lock_ui(self,title = u""):
""" Lock UI (menu, body and exit handler are disabled).
You may set a string to be shown in the title area.
"""

Window.__ui_lock = True
app.menu = []
app.exit_key_handler = lambda: None
if title:
app.title = title
 
def unlock_ui(self):
"Unlock UI. Call refresh() for restoring menu, body and exit handler."
Window.__ui_lock = False
 
def ui_is_locked(self):
"Chech if UI is locked or not, return True or False"
return Window.__ui_lock
 
class Application(Window):
""" This class represents the running application itself
and it is responsible for handling the termination semaphore.
Only one Application class must be instantiated per application.
"""

__highlander = None
__lock = None
 
def __init__(self, title, body, menu = None, exit_handler = None):
""" Only one application is allowed. It is resposible for starting
and finishing the program.
run() is overrided for controling this behavior.
"""

if Application.__highlander:
raise
Application.__highlander = self
 
if not Application.__lock:
Application.__lock = e32.Ao_lock()
 
Window.__init__(self, title, body, menu, exit_handler)
 
def close_app(self):
""" Signalize the application lock, allowing run() to terminate the application.
"""

Application.__lock.signal()
 
def run(self):
""" Show the the application and wait until application lock is
signalized. After that, make all necessary cleanup.
"""

old_title = app.title
self.refresh()
Application.__lock.wait()
# restore everything !
app.set_tabs( [], None )
app.title = old_title
app.menu = []
app.body = None
app.set_exit()
 
class Dialog(Window):
""" This class is in the charge of showing/hiding dialogs when necessary.
Many dialogs are allowed, each one with their own set of body+menu+exit
handler.
"""

def __init__(self, cbk, title, body, menu = None, exit_handler = None):
""" Create a dialog. cbk is called when dialog is closing.
Dialog contents, like title and body need
to be specified. If menu or exit_handler are not specified,
defautl values for dialog class are used.
"""

self.cbk = cbk
self.cancel = False
Window.__init__(self, title, body, menu, exit_handler)
 
def close_app(self):
""" When closing the dialog, a call do self.cbk() is done.
If this function returns True the dialog is not refreshed
and the latest dialog/window takes control. If something fails
during calback execution, callback function should return False
and does not call refresh(). Using self.cancel it is possible
to determine when the dialog was canceled or not.
"""

if self.cbk() == False:
self.refresh()
 
def cancel_app(self):
""" Close the dialog but turn the cancel flag on.
"""

self.cancel = True
self.close_app()

Exemplos de uso

Usando este framework, a atenção do desenvolvedor fica focada apenas na aplicação e não nos detalhes relacionados à interface gráfica, via 'appuifw. Considere o primeiro exemplo a seguir, com apenas uma janela e sem diálogos. Dado que o aplicativo é derivado de Application, várias ações já estão definidas para ele:

  • o semáforo de encerramento é controlado pela classe Application, sendo inicializado apropriadamente no seu construtor (Application.__lock = e32.Ao_lock()). Somente um objeto Application é permitido por programa.
  • a função de saída é configurada como Application.close_app(), onde o semáforo de encerramento é acionado (Application.__lock.signal()).
  • Application.run() inicializa a aplicação chamando refresh() para que a interface gráfica seja atualizada e passa a esperar pelo semáforo de término. Uma vez sinalizado, toda limpeza para a saída é feita e a aplicação encerrada.


O exemplo a seguir ilustra esta situação:

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from window import Application
from appuifw import Listbox, note
 
class MyApp(Application):
def __init__(self):
items = [ u"Option A",
u"Option B",
u"Option C" ]
menu = [ (u"Menu A", self.option_a),
(u"Menu B", self.option_b),
(u"Menu C", self.option_c) ]
body = Listbox(items, self.check_items )
Application.__init__(self,
u"MyApp title",
body,
menu)
 
def check_items(self):
idx = self.body.current()
( self.option_a, self.option_b, self.option_c )[idx]()
 
def option_a(self): note(u"A","info")
def option_b(self): note(u"B","info")
def option_c(self): note(u"C","info")
 
if __name__ == "__main__":
 
app = MyApp()
app.run()

Primeiro exemplo em ação:

MBA BFUI Example1.jpg

É claro que, caso sejam necessários ações especiais, como confirmação na saída, isto pode ser implementado somente sobrescrevendo close_app():

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from window import Application
from appuifw import Listbox, note, popup_menu
 
class MyApp(Application):
def __init__(self):
items = [ u"Option A",
u"Option B",
u"Option C" ]
menu = [ (u"Menu A", self.option_a),
(u"Menu B", self.option_b),
(u"Menu C", self.option_c) ]
body = Listbox(items, self.check_items )
Application.__init__(self,
u"MyApp title",
body,
menu)
 
def check_items(self):
idx = self.body.current()
( self.option_a, self.option_b, self.option_c )[idx]()
 
def option_a(self): note(u"A","info")
def option_b(self): note(u"B","info")
def option_c(self): note(u"C","info")
 
def close_app(self):
ny = popup_menu( [u"No", u"Yes"], u"Exit ?")
if ny is not None:
if ny == 1:
Application.close_app(self)
 
if __name__ == "__main__":
 
app = MyApp()
app.run()

Segundo exemplo, com função de saída personalizada:

MBA BFUI Example2.jpg

Diálogos

Suponha agora que o desenvolvedor deseje adicionar um diálogo. É relativamente simples e sem a necessidade de se envolver com o conteúdo de appuifw:

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from window import Application, Dialog
from appuifw import Listbox, note, popup_menu, Text
 
class Notepad(Dialog):
def __init__(self, cbk, txt=u""):
menu = [(u"Save", self.close_app),
(u"Discard", self.cancel_app)]
Dialog.__init__(self, cbk, u"MyDlg title", Text(txt), menu)
 
class MyApp(Application):
def __init__(self):
self.txt = u""
items = [ u"Text editor",
u"Option B",
u"Option C" ]
menu = [ (u"Text editor", self.text_editor),
(u"Menu B", self.option_b),
(u"Menu C", self.option_c) ]
body = Listbox(items, self.check_items )
Application.__init__(self,
u"MyApp title",
body,
menu)
 
def check_items(self):
idx = self.body.current()
( self.text_editor, self.option_b, self.option_c )[idx]()
 
def text_editor(self):
def cbk():
if not self.dlg.cancel:
self.txt = self.dlg.body.get()
note(self.txt, "info")
self.refresh()
return True
 
self.dlg = Notepad(cbk, self.txt)
self.dlg.run()
 
def option_b(self): note(u"B","info")
def option_c(self): note(u"C","info")
 
def close_app(self):
ny = popup_menu( [u"No", u"Yes"], u"Exit ?")
if ny is not None:
if ny == 1:
Application.close_app(self)
 
if __name__ == "__main__":
 
app = MyApp()
app.run()

Notepad adicionado ao menu:

MBA BFUI Example31.jpg

Notepad em ação:

MBA BFUI Example32.jpg

Dados do Notepad:

MBA BFUI Example33.jpg

Quando o diálogo é criado, uma função de retorno (callback) precisa ser definida. Este callback é chamado quando o usuário cancela ou fecha o diálog. Dentro do corpo da função de callback é possível verificar se o diálogo foi cancelado ou não, inspecionando a variável do diálogo chamada cancel. Todo o conteúdo do diálogo pode ser acessado normalmente, sendo esta a forma de troca de dados entre ele e a aplicação.

A função de callback deve ainda retornar True ou False no seu término. Se ela retorna True, self.refresh() deve ser chamado antes, dentro do corpo da função de callback. Desta forma, menu, corpo e função de saída, serão atualizados usando o contexto de quem chamou o diálogo (MyApp, neste caso). Se ela retorna False, self.refresh() é chamado mas dentro do contexto do diálogo, restaurando todos os elementos gráficos do diálogo. Com esta estratégia é possível verificar os dados do diálogo para uma possível validação, antes de fechá-lo, dando ao usuário a chance de corrigí-los. Um exemplo com esta característica em evidência pode ser visto a seguir (veja o método number_sel()).

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from window import Application, Dialog
from appuifw import Listbox, note, popup_menu, Text
 
class Notepad(Dialog):
def __init__(self, cbk, txt=u""):
menu = [(u"Save", self.close_app),
(u"Discard", self.cancel_app)]
Dialog.__init__(self, cbk, u"MyDlg title", Text(txt), menu)
 
class NumSel(Dialog):
def __init__(self, cbk):
self.items = [ u"1", u"2", u"a", u"b" ]
Dialog.__init__(self, cbk,
u"Select a number",
Listbox(self.items, self.close_app))
 
class MyApp(Application):
def __init__(self):
self.txt = u""
items = [ u"Text editor",
u"Number selection",
u"Option C" ]
menu = [ (u"Text editor", self.text_editor),
(u"Number selection", self.number_sel),
(u"Menu C", self.option_c) ]
body = Listbox(items, self.check_items )
Application.__init__(self,
u"MyApp title",
body,
menu)
 
def check_items(self):
idx = self.body.current()
( self.text_editor, self.number_sel, self.option_c )[idx]()
 
def text_editor(self):
def cbk():
if not self.dlg.cancel:
self.txt = self.dlg.body.get()
note(self.txt, "info")
self.refresh()
return True
 
self.dlg = Notepad(cbk, self.txt)
self.dlg.run()
 
def number_sel(self):
def cbk():
if not self.dlg.cancel:
val = self.dlg.items[self.dlg.body.current()]
try:
n = int(val)
except:
note(u"Invalid number. Try again.", "info")
return False
note(u"Valid number", "info")
self.refresh()
return True
 
self.dlg = NumSel(cbk)
self.dlg.run()
 
def option_c(self): note(u"C","info")
 
def close_app(self):
ny = popup_menu( [u"No", u"Yes"], u"Exit ?")
if ny is not None:
if ny == 1:
Application.close_app(self)
 
if __name__ == "__main__":
 
app = MyApp()
app.run()

Seleção de números adicionado ao menu:

MBA BFUI Example41.jpg

Seleção de números em ação:

MBA BFUI Example42.jpg

Tratando entradas inválidas (retorne False e não chame refresh() dentro do contexto da função que invocou o diálogo):

MBA BFUI Example43.jpg

Entrada válida (retorne True e chame refresh() dentro do contexto da função que invocou o diálogo):

MBA BFUI Example44.jpg

Como último exemplo, que tal modificar o corpo e o menu dinamicamente? Sim, isto é possível também, bastando sobrescrever o comportamento de refresh(). refresh() é responsável por toda atualização da interface gráfica, atribuindo os valores desejados ao menu, corpo e função de saída. Caso estes elementos sejam alterados, é necessário redesenhá-los com refresh(). No próximo exemplo, o diálogoNameList() tem seu menu e corpo mudados dinamicamente.

# -*- coding: utf-8 -*-
#
# Marcelo Barros de Almeida
# marcelobarrosalmeida (at) gmail.com
#
from window import Application, Dialog
from appuifw import Listbox, note, popup_menu, Text, query, app
 
class Notepad(Dialog):
def __init__(self, cbk, txt=u""):
menu = [(u"Save", self.close_app),
(u"Discard", self.cancel_app)]
Dialog.__init__(self, cbk, u"MyDlg title", Text(txt), menu)
 
class NumSel(Dialog):
def __init__(self, cbk):
self.items = [ u"1", u"2", u"a", u"b" ]
Dialog.__init__(self, cbk,
u"Select a number",
Listbox(self.items, self.close_app))
 
class NameList(Dialog):
def __init__(self, cbk, names=[]):
self.names = names
self.body = Listbox([(u"")],self.options)
# Do not populate Listbox() ! Refresh will do this.
Dialog.__init__(self, cbk, u"Name list", self.body)
 
def options(self):
op = popup_menu( [u"Insert", u"Del"] , u"Names:")
if op is not None:
if op == 0:
name = query(u"New name:", "text", u"" )
if name is not None:
self.names.append(name)
print self.names
elif self.names:
del self.names[self.body.current()]
# Menu and body are changing !
# You need to refresh the interface.
self.refresh()
 
def refresh(self):
menu = []
if self.names:
menu = map(lambda x: (x, lambda: None), self.names)
items = self.names
else:
items = [u"<empty>"]
self.menu = menu + [(u"Exit", self.close_app)]
self.body.set_list(items,0)
# Since your self.menu and self.body have already defined their
# new values, call base class refresh()
Dialog.refresh(self)
 
class MyApp(Application):
def __init__(self):
self.txt = u""
self.names = []
items = [ u"Text editor",
u"Number selection",
u"Name list" ]
menu = [ (u"Text editor", self.text_editor),
(u"Number selection", self.number_sel),
(u"Name list", self.name_list) ]
body = Listbox(items, self.check_items )
Application.__init__(self,
u"MyApp title",
body,
menu)
 
def check_items(self):
idx = self.body.current()
( self.text_editor, self.number_sel, self.name_list )[idx]()
 
def text_editor(self):
def cbk():
if not self.dlg.cancel:
self.txt = self.dlg.body.get()
note(self.txt, "info")
self.refresh()
return True
 
self.dlg = Notepad(cbk, self.txt)
self.dlg.run()
 
def number_sel(self):
def cbk():
if not self.dlg.cancel:
val = self.dlg.items[self.dlg.body.current()]
try:
n = int(val)
except:
note(u"Invalid number", "info")
return False
note(u"Valid number", "info")
self.refresh()
return True
 
self.dlg = NumSel(cbk)
self.dlg.run()
 
def name_list(self):
def cbk():
if not self.dlg.cancel:
self.names = self.dlg.names
self.refresh()
return True
 
self.dlg = NameList(cbk, self.names)
self.dlg.run()
 
def close_app(self):
ny = popup_menu( [u"No", u"Yes"], u"Exit ?")
if ny is not None:
if ny == 1:
Application.close_app(self)
 
if __name__ == "__main__":
 
app = MyApp()
app.run()

Lista de nomes adicionada ao menu:

MBA BFUI Example51.jpg

Nomes sendo adicionados ao menu e ao corpo:

MBA BFUI Example52.jpg . MBA BFUI Example53.jpg . MBA BFUI Example54.jpg

Baixe o código fonte: Media:MBA ui demo.zip

Travamento da interface

Para operações que consumam tempo, como conexões de rede, é interessante travar a interface gráfica, evitando ações indesejadas do usuário. Dois métodos são usados nesta situação: lock_ui() e unlock_ui(). Basta travar a interface gráfica, fazer o que se deseja, e destravá-la novamente. É possível também mudar o título da aplicação durante este travamento, para que o status da operação seja indicado.

self.lock_ui(u"Connecting...")
#
# your stuff here
#
self.unlock_ui()
self.set_title(u"My app")

Conclusão

O framework apresentado, apesar de simples, é bastante poderoso e fácil de usar, permitindo que protótipos de aplicação com múltiplos diálogos sejam criados rapidamente. Ele é usado no projeto Wordmobi, onde mais casos de uso podem ser encontrados.

This page was last modified on 7 May 2013, at 14:26.
130 page views in the last 30 days.
×