Киберфак – бесплатно скачать презентации PowerPoint, лекции, рефераты, шпоры, курсовые cyberfac logo
cyberfac.ru
На главную | Регистрация | Вход
  Статьи  
Главная » Статьи » Информатика » Высокоуровневые методы информатики и программирования

Иницилизация окон (MVS C++). Обработка сообщений (Delphi)

Полезная статья? Пожалуйста, поставьте "+"
К содержанию

Message методы, или обработка сообщений классами в Delphi

Данная статья предназначения для более глубокого понимания того, как реализована обработка сообщений Windows в VCL и как это можно и нужно использовать в своих целях и использовать правильно.

Наверняка каждый из нас хотя бы раз в своей практике, но встречался с кодом вида:
TForm1 = class(TForm)
private
procedure MyCoolHandler(var Message: TMessage); message WM_USER;
public
{some code here}
end;

procedure TForm1.MyCoolHandler(var Message: TMessage);
begin
Message.Result := 32767;
Caption := 'wow, this works!';
end;


Подобный код встречается очень часто в разного рода советах, приемах и воспринимается сразу как нечто органичное, не вызывающее вопросов. Ну да, добавили реакцию на сообщение для формы и всего делов, работает. Ай да кудесники эти разработчики Delphi! Можно обрабатывать сообщения в форме...

Однако, как это работает? Почему это работает? И как следует правильно это использовать? Какие могут возникнуть проблемы, подводные камни? Давайте посмотрим.
Предпосылки

Логика возникновения подобного трюка лично мне совершенно ясна - разработчики Delphi хотели ясный, понятный и читаемый код при разработке визуальных компонент. Как всем известно, при создании окна необходимо задать оконную функцию. То есть некую callback функцию, которая принимает по ссылке сообщение и возвращает результат. Во всех примерах приложений на чистом WinAPI эта оконная функия состоит из case с перечислением всех сообщений, которое окно хочет обрабатывать и вызова DefWindowProc для остальных сообщений. Для обработки небольшого числа сообщений этот case не страшен. Однако если окно обрабатывает большое количество сообщений и обработчики сообщений содержат ветвления, секции исключений и прочие запутывающие вещи, то отладка, поиск обработки нужного сообщения и чтение подобного превращается в сущий кошмар (А ведь если взглянуть на количество контролов и классов в Delphi можно прийти в ужас от подобной перспективы).

Разработчики RTL пошли хитрым путем. Вместо огорода из case они придумали хитрый, изящный и весьма интересный трюк - message методы.
Как это работает

Message методы - часть языка Delphi, эта функциональность реализована уже в TObject, так что создавая любой класс вы уже имеете возможность обрабатывать сообщения (об этом чуть позднее). При добавлении message метода (сигнатура процедуры при этом должна быть определенного вида: procedure Name(var Message: MessageRecord); message [число]) в класс этот метод располагается в vmt по адресу, кратному числовому значению сообщения, стоящего после ключевого слова message. Message методы - динамические, ведь обработка сообщения должна быть не только у базового класса, но и у его потомков. Чтобы "послать сообщение", классу нужно сформировать струкутру Message (не обязательно TMessage, об этом чуть позже) и вызвать метод Dispatch нужного класса. Этот метод производит поиск в vmt адрес метода по смещению Msg структуры Message у класса и, если его нет непосредственно у класса, среди message методов его родителей. Если адрес метода не найден (то есть обработка такого сообщения не присутствует ни у одного класса в иерархии), то производится вызов метода DefaultHandler. Таким образом, схема обработки сообщений в VCL приобретает более удобную для восприятия и модификации форму. Вместо:
function WindowProc(hwnd: HWND; uMsg: Cardinal; wParam: WPARAM; lParam: LPARAM): Integer; stdcall;
begin
case uMsg of
WM_NULL:
begin
{код обработки WM_NULL}
Result := 0;
end;
...{несколько километров кода с перечислением всех сообщений}
WM_USER:
begin
{код обработки WM_USER}
Result := 0;
end
else
Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
end;


получаем (приближенно):
TWinControl = class(TControl)
private
procedure WMNull(var Message: TMessage); message WM_NULL;
…{несколько километров кода с перечислением message методов всех сообщений}
procedure WMUser(var Message: TMessage); message WM_USER;
protected
procedure DefaultHandler(var Message); override;
procedure WndProc(var Message: TMessage); override;
end;

{ TWinControl }

procedure TWinControl.WMNull(var Message: TMessage);
begin
{код обработки WM_NULL}
Message.Result := 0;
end;
…{несколько километров кода методов всех сообщений}
procedure TWinControl.WMUser(var Message: TMessage);
begin
{код обработки WM_USER}
Message.Result := 1;
end;

procedure TWinControl.WndProc(var Message: TMessage);
begin
Dispatch(Message);
end;

procedure TWinControl.DefaultHandler(var Message);
begin
with TMessage(Message) do
Result := DefWindowProc(Handle, Msg, WParam, LParam)
end;

Если изобразить вышесказанное в виде схемы, то получим для классической обработки:



После выборки сообщения из очереди, оно направляетя в оконную процедуру (1), где пробегается через case (2) и направляется в блок обработки сообщения (3), затем результат возвращается в качестве результата оконной функции (4).

Типичная схема обработки сообщений в VCL будет выглядеть приблизительно так:



После выборки сообщения (1), оно направляется в оконную процедуру WndProc (2). В ней происходит вызов метода Dispatch (3), который ищет, есть ли у TWinControl обработчик данного сообщения (4). Пусть в обработчике будет произведен вызов inherited. Метод динамический, поэтому будет производиться поиск среди message методов родителя (то есть TControl). Для удобства понимания (хотя на деле конечно не так, убедитесь в этом, открыв окно CPU) пройдем такой же путь - через Dispatch. Структура, содержащая сообщение направляется в родительский класс (4.1, 4.2), в нем идет поиск обработчика. Если обработчик не найден, вызывается метод DefaultHandler. Поскольку DefaultHandler у TWinControl переопределен, то произойдет его вызов (4.3), в котором будет вызов обработчика сообщений по умолчанию, то есть для Windows это DefWindowProc. Далее будет воврат в обработчик (4) и в оконную процедуру вернется уже структура с измененным значением поля Result, которое в итоге отдается как результат обработки сообщения.

Исходя из вышеописанного алгоритма, можно сделать несколько выводов:

Во-первых, методы динамические. При этом не обязательно писать override или называть метод тем же именем и даже принимать структуру того же типа, размера и с тем же выравниванием полей, вызов inherited внутри message метода приведет к вызову message метода родителя с передачей туда структуры Message, либо, если у родительских классов нет обработки этого сообщения, к вызову DefaultHandler. Это играет очень важную роль при работе с VCL.

Во-вторых, message методы не обязательно должны принимать именно тип TMessage, описанный в модуле Messages. Достаточно того, чтобы структура имела первое поле типа DWORD, чтобы можно было осуществить переход по адресу, равному числовому значению этого поля. На остальные параметры структуры не налагается ограничений. VCL использует TMessage, поскольку все сообщения Windows, а так же пользовательские сообщения CN_BASE + xxx имеют одну (или сходную по размерам) структуру. Однако, структуру обязательно нужно передавать по ссылке.

Надо заметить, что на диапазон обрабатываемых сообщений налагается ограничение, а именно от 1 до 49151. Почему 49151? Потому что данный прием был введен прежде всего для обработки сообщений Windows, а в Windows номера сообщений от 1 до WM_USER-1 зарезервированы системой, от WM_USER до $7FFF - для пользовательских сообщений и от WM_APP до $C000-1 (49151) - для сообщений на уровне приложения. От $C000 до $FFFF идет диапазон строковых пользовательских сообщений уровня приложения, создаваемых через RegisterWindowMessage, результат вызова фунции невозможно предсказать на этапе компиляции, поэтому логику обработки подобных сообщений лучше делать в оконной процедуре. По поводу оконной процедуры также стоит отметить, что вначале сообщение идет в статический метод MainWndProc, а в нем уже идет безопасный вызов WndProc. Для того, чтобы Windows могла принять MainWndProc как оконную функцию, VCL использует функцию Classes.MakeObjectInstance. Функция возвращает адрес на процедуру, которую можно отдать Windows как оконную, и которая перенаправит все вызовы оконной функции в метод класса.
Inherited

Как уже было сказано, message методы динамические. А это значит, что каждый новый потомок может переопределять реакцию на сообщение. Как и любое перекрытие, его нужно делать правильно. Вызов inherited не в том месте или его отсутствие может повлиять на логику работы контрола, поэтому нужно четко понимать, для чего нужен или не нужен вызов родительской обработки сообщения. Если вызова родительской обработки не производится, то следите за возвращаемым результатом сообщения.
Подводные камни

Не всегда в VCL можно решить задачу обработки сообщения исключительно переопределением message метода. Иногда это не приводит ни к какому результату, потому, что помимо обработки сообщения в message методе идет ее обработка и в WndProc. Яркий тому пример. Автору вопроса необходимо было запретить рисование системной стрелки в выпадающем меню. Но обработка WM_DRAWITEM не приводила ни к чему, потому что в WndProc состояние Canvas пункта меню возвращалось в исходное. Поэтому иногда все-таки приходится лезть в WndProc, хоть это и нехорошо :).

Message методы также являются одной из причин, по которой классический сабклассинг (то есть переопределение оконной процедуры окна через SetWindowLong) в Delphi крайне не рекомендуется. Одной из причин, как известно, является метод Perform у TControl, перенаправляющий сообщения напрямую в оконную процедуру. Если внутри кода класса будет вызов Perform, то будет вызван метод WndProc класса, а не фактическая оконная процедура окна (которая, как известно получается из приведения WndProc к виду, которому требует Windows через MakeObjectInstance). Однако и message методы тут не самые лучшие помощники - метод Dispatch направляет сообщение сразу в обработчик, а не в оконную процедуру. Поэтому проводя сабклассинг нужно быть готовым к подобным фокусам.

Если читатель знаком с WTL или MFC в си, то он явно заметил, что подобный механизм есть и там - через карты сообщений. Там имеется похожая проблема - если сообщение пришло через непосредственный вызов SendMessage, то эти сообщения обходят фильтры сообщений.
Собственная выгода

Итак, стало понятно как это работает в VCL. Однако использование message методов не ограничивается только лишь оконными (то есть обладающими хендлом) компонентами, и класс TControl тому подтверждение - все неоконные контролы способны обрабатывать сообщения. И оконный компонент ответственнен за перенаправление сообщений подчиненным неоконным контролам. Именно message методы сделали это возможным! Более того, их использование также не ограничивается только обработкой сообщений Windows - вы можете с таким же успехом обрабатывать свои сообщения, вооружившись знанием о том, как они работают. К примеру, у вас есть задача обмена некими данными между классами, однако заранее нельзя предугадать какими именно и как обрабатывать результат. Можно нагородить лес из кучи методов, предоставляющих возможность обмена разнотипными данными, а можно воспользоваться механизмом передачи сообщений. Например, задав такую структуру:
type
TDataMessage = record
Msg: DWORD;
Data: Integer;
DataSize: Integer;
Result: Integer;
Tag: Integer;
end;

и 2 сообщения - передача строки и передача числа:
const
MSG_STRING = 1;
MSG_INT = 2;

можно обмениваться строковыми и целочисленными данными между классами через message методы:
type
TClass1 = class
private
FSomeString: WideString;
procedure OnStringGet(var Msg: TDataMessage); message MSG_STRING;
procedure OnIntegerReceived(var Msg: TDataMessage); message MSG_INT;
end;

procedure TClass1.OnIntegerReceived(var Msg: TDataMessage);
begin
Msg.Result := Msg.Data * 2;
end;

procedure TClass1.OnStringGet (var Msg: TDataMessage);
begin
FSomeString := 'test string example';
Msg.Result := Integer(PWideChar(FSomeString));
end;

...
var
Msg: TDataMessage;
begin
with Msg do
begin
Msg := MSG_STRING;
Data := 1321564;
end;
Class1.Dispatch(Msg);
Class2.SomeIntValue := Msg.Result;

FillChar(Msg, SizeOf(Msg), 0);
Msg.Msg := MSG_STRING;
Class1.Dispatch(Msg);
Class2.SomeStr := Copy(PWideChar(Msg.Result), 1, 10);


...

Опять же, взяв на вооружение механизмы в VCL, можно писать приложения на чистом винапи, не городя при этом многокилометровых case в оконных процедурах ;) Или можно писать свой набор неоконных компонент и при этом спокойно обрабатывать оконные сообщения. Это раскрывает широкие возможности для разгула фантазии:)
Несколько слов о передаче строк и потоках

Если вы решились использовать подобный прием в своих проектах, то должны четко понимать что и как следует передавать. Рекоммендуется в структурах не использовать слишком много полей, желательно использовать только целые поля, чтобы передавать адреса передаваемых данных. Если вы передаете строку string, то помните про счетчик ссылок (очень хорошая статья об этом вот тут), если передаете PChar, то позаботьтесь о корректном выделении и освобождении памяти. Я бы порекомендовал WideString, поскольку в них нет счетчика ссылок.

При использовании message методов в отдельном потоке не забывайте про синхронизацию. Dispatch - это не SendMessage, при передаче сообщения классу в потоке с другим контекстом остановки потока не будет!

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

 

Обработка сообщений Windows в Delphi

Конечно, нельзя придумать такую библиотеку объектов, которые бы полностью соответствовали потребностям программистов. Всегда возникнет необходимость дополнения или изменения свойств и поведения объектов. В этом случае, так же, как и при создании своих собственных компонент в Delphi, часто требуется обрабатывать сообщения Windows. Поскольку Object Pascal является развитием и продолжением Borland Pascal 7.0, то это выполняется сходным с BP способом.

Общий синтаксис для декларации обработчика сообщений Windows:
procedure Handler_Name(var Msg : MessageType);
message WM_XXXXX;

Handler_Name обозначает имя метода; Msg - имя передаваемого параметра; MessageType - какой либо тип записи, подходящий для данного сообщения; директива message указывает, что данный метод является обработчиком сообщения; WM_XXXXX - константа или выражение, которое определяет номер обрабатываемого сообщения Windows.

Рассмотрим обработку сообщений на примере. Например, при нажатии правой кнопки мыши на форме в программе появляется всплывающее меню (pop-up menu, если оно было привязано к этой форме). Программист может захотеть привязать к правой кнопке какое-нибудь другое событие. Это можно сделать так:
type
TForm1 = class(TForm)
PopupMenu1: TPopupMenu;
MenuItem1: TMenuItem;
MenuItem2: TMenuItem;
MenuItem3: TMenuItem;
private
{ Private declarations }
procedure WMRButtonDown(var Msg : TWMMouse); message
WM_RBUTTONDOWN;
public
{ Public declarations }
end;


Подчеркнут код, добавленный в декларацию объекта TForm1 вручную. Далее, в секции implementation нужно написать обработчик:
procedure TForm1.WMRButtonDown(var Msg : TWMMouse);
begin
MessageDlg('Right mouse button click.', mtInformation,
[mbOK], 0);
end;

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

Вообще-то, у класса TForm уже есть унаследованный от дальнего предка обработчик данного события, который называется точно также и вызывает то самое pop-up меню. Если в новом обработчике сообщения нужно выполнить действия, которые производились в старом, то для этого применяется ключевое слово inherited. Если слегка модифицировать наш обработчик, то после диалога будет появляться pop-up меню:
procedure TForm1.WMRButtonDown(var Msg : TWMMouse);
begin
MessageDlg('Right mouse button click.', mtInformation,
[mbOK], 0);
inherited;
end;


Однако, есть еще способ обработки всех сообщений, которые получает приложение. Для этого используется свойство OnMessage объекта Application, который автоматически создается при запуске программы. Если определен обработчик события OnMessage, то он получает управление при любом событии, сообщение о котором направлено в программу. Следующий код будет приводить к появлению диалога при двойном щелчке мыши на любом объекте в приложении.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage:=AOM;
end;
procedure TForm1.AOM(var Msg: TMsg; var Handled: Boolean);
begin
Handled:=False;
if Msg.Message = WM_LBUTTONDBLCLK then begin
MessageDlg('Double click.', mtInformation, [mbOK], 0);
Handled:=True;
end;
end;


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

 

Инициализация окон Microsoft Visual Studio C++

/*
Файл               : FrameWnd.cpp

Проект             : простейшее приложение Win32API с
использованием стандартных функций API-SDK
(создается главное окно, в которое
производится вывод текста и графики)

Microsoft Visual Studio C++ .NET 2005
*/

// Прекомпилируемый заголовочный файл
#include "stdafx.h"

// Прототипы используемых функций (!!! имена функций не системные
//   и могут быть иными - здесь нет связи с классами или
//   библиотеками)
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam );
BOOL InitApplication( HINSTANCE hInstance );
BOOL InitInstance( HINSTANCE hInstance, int nCmdShow );

// Указатель на имя регистрируемого класса
LPCSTR                    szClassName = "FrameWndAPI";       
// Указатель на заголовок окна приложения
LPCSTR                  szTitle = "Создание Windows-приложения"
" FrameWnd с использованием стандартных функций SDK-API";

// ****************************************************************
// Главная функция приложения
int WINAPI WinMain(                 // Возвращает TRUE при успехе
// Дескриптор данного приложения
HINSTANCE           hInstance,
// Дескриптор предыдущей запущенной копии приложения. В
//   Win32API этот параметр всегда равен NULL и оставлен
//   исключительно для совместимости с версиями ниже четвертой.
//   Связано это с тем, что каждое 32-разрядное приложение
//   запускается в собственном адресном пространстве, в
//   котором, естественно, нет никаких копий или других
//   приложений
HINSTANCE           hPrevInstance,
// Указатель на командную строку, которую можно в
//   интегрированной среде разработки задать по команде
//   "Properties" контекстного меню проекта в элементе "Program
//   arguments" вкладки "Debugging"
LPSTR               lpCmdLine,
// Режим начального отображения главного окна приложения -
//   после вызова параметр получает значение SW_SHOWNORMAL (1),
//   что соответствует отображению окна в нормальном виде
int                 nCmdShow )
{
// Хотя параметр hPrevInstance в Win32API всегда равен NULL,
//   все же продолжаем для совместимости проверять его значение
if ( !hPrevInstance )
{
// Инициализируем приложение - подготавливаем данные класса
//   окна и регистрируем его
if ( !InitApplication( hInstance ) )
return FALSE;
}

// Завершаем создание приложения - создаем и отображаем главное
//   окно приложения
if ( !InitInstance( hInstance, nCmdShow ) )
return FALSE; 

MSG                 msg;        // Для очередного сообщения
// Стандартный цикл обработки сообщений
while ( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}

return static_cast< int >( msg.wParam );
}

// Подготовка данных класса окна и его регистрация в ОС Windows
BOOL InitApplication(               // Возвращает TRUE при успешном
//   завершении
// Дескриптор (уникальное число), ассоциируемый с текущим
//   приложением
HINSTANCE           hInstance )
{
// Сведения о регистрируемом классе
WNDCLASS            wc;

// Заполняем структуру класса окна WNDCLASS - смысл
//   инициализирующих значений рассмотрен выше
wc.style         = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   = static_cast< WNDPROC >( WndProc );
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hInstance;
wc.hIcon         = LoadIcon( NULL, IDI_ASTERISK );
wc.hCursor       = LoadCursor( NULL, IDC_CROSS );
wc.hbrBackground = reinterpret_cast< HBRUSH >( COLOR_WINDOW+1 );
wc.lpszMenuName  = NULL;
wc.lpszClassName = szClassName;

// Регистрируем класс окна
return RegisterClass( &wc );
// Функция RegisterClass( ) определяет класс окна, которое
//   выводится на экран. Все окна ОС Windows - объекты, каждый
//   из которых имеет набор характерных черт. Заполняя
//   структуру WNDCLASS, Вы сообщаете ОС Windows, какими хотите
//   видеть конкретные объекты.
// Чаще всего вообще не нужно думать о методе регистрации.
//   Просто скопируйте приведенный фрагмент функции
//   InitApplication( ) в приложение и вызывайте ее даже не
//   думая о ее содержимом. Регистация класса окна не является
//   чем-то таким, о чем необходимо постоянно заботиться - чаще
//   всего фрагмент кода, отвечающий за нее, просто переносится
//   из одного приложения в другое
}

// ****************************************************************
// Создание окна
BOOL InitInstance(                  // Возвращает TRUE при успехе
// Дескриптор текущего приложения
HINSTANCE           hInstance,
// Режим отображения главного окна - определяет в каком виде
//   будет отображено окно приложения
int                 nCmdShow )
{
// Дескриптор главного окна
HWND                hWnd;

// Создание окна
hWnd = CreateWindow(
// Указатель на строку зарегистрированного имени класса
szClassName,
// Указатель на строку заголовка окна
szTitle,
WS_OVERLAPPEDWINDOW,        // Стиль окна
// Горизонтальная координата левого верхнего угла окна
//   (используется умалчиваемое значение)
CW_USEDEFAULT,
// Вертикальная координата левого верхнего угла окна
//   (используется умалчиваемое значение)
CW_USEDEFAULT,
CW_USEDEFAULT,              // Ширина окна (используется
//   умалчиваемое значение)
CW_USEDEFAULT,              // Высота окна (используется
//   умалчиваемое значение)
NULL,                       // Дескриптор родительского
//   окна (его нет)
NULL,                       // Дескриптор меню окна (его
//   нет)
hInstance,                  // Дескриптор экземпляра
//   приложения
NULL );                     // Указатель на дополнительные
//   данные окна (их нет)
if ( !hWnd )
return FALSE;


// Показать окно с дескриптором hWnd: передает в ОС Windows
//   информацию (nCmdShow) о том, в каком виде необходимо
//   отобразить окно
if ( ShowWindow( hWnd, nCmdShow ) )
return FALSE;


// Перерисовать окно: для перерисовки окна функция предписывает
//   ОС Windows послать окну сообщение WM_PAINT
if ( !UpdateWindow( hWnd ) )
return FALSE;

return TRUE;
}

// ****************************************************************
// Оконная процедура
//   Получает очередное сообщение и индивидуально обрабатывает его.
//   Вывод в клиентскую область окна не производится
LRESULT CALLBACK WndProc(           // Возвращает результат
//   обработки сообщения,
//   зависящий от посланного
//   сообщения: 0 - успех
HWND                hwnd,       // Дескриптор созданного окна
UINT                message,    // Номер сообщения
WPARAM              wParam,     // Дополнительная информация о
LPARAM              lParam )    //   сообщении, зависящая от
//   типа сообщения (wParam,
//   lParam)
{
// Обработчик сообщения - в данном случае приложение явно
//   отвечает иолько на сообщение WM_DESTROY, все остальные
//   сообщения передаются в DefWindowProc( ) - функцию,
//   управляющую поведением окна по умолчанию
switch( message )
{
case WM_DESTROY:

// Указывает системе, что сделан запрос о завершении
//   приложения: 0 - код завершения. Вызов этой функции
//   обычно используется в ответ на поступившее сообщение
//   WM_DESTROY
PostQuitMessage( 0 );
break;

default:

// Обработка сообщения по умолчанию
return DefWindowProc( hwnd, message, wParam, lParam );

}

return 0;
}


Категория: Высокоуровневые методы информатики и программирования | Добавил: Ni-Cd (01 Декабря 2011)
Просмотров: 1916 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
  Полезные материалы  

В нашем каталоге файлов можно найти много полезной информации. Также советуем заглянуть в каталог статей: в нем есть полезные статьи по темам: Экономика предприятия, Общая экономика, Финансы и Кредит, также Словарь терминов по экономике, Маркетинг, Бухучет и Мировая экономика
Также есть полезная страница Факультеты МИФИ, которая расскажет о том, какие есть в МИФИ факультеты.
Меню
 

Навигация
Высокоуровневые методы информатики и программирования [28]
Информатика и программирование [34]
Информационные системы в экономике [36]
Языки программирования и методы трансляции [15]
Алгоритмизация и программирование [61]
 

Поиск
 

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

Статистика


Рейтинг@Mail.ru

 


2007 - 2022 © Ni-Cd. All Rights Reserved