











































Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Tutorial de WxHaskell; Interface gráfica para a Linguagem Funcional Haskell.
Tipologia: Notas de estudo
1 / 51
Esta página não é visível na pré-visualização
Não perca as partes importantes!












































Elton M. Cardoso Lucília Figueiredo
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.
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.
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.
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.
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.
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.
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.
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.
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:
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: