Интернет Пресс - программы для Windows и Linux, статьи и материалы о компьютерах, бизнес-предложения.
Главная | Новости | Статьи | Веб-мастеру | Призы и подарки | Архив | RSS-канал | Карта сайта
Написать письмо автору сайта
Поиск
 

Интернет

Java-апплеты и Gif-анимация
 | 14:20:00 , 12 Ноября 2004

Автор: Alex , webdesign@hotbox.ru

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

Хотя бы отчасти решить эту проблему можно, пустив гулять по страницам Вашего сайта такого вот "младшего брата" Вашего любимца и здесь Вам на помощь может придти такая хорошо известная вещь как Gif-анимация - малюсенькое подобие  кинофильма, оформленное в виде графического Gif-файла. Однако у Gif-анимации есть как минимум один довольно серьезный недостаток - она хороша, когда движение происходит "на месте" или, иначе говоря, когда движущийся объект все время остается в пределах фиксированного прямоугольника желательно небольшого размера. Если же Вы захотите, чтобы сей симпатичный зверь, гулял у Вас если и не по всей площади Вашей страницы то, по крайней мере, по длине одной из ее сторон то Вы столкнетесь с тем что, во-первых, Ваш Gif-файл может приобрести довольно ощутимые размеры, а во-вторых - и это намного более существенно - с тем, что при любой его длине невозможно сделать так чтобы его движения автоматически подстраивались под любое разрешение экрана. И здесь Вам на выручку может придти такое средство как Java-апплет - небольшая программка на получившем довольно большое распространение языке Java встраиваемая прямо в Ваш текст на HTML.

Для начала сделаем <заголовок> нашего апплета, который мы назовем естественным образом MyDog. Этот <заголовок> будет выглядеть так:

import java.applet.*;
import java.awt.*;
public class MyDog extends Applet
{

Первые две строки говорят о том, что мы будем <импортировать> т.е. некоторым образом заимствовать информацию из Java-пакетов (что вполне естественно) и в котором, собственно говоря, и содержится, чуть ли не 90% всех ее изобразительных средств.

Следующая непустая строка (имеющаяся пустая строка, вообще говоря, необязательна и служит как здесь, так и во всех других местах только для удобочитаемости программы) говорит о том, что наш класс MyDog будет <публичным> т.е. доступным всем кому он может понадобиться и является (что опять-таки естественно) расширением класса Applet.

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

private final int xFrames[] = {5, 28, 49, 72, 92, 107};
private final int nFrames = xFrames.length, wDog = 122;
private int wFull, hFull, iFrame, xDog;
private Image imgDog, imgFull;
private Graphics  gFull;

Не буду сейчас останавливаться на смысле каждой из наших переменных - я объясню это дальше по мере возникновения необходимости в них, а отмечу только <их общие черты>: Все они описаны как <приватные> т.е. доступные только внутри нашего апплета; int, Image и Graphics являются их (переменных) типами т.е. указывают на то, какого рода информация будет храниться в каждой из них; А слово указывает на то, что значения переменных nFrames и wDog не будут меняться в процессе выполнения программы т.е. они, по сути, являются не <переменными> а совсем даже наоборот - постоянными (константами).

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

public void init()
{

Что такое мы уже знаем, а слово (<пустой>) указывает на то, что эта функция не возвращает никакого значения. Причем необходимо отметить, что у нас нет никакой свободы выбора в отношении ее описания, и мы не можем написать что она, например, возвращает целое значение, поскольку именно так и только так описанную функцию браузер вызывает, чтобы инициализировать апплет.

Самое основное, что нам нужно будет сделать это загрузить нашу картинку (анимированный Gif) в компьютер того, кто просматривает нашу страничку, для чего воспользуемся стандартной функцией Java getImage:

imgDog = getImage(getCodeBase(), "Dog1.gif");

Здесь нам понадобилась <имиджная> переменная imgDog, которую мы описали среди других переменных в самом начале нашего апплета. В качестве первого параметра функции загрузки картинки используется вызов другой стандартной функции Java getCodeBase() которая возвращает в качестве своего значения адрес в Сети (URL) того места, с которого был загружен наш апплет. Вторым же параметром является просто имя файла с нашей картинкой.

Но тут возникает некоторого рода проблема: Хотя объем этого файла и невелик - всего чуть более 4-х килобайт, но даже на его загрузку из Интернета может потребоваться некоторое время, в течение которого браться за его прорисовку совершенно бессмысленно и в этом случае нам на помощь придет стандартный Java-класс MediaTracker.

Для начала опишем переменную этого типа:

MediaTracker  newTracker;

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

Теперь создадим новый экземпляр значения класса MediaTracker:

newTracker = new MediaTracker(this);

Теперь нам нужно <сказать> созданному нами новому Media Tracker-у, загрузки какого именно изображения он должен ждать. Делается это при помощи его стандартной функции <добавления картинки>:

newTracker.addImage(imgDog, 0);

Смысл первого параметра очевиден - это, собственно говоря, наша картинка и есть, а второй параметр указывает идентификатор (номер) по которому наш Media Tracker будет узнавать ее во всех дальнейших операциях. Поскольку картинка у нас всего одна то выбор этого номера для нас непринципиален, и мы возьмем значение 0.

Ну а теперь запустим загрузку нашей картинки и подождем ее окончания. Делается это при помощи следующей конструкции:

try {
newTracker.waitForID(0);
} catch(InterruptedException e) {
}

Здесь waitForID(0) это как раз и есть указание Media Tracker-у начать загрузку картинки, которая для него обозначена идентификатором 0 (а это как раз наша <собачья> картинка и есть) а try-catch говорит Java-е, что нужно делать, если эта загрузка будет прервана - в данном случае мы предписали ей не делать ничего.

Последнее что нам осталось сделать на данном этапе нашей работы - проверить насколько успешно завершилась загрузка нашей картинки. Делается это при помощи еще одной стандартной функции класса Media Tracker isErrorID с, ясное дело идентификатором 0, которая выдает значение <истина> если при загрузке изображения имели место какие-либо ошибки. Делаем такую проверку:

if(newTracker.isErrorID(0))

И если ошибки при загрузке нашей картинки действительно имели место <обнулим> ее:

imgDog = null;

Ну вот - мы успешно (я надеюсь) добрались до конца функции инициализации нашего апплета.

}

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

Стандартно Java использует для этого функцию paint, которая в нашем случае будет крайне проста, поэтому я приведу ее всю полностью, а потом прокомментирую отдельные ее части.

public void paint(Graphics g)
{
gFull = (imgFull = createImage(wFull = size().width, hFull = size().height)).getGraphics();
DrawFull(g);
}

Ну, с заголовком этой функции я думаю, каких-либо проблем возникнуть не может, поскольку он очень похож на ранее разобранный нами заголовок функции init с той лишь разницей, что функции paint передается один параметр g - графический контекст нашего апплета. Причем, как и в случае с функцией init формат заголовка функции paint мы выбирать не можем. Единственное что мы вольны, изменить - это имя (но не тип) ее параметра.

Что касается ее тела, то первым оператором в нем является:

gFull = (imgFull = createImage(wFull = size().width, hFull = size().height)).getGraphics();

И здесь я хочу отметить следующее обстоятельство: Я считаю одним из достоинств языка Java возможность включать оператор присваивания внутрь других выражений, что позволяет делать их очень короткими и емкими что мы, и имеем в данном случае. Один этот оператор выполняет сразу несколько действий:

  • При помощи стандартной функции size определяет ширину и высоту нашего апплета и запоминает их значения для дальнейшего использования в описанных нами еще в его начале переменных wFull и hFull (wFull = size().width и hFull = size().height соответственно);
  • При помощи стандартной функции createImage создает в памяти картинку размером во весь апплет, которую мы в дальнейшем и будем использовать для формирования изображения нашего четвероного друга, и запоминает ее опять-таки для дальнейшего использования в описанной нами переменной imgFull (imgFull = createImage(wFul, hFull));
  • При помощи стандартной функции getGraphics выбирает графический контекст созданной нами картинки с соответствующим запоминанием его в переменной gFull (gFull = imgFull.getGraphics()).

Вторым оператором в теле функции paint является вызов другой функции DrawFull с передачей ей в качестве параметра графического контекста переданного в свою очередь функции pain.

DrawFull также является частью нашего апплета однако прежде чем перейти к ней нам необходимо разобраться еще с несколькими вопросами.

Для рисования в апплете Java наряду с уже знакомой нам функцией paint использует еще и функцию update, которая фактически состоит из вызова функции paint с одним маленьким добавлением - перед этим вызовом она очищает все поле апплета, что при выводе движущегося изображения может привести к очень неприятному эффекту его мелькания. Чтобы избежать этого просто переопределим эту функцию:

public void update(Graphics g)
{
paint(g);
}

И вот, наконец, настала пара перейти к, может быть самой важной, для того чтобы наш питомец начал свой бег по страницам сайта, функции (хотя конечно лишних функций обычно в программе не бывает, так что все они важны) - функции imageUpdate стандартно вызываемой Java в том случае если с какой-либо из прорисовываемых в апплете картинок произошли какие-либо существенные изменения. Поскольку эта функция, так же как и ранее рассмотренные нами функции init, paint и update является стандартно вызываемой Java мы, так же как и для этих функций обязаны точно соблюсти формат ее заголовка, который несколько сложнее, чем у них.

public boolean imageUpdate(Image img, int info, int x, int y, int w, int h)
{

Как мы видим, эта функция также <публичная> однако в отличие от всех предыдущих она возвращает значение, которое является логической (булевой) величиной <истина> (true) или <лож> (false).

Из всей массы ее параметров нас будут интересовать только первые два: img - указывающий для какой картинки эта функция была вызвана и info - указывающий, что же с этой картинкой произошло т.е. в связи с чем эта функция была вызвана.

Для начала мы как раз и проверим для нашей ли <собачьей> картинки функция была вызвана (img == imgDog) (хотя наш апплет и работает только с одной картинкой так что, вообще говоря, никаких сомнений быть не может, но такую проверку лучше сделать на случай его дальнейшего расширения) и в связи с тем ли событием она была вызвана ((info & FRAMEBITS) != 0) и на этой проверке нужно видимо остановиться подробнее.

Как я говорил еще в самом начале, анимированный-GIF представляет собой по сути дела малюсенький фильм <свернутый> в один GIF-файл и, следовательно, также как и его <старший брат> состоит из отдельных кадров называемых в данном случае фреймами. Готовность одного из таких кадров-фреймов к прорисовке и является той причиной вызова функции imageUpdate, которая нас интересует в данном случае, а поскольку info является битовой шкалой т.е. каждый ее разряд обозначает одну причину вызова этой функции, и по времени могут совпасть несколько из них, то для того чтобы удостоверится, что причина ее вызова именно та, что нас интересует нам нужно проверить, не равен ли нулю соответствующий бит называемый в данном случае FRAMEBITS.

Проверим еще заодно, а готова ли к прорисовке (т.е. создана ли) и наша <самая большая картинка> и получим следующую конструкцию:

if((img == imgDog) && ((info & FRAMEBITS) != 0) && (imgFull != null)) {

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

if(++iFrame >= nFrames) {

Где iFrame - номер текущего фрейма, а nFrames - общее число фреймов в нашем <микро-кинофильме> причем обе переменные были описаны нами еще в самом начале нашего апплета.

Ну а теперь, когда мы убедились еще и в том, что и фрейм именно тот, который нас интересует, увеличим начальную точку прорисовки нашей картинки (xDog) на величину некоторого шага необходимого, для того чтобы движение нашего <зверя> было плавным (wDog):

xDog += wDog;

и положим номер текущего фрейма равным нулю, показывая что, начался новый <показ> нашего <микро-кинофильма>:

iFrame = 0;

На этом наша работа по переходу от одного <показа> к другому завершена

}

но прежде чем пойти дальше остановлюсь вот на каком моменте: Совершенно очевидно, что на вышеприведенные два оператора мы попадем не раньше, чем при переходе от прорисовки первой последовательности фреймов ко второй, а чтобы была правильно прорисована и первая их последовательность переменным xDog и iFrame должны быть еще до начала всех прорисовок присвоены некоторые значения. Сделать это необходимо в функции init посредством следующей последовательности операторов:

xDog = 0;

iFrame = -1;

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

Делается это при помощи, следующей конструкции:

if((xFrames[iFrame] + xDog) >= wFull)

где xFrames описанный еще в самом начале нашего апплета массив координат нашего <объекта> (т.е. нашей собаки) относительно начала соответствующего <показа> и соответственно эта проверка определяет, не будет ли левая граница прорисовки <объекта> в <большой картинке> (xFrames[iFrame] + xDog) больше ее размера.

Если же это все-таки произойдет и наш пес все-таки <убежит от нас> за пределы экрана то мы вернем его назад, положив:

xDog -= wFull;

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

DrawFull(getGraphics());
}

Теперь мы, наконец, разберемся с этой функцией, но сначала покончим с функцией imageUpdate, для чего нам остался всего один оператор:

return(true);
}

который говорит Java, что прорисовка очередного фрейма осуществлена, и ничего больше ждать ненужно.

А теперь, наконец, пришла пора заняться уже два раза встретившейся нам функцией прорисовки всей <большой картинки>.

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

private synchronized void DrawFull(Graphics g)
{

Поскольку эта функция только <наша собственная> т.е. не относится к числу стандартно вызываемых Java, мы определили ее как <приватную> а слово synchronized говорит о том, что во время ее выполнения прервать сама себя она не может.

Для начала ее выполнения <нарисуем лужок>, по которому будет бегать наш маленький друг т.е. <покрасим> в зеленый цвет весь фон апплета для чего сначала при помощи стандартной для картинок функции setColor установим в качестве цвета <большой картинки> стандартный цвет green (зеленый):

gFull.setColor(Color.green);

а затем при помощи стандартной функции fillRect <зальем> ее всю этим цветом:

gFull.fillRect(0, 0, wFull, hFull);

Проверим теперь, была ли в свое время в функции init успешно считана картинка, которую мы собираемся прорисовывать:

if(imgDog != null)

и если все в порядке прорисуем ее при помощи стандартной функции drawImage, причем в качестве начальной точки по координате X возьмем значение переменной xDog которую, как вы помните, мы меняли в функции imageUpdate для каждой следующей группы фреймов, а по координате Y - нуль т.е. верхнюю кромку апплета.

gFull.drawImage(imgDog, xDog, 0, this);

Вот наша <большая картинка> и готова и нам осталось только нарисовать ее на апплете:

g.drawImage(imgFull, 0, 0, this);
}

Вот наш четвероногий друг и начал свою пробежку по страницам сайта и наша работа успешно завершена.

}

Но...

Но раз уж мы для решения возникшей у нас проблемы воспользовались таким мощным инструментом как Java-апплет то можно пойти и несколько дальше в использовании открывшихся перед нами возможностей и заставить, например нашего питомца <подавать голос> если его <потрепать по спине> т.е. кликнуть мышкой, когда курсор совмещен с его изображением.

Перво-наперво, прежде чем приступать к модификации нашего апплета нам нужно запастись соответствующим звуковым файлом этот самый <голос> содержащим. Конечно, логично чтобы это была запись собачьего лая но, вообще говоря, это совершенно необязательно - наш любимец может, например, говорить приятным женским голосом: <Здравствуйте хозяин!>. А вот что совершенно обязательно - это выдержать формат звукового файла, поскольку Java-апплеты могут воспроизводить только файлы в формате AU.

Итак, будем считать, что мы обзавелись соответствующим файлом и что называется он и первое что нам необходимо с ним сделать - это считать его, что осуществляется в нашей функции init посредством стандартной функции getAudioClip:

audioDog = getAudioClip(getCodeBase(), "Dog1.au");

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

private AudioClip audioDog;

Дальше - для того чтобы осуществить <трепание по спине> нашего пса нужно как минимум знать, где эта спина находится, что можно сделать при помощи оператора

rightCurs = (leftCurs = xFrames[iFrame] + xDog) + wFrames[iFrame];

поместив его в нашей функции imageUpdate перед вызовом функции DrawFull.

Что xFrames[iFrame] + xDog дает нам левую границу нашего <объекта> мы знаем еще с момента написания самой функции imageUpdate, ну а его правая граница получается очевидным образом путем добавления к левой границе его ширины, которая для всех фреймов содержится в массиве wFrames описание, которого аналогично описанию массива xFrames,  и которое также необходимо разместить в начале нашего апплета:

private final int wFrames[] = {88, 85, 83, 79, 79, 86};

Ну а для rightCurs и leftCurs даже нет необходимости заводить отдельное описание достаточно поместить их в уже имеющееся описание целочисленных переменных, которое приобретет после этого следующий вид:

private int wFull, hFull, iFrame, xDog, leftCurs, rightCurs;

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

public boolean  handleEvent(Event event)
{

Эта функция вызывается Java в случае возникновения каких-либо <внешних раздражителей> (событий) для апплета в частности различных <мышиных телодвижений> так-то: Перемещение ей курсора по апплету, нажатие клавиши на ней и т.д.

Возвращаемое этой функцией логическое (булево) значение показывает <наше отношение> к произошедшему событию, а ее параметр event содержит информацию это событие описывающую и в частности идентификатор события - id, показывающий какое именно событие произошло и, исходя из его значения, мы и будем строить свою реакцию на них, использовав его как управляющую переменную в переключателе:

switch(event.id) {

В том случае если происшедшее событие нас не интересует, вернем в доказательство этого значение <ложь> (false) предоставляя Java осуществить свою стандартную реакцию на это событие:

default:
return(false);

Первое событие, которое нас будет интересовать - это перемещение курсора по апплету. Индикатор этого события стандартно обозначается как MOUSE_MOVE:

case Event.MOUSE_MOVE:

и в качестве реакции на него мы выполним оператор:

setCursor(((fHand = (event.x > leftCurs) && (event.x < rightCurs)) ? handCursor : defCursor));

который:

  • определит, находиться ли курсор в пределах нашего <объекта> т.е. указывает ли он в данный момент, на изображение нашего пса, сравнив X-координату курсора event.x с границами <объекта> leftCurs и rightCurs и запомнит этот факт в логической (булевой) переменной fHand;
  • в зависимости от результатов предыдущей проверки при помощи стандартной функции setCursor установит вид курсора либо в стандартный (представляющий собой обычно стрелку), если курсор не указывает на нашего четвероного друга либо в вид руки в противном случае и мне представляется это вполне логичным: Кому же понравится, чтобы его <трепали по спине> каким-то заостренным предметом?

Описания переменной  fHand и переменных handCursor и defCursor хранящих курсор-руку и стандартный курсор нужно будет поместить вначале нашего апплета среди других описаний переменных:

private boolean fHand;

private Cursor defCursor, handCursor;

А в функции init поместить присваивание им начальных значений и это, и есть те мелочи за которыми, как я уже упоминал, нам еще придется обратиться к ранее описанным функциям:

fHand = false;

defCursor = Cursor.getDefaultCursor();

handCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);

где getDefaultCursor и getPredefinedCursor - стандартные для курсора функции получения его вида установленного по умолчанию и одного из его стандартных видов (в данном случае - вида руки) соответственно.

Теперь обработав событие так, как мы сочли нужным, мы можем выйти из переключателя:

break;

Следующее представляющее для нас интерес событие - выход курсора за пределы апплета. Индикатор этого события стандартно обозначается как MOUSE_EXIT:

case Event.MOUSE_EXIT:

Реакция апплета на это событие будет весьма проста:

fHand = false;

<сброс> значения fHand <в знак того> что курсор при этом заведомо не может показывать на нашего пса и выход из переключателя.

break;

И последнее (по очереди, но не по значению) событие которое мы будем обрабатывать - нажатие на клавишу мыши которое стандартно обозначается как MOUSE_DOWN:

case Event.MOUSE_DOWN:

Когда это событие происходит, мы проверяем, указывает ли в этот момент курсор на изображение нашего питомца:

if(fHand)

и если это действительно так при помощи стандартной для аудио-клипов функции play заставляем его <подать голос>:

audioDog.play();

Вот и все.

}

Нам осталось только вернуть в качестве значения функции handleEvent значение <истина> (true), говоря Java, что мы <прореагировали> на соответствующий <внешний раздражитель>:

return(true);

и эта функция, да и вся наша работа полностью завершена.

}

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

Ну, вот теперь действительно совсем все. Благодарю за внимание.

В следующий раз мы с Вами может быть поговорим о том, как при помощи апплетов одну картинку плавно преобразовать в другую или о том, как сделать <интеллектуальный> баннер или еще о какой-либо из многочисленных возможностей апплетов. Если конечно у меня дойдут до этого руки... :)

Ну а если кто-либо захочет посмотреть этот апплет в действии, скачать его текст или текст этой статьи - милости прошу: http://mywebdesign.fatal.ru/Articles

Источник: http://www.izcity.com



Оценок этой статье - 7. Средний балл - 3.67 Просмотров - 26158

Выставить оценку статье:


Читайте также:

  • Обзор самых популярных онлайн-СМИ
  • Электронные библиотеки
  • Популярность сайта
  • Как подружиться с поисковыми машинами
  • Оптимизация сайта: проблема выбора
  • Создание универсального выпадающего меню
  • Программы автоматической регистрации сайта
  • Игровые сайты сети
  • Сравнительный тест эффективности языков программирования для WEB
  • Советы по созданию и раскрутке сайта
  • Как бесплатно сайт построить:
  • WWW - почтой
  • Сокеты и Java
  • Вебсервер в домашних условиях
  • Некоторые секреты IP-протокола
  • Пластиковая карточка, как платежный инструмент
  • Практические советы по использованию DHTML

    Все статьи рубрики Интернет




  • Поиск
     

    Размещение рекламы | Контакты

    Главная | Новости | Статьи | Веб-мастеру | Призы и подарки | Архив | RSS-канал | Карта сайта

    Вверх
    Copyright © 2004 - 2024 г. При перепечатке гиперссылка на «Интернет Пресс» обязательна.