enumérer toutes les imprimantes installées sur le PC en local ou en réseau
Element>pour chaque imprimante afficher dans une ListView les paramètres suivants
Charger et Sauvegarder la ListView dans un fichier au format CSV
Consommer un minimum de resources Systèmes
Possibilité de debugging et de trace (Log File) par paramètre dans la ligne de commande
Date
Heure
JobId
ComputerName
User Name
Printer Name
Document
Page
Taille
2.
Solution Proposée
Image de l'application.
pour répondre au point 5 du cahier des charges on va utiliser les API Win32.
HANDLE FindFirstPrinterChangeNotification(
HANDLE hPrinter, // handle to printer or print server to monitor for changes DWORD fdwFlags, // flags that specify the conditions to monitor DWORD fdwOptions, // reserved, must be zero
LPVOID pPrinterNotifyOptions // pointer to structure specifying printer information to monitor
);
BOOL FindNextPrinterChangeNotification(
HANDLE hChange, // handle to change notification object of interest
PDWORD pdwChange, // pointer to a value that indicates the condition that changed
LPVOID pPrinterNotifyOptions, // pointer to a structure that specifies a refresh flag
LPVOID *ppPrinterNotifyInfo // pointer to a pointer that receives printer information buffer
);
BOOL FindClosePrinterChangeNotification(
HANDLE hChange // handle to change notification object to close
);
Ces fonctions s'utilisent en relation avec les "Wait Function" des API Win32 ce qui nécessite l'utilisation d'une
classe dédiée de type TThread
On doit tout d'abord réaliser les points suivants
Analyse de la ligne de commande pour déterminer si le mode debug est activé (param -debug)
Rechercher et stocker dans une StringList toutes les imprimantes installées
créer une classe de type TThread pour le monitoring
int__stdcall CustomSortProc(long Item1, long Item2, long ParamSort);
class TForm1 : public TForm
{
__published: // Composants gÉrÉs par l'EDI
TListView *ListView1; void__fastcall FormDestroy(TObject *Sender); private : int FileHandle; bool FDebug;
TStringList* FPrinterList; // Liste des Imprimantes
AnsiString FDefaultPrinter; // nom de l'imprimante par défaut
AnsiString FPrinter; // nom de l'imprimante
HANDLE FHandle;
TJobInfoRecord* FJobRecord; // Enregistrement d'un job
TList* FJobListRec; // Liste des Enregistrements de Job
TPrinterNotifier* FPrintNotifier; // Ptr sur ThreadPrinterNotifier
TSortJobThread* FSortJobThread ; // Ptr sur TSortJobThread int FThreadCount; // compteur de ThreadPrinterNotifier
TList* FThreadList; // Liste de Ptr sur ThreadPrinterNotifier
TStringList* FJobList; // Liste de Jobs (format csv) void__fastcall GPrinterNotifier(TMessage &msg); void__fastcall GUpdateListView(TMessage &msg); void__fastcall FindInstalledPrinters(); void__fastcall GetDefaultPrinter(); bool__fastcall IsValidHandle(AnsiString &PrinterName); void__fastcall SaveListViewToFile(); void__fastcall LoadListFromFile(); void__fastcall WriteLog(TJobInfoRecord *NewRec,int n);
// Déclarations de l'utilisateur public : // Déclarations de l'utilisateur __fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_PrinterNotifier,TMessage,GPrinterNotifier)
MESSAGE_HANDLER(WM_SortedListNotifier,TMessage,GUpdateListView)
END_MESSAGE_MAP(TForm)
on va tout d'abord s'intéresser a rechercher toutes les imprimantes installées
l'implémentation de la méthode FindInstalledPrinters() est définie de la manière suivante
// find all installed Printers and Save PrinterList in FPrinterList { ;
DWORD Count=0,InfoCount=0,Flags=0,Level=0; char ** PPBufContent; unsignedchar *PBuffer,* Buffer; if (Win32Platform == VER_PLATFORM_WIN32_NT) // test si NT,XP
{
Flags=PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL;
Level=4;
} else
{
MessageDlg("Désolé cette application ne fonctionne que sous environnement\nWindows NT ou Windows XP",
mtError,
TMsgDlgButtons() << mbOK, 0);
Application->Terminate();
}
EnumPrinters(Flags,
NULL,
Level, // API Windows
NULL, // Count retourne la taille
0, // nÉcessaire pour le Buffer
&Count,
&InfoCount); if (Count==0) return ; // pas d'imprimante -->Exit
Buffer= newunsignedchar [Count]; // crÉation du Buffer
PBuffer= Buffer; // PBuffer pointe sur le Buffer
EnumPrinters(Flags,
NULL,
Level, //InfoCount contient le
Buffer, // Nbre d'ÉlÉments de type
Count, // PRINTER_INFO_4
&Count, // stockÉ dansle Buffer
&InfoCount); if (InfoCount)
{
FPrinterList->Clear(); // on efface la Liste for (DWORD n=0;n<InfoCount;n++)
{
PPBufContent=(char **)Buffer; //PPBufContent contient le contenu
Buffer+=sizeof (PRINTER_INFO_4); // dÉplacement dans le Buffer
FPrinterList->Add(*PPBufContent); // on additionne le rÉsultat dans
}
} // la StringListe delete []PBuffer; // destruction du Buffer
}
;
}
on teste tout d'abord si le système d'exploitation est valide,car les fonctions de notifications ne sont implémentées que sous NT respectivement XP on utilise ensuite l'API win32 EnumPrinters qui retourne dans la variable Count la taille du buffer nécesaire pour stocker toutes les imprimantes trouvées dans le format PRINTER_INFO_4
on appelle donc cette API EnumPrinters 2 fois de suite
lors du second appel on a créé en mémoire un buffer d'unsigned char de taille égale
a la variable Count en sortie de la fonction la variable InfoCount est initialisée
avec le nombre d'imprimantes trouvées dans le format
PRINTER_INFO_4 un résultat de 0 indique une erreur
le format du Buffer retourné par Windows est le suivant
FPrinterList->Clear(); // on efface la Liste { ; for (DWORD n=0;n<InfoCount;n++)
{
PPBufContent=(char **)Buffer; //PPBufContent contient le contenu
Buffer+=sizeof (PRINTER_INFO_4); // dÉplacement dans le Buffer
FPrinterList->Add(*PPBufContent); // on additionne le rÉsultat dans la StringListe
}
}
;
pour les premiers test il peut être intéressant de travailler avec l'imprimante par défault
2.a.
méthode GetDefaultPrinter
void__fastcall TForm1::GetDefaultPrinter()
{ { ; long BuffLength=0L; // recherche de l'imprimante par dÉfault char DefaultPrinter [90];
BuffLength=GetProfileString("windows", "device",
NULL,
DefaultPrinter, sizeof (DefaultPrinter)); if (BuffLength)
{
FDefaultPrinter=DefaultPrinter;
FDefaultPrinter=FDefaultPrinter.SubString(1,FDefaultPrinter.Pos(",")-1);
} // RÉsultat dans FDefaultPrint
}
;
}
pour utiliser les fonctions de notifications on doit envoyer comme paramètre le Handle de l'imprimante
2.b.
méthode IsValidHandle
bool__fastcall TForm1::IsValidHandle(AnsiString & PrinterName)
{ bool ReturnValue=false; if (PrinterName!="")
{
ReturnValue=OpenPrinter(PrinterName.c_str(),
&FHandle,
NULL);
} // on essaye d'obtenir un Handle pour return ReturnValue; // l'imprimante PrinterName
} // on retourne le rÉsultat
le Handle est stoké si valide dans le champ FHandle
2.c.
sauvegarde de la listView
void__fastcall TForm1::SaveListViewToFile()
{
ListView1->Items->BeginUpdate(); if (FDebug)
{
SetCurrentDirectory("..");
ListView1->CustomSort(CustomSortProc,1);
}
TListItem* mListItem;
AnsiString Jobtxt;
AnsiString Separator=";"; for (int n=0;n<ListView1->Items->Count;n++)
{ // affectation de la premiÈre ligne
mListItem=ListView1->Items->Item[n]; // de la listView À mListItem
Jobtxt=mListItem->Caption; //affectation de Caption a Jobtxt
Jobtxt+=Separator; // ajout du sÉparateur for (int i=0;i<8;i++) // itÉration dans la ligne N
{ // ÉlÉment I
Jobtxt+=mListItem->SubItems->Strings[i];//affectation de Element[i]À Jobtxt
Jobtxt+=Separator; // ajout du sÉparateur
}
FJobList->Add(Jobtxt); // stockage de l'ansiString RÉsultant
} // dans la la JobList
FJobList->SaveToFile(ExtractFilePath(ParamStr(0))+JobFileName);// sauvegarde
ListView1->Items->EndUpdate(); // fin du traitement de la ListView
}
on commence la méthode avec BeginUpdate ce qui va accélérer le traitement en empêchant l'affichage de mise a jour
de la listView dans le mode debug les fichier de trace sont mémorisé dans le directory Log le séparateur de texte
est défini par <>
on itére simplement dans la listview, on fabrique une ligne complète avec les différents éléments
de l'affichage,on stocke la ligne résultante dans la StringList et enfin on sauve le contenu de la StringList dane un fichier au format csv
2.d.
Lecture du fichier et initialisation de la listVIew
void__fastcall TForm1::LoadListFromFile()
{ try
{
FJobList->LoadFromFile(ExtractFilePath(ParamStr(0))+JobFileName);
ListView1->Items->BeginUpdate(); // dÉbut du traitement de la listView
TListItem* mListItem;
AnsiString Jobtxt;
AnsiString Separator=";"; int PosSep; for (int n=0;n<FJobList->Count;n++)
{
FJobRecord=new TJobInfoRecord();
mListItem=ListView1->Items->Add();
Jobtxt= FJobList->Strings[n]; // extraction de la chaine
PosSep=Jobtxt.Pos(Separator); // recherche du premier sÉparateur
FJobRecord->jDate=Jobtxt.SubString(1,PosSep-1);
mListItem->Caption=FJobRecord->jDate; // Caption= 1 er ÉlÉment
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator); // on efface le premier ÉlÉment
FJobRecord->jtime=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jtime);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jID=atol(Jobtxt.SubString(1,PosSep-1).c_str());
mListItem->SubItems->Add(FJobRecord->jID);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jComputer=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jComputer);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jUser=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jUser);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jPrinter=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jPrinter);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jDocument=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jDocument);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jPages=atoi(Jobtxt.SubString(1,PosSep-1).c_str());
mListItem->SubItems->Add(FJobRecord->jPages);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
mListItem->SubItems->Add(Jobtxt.SubString(1,PosSep-1)); int Pos= Jobtxt.Pos("o"); if (Pos !=-1)
{
Jobtxt=Jobtxt.SubString(1,Pos-2);
FJobRecord->jSize=1024*atol(Jobtxt.c_str());
} else
FJobRecord->jSize=atol(Jobtxt.SubString(1,PosSep-1).c_str());
FJobListRec->Add(FJobRecord);
} // fin du traitement de la ListView
FJobList->Clear();
ListView1->CustomSort(CustomSortProc,0);
ListView1->Items->EndUpdate(); // on efface le contenu de la List
} catch (...){};
}
//---------------------------------------------------------------------------
on utilise le traitement des execptions pour empêcher intercepter C++ Builder
d'afficher une fenêtre d'erreur lors de la première utilisation du programme le fichier n'existant pas
l'appel à BeginUpdate empêche l'affichage de mise a jour de la listView
on itère ensuite dans le contenu de la stringList en utilisant le separateur<;>
pour séparer les éléments de la ListView
2.e. Procédure de tri en fonction de la date et l'heure
int__stdcall CustomSortProc(long Item1, long Item2, long ParamSort)
{
TDateTime n1,n2;
TListItem *i1,*i2;
i1=(TListItem*)Item1;
i2=(TListItem*)Item2;
n1=TDateTime(i1->Caption)+TDateTime(i1->SubItems->Strings[0]);
n2=TDateTime(i2->Caption)+TDateTime(i2->SubItems->Strings[0]); if (n1==n2) return 0; if (ParamSort)
{ if (n1>n2) return 1; if (n1;<n2) return -1;
} else
{ if (n1>n2) return -1; if (n1;<n2) return 1;
}
}
2.f. Constructeur de la Classe Principale
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{ if (ParamCount())
{ if (ParamStr(1).LowerCase()=="-debug")
{ if (!DirectoryExists("log"))
CreateDir("Log");
SetCurrentDirectory("Log");
FDebug=true;
AnsiString Temp;
Temp="Logfile -";
Temp+=Date();
Temp+=" .log";
FileHandle=FileCreate(Temp);
Temp="Date\tHeure\tThreadId\tJobId\tComputer\tUser\tPrinter\tDocument\tPages\tSize";
Temp+="\r\n";
FileWrite(FileHandle,Temp.c_str(),Temp.Length());
}
}
FJobListRec= new TList(); // Construction de la Liste des Enregistrements de Job
FPrinterList= new TStringList(); // Construction de la Liste des imprimantes
FJobList=new TStringList(); // Construction de la Liste des Jobs
LoadListFromFile(); // Chargement du fichier JobListe +
FSortJobThread= new TSortJobThread(false,FJobListRec);// création de ThreadSortedJobList
FThreadList= new TList(); // Remplissage de la ListeView
FindInstalledPrinters(); // Recherche des Imprimantes Installées for (int i=0;i<FPrinterList->Count;i++)
{
FPrinter=FPrinterList->Strings[i]; // GetDefaultPrinter(); // utilisé pour le Test // FPrinter=FDefaultPrinter; // avec imprimante par défaut if (IsValidHandle(FPrinter)) //si Handle Valide Construction d'un
{ // nouveau thread par imprimante
FPrintNotifier= new TPrinterNotifier(false,
FPrinter.c_str(),
FThreadList->Count,
FHandle,FDebug);
FThreadList->Add((TPrinterNotifier*)FPrintNotifier);
} // ajout du Pteur de thread dans la Liste
}
}
on teste tout d'abord si le programme a été lancé avec une ligne de commande
si la réponse est positive et la valeur du paramètre de la ligne de commande égale à<-debug>
on crée le directory Log s'il n'existe pas,on place la champ FDebug à true ,on crée un fichier log avec son entête
on crée une Liste FJobListRec de type TList,la StringList contenant les imprimantes, une StringListe contenant la Liste des Jobs
Chargement du fichier JobListText dans la ListView,création d'une Liste contenant tous les Threads crées
recherche des Imprimante
on itère ensuite dans la StringList , on teste si le Handle est valide,si oui construction d'un object Thread par imprimante
on stocke le pointeur résultant dans dans la Liste de Thread
2.g. Destruction de la Classe Principale
delete FSortJobThread;
SaveListViewToFile(); // on sauve le contenu de la Listview dans un fichier delete FPrinterList; // destruction de la Liste for (int n=0;n<FThreadList->Count;n++) // destruction du contenu de la liste delete (TPrinterNotifier*)FThreadList->Items[n]; delete FThreadList; // destruction de la Liste des PtrThreads for (int n=0;n<FJobListRec->Count;n++) // destruction du contenu de la liste delete (TJobInfoRecord*)FJobListRec->Items[n]; delete FJobListRec; // destruction de la Liste des PtrThreads delete FJobList; // destruction de la JobListe if (FDebug)
FileClose(FileHandle);
Sauvegarde de la listView dans le fichier JobList, destruction de la stringList contenant les imprimantes
destruction de tous les objets threads créés
destruction de la Liste de Threaad, fermeture du fichier de log si mode debug est activé
2.h. Classe TPrinterNotifier
la classe TPrinterNotifier encapsule les API Win32 FindFirstPrinterChangeNotification et FindNextPrinterChangeNotification
le but de cette classe est de transférer à la Form Principale (MainForm.pas) les informations sur le Job Courant
le problème principal en utilisant un object Thread est la synchronisation avec le Thread VCL Principal
l'utilisation de la méthode synchronize() fonctionne bien mais est relativement lente
dans notre cas si par exemple 3 imprimantes sont installées et que 3 taches d'impression arrivent des problèmes de vitesse vont apparaître
une solution relativement simple pour contourner ce problème est l'envoi de message aux ThreadVcl avec la méthode
PostMessage,dans ce cas c'est la boucle de message de Windows qui synchronizera l'ensemble
dans la FormPrincipale on va ajouter un gestionnaire pour le message cré ainsi qu'une méthode adéquate
void__fastcall GPrinterNotifier(TMessage &msg); public : // DÉclarations de l'utilisateur __fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_PrinterNotifier,TMessage,GPrinterNotifier)
END_MESSAGE_MAP(TForm)
le message WM_PrinterNotifier WM_APP+2000 est défini dans l'entête de fichier
le gestionnaire de message sera abordée ultérieurement
pour tansférer les informations sur le job j'ai défini des property permettant un accès en lecture seule des champs stockés dans la partie private de ma classe
j'ai ajouté également un nouveau constructeur avec mes besoins spécifiques,Id,PrinterName,PrinterHandle,debug)
2.j. constructeur de la classe TPrinterNotifier
// constructeur du ThreadNotifier avec passage des paramÈtres // PrinterName, ThreadId(Tid), Handle du Printer __fastcall TPrinterNotifier::TPrinterNotifier(bool CreateSuspended , constchar * PrinterName, int TId,HANDLE PrinterHandle, bool ActiveDebug)
: TThread(CreateSuspended)
{
FDebugMode=ActiveDebug;
FPrinterName=PrinterName;
FThreadID=TId;
hPrinter=PrinterHandle; if (FDebugMode)
{
AnsiString Temp(LogFileName);
Temp+=" ";
Temp+=FPrinterName.SubString(3,20); int pos=Temp.LastDelimiter("\\"); if (pos !=-1)
Temp.Delete(pos,2);
Temp+=" ";
Temp+=this ->ThreadID;
Temp+=FileExt;
FileHandle=FileCreate(Temp);
Temp="Date\tHeure\tThreadId\tJobId\tComputer\tUser\tPrinter\tDocument\tPages\tSize";
Temp+="\r\n";
FileWrite(FileHandle,Temp.c_str(),Temp.Length());
}
}
si le paramètre Activedebug est actif on crée un nouveau Fichier par Object TPrinterNotifier nommé par concaténation du printerName et du ThreadID
2.k. Méthode Execute
la méthode Execute est le coeur de toute l'application,on trouve à l'intérieur l'appel aux fonctions
FindFirstPrinterChangeNotification et FindNextPrinterChangeNotification
PRINTER_NOTIFY_INFO *Pni; // déclaration des différentes Variables unsignedlong i; char *pBuf; unsignedlong *adwData;
PRINTER_NOTIFY_OPTIONS Pob; // Pnobt contient
PRINTER_NOTIFY_OPTIONS_TYPE Pnobt[1];// 1 élément PRINTER_NOTIFY_OPTIONS_TYPE unsignedshort jobnf[10]; // Array JobInf unsignedlong ChangeReason;
i=0;
jobnf[i++] = JOB_NOTIFY_FIELD_PRINTER_NAME; // attribution des
jobnf[i++] = JOB_NOTIFY_FIELD_MACHINE_NAME; // différentes valeurs
jobnf[i++] = JOB_NOTIFY_FIELD_USER_NAME; // devant être notifiées
jobnf[i++] = JOB_NOTIFY_FIELD_DOCUMENT;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_PAGES;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_BYTES;
Pnobt[0].Type = JOB_NOTIFY_TYPE; // type de notification
Pnobt[0].Count = i; // nombre d'éléments dans PField
Pnobt[0].pFields = jobnf; // assignation de jobnf
Pob.Version = 2; // Version toujours = à 2
Pob.Count = 1; // 1 élément PRINTER_NOTIFY_OPTIONS_TYPE
Pob.pTypes = Pnobt; // assignation de Pnobt // FindFirstPrinterChangeNotification retourne un Handle sur un objet de // notification initialisé avec PRINTER_CHANGE_ALL et la variable Pob
hNotifier = FindFirstPrinterChangeNotification( hPrinter,
PRINTER_CHANGE_ALL,
0,
&Pob);
// pas de notifier;; while (!Terminated) // hNotifier égal au handle de
{ // l'object notification if (!hNotifier) return ;
WaitForSingleObject(hNotifier, INFINITE); // notification a eu lieu if (!Terminated) // ordre de fin du thread ?
{
Pob.Flags = 0; //demande de refresh // FindNextPrinterChangeNotification rempli le contenu du pointeur pointé par // Pni avec les éléments de notifications (jobinf) ayant changé
FindNextPrinterChangeNotification(hNotifier,
&ChangeReason,
&Pob,
(void **)&Pni);
if (Pni != NULL) // si pointeur diférent de NULL
{ for (i=0; i<Pni->Count; i++) // Itération
{
pBuf =(char *) Pni->aData[i].NotifyData.Data.pBuf;
adwData = Pni->aData[i].NotifyData.adwData; // assigantion des résultats
FId=Pni->aData[i].Id; switch (Pni->aData[i].Field) // itération dans la valeur
{ // des champs case JOB_NOTIFY_FIELD_PRINTER_NAME:FPrinter=pBuf; break ; // assignation des membres case JOB_NOTIFY_FIELD_MACHINE_NAME:FComputer=pBuf; // de la classe break ; case JOB_NOTIFY_FIELD_USER_NAME: FUser=pBuf ; break ; case JOB_NOTIFY_FIELD_DOCUMENT:FDocument=pBuf; break ; case JOB_NOTIFY_FIELD_TOTAL_PAGES:FTotPages=adwData[0] ; break ; case JOB_NOTIFY_FIELD_TOTAL_BYTES:FSize=adwData[0]; break ;
}
} // plusieurs notifications peuvent avoir lieu succéssivement // les champs ne sont pas tous remplis simultanément // on doit tester si le nombre de pages est // >=1 et que le champ Size soit >0 if (FDebugMode) PostLog(); if ((FSize>0)&&(FTotPages>=1))
PostMessage(Form1->Handle,WM_PrinterNotifier,FThreadID,0);
} // on poste le message au Thread VCL Principal (Form1)
FreePrinterNotifyInfo(Pni); // libération du Buffer
}
}
} //---------------------------------------------------------------------------
on crée tout d'abord 4 variable
PRINTER_NOTIFY_INFO *Pni; // Buffer initialisé en retour de l'appel à FindNextPrinterChangeNotification
PRINTER_NOTIFY_OPTIONS Pob; // Pnobt contient
PRINTER_NOTIFY_OPTIONS_TYPE Pnobt[1];// 1 ÉlÉment PRINTER_NOTIFY_OPTIONS_TYPE unsignedshort jobnf[10];
on va ensuite initialiser le Tableau Jobnf avec les informations devant être notifiées
i=0;
jobnf[i++] = JOB_NOTIFY_FIELD_PRINTER_NAME; // attribution des
jobnf[i++] = JOB_NOTIFY_FIELD_MACHINE_NAME; // diffÉrentes valeurs
jobnf[i++] = JOB_NOTIFY_FIELD_USER_NAME; // devant Être notifiÉes
jobnf[i++] = JOB_NOTIFY_FIELD_DOCUMENT;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_PAGES;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_BYTES;
on initialise ensuite le tableau Pnobt[],ainsi que la variable Pob de type PRINTER_NOTIFY_OPTIONS
Pnobt[0].Type = JOB_NOTIFY_TYPE; // type de notification
Pnobt[0].Count = i; // nombre d'ÉlÉments dans PField
Pnobt[0].pFields = jobnf; // assignation de jobnf
Pob.Version = 2; // Version toujours = À 2
Pob.Count = 1; // 1 ÉlÉment PRINTER_NOTIFY_OPTIONS_TYPE
Pob.pTypes = Pnobt; // assignation de Pnobt
l'appel suivant à FindFirstPrinterChangeNotification va nous fournir un Handle qui sera initialisé par
windows lors d'une notification de Job
on attend cette notification avec la procédure WaitForSingleObject
dés la signalisation de l'évenemment le programme sort de WaitForSingleObject
On initialise le membre Flags de la variable Pob à true ce qui a pour effet de demander un refresh a Windows
l'appel suivant à FindNextPrinterChangeNotification initialise la Pointeur pni avec les éléments notificateurs
if (Pni != NULL) // si pointeur diférent de NULL
{ for (i=0; i<Pni->Count; i++) // Itération
{
pBuf =(char *) Pni->aData[i].NotifyData.Data.pBuf;
adwData = Pni->aData[i].NotifyData.adwData; // assigantion des résultats
FId=Pni->aData[i].Id; switch (Pni->aData[i].Field) // itération dans la valeur
{ // des champs case JOB_NOTIFY_FIELD_PRINTER_NAME:FPrinter=pBuf; break ; // assignation des membres case JOB_NOTIFY_FIELD_MACHINE_NAME:FComputer=pBuf; // de la classe break ; case JOB_NOTIFY_FIELD_USER_NAME: FUser=pBuf ; break ; case JOB_NOTIFY_FIELD_DOCUMENT:FDocument=pBuf; break ; case JOB_NOTIFY_FIELD_TOTAL_PAGES:FTotPages=adwData[0] ; break ; case JOB_NOTIFY_FIELD_TOTAL_BYTES:FSize=adwData[0]; break ;
}
} // plusieurs notifications peuvent avoir lieu succéssivement // les champs ne sont pas tous remplis simultanément // on doit tester si le nombre de pages est // >=1 et que le champ Size soit >0 if (FDebugMode)PostLog();
Si le pointeur est non NULL on itère dans le buffer, on assigne les variables pBuf et adwData
il ne reste plus qu'a travers un switch/case a assigner les membres concernés
il reste un problème à régler dans le cas d'un document il se peut que l'on aie plusieurs notifications
de suite pour le même Job
par exemple une page supplémentaire arrive dans le spooler on devra donc filter les enregistrements recu par JobId
le premier filtre est simplement établi de la manière suivante un message est envoyé à la forme principale
uniquement si le nombres de pages est plus grand que 1 et que la taille du document soit supérieur à 0
en placant un <-debug> dans la ligne de commande l'étude des fichiers trace sera très utiles
pour comprendre la problématique de ces notifications multiples
if (FDebugMode)PostLog(); if ((FSize>0)&&(FTotPages>=1))
PostMessage(Form1->Handle,WM_PrinterNotifier,FThreadID,0);
} // on poste le message au Thread VCL Principal (Form1)
FreePrinterNotifyInfo(Pni); // libÉration du Buffer
dans cette méthode on crée un enregistrement par notification avec l'heure en format hh:mm:ss:ms
2.m. destructeur de la classe TPrinterNotifier
__fastcall TPrinterNotifier::~TPrinterNotifier()
{ if (Suspended) Resume();
SetEvent(hNotifier);// on sort du WaitforSingleObjet
Terminate(); // ordre de sortie du Thread (Terminated=true) if (hNotifier) WaitFor(); // Attente de la fin du Thread
FindClosePrinterChangeNotification(hNotifier); //Libération du Handle hNotifier
ClosePrinter(hPrinter); // libération du Handle hPrinter if (FDebugMode)FileClose(FileHandle);
}
Retour a la Classe Principale
on a vu dans la classe précédente que l'object PrinterNotifier comuniquait avec le thread principal
par message interposé,ainsi que de la nécessité de supprimer les notifications identiques pour le même Job
on créera donc une nouvelle classe qui aura pour fonctions de supprimer les multiples notifications
2.n. gestionnaire de message WM_PrinterNotifier
void__fastcall TForm1::GPrinterNotifier(TMessage &msg)
{ // FPrintNotifier contient le Ptr sur le Thread qui a transmis le Msg
FSortJobThread->Suspend(); // on suspend le fonction du SortedThread
FPrintNotifier=(TPrinterNotifier*)FThreadList->Items[msg.WParam];
FPrintNotifier->Suspend(); //on suspend le fonctionnemnt du thread émetteur
TJobInfoRecord *NewRec= new TJobInfoRecord();
NewRec->jDate=Date();
NewRec->jtime=Time();
NewRec->jID=FPrintNotifier->Id;
NewRec->jComputer=FPrintNotifier->Computer;
NewRec->jPrinter=FPrintNotifier->Printer;
NewRec->jUser=FPrintNotifier->User;
NewRec->jDocument=FPrintNotifier->Document;
NewRec->jPages=FPrintNotifier->TotPages;
NewRec->jSize=FPrintNotifier->Size;
FJobListRec->Add((TJobInfoRecord*)NewRec); if (FDebug)WriteLog(NewRec,msg.WParam);
FPrintNotifier->Resume();
FSortJobThread->Resume();
} //---------------------------------------------------------------------------
FSortJobThread est un pointeur sur le classe de tri que nous créerons ultérieurement
on stoppe le fonctionnement de ce Thread
FPrintNotifier contient le Ptr sur le Thread qui a transmis le Msg, on crée ensuite un nouvel objet de type
TJobInfoRecord et on initialise ses champs avec les property du Thread transmetteur
si mode debug on écrit dans le LogFile,on ajoute l'object de Type TJobInfoRecord dans une TList qui sera triée par
la classe de tri
2.o. gestionnaire de message WM_SortedListNotifier
le message WM_SortedListNotifier est généré par la classe de tri
void__fastcall TForm1::GUpdateListView(TMessage &msg)
{
FSortJobThread->Suspend();
ListView1->Items->BeginUpdate(); // début du traitement de la listView
ListView1->Items->Clear();
TListItem* mListItem;
TJobInfoRecord *First; for (int n=0;n<msg.WParam;n++)
{
First=(TJobInfoRecord*)FJobListRec->Items[n];
mListItem=ListView1->Items->Add();
mListItem->Caption=First->jDate;
mListItem->SubItems->Add(First->jtime);
mListItem->SubItems->Add(First->jID);
mListItem->SubItems->Add(First->jComputer);
mListItem->SubItems->Add(First->jUser);
mListItem->SubItems->Add(First->jPrinter);
mListItem->SubItems->Add(First->jDocument);
mListItem->SubItems->Add(First->jPages); if (First->jSize >1024)
{
AnsiString Size= First->jSize 1024; // test pour affichage
mListItem->SubItems->Add(Size+ " Ko"); // en octets ou en Ko
} else
mListItem->SubItems->Add(First->jSize);
}
ListView1->CustomSort(CustomSortProc,0);
ListView1->Items->EndUpdate();
FSortJobThread->Resume();
} //---------------------------------------------------------------------------
on suspend d'abord le fonctionnement de la classe de tri
on empêche la mise à jour de l'affichage dans la list View
on efface le contenu de la list View
on remplit itère dans la FJobListRec pour remplir la ListView
affichage en Ko de la taille du document
on trie la liste en sens inverse
on affiche le résultat
on reprend le fonctionnement de la classe de tri
2.p. Classe de Tri
cette classe a pour but d'éliminer les enregistrements en double
//--------------------------------------------------------------------------- #ifndef SortedJobThreadH #define SortedJobThreadH //--------------------------------------------------------------------------- #include <Classes.hpp> #define WM_SortedListNotifier WM_APP+2010 //--------------------------------------------------------------------------- class TSortJobThread : public TThread
{ private :
TList* FSortedList; int FCounter; int FThreadNbr; protected : void__fastcall Execute(); public : __fastcall TSortJobThread(bool CreateSuspended): TThread(CreateSuspended){}; __fastcall TSortJobThread(bool CreateSuspended, TList* JobList,int ThreadNbr); __fastcall ~TSortJobThread();
}; //--------------------------------------------------------------------------- #endif
on utilise la même méthode de communication avec le Thread VCl Principal ce qui nécessite la création d'un message
WM_SortedListNotifier
on déclare un nouveau constructeur avec comme paramètre supplémentaire un pointeur sur la JobList
void__fastcall TSortJobThread::Execute()
{ int Iter=0,FirstPos=1;
TJobInfoRecord *First,*Second; while (!Terminated)
{ while (Iter<=(FSortedList->Count-2)) // premier élément est égal au second
{ // on echange les éléments dans la liste,puis on efface le premoier élément if (Terminated) break ; // on teste si on trouve un doublon dans la liste
First=(TJobInfoRecord*)FSortedList->Items[Iter]; // n° Test (elem* elem/2)-elem; // teste en boucle avec tous les élément de la listewhile (FirstPos<FSortedList->Count)
{ if (Terminated) break ;
Second=(TJobInfoRecord*)FSortedList->Items[FirstPos]; if ((First->jID== Second->jID)&&(First->jDate==Second->jDate )) // même Id et Même Jour
{
FSortedList->Exchange(Iter,FirstPos);
FSortedList->Delete(FirstPos);
} else
FirstPos++;
}
Sleep(2);
Iter++;
FirstPos=Iter+1;
}
if (FCounter !=FSortedList->Count)
{
PostMessage(Form1->Handle,WM_SortedListNotifier,FSortedList->Count,0);
FCounter=FSortedList->Count;
}
Iter= FCounter-(FThreadNbr*2); if (Iter<0)Iter=0;
FirstPos=Iter+1;
Sleep(2);
}
} //---------------------------------------------------------------------------
principe de fonctionnement
tant que le premier élément de la liste est égal au second élément on echange les éléments trouvés sinon on recommence
a partir du 2 em élément ceci jusqu'a la fin de la liste
si le champ Counter est différent du nombre d'éléments de la liste triée on envoye un message au ThreadVcl
pour raffraichir la List View le champ Counter prend la valeur du nombre d'éléments de la liste triée et le cycle repend dès le début
Voir également mes autres articles :
Utilisation des Files Mapping sous C++Builder
C++ Builder
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de
l'autorisation de l'auteur.