Опрос
Вы участвуете в программе Windows Insider?
Популярные новости
Обсуждаемые новости

HTML-приложения: работа с файлами, папками и дисками (ч.3)

Напечатать страницу
12.05.2011 12:28 | dronov_va

Третья, последняя, часть статьи о работе с файлами, папками и дисками в HTML-приложениях.


4. Работа с дисками
Не только файлами и папками придётся нам заниматься. Иногда придётся иметь дело и самими дисками. Поэтому рассмотрим, какие средства WSH предоставляет нам для этого.

4.1. Получение нужного диска
Получить нужный диск мы можем двумя способами. Рассмотрим оба - кто знает, какой именно нам пригодится...

4.1.1. Получение произвольного диска
Проще всего получить произвольный диск - по букве, под которой он присутствует в системе. Для этого мы вызовем метод GetDrive объекта FileSystemObject в таком формате:

<экземпляр объекта FileSystemObject>.GetDrive(
<обозначение диска>
);

В качестве единственного параметра передаётся строка с обозначением нужного нам диска. Этим обозначение может быть:

  • буква, под которой этот диск присутствует в системе (например, "c");
  • собственно обозначение диска, представляющее собой комбинацию буквы, под которой он присутствует в системе, и символа двоеточия (например, "c:");
  • путь к корневой папке этого диска (например, "c:\\");
  • для сетевых дисков - сетевой путь к любой его папке (например, "\\someuser\\somefolder").


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

Метод GetDrive возвращает экземпляр объекта Drive, представляющего диск.

var oD = oFSO.GetDrive("c:");

Получаем диск C.

4.1.2. Получение дисков, присутствующих в системе
Ещё мы можем получить доступ сразу ко всем дискам, присутствующим в системе. Это может пригодиться, если мы собираемся предоставить пользователю выбор из всех доступных дисков.

Объект FileSystemObject поддерживает свойство Drives. Оно хранит экземпляр объекта-коллекции Drives, хранящего все имеющиеся в наличии диски в виде экземпляров объекта Drive.

Для доступа к элементам коллекции Drives мы можем использовать подход, описанный в параграфе 3.3.

var cDrives = oFSO.Drives;
var oEn = new Enumerator(cDrives);
var oF = null;
var s = "";
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
s += oF.Path + "<BR>";
}
var oDivDrives = document.getElementById("divDrives");
oDivDrives.innerHTML = s;

Этот код поместит список обозначений всех дисков, присутствующих в системе, в контейнер divDrives.

4.2. Получение сведений о диске
Получить различные сведения о диске мы можем с помощью различных свойств объекта Drive, которые сейчас рассмотрим. Почти все эти свойства доступны только для чтения; если какое-то из них доступно также и для записи, мы обязательно на это укажем.

Свойство Path возвращает строку с обозначением данного диска, которое представляет собой комбинацию буквы, под которой диск присутствует в системе, и символа двоеточия.

var sDrive = oD.Path;

Свойство RootFolder возвращает экземпляр объекта Folder, представляющий корневую папку данного диска.

var oFol = oD.RootFolder;

Свойство DriveLetter возвращает строку с буквой, под которой этот диск присутствует в системе. Для дисков, которым не присвоена буква, и для несмонтированных сетевых дисков возвращается пустая строка.

var sDriveLetter = oD.DriveLetter;
if (sDriveLetter == "") {
//Это несмонтированный сетевой диск или локальный диск без буквы
} else {
//Это диск с буквой
}

Свойство ShareName возвращает строку с именем, под которым данный сетевой диск разделён в сети. Для локальных дисков возвращается пустая строка.

var sShareName = oD.ShareName;
if (sShareName == "") {
//Это локальный диск
} else {
//Это сетевой диск
}

Свойство VolumeName хранит строку с меткой тома, присвоенной данному диску. Это свойство доступно не только для чтения, но и для записи; присвоив ему новое строковое значение, мы сможем указать для диска новую метку тома.

var oFlashDrive = oFSO.GetDrive("f:");
oFlashDrive.VolumeName = "FLASH_DISK";

Задаём для флеш-диска F новую метку тома - FLASH_DISK.

Свойство DriveType возвращает целое число, обозначающее тип диска. Все доступные типы диска и соответствующие им числа перечислены ниже.

  • 0 - диск неизвестного типа.
  • 1 - сменный диск, например, привод гибких дисков или флеш-диск.
  • 2 - несменный диск, например, жёсткий диск.
  • 3 - сетевой диск.
  • 4 - привод компакт-дисков.
  • 5 - RAM-диск (диск, сформированный в оперативной памяти компьютера).


var cDrives = oFSO.Drives;
var oEn = new Enumerator(cDrives);
var oF = null;
var aDrives[];
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
if (oF.DriveType == 3)
aDrives[] = oF;
}

Перебираем все доступные диски и заносим в массив aDrives только сетевые.

Свойство IsReady возвращает true, если данный диск готов к работе, и false в противном случае. Это свойство имеет смысл использовать только для сменных дисков - оно позволяет узнать, вставлен ли диск в привод.

var cDrives = oFSO.Drives;
var oEn = new Enumerator(cDrives);
var oF = null;
var aDrives[];
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
if (((oF.DriveType == 1) || (oF.DriveType == 4)) && (oF.IsReady))
aDrives[] = oF;
}

Перебираем все доступные диски и заносим в массив aDrives только сменные и приводы компакт-дисков, готовые к работе (те, в которые вставлены диски).

Свойство FileSystem возвращает строку с обозначением файловой системы, в которой отформатирован данный диск. Доступные значения: "FAT", "NTFS" и "CDFS".

if (oD.FileSystem == "NTFS") {
//Диск отформатирован в файловой системе NTFS
} else {
//Диск отформатирован в другой файловой системе
}

Свойство TotalSize возвращает общий объём данного диска в байтах в виде числа.

if ((oD.FileSystem == "FAT") && (oD.TotalSize >= 42949672960)) {
//Диск объёмов в 40 Гбайт и более лучше отформатировать в NTFS
} else {
//Претензий нет
}

Свойства FreeSpace и AvailableSpace возвращают объём свободного пространства на данном диске в байтах в виде чисел. Различие между ними заключается в том, что первое свойство не учитывает квоты, а второе - учитывает.

if (oD.AvailableSpace < 1048576)
//Мало места. Нужен, по меньшей мере, 1 Мбайт

Свойство SerialNumber возвращает серийный номер диска в виде числа.

iNumber = oD.SerialNumber;

4.3. Проверка существования диска
А ещё мы можем выяснить, существует ли в системе диск с указанным нами обозначением. Для этого мы вызовем метод DriveExists объекта FileSystemObject. В качестве единственного параметра он принимает либо обозначение диска в одном из форматов, упомянутых в параграфе 4.1.1, либо путь к любой папке на этом диске. Возвращает он true, если такой привод существует, и false в противном случае.

var sDrive = "h:";
if (oFSO.DriveExists(sDrive)) {
var oD3 = oFSO.GetDrive(sDrive);
. . .
}


5. Работа с путями
В предыдущих параграфах этой статьи имели дело с путями к файлам и папкам. Эти пути включали в себя обозначение диска, список папок, в которых последовательно вложен нужный нам файл (папка), и имя самого файла (папки).

WSH предоставляет нам несколько инструментов для работы с этими путями. Мы можем получать различные составные их части, а также создавать их. Поскольку такие операции приходится выполнять довольно часто, давайте рассмотрим эти инструменты.

5.1. Получение составных частей пути
Объект FileSystemObject предоставляет несколько методов, позволяющих извлечь из пути его составные части: обозначение диска, имя файла, расширение и др. Рассмотрим их.

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

Метод GetBaseName возвращает имя файла без расширения и без точки, отделяющей имя файла от расширения. Если имя файла в пути отсутствует, возвращается пустая строка.

var sFileName = oFSO.GetBaseName("c:\\Work\\testfile.txt");

Выполнив это выражение, мы получим в переменной sFileName строку "testfile".

Метод GetDriveName возвращает обозначение диска (комбинацию буквы, присвоенной диску системой, и символа двоеточия). Если таковое в пути отсутствует, возвращается пустая строка.

var sDrive = oFSO.GetDriveName("c:\\Work\\testfile.txt");

Выполнив это выражение, мы получим в переменной sDrive строку "c:".

Метод GetExtensionName возвращает расширение файла без символа точки. Если такового в пути нет, возвращается пустая строка.

var sExtension = oFSO.GetExtensionName("c:\\Work\\testfile.txt");

Данное выражение поместит в переменной sExtension строку "txt".

Метод GetFileName возвращает имя файла с расширением. Если такового в пути нет, возвращается пустая строка.

var sFileName = oFSO.GetFileName("c:\\Work\\testfile.txt");

После выполнения данного выражения в переменной sFileName окажется строка "testfile.txt".

Метод GetParentFolderName возвращает путь к папке, в которой хранится данный файл, если ему в качестве параметра был передан путь к файлу, либо путь к папке, где хранится данная папка, если был передан путь к папке (то есть путь к папке предыдущего уровня вложенности). Если путь к этой папке не может быть получен, возвращается пустая строка.

var sFolder = oFSO.GetParentFolderName("c:\\Work\\testfile.txt");

Получаем в переменной sFolder строку "c:\\Work".

5.2. Формирование путей
Помимо получения составных частей уже существующего пути, нам потребуется создавать их. Это можно сделать и очевидными средствами - складывая строки, содержащие отдельные части пути, - но удобнее использовать для этого особый метод объекта FileSystemObject.

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

Формат вызова метода BuildPath таков:

<экземпляр объекта FileSystemObject>.BuildPath(
<уже имеющийся путь>,
<добавляемый элемент>
);

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

Метод BuildPath возвращает строку со сформированным путём.

var sPath = oFSO.BuildPath("c:\\Work", "testfile.txt");

После выполнения этого выражения в переменной sPath окажется строка "c:\\Work\\testfile.txt". Отметим, что метод BuildPath при этом сам вставил разделитель составных частей пути - символ обратного слеша.


6. Текущая папка HTML-приложения
Напоследок рассмотрим ещё один важный вопрос, касающийся доступа к файлам и папкам, которые находятся в папке, где расположено само HTML-приложение.

Ранее, чтобы получить файл или папку, мы указывали в вызове соответствующего метода полный путь к нему или ней.

var oF = oFSO.GetFile("c:\\Work\\testdata.txt");

Получаем файл testdata.txt, находящийся в папке Work диска C.

var oFol = oFSO.GetFolder("c:\\Work");

Получаем папку Work диска C.

Но мы можем указать и сокращённый, или относительный, путь к файлу или папке. При этом файл или папка будут искаться в папке, где находится запускаемый HTA-файл приложения. Эта папка называется текущей.

var oF = oFSO.GetFile("testdata.txt");

Получаем файл testdata.txt, находящийся в текущей папке.

var oF = oFSO.GetFile("Others\\testdata.txt");

Получаем файл testdata.txt, находящийся в папке Others текущей папки.

var oFol = oFSO.GetFolder("..\\SomeFolder");

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

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

Но где именно находится текущая папка приложения? Узнать это нам поможет свойство CurrentDirectory объекта WScript.Shell, описанного в параграфе 3.1.2.2. Оно хранит строку с полным путём к текущей папке.

var sCurrentFolderPath = oWSS.CurrentDirectory;

Интересно, что свойство CurrentDirectory доступно не только для чтения, но и для записи. Так что мы при желании сможем сменить текущую папку на другую.

oWSS.CurrentDirectory = oWSS.SpecialFolders("Desktop");

Задаём в качестве текущей папки папку Рабочего стола текущего пользователя. Ох мы там и порезвимся!..

Также нам можем пригодиться метод GetAbsolutePathName объекта FileSystemObject. Он принимает в качестве единственного параметра строку с относительным путём и возвращает строку с полным путём, сформированным на его основе относительно текущей папки.

oWSS.CurrentDirectory = "c:\\Work";
var sPath = oFSO.GetAbsolutePathName("testdata.txt");

Получаем в переменной sPath строку "c:\\Work\\testfile.txt".

var sPath = oFSO.GetAbsolutePathName("Others\\testdata.txt");

А теперь в переменной sPath окажется строка "c:\\Work\\Others\\testfile.txt".

var sPath = oFSO.GetAbsolutePathName("..\\SomeFolder");

Это выражение поместит в переменную sPath строку "c:\\SomeFolder".


7. Пример HTML-приложения, работающего с файлами, папками и дисками
Давайте создадим небольшое HTML-приложение для просмотра папок и файлов, имеющихся на всех присутствующих в системе дисках.

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

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

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

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

Как видим, это приложение ведёт себя как диалоговое окно открытия файлов старого образца; такие окна ещё можно встретить в некоторых приложениях.

HTML-код приложения приведён ниже.

<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1251">
<TITLE>Просмотровщик дисков</TITLE>
<HTA:APPLICATION ID="hta1" APPLICATIONNAME="appDiskViewer" BORDER="thin"
ICON="Disk_viewer.ico" MAXIMIZEBUTTON="no" SCROLL="no" SELECTION="no"
VERSION="1.0"></HTA:APPLICATION>
<SCRIPT>
window.resizeTo(660, 500);

var oFSO = new ActiveXObject("Scripting.FileSystemObject");
var oSelDrives, oSelFolders, oSelFiles, oPCurrentPath, sCurrentFolder = ""

function fillDrivesList()
{
oSelDrives = document.getElementById("selDrives");
oSelFolders = document.getElementById("selFolders");
oSelFiles = document.getElementById("selFiles");
oPCurrentPath = document.getElementById("pCurrentPath");

var oEn = new Enumerator(oFSO.Drives);
var oF, oOption;
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
oOption = document.createElement("OPTION");
oOption.text = oF.Path;
oOption.value = oF.Path;
oSelDrives.add(oOption);
}
}

function fillFoldersList()
{
oPCurrentPath.innerHTML = sCurrentFolder;

while (oSelFolders.options.length > 0)
oSelFolders.options.remove(0);

var oDrive = oFSO.GetDrive(oSelDrives.value);
if (oDrive.IsReady) {
var oFol = oFSO.GetFolder(sCurrentFolder);
var oEn = new Enumerator(oFol.SubFolders);
var oF, oOption;
if (!(oFol.IsRootFolder)) {
oOption = document.createElement("OPTION");
oOption.text = "..";
oOption.value = "..";
oSelFolders.add(oOption);
}
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
oOption = document.createElement("OPTION");
oOption.text = oF.Name;
oOption.value = oF.Name;
oSelFolders.add(oOption);
}
}
}

function fillFilesList()
{
while (oSelFiles.options.length > 0)
oSelFiles.options.remove(0);

var oDrive = oFSO.GetDrive(oSelDrives.value);
if (oDrive.IsReady) {
var oFol = oFSO.GetFolder(sCurrentFolder);
var oEn = new Enumerator(oFol.Files);
var oF, oOption;
for (; !oEn.atEnd(); oEn.moveNext()) {
oF = oEn.item();
oOption = document.createElement("OPTION");
oOption.text = oF.Name;
oOption.value = oF.Name;
oSelFiles.add(oOption);
}
}
}

function goToDrive()
{
sCurrentFolder = oSelDrives.value + "\\";
fillFoldersList();
fillFilesList();
}

function goToFolder()
{
if (oSelFolders.value == "..")
sCurrentFolder = oFSO.GetParentFolderName(sCurrentFolder);
else
sCurrentFolder = oFSO.BuildPath(sCurrentFolder, oSelFolders.value);
fillFoldersList();
fillFilesList();
}
</SCRIPT>
</HEAD>
<BODY ONLOAD="fillDrivesList();">
<FORM>
<TABLE>
<TR>
<TD>
Диски:<BR>
<SELECT ID="selDrives" SIZE="20" STYLE="width: 200px; height: 350px"
ONDBLCLICK="goToDrive();"></SELECT>
</TD>
<TD>
Папки:<BR>
<SELECT ID="selFolders" SIZE="20" STYLE="width: 200px; height: 350px"
ONDBLCLICK="goToFolder();"></SELECT>
</TD>
<TD>
Файлы:<BR>
<SELECT ID="selFiles" SIZE="20" STYLE="width: 200px; height: 350px"></SELECT>
</TD>
</TR>
</TABLE>
</FORM>
<P ID="pCurrentPath"></P>
</BODY>
</HTML>

Веб-форму, в которой находятся все три списка, мы оформили в виде таблицы - так проще всего расположить списки в ряд. Для каждого списка мы задали размеры, причём сделали это дважды. Во-первых, в атрибутах SIZE тегов SELECT мы указали размеры, большие 1, а именно, 20 (число выбрано произвольно), чтобы создать обычные списки (если этого не делать, будут созданы раскрывающиеся списки, поскольку значение атрибута SIZE по умолчанию - 1). Во-вторых, с помощью стилей мы задали размеры, которые списки будут иметь на веб-странице, - 200х350 пикселов. В результате мы получим обычные списки именно таких размеров.

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

Сразу по окончании загрузки веб-страницы возникнет событие load. В качестве обработчика этого события мы указали функцию fillDrivesList, которая выполнит некоторые предварительные действия и заполнит список дисков selDrives.

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

Далее эта функция заполняет список дисков. Она создаёт на основе коллекции дисков экземпляр объекта Enumerator и в цикле "обходит" все его элементы (подробности в параграфах 3.3 и 4.1.2). Для каждого такого элемента-диска создаётся пункт списка дисков selDrives.

Для создания любого элемента веб-страницы на основе имени тега мы используем метод createElement объекта HTMLDocument. (Этот объект представляет саму веб-страницу; единственный его экземпляр, созданный самим браузером, доступен в любом веб-сценарии через переменную document.) Он принимает в качестве параметра строку с именем тега без символов < и > и возвращает экземпляр объекта, представляющий созданный элемент.

Пункт списка HTML представляется как экземпляр объекта HTMLOptionElement. Именно его мы получим, вызвав метод createElement с параметром "OPTION" (этим тегом и создаётся пункт списка). Объект HTMLOptionElement поддерживает свойства text и value, хранящие, соответственно, текст и значение пункта в виде строк. Мы присваиваем этим свойствам обозначение диска.

Созданный экземпляр объекта HTMLOptionElement мы добавим в список дисков. Для этого мы используем метод add объекта HTMLSelectElement, представляющего список HTML.

На этом выполнение функции fillDrivesList заканчивается.

При двойном щелчке мышью на пункте списка возникает событие dblclick. В списке дисков selDrives в качестве обработчика этого события мы указали функцию goToDrive. Она выполнит переход на корневую папку соответствующего диска и заполнит списки папок selFolders и файлов selFiles.

Функция эта очень проста. Сначала она получает обозначение выбранного диска, извлекая значение пункта списка дисков; это значение доступно из упомянутого ранее свойства value объекта HTMLOptionElement. Далее она добавляет к этому обозначению символ обратного слеша, формируя тем самым путь к корневой папке диска, и сохраняет этот путь в объявленной ранее глобальной переменной. После этого она вызывает функции fillFoldersList (выполняет заполнение списка папок) и fillFilesList (заполнение списка файлов).

Рассмотрим функцию fillFolderList. Первым делом она извлекает из глобальной переменной путь к папке, содержимое которой отображается в данный момент (этот путь сформировала функция goToDrive), и выводит его в абзаце, расположенном под списками.

Далее нам следует очистить список папок selFolders от пунктов, которые, возможно, там уже присутствуют; это понадобится при переходе между папками двойными щелчками на пункте этого списка. Здесь приходится применять смекалку, так как простого способа сделать это не существует.

Объект HTMLSelectElement, представляющий список, поддерживает свойство options. Оно возвращает экземпляр объекта-коллекции HTMLCollection, хранящий все пункты этого списка в виде экземпляров объекта HTMLOptionElement.

Объект HTMLCollection поддерживает свойство length, возвращающее количество элементов в коллекции (в нашем случае - пунктов в списке) в виде целого числа. Ещё он поддерживает метод remove, принимающий в качестве единственного параметра целочисленный номер пункта и удаляющий его.

Чтобы удалить все пункты списка, мы запускаем цикл, который будет удалять первый пункт списка (с номером 0), пока в списке есть элементы (пока значение свойства length не равно нулю). Обычный, кстати, приём, применяемый в таких случаях.

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

Сначала мы собственно получаем папку, содержимое которой сейчас отображается; её путь всё ещё хранится в соответствующей глобальной переменной. После этого мы создаём экземпляр объекта Enumerator на основе коллекции папок, вложенных в неё. Эта операция нам уже знакома по функции fillDrivesList.

А теперь - внимание! Мы проверяем, является ли эта папка некорневой, и, если это так, создаём в списке пункт .. (две точки), предназначенный для перехода на папку "выше". Процесс создания нового пункта списка также знаком нам по функции fillDrivesList.

Остальной код этой функции создаёт пункты в списке папок. Там рассматривать нечего - всё нам давно знакомо.

Также нечего рассматривать и в функции fillFilesList.

Осталось рассмотреть функцию goToFolder - обработчик события dblclick списка папок selFolders. Она выполняет переход на папку, соответствующей пункту списка, на котором дважды щёлкнули мышью.

Первым делом эта функция проверяет, на каком именно пункте был выполнен двойной щелчок. Если это пункт .., то выполняется получение папки предыдущего уровня вложенности. В противном случае она добавляет имя выбранной папки к пути к папке, чьё содержимое отображается в данный момент, формируя тем самым путь к выбранной папке. Напоследок она вызывает уже знакомые нам функции fillFoldersList и fillFilesList.

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


Дополнительные материалы



dronov_va, TheVista.Ru Team
Май 2011

Комментарии

Комментариев нет...
Для возможности комментировать войдите в 1 клик через

По теме

Акции MSFT
420.55 0.00
Акции торгуются с 17:30 до 00:00 по Москве
Все права принадлежат © ms insider @thevista.ru, 2022
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 2.34 (Страница создана за 0.258 секунд (Общее время SQL: 0.237 секунд - SQL запросов: 55 - Среднее время SQL: 0.0043 секунд))
Top.Mail.Ru