Статьи

Как Griffon помогает MigLayout

Удивительно недооцененный аспект (судя по тому, как мало кто о нем слышит) в Groovy — это поддержка веб-сервисов. Буквально, это все, что вам нужно, чтобы получить доступ к веб-сервису, чтобы получить кусочек шекспировской мудрости:

import groovyx.net.ws.WSClient

class ShakesWSClient {

    /*
* Searches for a Shakespeare speech,
* which will be in this form:
    * <SPEECH>
    *    <PLAY>MACBETH</PLAY>
    *        <SPEAKER>ALL</SPEAKER>
    *          Fair is foul, and foul is
    *          fair: Hover through the fog
    *          and filthy air.
    * </SPEECH>
    */

    String processRequest(searchString) {
       
def proxy = new WSClient
("http://www.xmlme.com/WSShakespeare.asmx?WSDL",
        this.class.classLoader)

        def xml = proxy.GetSpeech(searchString)
       
        def XmlParser parser = new XmlParser()
       
        def speech = parser.parseText (xml)
       
        ["PLAY: ${speech.PLAY.text()}\n",
        "SPEAKER: ${speech.SPEAKER.text()}\n",
        "TEXT: ${speech.text()}"].sum("")
       
    }

}

Примечание . Последняя строка выше является оператором return, поскольку ключевое слово return является необязательным в последней строке метода Groovy. Последняя строка выше также анализирует речь, извлеченную из веб-службы, добавляет разрывы строк и добавляет пояснительный текст к каждой части.

Теперь, приближая нас к сути этой статьи, давайте создадим графический интерфейс, который отправляет строку поиска вышеуказанному методу Groovy и отображает проанализированный полезный груз. Конечный результат графического интерфейса должен быть следующим:

Давайте использовать MigLayout и давайте использовать Java для создания вышеуказанного GUI. Главы, новички в MigLayout! MigLayout работает с невидимой сеткой, помещенной в контейнер, в который вы добавляете компоненты. По умолчанию каждый компонент, добавляемый в контейнер, занимает одну новую ячейку в сетке слева направо до бесконечности. Однако вы вряд ли захотите, чтобы все ваши компоненты располагались слева направо до бесконечности, хотя для начала это хороший шаблон по умолчанию. Однако, когда вы продвинетесь немного дальше (т.е. примерно после третьего компонента или около того), вы захотите при добавлении каждого компонента указать ограничениядля каждого компонента, вдоль строк «следующая строка должна начинаться после этого компонента», «ячейка, занимаемая этим компонентом, также должна быть разделена следующим компонентом», «пространство между этим компонентом и следующим должно быть X «и т. д. Вы также можете указать эти ограничения на уровне макета, например,» после каждого третьего компонента должна начинаться следующая строка «.

Все эти ограничения называются, ну, именно так. Чуть более подробно, это ограничения MigLayout, которые будут использоваться в следующем коде:

  • Align. Выравнивает компонент, например, «влево» или «вправо», такой как кнопка «Поиск» выше, которая выровнена вправо внутри своей ячейки, которая покрывает всю строку из-за ограничения «span», как описано ниже.
  • growx. Устанавливает, будет ли компонент автоматически увеличиваться в пределах доступного пространства, такого как JScrollPane и JSeparator выше, которые оба используют это ограничение для автоматического увеличения в своей ячейке.
  • вставки. Устанавливает пространство вокруг компонента, например, в этом случае JPanel на скриншоте выше, который (в точке, где он инициализируется) имеет аргумент «вставки 10» для создания некоторого пространства вокруг себя. Вместо «вставки 10» можно указать конкретные вставки на каждую сторону, например «вставки 0 25 10 15».
  • пяди.Определяет, сколько ячеек будет покрывать компонент. Например, «span 2» означает, что он будет занимать две ячейки. Если целое число не указано, компонентом будет покрыта вся строка. Если вы указываете два целых числа, вы устанавливаете значения x и ay, то есть ширину и высоту. На приведенном выше снимке экрана в первой строке, содержащей строку «Введите строку поиска» и JSeparator (создающий строку) [которые находятся вместе из-за «split», описанного ниже], не будет отображаться JSeparator if «span» «не был установлен как ограничение для метки, потому что без» span «покрыта только одна ячейка, которая не обеспечила бы место как для метки, так и для разделителя. Кнопка «Поиск» также имеет ограничение «span», так что в ней есть целая строка для воспроизведения с добавлением «align right»,чтобы выровнять его по правой стороне ячейки, то есть по правой стороне строки.
  • Трещина. Разделяет ячейки, так что несколько компонентов могут совместно использовать одну ячейку. На приведенном выше снимке экрана одно из ограничений, примененных к первой метке, является «разделенным», так что строка, которая является JSeparator, может использовать то же пространство. Если бы для метки не было установлено ограничение «split», JSeparator (то есть строка) автоматически появился бы в следующей строке.
  • заворачивать. Помещает следующий компонент в следующую строку. Кроме того, вы можете автоматически обернуть в определенный столбец, установив ограничение макета при инициализации менеджера макета. Добавьте целое число сразу после «wrap», и тогда вы определили разрыв между этой строкой и новой. Если бы «обтекание» не было установлено в качестве ограничения для JSeparator на скриншоте выше, метка «Поиск» с соответствующим JTextField появилась бы в той же строке.

Это все, что нам нужно знать о MigLayout для целей следующего кода. (Любопытно больше? Зайдите сюда, чтобы познакомиться с Микаэлем Гревом здесь, на Javalobby. Зайдите сюда, чтобы просмотреть бланк HTML-кода, который, по моему скромному мнению, выглядел бы еще лучше, как DZone Refcard.) Хорошо, поэтому ниже следует весь код, который создает вышеуказанный графический интерфейс:

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import net.miginfocom.swing.MigLayout;

public class ShakesMigWSChooser extends JFrame {

ShakesWSClient client = new ShakesWSClient();

JTextField searchField = new JTextField(50);
JTextArea resultArea = new JTextArea(50, 30);
JScrollPane scrollPane = new JScrollPane();

//Main method:
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ShakesMigWSChooser().setVisible(true);
}
});
}

//Constructor:
public ShakesMigWSChooser() {
initComponents();
}

private void initComponents() {

//Set up the JFrame:
setSize(new java.awt.Dimension(600, 200));
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Shakespeare Searcher");

//Create the JPanel in MigLayout:
JPanel p = new JPanel(new MigLayout("insets 10"));

//Add the JLabel with "Enter the search string",
//let its cell be shared by the next component,
//and let it cover the whole row.
p.add(new JLabel("Enter the search string"), "split, span");

//Add the JSeparator,
//let it grow to 100,
//and let the next component start a new row:
p.add(new JSeparator(), "growx, wrap");

//Add the JLabel with "Search":
p.add(new JLabel("Search:"));

//Add the Search Field,
//and let the next component start on a new row:
p.add(searchField, "wrap");

//Add the JLabel with "Response":
p.add(new JLabel("Response:"));

//Set up the JTextArea in a JScrollPane:
resultArea.setLineWrap(true);
resultArea.setEditable(false);
resultArea.setBackground(
javax.swing.UIManager.getDefaults().
getColor("TextArea.selectionBackground"));
resultArea.setAutoscrolls(true);
resultArea.setWrapStyleWord(true);
scrollPane.setViewportView(resultArea);

//Add the JScrollPane,
//let it grow,
//and let the next component begin a new row:
p.add(scrollPane, "growx, wrap");

//Create the "Search" JButton,
//with an ActionListener to interact with the Groovy web service:
JButton searchButton = new JButton("Search");
searchButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
String searchString = client.processRequest(searchField.getText());
resultArea.setText(searchString);
}
});

//Add the JButton to the JFrame,
//let it play in the whole row,
//and within that row align it to the right of the cell:
p.add(searchButton, "span, align right");

//Add the JPanel to the JFrame:
add(p);

}

}

В связи с этой статьей необходимо сделать два замечания. Во-первых, MigLayout качается. Только шестьОграничения MigLayout под нашим поясом, мы создали хороший графический интерфейс для нашего приложения. Во-вторых, в приведенном выше коде много разных проблем. Во-первых, есть код, который инициализирует приложение; во-вторых, есть код, который отображает наш графический интерфейс; в-третьих, наше действие застряло в середине кода GUI; в-четвертых, на стороне Groovy есть взаимодействие с веб-сервисом. Действительно беспорядок, особенно в свете Гриффона, конечно, к которому эта статья неумолимо ведет. Поэтому давайте разберем все вышеперечисленное в более управляемые части Griffonic, продолжая пользоваться преимуществами веб-сервисов Groovy и MigLayout. Это также будет первым знакомством с тем, как Griffon может обрабатывать веб-сервисы Groovy, т. Е.введение в простой сценарий использования поддержки жизненного цикла, который Griffon предоставляет «из коробки».

Быстрый прототип

Давайте начнем с малого. Мы сделаем не более чем размещение веб-службы в Гриффоне. Чтобы доказать наш успех в этом, мы жестко закодируем строку поиска и затем отобразим результат (даже не анализируя его) в абсолютно минимальном виде:

ОК, как добраться до этого первого этапа? Используйте Startup.groovy, чтобы сделать тяжелую работу. Просто добавьте следующее:

import groovyx.net.ws.WSClient

def rootModel = app.models.root

def proxy = new WSClient
    ("http://www.xmlme.com/WSShakespeare.asmx?WSDL",
        this.class.classLoader)

rootModel.message = proxy.GetSpeech("fair is foul")

Таким образом, вышеизложенное установит нашу модель, основанную на жестко запрограммированной строке поиска «fair is foul». Модель, в свою очередь, выглядит следующим образом:

import groovy.beans.Bindable

class ShakesWSClientModel {

    @Bindable message
   
}

@Bindable предоставляет нам поддержку PropertyChangeListener бесплатно, позволяя полезной нагрузке, предоставленной Startup.groovy, установить значение «message» в модели. В результате мы можем привязать вышеуказанный объект домена к представлению:

application(title:'ShakesWSClient',  size:[500, 50],
    location:[50,50], pack:true, locationByPlatform:true) {

    label(text:bind(source:model, sourceProperty:'message'))
   
}

Примечание . Метка также может быть связана следующим образом, которая короче, но, возможно, менее четкая, чем указанная выше:

label(text:bind{model.message}) 

Наконец, учтите, что приведенный выше код — это все, что я добавил после запуска «griffon create-app». Ничего, кроме вышеперечисленного, не требуется для создания диалога с Шекспиром, который вы видите в начале этого раздела.

Наш прототип завершен. Давайте использовать его как основу для нашего реального приложения, которое мы начнем ниже.

Кодирование вида

Теперь пришло время создать представление. Поскольку мы работаем с MigLayout и у нас уже есть его версия на Java, переход на Griffon становится простым, а с Groovy мир становится более разумным:

import net.miginfocom.swing.MigLayout;

application(title:'Shakespeare Searcher', size:[600, 200],
location:[50,50], pack:true, locationByPlatform:true,) {

panel(layout:new MigLayout('insets 10')) {
label('Enter the search string', constraints: 'split, span')
separator(constraints: 'growx, wrap')
label('Search:')
textField(id:'searchField', columns: 50, constraints:'wrap')
label('Response:')
scrollPane(constraints:'growx, wrap'){
textArea(rows: 30, columns: 50,
id:'resultArea',
editable:false, autoscrolls:true,
wrapStyleWord:true, lineWrap: true,
background: javax.swing.UIManager.getDefaults().getColor("TextArea.selectionBackground"))
}
button("Search", constraints:'span, align right')
}

}

Выше, более или менее, смысл этой статьи. Можно увидеть, как Griffon помогает MigLayout по тому факту, что наше представление «viewy» (т. Е. Чрезвычайно ориентировано на представление, другого кода не видно). Основное отличие от Java-версии нашего дизайна MigLayout заключается в том, что у нас нет лишних (и ретроспективно шумных) вызовов add, потому что все, что находится внутри фигурных скобок, добавляется в любой ближайший контейнер. Посмотрите на JScrollPane для некоторого хорошего вложения, то есть JTextArea добавлен в JScrollPane, потому что он вложен в него; аналогично, JScrollPane добавляется в JPanel, который добавляется в приложение … все без единого экземпляра «add», найденного в нашем коде. (Разве вы не хотите сразу переключиться на этот более разумный мир?)

Как и ранее, результат выглядит следующим образом:

Кодирование контроллера

Далее, поскольку наша кнопка «Поиск» в настоящее время ничего не делает, нам нужно подумать о том, куда переместить наш оригинальный ActionListener.

Вот хорошее эмпирическое правило: все, что контролирует все, принадлежит контроллеру.

Давайте поместим его в контроллер следующим образом, очень напоминающим код, с которого начиналась эта статья (обратите внимание, что именно поэтому вам нужны ‘id: searchField’ и ‘id: resultArea’ как свойства в JTextField и JTextArea, по вашему мнению, чтобы сделать такие вещи, как «view.searchField.text» в коде ниже возможно):

class ShakesWSClientController {

    // these will be injected by Griffon
    def model
    def view

    def searchAction = { evt ->

        def xml = model.proxy.GetSpeech(view.searchField.text)

        def XmlParser parser = new XmlParser()

        def speech = parser.parseText (xml)

        view.resultArea.text =  ["PLAY: ${speech.PLAY.text()}\n",
         "SPEAKER: ${speech.SPEAKER.text()}\n",
         "TEXT: ${speech.text()}"].sum("")
       
    }

}

Модель должна измениться, как и файл Startup.groovy. Почему? Потому что изначально мы жестко закодировали строку поиска в файле Startup.groovy, а затем привязали к ней объект домена. Вместо этого мы просто определим наш прокси как объект домена, без необходимости привязывать его к чему-либо, поскольку он не изменится:

import groovy.beans.Bindable

class ShakesWSClientModel {

def proxy

}

Теперь наш Startup.groovy выглядит просто так:

import groovyx.net.ws.WSClient

def rootModel = app.models.root

rootModel.proxy = new WSClient
("http://www.xmlme.com/WSShakespeare.asmx?WSDL",
this.class.classLoader)

Вот и все. Наш контроллер готов, и мы настроили нашу модель так, чтобы мы могли выполнить наше действие в контроллере. Далее все, что нам нужно сделать, это подключить наше действие к нашей кнопке. Помните, как на нашей простой кнопке был текст «Поиск» вместе с двумя ограничениями? Просто замените текст на «action: searchAction» и введите «build (ShakesWSClientActions)» под оператором импорта представления. Затем создайте файл (первый файл, который вы должны были создать вручную) с именем «ShakesWSClientActions» и добавьте следующее содержимое:

actions {

action( id: 'searchAction',
name: "Search",
closure: controller.searchAction,
accelerator: shortcut('S'),
mnemonic: 'S',
shortDescription: "Looking for Shakespeare"
)

}

Your application, now all in Groovy, using MigLayout for the UI design, with the sources structured very rigidly according to the Griffon framework… is complete. Run it and you’ll have the same result as before:

Conclusion

This article has provided a brief introduction to MigLayout, both in the context of Java and in the context of Griffon. The advantages of Groovy over Java have been pointed out. The coolness of MigLayout has been demonstrated. The integration of a Groovy web service has also been shown, within a Java/Groovy application, as well as within a Griffon application.

Postscript: Interested in creating the samples discussed here yourself? In addition to the JDK and Griffon, you’ll also need ant.jar on your classpath together with groovyws-standalone.jar (currently groovyws-standalone-0.3.1.jar) and miglayout-swing.jar (currently miglayout-3.6-swing.jar).