Воскресенье, 19.05.2024, 01:41
Мой сайт
Главная » 2014 » Август » 8 » Свинг это нормально или нет. JAVA+Swing в 2013. Стоит ли?
14:52

Свинг это нормально или нет. JAVA+Swing в 2013. Стоит ли?





свинг это нормально или нет

13 мая 2013 в 11:50

JAVA+Swing в 2013. Стоит ли?

На Хабре Swing не любят. Поиск по «Swing» дает либо нейтральные, либо негативные упоминания. Вот некоторые из них:
  • «Java-апплеты (доразвивались до смертельной болезни под названием Swing)»
  • «Swing — мягко говоря не самый оптимальный UI фреймворк»
  • «Swing был ужасен»

Я не возьмусь утверждать, что Swing — идеал. Это неправда. Однако я постараюсь описать те плюсы и минусы с которыми пришлось столкнуться. Visual Watermark для групповой защиты фотографий. Java версия у меня появилась в 2011. Мне захотелось сделать порт под Mac и вылизать интерфейс, но писать отдельную программу под каждую платформу у меня не было никакой возможности.

В начале 2011 UI-библиотеки для кросс-платформенной разработки были в таком состоянии:
  • QML был весь в багах: меню появлялись под компонентами, демка падала, поддержки в QtCreator не было. Ускоренная отрисовка появилась только в Qt5 прошлой осенью.
  • Qt не подошел, т.к. был целиком на “нативных” компонентах, а часто требовалось где-то изменить отрисовку.
  • Juce подходила по функционалу, не глючила и не падала. Стоила приемлемых денег да еще и с открытым кода. Отпугнул меня C++. Это замечательный язык на котором пишут огромное количество умнейших людей. Учитывая мой маленький опыт и размер задачи, сложность C++ – это перебор. Плюс, выяснилось, что Xcode не умеет рефакторить C++.
  • Adobe Air не поддерживает multi-threading.
  • Mono+GTK Мне кажется, что к эту комбинация могла бы решить мои проблемы. В тот момент отпугнул очевидный косяк с неработающими горячими клавишами в GTK. Судя по MonoDevelop, он не пофикшен до сих пор.
  • JavaFX не было под Mac.
  • SWT намного легче, чем Swing и в целом хорош. Не стал писать на SWT потому, что смотрел я его самым последним. Уже было потрачена уйма времени и я закончил эксперименты на первом попавшемся баге («плавали» кнопки по высоте на тулбаре).

На тот момент Java была частью Mac OS X, имела отличный Native Look & Feel, а JRE под Windows весила всего 12 мегабайт. Я был наивно уверен в успехе. В итоге, после 2 или 3х месяцев работы я оказался с первой версией программы на Java Swing.

На сегодняшний день в QML и JavaFX исправлены описанные проблемы. Поэтому, если вы готовы работать со сценическим графом, то вам стоит взять их на тест-драйв.

Qt перешел под крыло фирмы Digia. Выпуск бета-версии под iPhone и Android дает надежду на дальнейшее развитие библиотеки.

JavaFX стала библиотекой с открытым кодом в феврале этого года. Её совместимость с OpenJDK планируется на JDK 9. Когда выйдет 9ка неизвестно. Релиз 8ой версии запланирован на начало 2014 года.

О хорошем


Начну с хорошего. Вдруг подумаете, что я тоже в Swing-хейтеры записался?!

Вся отрисовка hardware-accelerated. Любое Swing-приложение отрисовывается на GPU, от разработчика ничего не требуется. Это делает возможным анимации в приложении. В том числе, когда приложение полноэкранное или развернуто на 24’’ мониторе.

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

Java – это managed код. Вы избавляетесь от кучи возможных багов, «доступных» только для C++-разработчиков. Риск Access Violation сведен к минимуму. Хотя это совершенно не означает, что у вас не будет других багов. Утечек памяти, например.

Отличная среда разработки. Eclipse, Intellij IDEA, NetBeans – выбор огромный. Везде есть рефакторинги, форматирование кода, авто-комплит, поддержка unit-тестов,

Очень много библиотек. LayoutManager’ы, работа с нативными объектами, строками, вебом – всего не перечислить. Это огромный козырь Java как платформы.

Очень много ответов на вопросы. Вот, например, доля вопросов на StackOverflow по каждой из UI-библиотек.



Примерно каждый сотый вопрос на StackOverflow – это вопрос о Swing. На практике, это означает, что большинство проблем уже решены. Скорее всего, один-на-один с проблемой вы не останетесь.

О плохом


Предыдущая часть похожа на сладкий пресс-релиз. Исправляюсь. Вот с чем вы можете столкнуться.

Не фиксятся критичные баги. File.exists не работает с момента релиза JDK7 и фикса нет до сих пор. Даже если баг критический, вы можете ждать фикса годами.

Ситуация может стать еще хуже, если вы планируете использовать нативный код. Я столкнулся с ситуацией, когда использование модальных окон (например, открытие OpenFileDialog) приводит к зависаниям на некоторых компьютерах. При том, что Java Native Foundation используется согласно примерам в документации. И баунти на StackOverflow мне не помог:-)

Баг с file.exists можно обойти с помощью классов из java.nio. Это новый API, который был призван решить проблемы производительности с развесистыми папками.

Что нужно сделать:
  1. Запустить приложение с параметром –Dfile.encoding=UTF-8
  2. Вместо File.exists используем Files.exists(Paths.get(fileName))
  3. Вместо File.listFiles используем
    try (DirectoryStream<Path> ds = Files.newDirectoryStream(folder)) { for (Path file : ds) { // do something } } catch (IOException e) { e.printStackTrace(); }

Или фиксить этот баг самостоятельно и проталкивать закладку через серию ревью.

Swing – только hardware accelerated. Это значит ваше приложение не будет работать в VMware, Parallels или через удаленный рабочий стол. Если вы не готовы с этим мириться, то смотрите в сторону SWT.

Нет 32-битных билдов под Mac. Официальная сборка только 64 бит. К сожалению, я не знаю в чем причина этого решения. Могу лишь гадать, что дело в каких-то багах.

Некоторое время Henri Gomez поддерживал 32-битные и universal билды. Готовые билды можно было скачать с его странички на code.google.com. К моему сожалению, нехватка времени и новая работа заставили Генри свернуть этот проект. Попрощавшись, он выложил свои билдежные скрипты на GitHub:
https://github.com/hgomez/obuildfactory

С их помощью можно собрать OpenJDK под Mac и Linux. Здорово, но не совсем. С помощью этих скриптов 32-битная версия под Mac не собирается. Внутри JDK огромное количество конфигурационных файлов, в которых зашита сборка строго 64-битной версии для Mac. Изменишь ключ в главном файле и получишь неработоспособную сборку. Каким образом Henri Gomez собирал 32-битные билды мне неизвестно.

Включайте JRE в дистрибутив. Мнение руководителей Oracle о дистрибуции приложений: “standalone self-contained инсталлятор с bundled JRE для целевой платформы – это более удачная модель распространения приложений” (источник). Наиболее вероятная причина этого решения – огромное кол-во уязвимостей в апплетах: Java приняла знамя решета у Flash.

Наиболее жестко поддерживает это ограничение фирма Apple, которая удалила Java в версии Mac OS 10.7 Lion. Также они принудительно отключают ее при установке новых системных обновлений.

JRE 7 весит около 100 Мб. В архиве получается около 50. К сожалению, размер JRE от апдейта к апдейту растет и нам проблему распухшего дистрибутива придется решать.

Не все объекты BufferedImage используют аппаратное ускорение. Только для BufferedImage.TYPE_INT_*. Поэтому, начиная с JDK7, работать с TYPE_4BYTE*, TYPE_3BYTE нецелосообразно.

При доступе к данным растра BufferedImage, картинка перестает рисоваться через GPU. Зачем это сделано понятно: пользователь меняет данные, метода “закончил менять” нет и не понятно когда их пере-заливать в видео-память. По крайней мере, это логично.

В Visual Watermark я использовал C++ библиотеку для загрузки изображений и нужно было полученные пикселы превратить в объект BufferedImage. Менять по-одному пикселу очень медленно и пришлось писать напрямую в буфер растра картинки. Как только я вызвал у растра getData(), все мои картинки перестали ускоряться. Покопавшись в коде DataBufferInt, я нашел решение этой проблемы с помощью reflection и написал небольшой класс-помощник:

import java.awt.*; import java.awt.image.*; import java.lang.reflect.Field; import sun.awt.image.SunWritableRaster; import sun.java2d.StateTrackableDelegate; // Standard library prevents image acceleration once getData() method is called // This class provides a workaround to modify data quickly and still get hw-accel graphics public class AcceleratedImage { // Returns data object not preventing hardware image acceleration public static int[] getDataBuffer(DataBufferInt dataBuffer) { try { Field field = DataBufferInt.class.getDeclaredField("data"); field.setAccessible(true); int[] data = (int[])field.get(dataBuffer); return data; } catch (Exception e) { return null; } } // Marks the buffer dirty. You should call this method after changing the data buffer public static void markDirty(DataBufferInt dataBuffer) { try { Field field = DataBuffer.class.getDeclaredField("theTrackable"); field.setAccessible(true); StateTrackableDelegate theTrackable = (StateTrackableDelegate)field.get(dataBuffer); theTrackable.markDirty(); } catch (Exception e) { } } // Checks whether current image is in acceleratable state public static boolean isAcceleratableImage(BufferedImage img) { try { Field field = DataBuffer.class.getDeclaredField("theTrackable"); field.setAccessible(true); StateTrackableDelegate trackable = (StateTrackableDelegate)field.get(img.getRaster().getDataBuffer()); if (trackable.getState() == sun.java2d.StateTrackable.State.UNTRACKABLE) return false; field = SunWritableRaster.class.getDeclaredField("theTrackable"); field.setAccessible(true); trackable = (StateTrackableDelegate)field.get(img.getRaster()); return trackable.getState() != sun.java2d.StateTrackable.State.UNTRACKABLE; } catch(Exception e) { return false; } } public static BufferedImage convertToAcceleratedImage(Graphics _g, BufferedImage img) { if(!(_g instanceof Graphics2D)) return img; // We cannot obtain required information from Graphics object Graphics2D g = (Graphics2D)_g; GraphicsConfiguration gc = g.getDeviceConfiguration(); if (img.getColorModel().equals(gc.getColorModel()) && isAcceleratableImage(img)) return img; BufferedImage tmp = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency()); Graphics2D tmpGraphics = tmp.createGraphics(); tmpGraphics.drawImage(img, 0, 0, null); tmpGraphics.dispose(); img.flush(); return tmp; } }

Использовать его нужно вот так:
DataBufferInt dataBuffer = (DataBufferInt)bufferedImage.getRaster().getDataBuffer(); int[] data = AcceleratedImage.getDataBuffer(dataBuffer); // Меняем данные AcceleratedImage.markDirty(dataBuffer);
Я не проверял этот код для изображений, которые уже были выведены на экран.

Нет встроенной анимации и полу-прозрачности. Объект javax.swing.Timer делает две вещи:
  1. Можно сделать анимацию компоентов.
  2. Из-за простоты класса, делать ее очень долго.

Есть библиотека Timing Framework, которая позволяет создавать анимации проще. Анимацию можно сделать вот так:

Animator viewAnimator = new Animator.Builder() .setDuration(duration, TimeUnit.MILLISECONDS) // Устанавливаем длительность анимации .setStartDirection(Direction.FORWARD) .setInterpolator(new AccelerationInterpolator(0.3, 0.7)) // Заставляем двигаться с ускорением .setRepeatCount(1).addTarget(new TimingTargetAdapter() { @Override public void timingEvent(Animator source, double fraction) { // Меняем состояние repaint(); } @Override public void end(Animator source) { // Делаем что-то по окончанию анимации } }).build(); viewAnimator.start();
Чаще всего используется анимация положения и полу-прозрачности. Если с контролем положения в Swing все OK, то полу-прозрачность стандартные компоненты не поддерживают. Проблема не в возможностях графического движка, а в том, что компоненты не имеют свойства getAlpha/setAlpha.

Java-приложение не будет запускаться в Mountain Lion из-за GateKeeper. Чтобы решить эту проблему вам нужно подписаться на программу Mac Developer за $99/год. В замен фирма Apple выдаст вам сертифакт для подписи кода и проблема уйдет.
Подписать бандл с приложением можно вот так:
codesign –s “Developer ID” –f “path-to-my-app.app”

В сумме


На мой взгляд, самый главный минус Swing – это неуверенность в будущем платформы, т.к критичные баги остаются открытыми. Своих фиксов дожидаются только уязвимость в браузерных плагинах. Складывается ощущение, что библиотеку бросили. Все остальные проблемы уже не так важны.

И мне будет очень грустно, если это действительно так. Потому что писать десктоп приложения на Swing быстро, просто и вокруг огромное количество готового и бесплатного кода.

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

UPDATE: Из комментариев выяснил, что кастомизация контролов в Qt возможна:

Спасибо пользователю silvansky
UPDATE: Пользователь Skyggedans заметил, что в Air появилась поддержка много-поточности. Делается она за счет дополнительных SWF файлов, с которыми можно настроить коммуникацию.

Свежий взгляд
на бег

протестируй кроссовки
нового поколения

Стань
первоиспытателем!

Скачай Windows Server 2012 R2
и выиграй почетную футболку!

Скачать

Немножко отсебятины, почему Свинг не есть очень хорошо. Просто личное впечатление.

В своё время мне, РНР-шнику, пришлось поучаствовать в разработке одного приложения на Джаве. Я делал UI. На Свинге. Вобщем-то, всё было более-менее нормально, пока не возникла одна особенность — проблема с отрисовкой lightweight-компонентов на JDialog (на фрейме всё было отлично, а вот с диалогом — проблемы). Печаль ситуации была еще и в том, что мы были скованы рамками JRE 6 (в JRE 7 это уже работало нормально, но радости не приносило, т.к. использовать всё-равно надо было JRE 6).

Полез гуглить. Чисто случайно нашел данный баг на еще sun-овском(!) багтрекере. Страниц 5 разного рода изобретательности матюков по этому поводу и workaround в почти самом конце + пара комментов, что это должно работать. Workaround базировался на том, что в реализации Свинга для JRE 6 был то ли один метод в реализации JDialog был пабликом вместо логичного привата, то ли что-то в этом роде. Вобщем, работало с использованием глюка самой JRE, вернее, реализации Свинга в ней.

Но — не заработало. Погуглив еще, нашел этот же воркэраунд на каком-то форуме, где люди в комментариях отписались, что в очередном апдейте JRE 6 такой вот хак закрыли, и теперь нифига не работает. Баг с отрисовкой никуда при этом не делся, его никто не исправлял, хотя жалобы были у очень многих. И т.к. у конечного юзера, скорее всего, будет версия JRE 6 с последним апдейтом, надеяться на то, что чисто случайно воркэраунд заработает, нельзя.

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

Программирую на свинге с 96 года (swing-1.1.1, когда еще не был включен в JRE). На мой взгляд, плохая реализация сомнительной идеи (мимикрировать под нативные ОС). Свинг полностью изжил себя лет 7 назад, когда из Сана ушла вся swing-команда: Chet Haase, Romain Guy, Alex Potochkin, Kirill Grouchnikov, Amy Fowler, и др. Java полностью потеряла позиции на рынке десктопов, даже JavaFX теперь уже не спасет. Императивная отрисовка графики, разработанная в 80-х кодах, изжила себя лет 10 назад и во всех системах была заменена декларативным описанием дерева объектов как JavaFX, SVG, HTML. Кроме того, основным недостатком свинга являлись:
— скудный набор компонентов.
— любой шаг влево связан с жуткими сложностями. Я задолбался в сотый раз писать TableCellRenderer для нормального отображения данных в таблице.
— нет нормального Layout-а. GridBagLayout создавался для IDE tools, но никак не для человека. Точку поставил только MigLayout.
— нет нормального LAF. Мимикрия под системные просто ужасна. Был интересный LAF — Substance.
— общая тормознутость интерфейса. Я не знаю почему так получается, но свинговый интерфейс любой апликации лагает. С SWT такой проблемы нет.

Один вопрос: а почему Вы не выбрали архитектуру веб клиента? Например Embedded Jetty + GWT/SmartGWT или Vaadin?

Ниже прокомментирую некоторые ваши высказывания:

> На сегодняшний день в QML и JavaFX исправлены описанные проблемы. Поэтому, если вы готовы работать со сценическим графом, то вам стоит взять их на тест-драйв.
Интерфейс на JavaFX лагает как слайдшоу на моей рабочей тачке, даже банальный скроллинг. Не ведитесь на удочку Oracle, обещающей светлое будущее JavaFX. Поезд ушел 7 лет назад. Все разговоры о портировании JFX на мобильные завораживают, но, пардон, где Java под iOS/Android? Теперь у нас есть HTML5/Canvas/CSS3/SVG.

> Очень много библиотек.
80% из них уже давно не поддерживается.

> Вся отрисовка hardware-accelerated. Любое Swing-приложение отрисовывается на GPU, от разработчика ничего не требуется.
Это не так. Акселерировано толька отрисовка примитивов и опции с растрами. Поскольку разработчик сам пишет код отрисовки компонентов, то он ответственен за то, чтобы акселерация использовалась по-максимому. Известен такой хак как двойная буферизация. У меня есть книга Swing Hacks по всем подобным хакам свинга, которая занимает 300 страниц.

> Swing – только hardware accelerated.
Это тоже не верно. Swing лежит на AWT, который в свою очередь использует Toolkit, который использует graphics pipeline. Последний по возможности использует ресурсы машины. Так что swing работает и на машинах без акселерации, и на remote desktop, только отрисовка долгая, поскольку транслируется весь растр окна.

> Не все объекты BufferedImage используют аппаратное ускорение.
BufferedImage вообще не accelerated. Это просто растр в памяти. ТруЪ accelerated — это VolatileImage и CompatibleImage. При рендеринге BufferedImage по возможности внутренне создается VolatileImage (или CompatibleImage), данные которого лежат на видеокарте. Битность BufferedImage имеет значение только при переводе в VolatileImage: если она у них совпадает, перевод делается на порядок быстрее. Битность VolatileImage зависит от девайса: например X Window не поддерживала альфа канал (не знаю как в JDK7 с ее новой rendering pipeline)

> При доступе к данным растра BufferedImage, картинка перестает рисоваться через GPU.
Не при доступе, а при изменении данных связанный VolatileImage перестает быть валидным до тех пор, пока BufferedImage не отрисуется заново. Поэтому, если растр часто меняется, лучше сразу пользоваться VolatileImage. Единственная деталь — VolatileImage не гарантирует сохранность данных и в любой момент может быть очищет видеокартой.

> Нет встроенной анимации и полу-прозрачности.
Есть (был) проект SwingX, который расширял компоненты, добавляя дополнительные возможности по отрисовке (Painter).

>Один вопрос: а почему Вы не выбрали архитектуру веб клиента? Например Embedded Jetty + GWT/SmartGWT или Vaadin?
Мое мнение: пакетная обработка графики и веб плохо уживаются. Гонять туда-сюда гигабайты файлов: терять время и дорого платить за трафик.

>Я не знаю почему так получается, но свинговый интерфейс любой апликации лагает.
Может вам такие приложения попадались? Например, отзывчивость интерфейса не была приоритетом при разработке? Для бухгалтера не важно, а ведь надо время потратить, вынести часть обработки в отдельный поток…

>во всех системах была заменена декларативным описанием дерева объектов как JavaFX, SVG, HTML
Не могу согласиться, что во ВСЕХ и что декларативное описание дерева — ВСЕГДА хорошо. Например, на дерево объектов очень плохо ложатся интерфейсы с огромным количеством записей. Приходится идти на хитрости с виртуализацией объектов и урезанием пересчета дерева. И даже со всеми ухищрениями не получается достичь производительности императивной отрисовки. Для примера можете поскролить какой-нибудь TableView/Grid на WPF и сравнить его с WinForms, QT или Delphi.
Насчет во ВСЕХ системах даже и не знаю что сказать. На память только WinRT приходит.

> нет нормального LAF. Мимикрия под системные просто ужасна.
Нативный L&F в JDK 6 был просто замечательный: составные кнопки, выплывающие модальные окна — все это было. В JDK 7 несколько поломали.

>Поскольку разработчик сам пишет код отрисовки компонентов, то он ответственен за то, чтобы акселерация использовалась по-максимому.
Не понял этого высказывания. Приведете пример?

> Так что swing работает и на машинах без акселерации, и на remote
Это верно только для Win. Swing не работает без ускорителя на Mac и поэтому не работает под виртуалками и удаленном доступе.

>Не при доступе, а при изменении данных связанный VolatileImage перестает быть валидным до тех пор, пока BufferedImage не отрисуется заново.
Нет. В текущей реализации OpenJDK 7 растр перестает быть ускоренным навсегда.

> Мое мнение: пакетная обработка графики и веб плохо уживаются. Гонять туда-сюда гигабайты файлов: терять время и дорого платить за трафик.
Я не совсем про это. Сервер запускать именно локально а ГУИ рисовать в браузере. При желании все это можно сделать в один клик по иконке: запускается сервер и встроенный в окно браузер без URLя, вкладок и меню. Количество библиотек с виджетами для веба зашкаливает. Взять хотя бы SmartClient или ExtJS. Для любителей Java есть врапперы для GWT. Кроме того, битмапы и императивную графику можно рисовать на Canvas.

> Может вам такие приложения попадались? Например, отзывчивость интерфейса не была приоритетом при разработке?
Нет, это общее впечатление многих пользователей от любого свинг-интерфейса. Netbeans/Idea ощутимо лагают, интерфейс Eclipse более тормозной, но ощущения лагания нет. Еще тормознутость многих апликаций хорошо заметна, когда меняешь лейаут, например тащя JSplitPane. Лейаут в свинге пересчитывается крайне долго, и еще дольше, если количество объектов достаточно большое.

> Нативный L&F в JDK 6 был просто замечательный
Я пришел из тех времен, когда LAF был Metal и Basic. С грехом пополам сделали мимикрию под XP. Потом под Висту с опозданием на год. Последним запилили Nimbus, который ужасен. Сторонние LaF были еще ничего (Alloy, Plastic). В последние годы высказывались идеи по поводу адаптивных лейаутов: это когда например Label выравнивается по font baseline с текстом в следующем TextField, или когда автоматически отключаются бордеры внутри других контейнеров с бордерами. В Substance LaF были потуги на этот счет. Но массово все это так и не было доведено до ума.

> Например, на дерево объектов очень плохо ложатся интерфейсы с огромным количеством записей.
Согласен. С деревом MVC реализовывать неудобно. Но можно. Html, Windows, SWT живут как-то с обычными таблицами, не MVC. Строки для huge-таблицы можно генерить динамически при скроллинге даже в модели с деревом.

> И даже со всеми ухищрениями не получается достичь производительности императивной отрисовки.
А вот здесь не согласен. Умный рендерер может эффективно отсекать невидимые узлы дерева, плюс автоматически делать оптимизации рендеринга: статичное поддерево может быть прозрачно закешировано в виде битмапа. Плюс такие плюшки как декларативная анимация, лееры, эффекты, аффинные преобразования, фильтры, etc… Кроме того, декларативный рендерер позволяет использовать композитные возможности низлежащей платформы (OpenGL, DirectX): лееры и Z-order компонентов могут управляються нативно, тогда как в Swing их каждый раз всех приходится перерисовывать в правильном порядке на канве Graphics2D. Ну и самое главное — асинхронный рендеринг отдельно от Event Dispatch.

> Не понял этого высказывания. Приведете пример?
Рисуя что-то ручками в Graphics2D постоянно приходилось профилировать и думать как это лучше оптимизировать. Большинство оптимизаций сводилось к банальному битмап-кешированию сложных отрисовок, что декларативный рендерер делает автоматически. Другая рекомендуемая оптимизация — брать из Graphics2D clip-регион и не рендерить объекты, которые находятся за его пределами. И еще over 9000 рекомендаций. И все ручками.



Источник: habrahabr.ru
Просмотров: 873 | Добавил: almoth | Рейтинг: 0.0/0
Всего комментариев: 0
Форма входа
Поиск
Календарь
«  Август 2014  »
ПнВтСрЧтПтСбВс
    123
45678910
11121314151617
18192021222324
25262728293031
Архив записей
Наш опрос
Оцените мой сайт
Всего ответов: 0
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0