Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas


Tutorial Wx, Notas de estudo de Informática

Tutorial de WxHaskell; Interface gráfica para a Linguagem Funcional Haskell.

Tipologia: Notas de estudo

Antes de 2010

Compartilhado em 30/11/2009

ygor-dos-santos-luz-3
ygor-dos-santos-luz-3 🇧🇷

4.7

(3)

55 documentos

1 / 51

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
Interfaces Gráficas em Haskell
com WxHaskell
Tutorial
Elton M. Cardoso
Lucília Figueiredo
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33

Pré-visualização parcial do texto

Baixe Tutorial Wx e outras Notas de estudo em PDF para Informática, somente na Docsity!

Interfaces Gráficas em Haskell

com WxHaskell

Tutorial

Elton M. Cardoso Lucília Figueiredo

Índice analítico

Capítulo 1

Introdução

1.1 – WxWindows e FFI

A biblioteca WxHaskell [2] provê uma interface para o uso, em programas Haskell [1], de classes da biblioteca WxWindows [3], escrita na linguagem C++. As classes da biblioteca WxWindows provêm funcionalidades para criação de componentes de interfaces gráficas (tais como widgets (janelas), containers (componentes usados para conter outros componentes gráficos), buttons (botões) etc), assim como para tratamento de eventos que ocorrem nessas interfaces gráficas. Para prover essa interface, a biblioteca WxHaskell é construída com base em funções disponíveis na biblioteca FFI ( Foregin Function Interface ) [4], a qual possibilita o uso de código escrito em outras linguagens, dentro de programas Haskell. A biblioteca FFI provê uma interface programável entre o contexto de Haskell e o contexto de uma linguagem externa (tal como C++, Java, Pascal etc). Como resultado, threads de Haskell podem fazer acesso a dados de um contexto externo, assim como invocar funções que serão executadas nesse contexto externo, e vice-versa. Neste tutorial, não iremos entrar em detalhes sobre a biblioteca FFI e suas aplicações. Entretanto, pode ser útil ter uma compreensão mínima sobre como WxWindows foi “portada” para Haskell, o que comentamos a seguir. De maneira bastante simplificada, podemos dizer que WxHaskell faz um mapeamento dos tipos da biblioteca WxWindows para tipos de dados em Haskell. Note que, como C++ é uma linguagem orientada a objetos, a biblioteca WxWindows pode possuir variáveis cujo conteúdo tem tipo definido dinamicamente, em face do mecanismo de subtipos e ligação dinâmica existente em linguagens orientadas a objetos. Em contraposição, Haskell possui um sistema de tipos totalmente estático. Como solução, a implementação de WxHaskell introduz o tipo abstrato Object a, onde o parâmento “a” pode ser usado para expressar relações da hierarquia de classes de WxWindows. Além desse mapeamento entre tipos, WxHaskell cria tipos abstratos correspondentes às classes C++ da biblioteca WxWindows (tais como Button , Window , EvtHandler etc.) e mapeia as relações de hierarquia existentes entre estas classes. Note, portanto, que, em um programa Haskell que utiliza a biblioteca WxHaskell , a criação e manutenção de uma interface gráfica não é executada no contexto de Haskell, mas sim no contexto externo, de C++. Por outro lado, a interação do programa com essa interface é feita por meio dos tipos e construções naturais de Haskell.

1.2 – Criando uma janela

Como um ponto de partida para explorar a biblioteca WxHaskell , apresentamos a seguir um exemplo que ilustra a criação de uma janela com um único botão, que permitirá fechá-la. module MyFirstProgram where {- importa a biblioteca gráfica WxHaskell. -} import Graphics.UI.WX {- função principal que inicia a interface gráfica -} main :: IO () main = start interface {- função que gera a interface gráfica propriamente dita. -} interface :: IO () interface = do f  frame [text := "Criando uma janela simples"] b  button f [text := "Fechar", on command := close f] set f [layout := margin 4 (floatCentre (widget b) ), clientSize := sz 280 100] A função main chama a função start :: IO a  IO (), que “inicia” a interface gráfica. A função interface consiste no seqüenciamento de ações que criam os objetos de interface, com propriedades pré-definidas. O primeiro objeto criado é um frame (de tipo Frame), um tipo de container para outros objetos. A assinatura função que cria o frame é: frame :: [Prop (Frame () )]  IO (Frame () ) Esta função tem como parâmetro uma lista de propriedades de Frame – propriedades e atributos de um Frame são discutidas mais adiante – e retorna um valor do tipo IO (Frame ()). Este tipo indica que o valor retornado, quando avaliado, resulta na ação de exibir na tela do computador o Frame encapsulado no valor deste tipo. No corpo da função interface, o operador () é então usado desencapsular o Frame e atribuir o valor correspondente à variável f. Em seguida. É criado um botão, por meio da função button: button :: Window a  [Prop (Button () ) ]  IO (Button ())

Figura 1 - Janela do GHCi como o programa carregado. Note que os módulos da biblioteca WxHaskell foram carregados. Basta digitar main e teremos o resultado: Figura 2 - Resultado da execução do programa. Também é possível gerar código binário a partir do programa, mas isto irá depender do tipo e da versão do compilador que você estiver usando. No caso do GHC-6.2.2, deve ser usado o seguinte comando, para compilar o programa: ghc –package wx -main-is MyFirstPogram --make MyFirstPogram.hs O compilador irá então gerar os códigos objetos e ligá-los em um código executável. Em geral, a biblioteca é de linkagem estática, o que significa que o programa executável conterá todas as funções da biblioteca, mesmo as que não são usadas no programa. Isto faz com que o código executável fique enorme! Uma solução para esse problema é usar o strip [referência], um programa capaz de vasculhar o código executável e remover símbolos que não são utilizados.

1.4 – Atributos e Propriedades:

Objetos em geral possuem atributos, que são representados por variáveis (de instância) de um determinado tipo, podendo assumir certos valores. Em WxHaskell atributos estão definidos da seguinte forma: data Attr w a onde w é um Widget (um objeto de interface) e a é o tipo do atributo em questão. Esta construção pode, portanto, ser interpretada da seguinte maneira: o Widget w possui um atributo do tipo a. Um atributo muito comum é o text, com o seguinte tipo: text :: Attr (Window a) String Isto significa que todo objeto derivado da classe Window possui um atributo text. Os atributos podem ser somente de leitura, somente de escrita ou de leitura-escrita, o que é mais comum. Uma propriedade é um atributo associado a um valor, sendo definida como segue: Observe que existem quatro construtores para o tipo.

  • := Associa um valor a um atributo, ou seja, constrói a propriedade a partir de um atributo e de seu respectivo valor. Ex: text := “Haskell”
  • : Constrói a propriedade a partir de uma função de atualização. Isto é, o valor da propriedade é obtido pela aplicação de uma função ao valor atual. Ex: text :~ (++”_Op”)
  • ::= Associa um atributo a um valor, com o uso de uma função que leva em conta o widget que estamos modificando.
  • :: Atualiza o valor de uma propriedade com uso de uma função que leva em conta o widget que estamos modificando Podemos ler e modificar as propriedades de um objeto de interface por meio das funções get, set e swap:
  • get :: w Attr w a  IO a: Recebe um widget e um atributo deste widget, ao qual se deseja saber o valor associado, e retorna o valor da propriedade. Ex.: get w text retorna um valor do tipo IO (String)
  • set :: w  [Prop w]  IO (): Atribui uma lista de propriedades ao widget. Ex.: set w [text := “WxHaskell”, color := red,... ]. data Prop w = forall a. (:=) (Attr w a) a | forall a. (:~) (Attr w a) (a  a) | forall a. (::=) (Attr w a) (w  a) | forall a. (::~) (Attr w a) (w  a  a)

command :: Commanding w => Event w (IO ()) um tipo de evento que é disparado quando uma ação padrão acontece, como, por exemplo, um click em um botão ou o pressionamento da tecla enter em um campo de texto. A classe Selecting define o evento select, que ocorre sempre que um item é selecionado de uma lista de itens em objetos como listBox, choice etc. Já a classe Reactive (classe dos objetos visíveis) define eventos de resposta, como, por exemplo, o evento resize, que ocorre quando alteramos o tamanho de um widget , e o evento closing, que ocorre quando o objeto é fechado. É a esta classe que pertencem os eventos de mouse e de teclado.

1.4.1.1 – Eventos de mouse

O evento mouse tem o tipo mouse :: Reactive w => Event w (EventMouse  IO (). A definição do tipo do tratador de evento – EventMouse – é mostrada a seguir: data EventMouse = MouseMotion !Point !Modifiers MouseEnter !Point !Modifiers MouseLeave !Point !Modifiers MouseLeftDown !Point !Modifiers MouseLeftUp !Point !Modifiers MouseLeftDClick !Point !Modifiers MouseLeftDrag !Point !Modifiers MouseRightDown !Point !Modifiers MouseRightUp !Point !Modifiers MouseRightDClick !Point !Modifiers MouseRightDrag !Point !Modifiers MouseMiddleDown !Point !Modifiers MouseMiddleUp !Point !Modifiers MouseMiddleDClick !Point !Modifiers MouseMiddleDrag !Point !Modifiers MouseWheel !Bool !Point !Modifiers

Todos os eventos de mouse têm em comum o ponto onde o evento ocorreu (especificado em termos de coordenadas x e y) e os modificadores do evento – teclas como alt, shift, ctrl, que podiam estar pressionadas quando o evento ocorreu. O exemplo a seguir ilustra como usar o evento mouse. A função trataEvento1 trata o evento de o usuário “pressionar” o botão esquerdo do mouse. Essa função tem como argumentos a janela principal – apenas para mostrar um diálogo – e um EventMouse. gui :: IO () gui = do f  frame [text := "Exemplos: Eventos e Tratadores de eventos"] p  panel f [] b1  button p [text := "Click Here", on mouse := trataEvento1 f] set p [layout := (floatCentre.widget) b1] set f [layout := margin 5 (widget p)] where trataEvento1 w (MouseLeftDown p m) = infoDialog w "Evento de mouse" "b1 down" trataEvento1 w _ = return () A função tratadora de evento captura, por casamento de padrão, o tipo de EventMouse ocorrido. No caso de ser pressionado do botão esquerdo do mouse, o EventMouse gerado casa com o padrão especificado na primeira equação de definição da função trataEvento1 e o código correspondente é executado, exibindo na tela uma janela de um diálogo. A função infoDialog tem comportamento similar ao da função JOptionPane.showMessageDialog de Java. O tipo desta função é: infoDialog :: Window a  String  String  IO () onde o primeiro argumento é a janela pai do diálogo, o segundo argumento é o título e o terceiro é a mensagem a ser mostrada. Por conveniência os eventos de mouse e de teclado têm uma série de filtros que facilitam a descrição de tratadores de eventos. Os filtros para os eventos de mouse são: enter :: Reactive w => Event w (Point  IO ()) leave :: Reactive w => Event w (Point  IO ()) motion :: Reactive w => Event w (Point  IO ()) drag :: Reactive w => Event w (Point  IO ()) click :: Reactive w => Event w (Point  IO ()) unclick :: Reactive w => Event w (Point  IO ()) doubleClick :: Reactive w => Event w (Point  IO ()) clickRight :: Reactive w => Event w (Point  IO ()) unclickRight :: Reactive w => Event w (Point  IO ()) Todos os filtros funcionam simultaneamente e de forma independente. Sempre que o evento mouse for definido, todos os filtros são sobrescritos. Se for necessário redefinir o evento mouse, mas manter os antigos filtros, deve-se ler o evento mouse e chamá-lo novamente no novo handler :

onde Key representa uma tecla, Modifiers representas teclas modificadoras (como shift, alt e ctrl), que podem ter sido pressionadas quando o evento ocorreu, e Point representa o ponto de foco (coordenadas do cursor do mouse, em relação às coordenadas do widget , quando o evento ocorreu.). Os eventos de teclado também possuem filtros: anyKey :: Reactive w => Event w (Key  IO ()) key :: Reactive w => Key  Event w (IO ()) charKey :: Reactive w => Char  Event w (IO ()) enterKey :: Reactive w => Event w (IO ()) tabKey :: Reactive w => Event w (IO ()) escKey :: Reactive w => Event w (IO ()) helpKey :: Reactive w => Event w (IO ()) delKey :: Reactive w => Event w (IO ()) homeKey :: Reactive w => Event w (IO ()) endKey :: Reactive w => Event w (IO ()) pgupKey :: Reactive w => Event w (IO ()) pgdownKey :: Reactive w => Event w (IO ()) downKey :: Reactive w => Event w (IO ()) upKey :: Reactive w => Event w (IO ()) leftKey :: Reactive w => Event w (IO ()) rightKey :: Reactive w => Event w (IO ()) rebind :: Event w (IO ())  Event w (IO ()) O que foi dito anteriormente sobre filtros de mouse também vale para filtros de teclado. O exemplo a seguir mostra a utilização de eventos do teclado, para filtrar uma entrada de texto de modo que apenas números inteiros positivos possam ser digitados: main = start gui gui = do f  frame [text := "Entrada de texto com Filtros"] e  entry f [on anyKey := handler] set f [layout := row 2 [label "Digite um número",widget e] ] where handler (KeyChar ch) = if ((ch >= '0') && (ch < '9')) -- caractere de 0- then propagateEvent -- propaga evento else return () -- "barra" evento handler (KeySpace) = return () -- trata separamente espaços em branco handler _ = propagateEvent -- propaga outros eventos, como teclas de -- edição data EventKey = EventKey !Key !Modifiers !Point Instances Eq EventKey Show EventKey

Modifique o filtro acima de maneira que também seja possível digitar números com casa decimais.

1.4.1.3 – Outros eventos:

Além dos eventos de teclado e de mouse, a classe Reactive também define o tipo de outros eventos, sendo os principais listados a seguir: closing :: Reactive w => Event w (IO ()) : Ocorre sempre que uma janela é fechada. resize :: Reactive w => Event w (IO ()) : Ocorre quando uma janela é redimensionada focus :: Reactive w => Event w (Bool  IO ()) : Ocorre quando o widget ganha o foco. activate :: Reactive w => Event w (Bool  IO ()) : Ocorre quando a janela é ativada.

1.5 – Layouts

A biblioteca WxHaskell provê o tipo Layout para a representação de layouts , assim como funções (ou combinadores, como são chamados pelo autor da biblioteca) para criar e estruturar layouts a partir de widgets. O layout mais simples possível é definido pela função widget :: Widget w => w  Layout que cria um layout a partir de um widget qualquer. Podemos ainda agrupar layouts em containeres , sendo os mais comuns os seguintes: row :: Int  [Layout]  Layout : distribuição horizontal, com certo espaço (em pixels) entre os componentes column :: Int  [Layout]  Layout : distribuição vertical, com certo espaço entre os componentes grid :: Int  Int  [[Layout]]  Layout : Uma distribuição em grade dos layouts , com um certo espaço separando os componentes verticalmente e horizontalmente. Veja o exemplo a seguir:

layoutFrom [] = [] layoutFrom (x:xs) = map widget x:layoutFrom xs A execução deste programa resulta na exibição da seguinte janela: Figura 4 - Layout em grade Uma observação importante sobre esses containers é que, por default , eles são estáticos, isto é, não acompanham o redimensionamento da janela. Se a intenção for que isso aconteça, é necessário tornar o layout extensível, usando o combinador stretch :: Layout  Layout. Os containers row e column, entretanto, só serão extensíveis se todos os seus elementos forem extensíveis. No caso do container grid, cada linha ou coluna será extensível se todos os elementos dessa linha ou coluna forem extensíveis. Vamos então substituir a ocorrência da função widget pela função composta stretch.widget, no corpo da função layoutFrom. Execute novamente o programa e observe que agora o layout se expande quando aumentamos a janela, mas os botões apenas se realinham na janela. Para que os botões se expandam, encobrindo toda a área da janela, devemos usar o combinador expand :: Layout  Layout. Nossa função de layout ficaria então do seguinte modo: expand.stretch.widget. Isto é equivalente a usar o combinador fill, (fill.widget). A função label :: String  Layout permite que legendas sejam criadas diretamente como layouts. Isso é muito útil quando usamos legendas que não são alteradas em tempo de execução, como no exemplo a seguir: import Graphics.UI.WX main = start gui gui = do f  frame [text := "Layout demo 2 - labels"] name  entry f [] tel  entry f [] set f [layout := grid 2 2 [ [label "Nome:", widget name], [label "Telefone:", widget tel] ] ]

Figura 5 - Utilização de labels É possível adicionar margens (margin :: Int  Layout  Layout) e boxes (boxed :: String  Layout  Layout) a um determinado layout. A margem pode ser adicionada a todo o layout , ou individualmente, à direita (marginRight), à esquerda (marginLeft), ao cabeçalho (marginTop) ou ao roda-pé (marginBottom). A seguir, modificamos o exemplo anterior, para adicionar um box e uma margem de cinco pixels. import Graphics.UI.WX main = start gui gui = do f  frame [text := "Layout demo 2 - labels"] name  entry f [] tel  entry f [] set f [layout := layoutAdapter (grid 2 2 [[label "Nome:",widget name], [label "Telefone:", widget tel]]) ] where layoutAdapter = (margin 5).(boxed "Dados pessoais") Figura 6 - Janela com margem e box É possível, ainda, criar um layout “vazio”. A função empty:: Layout retorna um layout vazio, extensível em todas as direções. Existem ainda as funções: hspace (^) :: Int  Layout : espaço horizontal com certo comprimento vspace (^) :: Int  Layout : espaço vertical com uma certa altura space (^) :: Int  Int  Layout : espaço com um comprimento e uma altura Por fim, existem também funções para alinhamento, que são: centre (^) :: Layout  Layout : alinha o layout ao centro alignTopLeft (^) :: Layout  Layout : alinha o layout ao canto superior esquerdo alignTop (^) :: Layout  Layout : alinha o layout ao cabeçalho

Figura 7 - Usando panels para criar layouts mais elaborados Neste exemplo, dividimos o layout em três panels, p1, p2 e p3. Em p1 temos um layout em coluna, em p2 um layout grid e em p3 temos um botão alinhado à direita.

1.6 – Variáveis Mutáveis:

Algumas vezes, podemos querer armazenar um valor de algum tipo, de modo que ele possa ser modificado posteriormente. Geralmente, tal modificação envolve a reconstrução do tipo com um novo valor. A utilização de variáveis mutáveis é uma maneira simples de lidar com estas situações. Elas permitem que, em uma mônada do tipo IO (), um determinado valor seja “armazenado” e posteriormente modificado. As operações definidas para variáveis simples são: varCreate (^) :: a  IO (Var a) : Cria uma variável mutável para armazenar valores do tipo a. varGet (^) :: Var a  IO a: Obtém o valor da variável. varSet (^) :: Var a  a -> IO () : Define um valor para a variável. varUpdate (^) :: Var a  (a  a)  IO a : Altera o valor da variável e retorna o valor antigo. varSwap (^) :: Var a  a  IO a : Troca o valor da variável e retorna o valor antigo.

2 – Controles de interface gráfica.

Nesta seção, descrevemos, de maneira um pouco mais detalhada, os principais controles e containeres implementados na biblioteca, bem como os principais atributos destes controles. Para tal, vamos nos concentrar nos seguintes tópicos:

  • Criação: Como criar o objeto.
  • Styles: Estilos aceitos pelo objeto (se houverem) – um conjunto de constantes que permitem modificar o comportamento de alguns controles da interface gráfica. Para combinar estas constantes utiliza-se o operador (.+.) :: Int -> Int -> Int (bitwise or), definido no módulo WXCore.
  • Funções utilitárias: Funções especiais usadas para manipular o objeto.
  • Instâncias e atributos relevantes: Classes instanciadas e/ou atributos do objeto.
  • Lista de instâncias: Lista das demais classes das quais o objeto é instância.

2.1 – Window

O Window é a origem de praticamente todos os demais widgets visíveis. Por isso, os atributos presentes neste objeto também estão presentes na maioria dos demais controles de interface gráfica. Criação:

  • Window a  [Prop (Window ())] IO (Window ()): Cria uma janela padrão. Styles: Nome da constante Descrição wxBORDER (^) Exibe uma borda simples ao redor da janela. wxDOUBLE_BORDER (^) Exibe uma borda dupla ao redor da janela (apenas Windows) wxSUNKEN_BORDER (^) Exibe uma borda rebaixada ao redor da janela. wxRAISED_BORDER (^) Exibe uma borda em alto relevo. (apenas GTK) wxSTATIC_BORDER (^) Exibe uma borda justa para um controle estático (apenas Windows) wxTRANSPARENT_WINDOW (^) Define a janela como transparente, i.e., ela não receberá nenhum evento paint. (apenas Windows) wxNO_3D (^) Desabilita efeitos 3D dos controles alojados nesta janela. (apenas Windows) wxTAB_TRAVERSAL (^) Use este flag para impedir que a janela reaja a eventos tab. wxWANTS_CHARS (^) Use isto para fazer com que a janela processe todos os eventos de teclado. wxNO_FULL_REPAINT_ON_RESIZE (^) Impede que a janela seja completamente redesenhada quando redimensionada. (apenas Windows) wxVSCROLL (^) Adiciona uma barra de rolagem vertical à janela wxHSCROLL (^) Adiciona uma barra de rolagem horizontal janela wxCLIP_CHILDREN (^) Utilize este estilo para corrigir distorções causadas pelo evento paint do plano de fundo.