C++ et Poco peuvent sauver des bureaux! (1/2)

Publié dans: 

 

 

Il m’est très difficile de voir des bureaux pleins d’icônes sans même pas un petit coin pour accueillir un nouveau raccourcis.

Devant l’insuffisance des outils proposés par les systèmes d’exploitation, en particulier celui proposé par Windows, je me suis dit qu’il serait intéressant d’en faire un! ça sera à la fois pour avoir des bureaux plus spacieux mais aussi pour introduire Poco, qui est une excellente bibliothèque C++.

A travers cet article et un autre qui le suivra, on proposera une approche qui n’exige aucun changement de comportement de la part des propriétaires de tels bureaux. On va cependant leurs offrir un outil qui automatisera l’opération de rangement selon leurs préférences prédéfinis.

On commencera par préparer un squelette applicativ multithread permettant d’interagir avec l’utilisateur via une icône de la Taskbar. Dans le second article, je vous présenterai une bibliothèque intéressante, Poco C++, qui grâce à l’exploitation de certaines de ses routines, notamment la DirectoryIterator, on va pouvoir implémenter les fonctionnalités qu’on désire mettre en place.

Notre squelette sera dérivée de celle d’une application MFC native à base de boite dialogue :

Le point d’entrée InitInstance de l’objet CWinApp crée et initialise une boite dialogue dont les composants sont batis à l’aide du designer.

CMainDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();

La fonction DoModal déclenche la fonction virtuelle CDialog::OnInitDialog avant de rentrer dans une boucle implicite d’appel à ::PeekMessage et AfxPumpMessage (regarder le code de RunModalLoop). C’est notre thread principal qui permet d’interagir avec l’utilisateur.

Au niveau de CDialog::OnInitDialog nous avons la main pour initialiser le menu Tray à travers lequel l’utilisateur pourra interagir avec l’application:

m_nid.hWnd = GetSafeHwnd ();
m_nid.uCallbackMessage = UM_TRAYNOTIFY;

// Set tray icon and tooltip:
m_nid.hIcon = m_hIcon;

CString strToolTip = _T("Organize my files..");
_tcsncpy_s (m_nid.szTip, strToolTip, strToolTip.GetLength ());

Shell_NotifyIcon (NIM_ADD, &m_nid);

C’est grâce au message UM_TRAYNOTIFY que nous pouvons insérer un callback qui intercepte l’évènement de clique droit sur l’icône tray et affiche un menu contextuel :

LRESULT CMainDlg::OnTrayNotify( WPARAM wp, LPARAM lp )
{
	UINT uiMsg = (UINT) lp;

	switch (uiMsg)
	{
	case WM_RBUTTONUP:
		{
			CPoint point;
			::GetCursorPos (&point);

			CMenu menu;
			menu.LoadMenu (IDR_MENU1);

			CMFCPopupMenu::SetForceShadow (TRUE);

			HMENU hMenu = menu.GetSubMenu (0)->Detach ();
			CMFCPopupMenu* pMenu = theApp.GetContextMenuManager()->ShowPopupMenu(hMenu, point.x, point.y, this, TRUE);

			pMenu->SetForegroundWindow ();
		}

		return 1;
	}

Pour le menu IDR_MENU1, on a construit quatre boutons simples : un bouton Start permettant de démarrer/arrêter un thread silencieux exécutant sa tâche en arrière plan, un bouton About, Configuration et Exit. Chaque bouton est relié à une méthode de la boite de dialogue.

Particulièrement, nous voulons que le texte du bouton start change en fonction de l’état du thread (démarré ou arrêter). De même, on ne doit pas permettre l’accès au bouton configuration quand le thread est en cours d’exécution. Cela est possible en insérant des handlers ON_UPDATE_COMMAND_UI à nos deux commandes et exploitant les méthodes Enable et SetText du paramètre CCmdUI en input.

Le bouton configuration permettra d’afficher la boite de dialogue qu’on s’efforcera de cacher initialement. Là, l’utilisateur pourra spécifier les paramètres du programme qui affecteront l’algorithme exécuté par le thread silencieux.

Dans cette première partie, on se contentera du fonctionnement suivant: L’application s’exécute et se place dans le taskbar. A chaque commande start (exécutée à partir du menu tray), on lance un thread qui à l’échéance d’un durée configurable émet un simple beep. La boite de dialogue permettra de saisir quelques paramètres comme la durée de la période du thread background. La sauvegarde de ces paramètres se fait dans la base de registre.

Une classe abstraite pure CBackgroundThread contiendra la logique de l’exécution de la tâche de fond :

void CBackgroundThread::start()
{
	m_hAbortEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	ResetEvent(m_hAbortEvent);
	AfxBeginThread(threadProc, this);
}

void CBackgroundThread::stop()
{
	SetEvent(m_hAbortEvent);
}

L’évènement m_hAbortEvent permet de demander au thread lancé de s’arrêter. Ce qui suit est l’algorithme exécuté par le thread d’arrière plan :

void CBackgroundThread::sleepAndExecute()
{
	m_bRunning=true;

	m_hSleepEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	ResetEvent(m_hSleepEvent);
	//
	HANDLE            hWaits[2];
	hWaits[0]        = m_hSleepEvent;
	hWaits[1]        = m_hAbortEvent;

	UINT    sleepTimeMs=theApp.GetInt(_T(”Sleep Duration”),3)*1000;

	//
	while(true)
	{
		MMRESULT result = timeSetEvent(sleepTimeMs, 1,
		(LPTIMECALLBACK) m_hSleepEvent, 0, TIME_ONESHOT|TIME_CALLBACK_EVENT_SET);
		if (result==NULL)
		break;

		if(WaitForMultipleObjects(2, hWaits, FALSE, INFINITE)==(WAIT_OBJECT_0 + 0))
		{
			//
			executePeriodicJob();
			ResetEvent(m_hSleepEvent);
		}
		else
		break;
	}
	//
	CloseHandle(m_hAbortEvent);
	CloseHandle(m_hSleepEvent);
	m_bRunning=false;
}

Quand le thread d’interface intercepte la command Stop, si le thread d’arrière plan est bloqué sur WaitForMultipleObject, celui-ci retourne immédiatement en retournant l’offset 1 par rapport à la constante WAIT_OBJECT_0 indiquant que l’objet m_hAbortEvent a été signalé, ce qui nous mène à faire un break. Si le thread de travail est en cours d’exécution de executePeriodicJob alors il s’apercevra du signal de terminaison à la prochaine boucle.

Pour notre première version, on a dérivé une classe qui surcharge executePeriodicJob pour exécuter un Beep avant de collecter les paramètres nécessaires, déjà saisis par l’utilisateur.

L’article suivant modifiera cette logique pour boucler sur les fichiers se trouvant dans le répertoire du bureau pour les transférer dans d’autres répertoires selon des règles prédéfinis.

Téléchargements

Si vous préférez tester l’exemple sans avoir à compiler les sources, vous pouvez téléchearger l’exécutable Windows (.exe):

         CheckSum de l’exécutable (à calculer après décompression du ZIP)

        CRC32: 3E4CF456

        MD5: 3C26191224AC4667CBD69AF1344C072F

        SHA-1: 290220D84FBEBEEBE33E9CA7395B2199997E387F