(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Интеграция XForms и Google Web Toolkit: Часть 2. Создаем формы управления исполнителями и альбомами (исходники)

Майкл Галпин

Введение

В первой статье нашей серии рассказывалось об основах GWT и XForms, а так же демонстрировалось, как совместное использование обеих технологий может повысить гибкость создания Web-приложений. В данной статье вы создадите простое приложение, содержащее две страницы: одну для просмотра самих рок-исполнителей, а другую - для просмотра их альбомов. Первую страницу вы создадите с помощью GWT на основе интерфейсных компонентов (также называемых виджетами) и Ajax-абстракций, предоставляемых GWT. На неё вы поместите ссылку на вторую страницу, написанную на основе XForms, с использованием модели данных и элементов интерфейса XForms.

Предварительные требования

В данной статье используется GWT версии 1.4 и подключаемый модуль Mozilla XForms версии 0.8. Модуль Mozilla XForms работает с любым браузером на основе Mozilla, например, Firefox или Seamonkey. Использование GWT предполагает определенные знания Java, а так же Web-технологий, таких как HTML и CSS. Кроме этого, в статье широко используется JavaScript. Наконец, поскольку технология XForms разработана в соответствии с парадигмой MVC (Model-View-Control), желательно знакомство с MVC. В то же время опыт использования GWT или XForms хотя и полезен, но необязателен. Мы пользовались Eclipse 3.3 для написания кода к статье, но знание Eclipse тоже не является необходимостью.

Использование GWT для управления списком исполнителей

Первая необходимая вещь в нашем приложении - это список исполнителей, разумеется, с возможностью добавлениях новых звезд. Вдобавок к этому нужно реализовать возможность перехода от конкретного исполнителя к странице, отображающей список его альбомов. Первую страницу мы напишем с помощью GWT. При этом содержащиеся в нем виджеты позволят создать симпатичный интерфейс, а его Ajax-абстракции помогут упростить загрузку и сохранение данных.

Данные приложения

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

Модель исполнителя в GWT

Нам понадобится некая простая модель для манипулирования исполнителями в нашем приложении. При использовании GWT в качестве такой модели легко использовать простой Java-класс, как показано в листинге 1.

Листинг 1. Java Bean, описывающий модель исполнителя

                
package org.developerworks.rockstar.client;

import com.google.gwt.user.client.rpc.IsSerializable;

public class Artist implements IsSerializable{
     private int id;
     private String name;
     private String genre;
     
     public Artist(){
          // needed for GWT's RPC mechanism
     }
     public Artist(int id, String name, String genre) {
          this.id = id;
          this.name = name;
          this.genre = genre;
     }
     public int getId() {
          return id;
     }
     public void setId(int id) {
          this.id = id;
     }
     public String getName() {
          return name;
     }
     public void setName(String name) {
          this.name = name;
     }
     public String getGenre() {
          return genre;
     }
     public void setGenre(String genre) {
          this.genre = genre;
     }
}

Повторимся, эта модель - это всего лишь стандартный класс-компонент, удовлетворяющий спецификации Java Bean. Он содержит три поля и предоставляет к ним соответствующие методы доступа и модификации. Вам понадобился бы примерно такой же компонент, если бы вы использовали в качестве модели исполнителя таблицу БД. Заметим, что этот класс находится в клиентском пакете нашего приложения и, соответственно, будет скомпилирован в код на JavaScript. Но в данном случае это не играет никакой роли, т.к., вы можете смело писать на Java, не заботясь о последующем преобразовании в JavaScript. Также отметим, что класс реализует интерфейс IsSerializable, что необходимо для любого класса, экземпляры которого буду передаваться по сети, например, в составе запроса или ответа на запрос Ajax. Далее посмотрим, как можно использовать GWT для создания интерфейса для просмотра списка объектов типа Artist.

Использование виджетов GWT для отображения списка исполнителей

Одним из многочисленных преимуществ GWT является входящий в него набор виджетов для распространенных элементов пользовательского интерфейса. При этом вы можете описывать ваш интерфейс на Java, что очень просто, особенно если у вас есть опыт использования Swing или SWT. В нашем случае мы воспользуемся виджетом FlexTable, который представляет собой динамически расширяемую таблицу. Число строк FlexTable будет увеличиваться по мере добавления исполнителей, что удобно, т.к. число последних заранее неизвестно. Код заполнения таблицы показан в листинге 2.

Листинг 2. Создание таблицы исполнителей

                
  private void populateTable(Artist[] artists){
      // clear the table
       int rowCount = this.artistTable.getRowCount();
       for (int i=0;i<rowCount;i++){
            this.artistTable.removeRow(i);
       }
      // create the header
      this.artistTable.getRowFormatter().addStyleName(0, "tableHeader");
      this.artistTable.setText(0, 0, "Name");
      this.artistTable.setText(0, 1, "Genre");
      // now add artists
      for (int i=0;i<artists.length;i++){
           this.artistTable.setText(i+1, 0, artists[i].getName());
           this.artistTable.setText(i+1, 1, artists[i].getGenre());
      }
      this.artistTable.setBorderWidth(4);
  }

Код в листинге 2 вначале очищает таблицу, если это необходимо. Затем создается заголовок. При этом единственной особенностью является присвоение заголовку стиля; обратите внимание на этот момент. Далее исполнители добавляются в таблицу по мере итерирования по списку. В листинге 3 приведено HTML-содержимое страницы, на которую помещается таблица с исполнителями.

Листинг 3. Страница исполнителей

                
<html>
     <head>
          <title>RockStars</title>

          <!--                                           -->
          <!-- This script loads your compiled module.   -->
          <!-- If you add any GWT meta tags, they must   -->
          <!-- be added before this line.                -->
          <!--                                           -->
          <script language='javascript' src='org.developerworks.
      rockstar.RockStarMain.nocache.js'></script>
          <style type="text/css">
               .tableHeader{
                    background-color:#AAAAAA;
               }
          </style>
     </head>
     <body>
     </body>
</html>

Как видите, страница крайне простая, всю "грязную" работу делает GWT. Единственное, что от вас требовалось по части HTML - это добавить определение стиля CSS для заголовка таблицы. Теперь, когда интерфейс для нашей страницы готов, осталось только добавить данные. Мы их получим стандартными методами GWT, т.е. через Ajax.

Получение данных об исполнителе: использование асинхронного вызова процедур (RPC)

Итак, виджет для отображения списка исполнителей готов, осталось лишь получить сам список. Для этого создадим сервис для управления исполнителями и будем вызывать его асинхронно, например, используя Ajax через GWT. Это стандартный метод асинхронного вызова процедур через GWT (GWT RPC). Начем с объявления интерфейса сервиса, как показано в листинге 4.

Листинг 4. Интерфейс сервиса для манипулирования списком исполнителей

                
package org.developerworks.rockstar.client;

import com.google.gwt.user.client.rpc.RemoteService;

public interface ArtistService extends RemoteService {
     public Artist[] getAllArtists();
     public void addArtist(Artist newArtist);
}

Отметим, что наш сервис расширяет стандартный интерфейс RemoteService - это необходимое условие GWT. Также заметим, что вызов getAllArtists() возвращает массив объектов типа Artist - экземпляров того самого класса Artist, что был объявлен выше для модели данных. Возможно, вам бы хотелось возвращать параметризированную коллекцию, типа Collection<Artist> или List<Artist>, однако это невозможно, т.к. GWT не поддерживает параметризированные типы данных на клиентской стороне. Другими словами, в коде, который будет впоследствии скомпилирован в JavaScript, нельзя использовать шаблоны, потому что, во-первых, шаблоны непредставимы в JavaScript, а во-вторых, информация о параметре типа недоступна на этапе исполнения. Разумеется, можно возвращать коллекцию и в виде java.util.List, но массив в данном случае ничем не хуже, и к тому же строго типизирован.

Для любого сервиса, который будет асинхронно вызываться через GWT, необходимо создать асинхронную версию интерфейса, как показано в листинге 5.

Листинг 5. Асинхронная версия интерфейса сервиса

                
package org.developerworks.rockstar.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface ArtistServiceAsync {
     public void getAllArtists(AsyncCallback callback);
     public void addArtist(Artist newArtist, AsyncCallback callback);
}

Самое главное при создании асинхронных версий - это добавление суффикса Async к названию интерфейса сервиса. Благодаря этому правилу именования GWT связывает асинхронный интерфейс с исходным, показанным в листинге 4. Также заметим, что из-за того, что все методы будут вызываться асинхронно, они напрямую не возвращают значения. Вместо этого они принимают дополнительный параметр - объект типа AsyncCallback, который будет вызван GWT, как только метод завершит выполнение на серверной стороне. Взгляните на серверную реализацию нашего интерфейса в листинге 6.

Листинг 6. Реализация интерфейса на стороне сервера

                
package org.developerworks.rockstar.server;

import java.util.List;

import org.developerworks.rockstar.client.Artist;
import org.developerworks.rockstar.client.ArtistService;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class ArtistServiceImpl extends RemoteServiceServlet implements ArtistService {

     private static final long serialVersionUID = -1801240935065207659L;
     
     private List<Artist> artists;
     private ArtistDao dao;
     
     public ArtistServiceImpl(){
          this.dao = new ArtistFileDao();
          this.artists = this.dao.getAllArtists();
     }

     public void addArtist(Artist newArtist) {
           newArtist.setId(this.artists.size());
          this.artists.add(newArtist);
          dao.saveArtists(this.artists);
     }

     public Artist[] getAllArtists() {
          Artist[] array = new Artist[this.artists.size()];
          return this.artists.toArray(array);
     }
     
}

Необходимо отметить несколько важных моментов в данной реализации. Во-первых, вдобавок к реализации нашего интерфейса, класс расширяет RemoteServiceServlet - классический Java-сервлет, ответственный за обработку запросов Ajax. Методы данного суперкласса будут вызываться на начальном этапе обработки клиентских запросов, а затем уже методы нашего сервиса будут вызываться сервлетом через механизм отражения (reflection). Во-вторых, в реализации используется объект доступа к данным (Data Access Object - DAO) через интерфейс ArtistDao. В данном примере используется файловая реализация ArtistFileDao, но ее легко можно заменить реализацией, работающей с базой данных. Этот класс выполняет всю "грязную" работу по записи-чтению данных из файлов, включая парсинг XML. Теперь попробуем запустить наше приложение.

Просмотр списка исполнителей в режиме хоста

До этого моменты мы имели дело только с чистым GWT. Одним из преимуществ GWT является возможность запуска приложения в режиме хоста, например, прямо из Eclipse. Но для начала нам понадобятся данные. Пример простого файла данных приведен в листинге 7.

Листинг 7. Тестовые данные

                
<?xml version="1.0" encoding="UTF-8"?>
<Data>
     <Artist>
          <Id>0</Id>
          <Name>The Struts Five</Name>
          <Genre>Classic Rock</Genre>
     </Artist>
     <Artist>
          <Id>1</Id>
          <Name>Spring Flow</Name>
          <Genre>Techno</Genre>
     </Artist>
     <Artist>
          <Id>2</Id>
          <Name>The Holy Grails</Name>
          <Genre>Funk</Genre>
     </Artist>
     <Artist>
          <Id>3</Id>
          <Name>The Rails Way</Name>
          <Genre>Pop</Genre>
     </Artist>
     <Artist>
          <Id>4</Id>
          <Name>Cake Clone</Name>
          <Genre>Pop</Genre>
     </Artist>
     <Artist>
          <Id>5</Id>
          <Name>Obscure Tapestry</Name>
          <Genre>Techno</Genre>
     </Artist>
     <Artist>
          <Id>6</Id>
          <Name>Dojo Darling</Name>
          <Genre>Classic Rock</Genre>
     </Artist>
     <Artist>
          <Id>7</Id>
          <Name>Cairingorm</Name>
          <Genre>Progressive</Genre>
     </Artist>
     <Artist>
          <Id>8</Id>
          <Name>ProtoStripes</Name>
          <Genre>Thrash</Genre>
     </Artist>
</Data>


Теперь вы можете запустить приложение. После запуска вы увидите интерфейс, схожий с представленным на рисунке 1.

Рисунок 1. Просмотр списка исполнителей в режиме хоста
viewing artists in hosted mode

Далее, после отображения списка исполнителей, необходимо создать форму для добавления новых рок-звезд.

Добавление исполнителя: создание формы с использованием GWT

Первым делом нам нужна форма для ввода данных о новом исполнителе. К счастью, GWT предоставляет виджеты для создания форм. Формы можно создавать программно, например, как показано в листинге 8.

Листинг 8. Форма ввода данных на GWT

                
public class RockStarMain implements EntryPoint {

     // Widgets for the page
     final FlexTable artistTable = new FlexTable();
     final VerticalPanel outerPanel = new VerticalPanel();
     final HorizontalPanel formPanel = new HorizontalPanel();
     final Label artistLabel = new Label("Artist Name:");
     final TextBox artistInput = new TextBox();
     final Label genreLabel = new Label("Genre:");
     final TextBox genreInput = new TextBox(); 
     final Button addButton = new Button("Add Artist");
     
  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
     // add the outer panel, then add to it
     RootPanel.get().add(outerPanel);
     outerPanel.add(artistTable);
     outerPanel.add(formPanel);
     
     // arrange form elements horizontally
     formPanel.add(artistLabel);
     formPanel.add(artistInput);
     formPanel.add(genreLabel);
     formPanel.add(genreInput);
     formPanel.add(addButton);
     
     // add event listener to our button
     ClickListener listener = new ClickListener(){

          public void onClick(Widget sender) {
               addNewAritst();
          }
          
     };
     addButton.addClickListener(listener);
     
    // load the artists now all the widgets are ready
    this.loadArtists();
  }

Как и ранее, код радует глаз своей простотой. Все, что нужно - это создать набор виджетов: надписей, полей для ввода текста и кнопок (а также уже знакомую FlexTable). Кроме этого, нам понадобится пара панелей для размещения виджетов. Наконец, добавим обработчик нажатия на кнопку добавления исполнителя, который будет вызывать один из методов нашего сервиса. Код обработчика показан в листинге 9.

Листинг 9. Метод добавления нового исполнителя

                
  private void addNewAritst(){
       Artist artist = new Artist(-1, artistInput.getText(), genreInput.getText());
       ArtistServiceAsync artistService = getArtistService();
       
       AsyncCallback callback = new AsyncCallback(){

          public void onFailure(Throwable caught) {
               // remove last row because of failure
               removeLastArtist();
          }

          public void onSuccess(Object result) {
               // nothing to do here since we added optimistically
          }
            
       };
       
       artistService.addArtist(artist, callback);
       // we'll be optimistic and go ahead and add to the table
       int size = this.artistTable.getRowCount();
       this.artistTable.setText(size, 0, artist.getName());
       this.artistTable.setText(size, 1, artist.getGenre());
  }

Как видите, обращение к сервису происходит через метод асинхронной версии интерфейса. В коде используется очень простой анонимный обработчик обратного вызова, который делает что-то полезное, только если метод сервиса завершается с ошибкой. Такое поведение объясняется тем, что клиентский код добавляет исполнителя в таблицу, не дожидаясь окончания серверной обработки. Это обеспечивает высокую скорость обновления интерфейса при условии редкости возникновения ошибок на стороне сервера. Исходный код приложения можно скачать по ссылке в конце статьи. Пока же посмотрим на пользовательский интерфейс после добавления формы (см. рисунок 2).

Рисунок 2. Просмотр и добавление новых исполнителей
view artists and add new artists

Введите данные форму и нажмите на кнопку Add Artist. Список исполнителей должен мгновенно принять вид, как на рисунке 3.

Рисунок 3. Страница после добавления исполнителя
artist added

Теперь, после того, как вы научились создавать интерфейсы пользователя с использованием GWT, а также использовать Ajax для передачи и получения данных от серверной части приложения, пришло время попробовать XForms в деле создания страницы для управления альбомами.

Использование XForms для управления альбомами

Вторая часть разработки нашего приложения будет заключаться в просмотре альбомов конкретных исполнителей. Разумеется, хотелось бы создать всего одну страницу, способную отображать альбомы любого исполнителя. Каким же образом определить, какие именно альбомы должны быть отображены при загрузке страницы? Существует несколько способов решения этой задачи, но мы будем использовать паттерн "команда". Команда должна нести в себе всю информацию, необходимую для создания страницы, а именно: альбомы какого именно исполнителя необходимо отобразить. Для передачи этой информации нам понадобится дополнительный параметр запроса, указывающий на исполнителя; назовем его artistId. Также необходима гиперссылка, содержащая идентификатор исполнителя, для перехода с первой страницы на вторую.

Создание ссылки с GWT-страницы на страницу XForms

Вернемся временно на нашу первую страницу и добавим ссылку на вторую. Код после незначительных изменений показан в листинге 10.

Листинг 10. Добавление ссылок к списку исполнителей

                
  private void populateTable(Artist[] artists){
      // clear the table
       int rowCount = this.artistTable.getRowCount();
       for (int i=0;i<rowCount;i++){
            this.artistTable.removeRow(i);
       }
      // create the header
      this.artistTable.getRowFormatter().addStyleName(0, "tableHeader");
      this.artistTable.setText(0, 0, "Name");
      this.artistTable.setText(0, 1, "Genre");
      // now add artists
      for (int i=0;i<artists.length;i++){
           //this.artistTable.setText(i+1, 0, artists[i].getName());
           String html = "<a href=\"Albums.jsp?artistId=
         "+artists[i].getId()+"\">"+artists[i].getName()+"</a>";
           this.artistTable.setHTML(i+1, 0, html);
           this.artistTable.setText(i+1, 1, artists[i].getGenre());
      }
      this.artistTable.setBorderWidth(4);
  }

Все, что изменилось - это строчка кода, устанавливающая значение левой колонки в таблице исполнителей. Изменив setText(...) на setHTML(...), мы получили возможность добавлять в ячейки таблицы фрагменты HTML, в данном случае - гиперссылку на новую страницу "Albums.jsp". Благодаря использованию JSP мы можем динамически генерировать содержимое второй страницы, а именно - заполнять список альбомов исполнителя с указанным artistId. Отметим, что параметр artistId напрямую указывается в ссылке. Теперь все, что нам осталось - это создать вторую страницу.

Создание страниц XForms с помощью GWT

Страницы JSP можно создавать аналогично другим Web-ресурсам, например, страницам HTML или CSS. Мы можем продолжать использовать GWT на нашей странице, как и раньше, все, что нужно - это добавить ссылку на файл JavaScript, сгенерированный GWT. Далее необходимо вызвать ранее созданный сервис для загрузки списка альбомов указанного исполнителя. Полученные альбомы можно записывать прямо в модель данных XForms, как показано в листинге 11.

Листинг 11. Модель данных с обработкой на стороне сервера

                
<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms" 
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<%@page import="org.developerworks.rockstar.client.*" %>
<%@page import="org.developerworks.rockstar.server.*" %>
<%@page import="java.util.List" %>
    <xhtml:head>
        <xhtml:title>Albums</xhtml:title>
        <xforms:model id="albums">
            <xforms:instance id="albumData" xmlns="">
                 <Data>
                 <%
                      int artistId = Integer.parseInt(request.getParameter("artistId"));
                      AlbumDao dao = new AlbumFileDao();
                      List<Album> albums = dao.getAllAlbums();
                      for (Album album : albums){
                           if (album.getArtistId() == artistId){
                 %>
                      <Album>
                           <Title><%= album.getTitle() %></Title>
                           <Year><%= album.getYear() %></Year>
                      </Album>
                 <% 
                           }
                      }
                 %>
                 </Data>
            </xforms:instance>
        </xforms:model>
</xhtml:head>
    <xhtml:body>
         <xhtml:div id="albumList">
              <xforms:repeat id="repeatItem" nodeset="/Data/Album">
                   <xhtml:div>
                        <xforms:output ref="Title">
                             <xforms:label>Title:</xforms:label>
                        </xforms:output>
                        <xforms:output ref="Year">
                             <xforms:label>Year:</xforms:label>
                        </xforms:output>
                   </xhtml:div>
              </xforms:repeat>
         </xhtml:div>
    </xhtml:body>
</xhtml:html>

Приведенный в листинге фрагмент Java-кода (скриптлет) управляет отображением списка альбомов, загружаемых, как и ранее, из XML-файлов. Код отбора альбомов находится во внешнем классе и доступен для скачивания как часть исходного кода к статье. В архив также включены необходимые тестовые данные в формате XML.

Тестирование страниц XForms в Web-режиме

Пришло время протестировать нашу страницу для отображения альбомов. До этого момента мы тестировали только страницу исполнителей, используя режим хоста GWT. Хотя приложение можно по-прежнему запускать в режиме хоста, нам придется переключиться в Web-режим для тестирования новой страницы. Почему такие сложности? Вся проблема в том, что для функционирования XForms необходим специальный модуль подключения браузера, которого нет в режиме хоста. На рисунке 4 показано, как переключиться в Web-режим.

Рисунок 4. Переключение приложения в Web-режим
switching to Web mode

После нажатия на кнопку Compile/Browse приложение должно переключиться в Web-режим, как показано на рисунке 5.

Рисунок 5. Приложение в Web-режиме
Application in Web Mode

После переключения приложение должно запуститься в вашем браузере по умолчанию. Теперь вы можете перейти от исполнителя к списку его альбомов (см. рисунок 6), просто щелкнув по ссылке на его имени.

Рисунок 6. Тестирование страницы для показа списка альбомов исполнителя
Testing the Artists Page

В итоге страница показывает альбомы исполнителей; при этом для итерирования по XML-данным модели используются простые элементы управления XForms. В следующей статье мы покажем, как добавить элементы XForms для ввода данных, что полезно представителям звукозаписывающих компаний для пополнения списка альбомов. Альбомы будут сохраняться путем асинхронных запросов через Ajax к GWT-части нашего приложения.

Заключение

Во второй статье нашей серии было показано, как создавать интерактивные страницы с использованием GWT и связывать их со страницами, созданными на XForms. В третьей части мы немного переделаем наше приложение. Не изменяя функциональности, мы начнем использовать элементы GWT на странице XForms для повышения интерактивности приложения.

Файлы для загрузки


 Распечатать »
 Правила публикации »
  Обсудить материал в конференции Дискуссии и обсуждения общего плана »
Написать редактору 
 Рекомендовать » Дата публикации: 15.08.2008 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Kerio Control - Server (incl 5 users, 1 yr SWM)
DevExpress / ASP.NET Subscription
Bamboo
ESET NOD32 Антивирус - лицензия на 2 года на 3ПК
Radmin 3.x - Стандартная лицензия 1 компьютер
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
СУБД Oracle "с нуля"
eManual - электронные книги и техническая документация
Новые материалы
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
Мастерская программиста
Работа в Windows и новости компании Microsoft
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
Обсуждения в форумах
Рабочее зеркало букмекера 1win (1)
Сегодня в случае недоступно официального сайта есть рабочие зеркала БК 1win...
 
Автомобиль (4)
Доброй ночи. Планируем приобрести авто, рассматриваем б.у варианты, как проще всего подобрать...
 
Сдать часы (3)
Подскажите, где можно сдать часы по хорошей цене.
 
Новости спорта - все послдение события в одном месте (1)
Пользователи, которые увлекаются ставками на спорт, должно следить за последними изменениями в...
 
Программы Delphi на заказ (241)
Пишу программы в среде Delphi на заказ http://bddelphi.ucoz.ru/
 
 
 



    
rambler's top100 Rambler's Top100