본문 바로가기
Programming/Win32

트레이 프로그램 작성

by 작은별하나 2011. 11. 28.
반응형

프로그램을 개발할 때, 트레이(tray)에만 표시되고 화면에 표시되지 않는 프로그램을작성하고 싶을 때가 있습니다. 프로그램이 화면에 표시되지 않게 하는 방법은 간단합니다. 화면을 보여주지 않으면 됩니다. 그렇게 하면 작업 표시줄에도 아무런 표시가 생기지 않습니다. 그렇게 한 후 트레이에 아이콘만 표시하면 됩니다.

트레이에 아이콘을 표시하는 방법은 윈도우 API에 있지는 않습니다. 트레이를 관리하는 주체는 쉘(Shell)입니다. 쉘에 대해서 간단하게 설명하자면, 윈도우 운영체제와 사용자를 이어주는 역할을 해준다고 보시면 됩니다. 트레이 영역은 윈도우 운영체제가 아닌 쉘이 담당하는 부분입니다. 쉘 명령어를 이용하면 간단하게 트레이에 아이콘을 표시할 수 있고, 트레이 아이콘에서 간단한 마우스 입력을 받을 수도 있습니다.

셀과 관련한 프로그램을 작성하기 위해서는 windows.h 뿐만 아니라 ShellAPI.h 헤더 파일도 프로그램에 포함시켜주어야 합니다.

 

#include <windows.h>
#include <ShellAPI.h>

 

다음으로는 트레이에 보여주는 아이콘을 만들어주셔야 합니다. 일반적으로 16x16 크기가 사용되기 때문에 리소스에서 16x16 아이콘을 만들어주시면 됩니다. 트레이에 표시되는 아이콘은 단순 표시용이기 때문에 여러종류를 만들어서 다양하게 표시해줄 수 있습니다. 또는 필요하다면 애니메이션을 적용할 수도 있습니다.

아이콘은 비쥬얼스튜디오에 포함되어 있는 아이콘 편집기를 이용했습니다. 전용 도구를 이용하면 보다 잘 만들어줄 수도 있겠죠.

제 경우에는 16x16 아이콘과 32x32 아이콘을 만들어주었습니다. 필요에 따라서 아이콘을 달리 표현해줄 수도 있습니다. 아이콘의 리소스 아이디는 IDI_TRAY 로 지정을 했습니다.

일반적으로 트레이형 프로그램은 트레이 아이콘을 클릭하면 팝업메뉴(Popup menu)를 표시해서 간단한 설정 및 프로그램 종료를 해줄 수 있습니다.

메뉴는 간단하게 Option 메뉴와 Exit 메뉴만 표시했습니다. 일반 프로그램과 달리 단축키를 사용하기 힘드므로 단축키 설정을 하지 않으셔도 됩니다. 메뉴바를 만들고 첫번재 메뉴 아이템에 팝업메뉴를 지정하도록 합니다.

메뉴 리소스 아이디는 IDR_TRAYMENU라고 지정을 해주었습니다. Option은 IDM_OPTION, Exit는 IDM_EXIT로 리소스 아이디를 지정해주었습니다.

트레이에 아이콘을 표시해주기 위해서는 NOTIFYICONDATA라는 구조체 변수가 가장 중요합니다. 트레이에 표시되는 아이콘은 사용자에게 무엇인가 알려주기 위한 용도입니다. 그렇기 때문에 한시적으로 표시해줄 수도 있지만, 프로그램이 끝날 때까지 계속 표시해줄 수도 있습니다.

NOTIFYICONDATA의 구조는 다음과 같습니다.

 

typedef struct _NOTIFYICONDATAW {
  DWORD cbSize;
  HWND hWnd;
  UINT uID;
  UINT uFlags;
  UINT uCallbackMessage;
  HICON hIcon;
#if (NTDDI_VERSION < NTDDI_WIN2K)
  WCHAR szTip[64];
#endif
#if (NTDDI_VERSION >= NTDDI_WIN2K)
  WCHAR szTip[128];
  DWORD dwState;
  DWORD dwStateMask;
  WCHAR szInfo[256];
  union {
    UINT uTimeout;
    UINT uVersion; // used with NIM_SETVERSION, values 0, 3 and 4
  } DUMMYUNIONNAME;
  WCHAR szInfoTitle[64];
  DWORD dwInfoFlags;
#endif
#if (NTDDI_VERSION >= NTDDI_WINXP)
  GUID guidItem;
#endif
#if (NTDDI_VERSION >= NTDDI_VISTA)
  HICON hBalloonIcon;
#endif
} NOTIFYICONDATAW, *PNOTIFYICONDATAW;

 

cbSize는 구조체의 크기를 전달해주면 됩니다. 일반적으로 sizeof 연산자를 이용해서 구조체의 크기를 넘겨줍니다.

hWnd는 트레이 아이콘에 발생하는 이벤트를 어떤 윈도우에 전달할 것인지를 결정합니다. 윈도우 메시지를 받을 수 있는 윈도우는 생성이 되어 있어야 합니다.

uID는 트레이 아이콘을 구별해줄 수 있는 인식자입니다. 트레이 아이콘을 여러 개 표시하는 응용 프로그램인 경우 uID 값으로 이벤트가 발생한 트레이 아이콘을 구별할 수 있습니다.

uFlags는 구조체에 있는 내용중 어떤 것들을 사용할 것인지를 비트 플래그로 알려줍니다.

 

NIF_MESSAGE uCallbackMessage 통지 메시지를 받습니다.
NIF_ICON hIcon 지정된 아이콘을 표시합니다.
NIF_TIP szTip 툴팁메세지를 표시해줍니다.

                  

uCallbackMessage는 uFlags에 NIF_MESSAGE가 설정된 경우에 사용되며, 통지 메시지를 전달해줄 때, 주어진 값으로 보내집니다. wParam에는 이벤트가 발생한 트레이 아이콘의 ID가 들어가며 lParam에는 마우스 이벤트 종류가 들어갑니다. 일반 마우스 이벤트에 마우스의 위치 등의 정보가 들어가는데 비해서 트레이 아이콘 이벤트에는 마우스 위치 등의 정보가 빠집니다.

hIcon은 트레이 아이콘에 표시될 아이콘의 핸들을 지정합니다.

szTip은 트레이 아이콘에 마우스가 오버된 경우 표시될 문자열을 저장합니다. 최대 64글자(버전 5.0 이후에는 128글자)까지 저장할 수 있습니다.

이 구조체 내용을 채웠으면 Shell_NotifyIcon 함수를 이용하여 트레이 아이콘을 표시해줄 수 있습니다.

간단하게 작성해본 프로그램은 다음과 같습니다.

 

#include <windows.h>
#include <ShellAPI.h>

#include "resource.h"

#define    WM_TRAYNOTIFY    (WM_USER+100)

HINSTANCE g_hInstance;                            //<    Windows Instance of this application.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

void OnCreate(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnTrayNotify(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
    //    Register window class
    {
        WNDCLASSEXW wcex;

        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style            = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = WndProc;
        wcex.cbClsExtra        = 0;
        wcex.cbWndExtra        = 0;
        wcex.hInstance        = hInstance;
        wcex.hIcon            = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CIDLAUNCHER));
        wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground    = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName    = NULL;
        wcex.lpszClassName    = L"CidLauncher";
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

        RegisterClassExW(&wcex);
    }

    //    Save current instance value.
    g_hInstance = hInstance;

    HWND hWnd = CreateWindowW(L"CidLauncher", L"Cid Launcher", 0, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    if( !hWnd ) return FALSE;

    ShowWindow(hWnd, SW_HIDE);
    UpdateWindow(hWnd);

    MSG msg;
    while( GetMessage(&msg, NULL, 0, 0) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        OnCreate(hWnd, message, wParam, lParam);
        return 0;
    case WM_COMMAND:
        OnCommand(hWnd, message, wParam, lParam);
        return 0;
    case WM_PAINT:
        OnPaint(hWnd, message, wParam, lParam);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_TRAYNOTIFY:
        OnTrayNotify(hWnd, message, wParam, lParam);
        return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

void OnCreate(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    NOTIFYICONDATAW nid;
    ZeroMemory(&nid, sizeof(nid));
    nid.cbSize = sizeof(NOTIFYICONDATAW);
    nid.hWnd = hWnd;
    nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
    nid.uCallbackMessage = WM_TRAYNOTIFY;
    nid.hIcon = LoadIconW(g_hInstance, MAKEINTRESOURCEW(IDI_TRAY));
    lstrcpyW(nid.szTip, L"Tray example");
    Shell_NotifyIconW(NIM_ADD, &nid);
}

void OnCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch( LOWORD(wParam) )
    {
    case IDM_OPTION:
        MessageBoxW(NULL, L"Option Menu", L"Tray", MB_OK);
        break;
    case IDM_EXIT:
        if( MessageBoxW(NULL, L"Do you want to exit?", L"Info", MB_OKCANCEL) == IDOK )
            DestroyWindow(hWnd);
        break;
    }
}

void OnPaint(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
}

void OnTrayNotify(HWND hWnd, UINT messaged, WPARAM wParam, LPARAM lParam)
{
    HMENU hMenu, hPopupMenu;
    POINT pt;
    switch(lParam)
    {
    case WM_LBUTTONDOWN:
        hMenu = LoadMenuW(g_hInstance, MAKEINTRESOURCEW(IDR_TRAYMENU));
        hPopupMenu = GetSubMenu(hMenu, 0);
        GetCursorPos(&pt);
        SetForegroundWindow(hWnd);
        TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hWnd, NULL);
        SetForegroundWindow(hWnd);
        DestroyMenu(hPopupMenu);
        DestroyMenu(hMenu);
        break;
    case WM_RBUTTONDOWN:
        break;
    }
}
728x90

'Programming > Win32' 카테고리의 다른 글

Win32 Sierpinsky Triangle 그리기  (0) 2014.04.29

댓글