EnumJobs, JOB_INFO_2, количество страниц отправленных напечать
С помощью EnumJobs() данные о задании принтера JOB_INFO_2. Все данные верные, а количество страниц (TotalPages) всегда показывает равным 0.
В MSDN написано:
TotalPages
Specifies the number of pages required for the job. This value may be zero if the print job does not contain page delimiting information.
Ладно пусть там будет ноль, но стандартный виндовый диспетчер всегда знает количество страниц в задании.
как тогда узнать количество страниц?
On the client side, on devices and printers, right-click on any printer, and select printer properties, and click the Sharing tab. Tick off "Render print jobs on client computers". This will cause the events to fire on the server spool. If you're running server 2008 you can do this on the server as well and it will affect all client jobs.
To set programatically or for more info:
http://msdn.microsoft.com/en-us/library/ff556443.aspx
т.е. если все страницы уже попали в память принтера то info.TotalPages - всегда будет равно 0, а вот если задание будет листов так на 1000 то память все и сразу принять не сможет, вот тогда там будет отличное от 0 значение...
т.е. если все страницы уже попали в память принтера то info.TotalPages - всегда будет равно 0,
сомневаюсь, с чего бы обнулять это поле?
Перед получением информации задание приостанавливается, по этому PagesPrinted тоже равно 0
Практика и исходники 2000-ка, которые мне однажды удалось посмотреть, к то му же это подтверждают. + если задание приостанавливается, то приостанавливается весь процесс обработки задания, там вообще ничего меняться не будет...
Входные данные:
1. Буфер печати принтера.
2. Очередь сервера печати.
3. Приложение, которое печатает.
Работа:
Буфер чист, Очередь чиста. Приложение начинает печатать - StartDoc(hdc,&document);
Буфер чист, Очередь инициализируется.
Приложение - StartPage(hdc);
Очередь получает данные
Приложение - EndPage(hdc);
Страница помещается в буфер - PagesPrinted+1
Приложение - StartPage(hdc);
Очередь получает данные
Приложение - EndPage(hdc);
Страница помещается в буфер - PagesPrinted+1
Приложение - StartPage(hdc);
Очередь получает данные
Приложение - EndPage(hdc);
Страница помещается в буфер но буфер полон - TotalPages+1 - остается в очереди
Приложение - StartPage(hdc);
Очередь получает данные
Приложение - EndPage(hdc);
В очереди еще есть данные - TotalPages+1 - остается в очереди
...
Буфер освобождается
Страница помещается в буфер - PagesPrinted+1,TotalPages-1
...
Приложение заканчивает печать - EndDoc(hdc);
Общее количество страниц - PagesPrinted+TotalPages...
Т.е. оно будет известно только тогда, когда закончится помещение задания в очередь...
Вот код который мониторит принтер и приостанавливает задание, может тут, что не так делаю... (подробные комментарии на русском - желание заказчика:))
{
PRINTER_INFO_2 prninfo = *(PRINTER_INFO_2*)pParam; // копируем информацию о притнтере
HANDLE hPrinter; // дескриптор принтера
HANDLE chObj; // дескриптор события-изменения
DWORD pdwChange; // тип события-изменения
DWORD dwNeeded; // необходимо зарезервировать для массива задач
DWORD dwReturned; // количество задач
JOB_INFO_2* pJobInfo; // указатель на массив задач принтера
DWORD blah;
PRINTER_INFO_2 pin2;
// открываем принтер
if(!OpenPrinter(prninfo.pPrinterName, &hPrinter, NULL))
// если возникла ошибка при открытии
MessageBox(0, "Printer not open", "Error", MB_OK);
// ------ "мониторим" изменения заданий принтера ----- //
chObj = FindFirstPrinterChangeNotification(hPrinter, PRINTER_CHANGE_JOB, 0, NULL);
if (chObj != INVALID_HANDLE_VALUE)
{
while (true)
{
// ждем уведомления об изменении
WaitForSingleObject(chObj, INFINITE);
// принимаем изменение
BOOL fcnreturn = FindNextPrinterChangeNotification(chObj, &pdwChange, NULL, NULL);
if (fcnreturn)
{
// если изменение это добавление нового задания, то...
if(pdwChange == PRINTER_CHANGE_ADD_JOB)
{
// получаем список задач
EnumJobs( hPrinter, 0, 0xFFFFFFFF, 2, NULL, 0, &dwNeeded,&dwReturned );
pJobInfo = new JOB_INFO_2[dwNeeded];
if(!EnumJobs( hPrinter, 0, 0xFFFFFFFF, 2, (LPBYTE)pJobInfo,dwNeeded, &dwNeeded, &dwReturned) )
// если возникла ошибка
MessageBox(0, "Monitoring fail", "Error", MB_OK);
// --- выбираем последнюю задачу и добавляем в нашу очередь --- //
// ждем пока освободится доступ к g_Jobs и захватываем мьютекс
WaitForSingleObject(g_h[0], INFINITE);
// приостанавливаем последнюю задачу
SetJob(hPrinter, pJobInfo[dwReturned - 1].JobId, 2, (LPBYTE)&pJobInfo[dwReturned - 1], JOB_CONTROL_PAUSE);
// добавляем в очередь
g_Jobs.push(pJobInfo[dwReturned - 1]);
// изменяем состаяние семафора
ReleaseSemaphore(g_h[1], 1, NULL);
// освобождаем мьютекс
ReleaseMutex(g_h[0]);
}
}
}
}
else
{
// если возникла ошибка
MessageBox(0, "Monitoring fail", "Error", MB_OK);
}
// закрываем описатели и освобождаем память
FindClosePrinterChangeNotification(chObj);
ClosePrinter(hPrinter);
delete[] pJobInfo;
return 0;
}
К чему это все приводит, сейчас затрудняюсь ответить предметно, не проверял... Но вполне может быть что и к некорректному отображению инфы по заданию(ям) - онож стоит...
У меня была другая реализация - привязка моей очереди не через добавление только нового, а анализ существующих через .JobId. Т.е. если такой есть, обновляем данные, если нет, добавляем новое задание. А если в моей есть а в выданных нет - то удаляем:
//*
//* Поток процесса
//*
//*************************************************************
void TJobsUpdater::ExecuteThreadProcess(void)
{
//****
HANDLE hPrinter;
JOB_INFO_2 info[MAX_ITEM_COUNT];
CHAR value[MAX_TEXT_SIZE];
DWORD size;
DWORD count;
DWORD row;
DWORD id;
DWORD i;
BOOL search;
SYSTEMTIME time;
FILETIME system;
FILETIME local;
// если есть отображаемый список
if( pJobsList )
{
// открываем принтер
if( OpenPrinter(pFrame->PrinterPath,&hPrinter,NULL) )
{
// получаем список заданий печати
if( EnumJobs(hPrinter,0,MAX_ITEM_COUNT,2,(LPBYTE)info,MAX_ITEM_COUNT*sizeof(JOB_INFO_2),&(size=0),&(count=0)) )
{
// выводим информацию о количестве заданий печати
if( count > 0 ) wsprintf(value,"Количество заданий: %d",count);
else wsprintf(value,"Нет заданий на печать");
pStatBar->SetItemName(value,1);
// проходимся по всем строкам
for( row=0; pJobsList->GetRowID(row,&id); row++ )
{
// сбрасываем признак существования элемента
search = FALSE;
// проходимся по всем заданиям на печать
for( i=0; i<count; i++ )
{
// если нашли элемент
if( id == info.JobId )
{
// устанавливаем признак существования элемента
search = TRUE;
break;
}
}
// если элемента нет
if( !search )
{
// удаляем элемент и корректируем номер
// так как после удаления происходит сдвижка строк
if( pJobsList->DeleteRow(row) ) row -= 1;
}
}
// проходимся по всем заданиям на печать
for( i=0; i<count; i++ )
{
// добавляем строку
if( pJobsList->AppendRow(0,info.JobId,&row) )
{
// выводим название документа
pJobsList->SetValue(0,row,info.pDocument);
// если идет печать
if( info.Status & JOB_STATUS_PRINTING )
{
// если ошибка
if( info.Status & (JOB_STATUS_ERROR | JOB_STATUS_OFFLINE | JOB_STATUS_PAPEROUT | JOB_STATUS_BLOCKED_DEVQ) ) wsprintf(value,"Ошибка - Идет печать");
// все нормально
else wsprintf(value,"Идет печать");
}
// просто очередь
else
{
// анализируем
if( info.Status )
{
if( info.Status & JOB_STATUS_BLOCKED_DEVQ ) wsprintf(value,"Документ не может быть напечатан"/*"The driver cannot print the job"*/);
if( info.Status & JOB_STATUS_COMPLETE ) wsprintf(value,"Ожидание"/*"Job is sent to the printer, but the job may not be printed yet"*/);
if( info.Status & JOB_STATUS_DELETED ) wsprintf(value,"Удалено"/*"Job has been deleted"*/);
if( info.Status & JOB_STATUS_DELETING ) wsprintf(value,"Удаление"/*"Job is being deleted"*/);
if( info.Status & JOB_STATUS_ERROR ) wsprintf(value,"Ошибка"/*"An error is associated with the job"*/);
if( info.Status & JOB_STATUS_OFFLINE ) wsprintf(value,"Принтер выключен"/*"Printer is offline"*/);
if( info.Status & JOB_STATUS_PAPEROUT ) wsprintf(value,"Нет бумаги"/*"Printer is out of paper"*/);
if( info.Status & JOB_STATUS_PAUSED ) wsprintf(value,"Пауза"/*"Job is paused"*/);
if( info.Status & JOB_STATUS_PRINTED ) wsprintf(value,"Готово"/*"Job has printed"*/);
if( info.Status & JOB_STATUS_PRINTING ) wsprintf(value,"Идет печать"/*"Job is printing"*/);
if( info.Status & JOB_STATUS_RESTART ) wsprintf(value,"Перезапуск"/*"Job has been restarted"*/);
if( info.Status & JOB_STATUS_SPOOLING ) wsprintf(value,"Идет печать"/*"Job is spooling"*/);
if( info.Status & JOB_STATUS_USER_INTERVENTION ) wsprintf(value,"Ожидание действий пользователя"/*"Printer has an error that requires the user to do something"*/);
}
else wsprintf(value,"");
}
// выводим состояние
pJobsList->SetValue(1,row,value);
// выводим пользователя
pJobsList->SetValue(2,row,info.pUserName);
// выводим печатаемые страницы
wsprintf(value,"%d/%d",info.PagesPrinted,info.PagesPrinted+info.TotalPages);
pJobsList->SetValue(3,row,value);
// выводим время постановки в очередь
SystemTimeToFileTime(&info.Submitted,&system);
FileTimeToLocalFileTime(&system,&local);
FileTimeToSystemTime(&local,&time);
wsprintf(value,"%02d.%02d.%04d в %02d:%02d:%02d",time.wDay,time.wMonth,time.wYear,time.wHour,time.wMinute,time.wSecond);
pJobsList->SetValue(4,row,value);
}
}
}
// нет заданий печати - удаляем все задания
if( !count ) pJobsList->DeleteAllRows();
// закрываем принтер
ClosePrinter(hPrinter);
}
}
// ожидаем 1 секунду - некорректно, но раз никто не жалуется - оставляем пока так
Sleep(1000);
}
PS В MSDN кстати много неточностей и недсказанностей, особенно если там указано что в 2000 так же как и последующих версиях...
PPS А коментарии к коду это правильно без всяких там заказчиков. У меня в рабочих проектах как правило их в два раза больше чем самого кода. При этом кроме меня исходники никто больше не видит, зато если необходимо вернуться (типа выпустить патч) через пару месяцев или лет, то процесс "вспоминания что я там делал" не является проблемой...
Программа должна отловить и приостановить задание, затем вывести информацию о нем (в том числе и количество страниц), юзер жмет одну из кнопок: продолжить или отменить задание.
реализовано у меня это так:
1. "перечисляем" принтеры EnumPrinters
2. для каждого принтера запускаю, приведенный выше, monitorThread
3. "монитор" останавливает задания и инфу отправляет в мою очередь
4. есть один поток, который, если моя очередь не пуста, выбирает из нее задания, по одному, и отображает инфу
5. юзер "продолжает" или "отменяет" задание, поток даем ему еще задание, если есть
По этому надо делать следующее - анализировать изменение задания (проверил, приостановка - это приостановка печати, а не постановки в очередь) и динамически менять состояние о задании, которое дается пользователю. Так как количество страниц будет известно только тогда, когда все задание будет поставлено в очередь и закрыто EndDoc.