Game Develop

[C++][WINAPI] 자신의 프로그램에 파일 드래그앤드랍하기 본문

C++/winAPI

[C++][WINAPI] 자신의 프로그램에 파일 드래그앤드랍하기

MaxLevel 2021. 11. 17. 09:12

프로그램을 개발하다보면 바탕화면에 있는 파일을 자신의 프로그램에 드래그앤드랍해서 사용해야하는 경우가있다.

예를들어 유니티에 새 프로젝트를 만든다면 그 프로젝트만의 Assets 폴더가 생길것이고 프로그램에서 폴더의 내용물을 표시할것이다. 이 때 바탕화면의 png파일이라던가 하는 파일들을 프로그램의 AssetsWindow에 드래그앤드랍하면 파일이 프로젝트의 Assets폴더에 복사가 돼서 프로그램에 표시가된다. 

 WINAPI프로젝트의 디폴트로는 드래그드랍이벤트가 작성되어있지 않기때문에 따로 해줘야한다.

 

보통 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 에다가 HWND변수를 생성 후 초기화시켜줄텐데, 그다음에  DragAcceptFiles(hWnd, true);  실행 한번 해주면 윈도우핸들러가(HWND) 드랍이벤트를 받을수있게 설정이 된다. 설정이 되었다면 WndProc()에 아래와같이 WM_DROPFILES 를 추가해주면 된다. 

DragAceeptFiles()는 shellapi에 들어있으니, shellapi를 include 해주면 된다.

 

그다음 DragQueryFile()을 이용해서 드래그한 파일들의 경로리스트를 받아오면 된다.

아래와 같이 하면 된다. 복수개의 파일도 드래그앤드랍이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
    case WM_DROPFILES: // 드랍 시 이벤트.    
    {
        HDROP hDrop = (HDROP)wParam;
 
        vector<wstring> draggedFiles;
        TCHAR* refFiles = nullptr;
        UINT refFilesLen = 0;
        POINT refPoint;
 
        UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, refFiles, refFilesLen);
 
        if (fileCount > 0)
        {
            for (int i = 0; i < fileCount; i++
            {
                int fileLength = DragQueryFile(hDrop, i, NULL0+ 1
                TCHAR* fileName = new TCHAR[fileLength]; 
                memset(fileName, 0sizeof(TCHAR) * fileLength); 
 
                DragQueryFile(hDrop, i, fileName, fileLength); 
            
                wstring newFile = fileName;
                draggedFiles.push_back(newFile);
 
                delete[] fileName;
            }
        }
    }
    break;    
cs

 

draggedFiles에 각 파일의 절대경로가 담겨있다. wstring형이니 string으로 쓰려면 변환해서 쓰든지 하면 된다.

 

 

 

자 근데 주의할점이 하나 있다. 주의할점이라기보다는 이런것도 알아두면 도움이 될거같다..는게 더 정확한거같다.

참고로 내가 직접 구현하다가 막히고 해결한 과정을 설명한것이다.

 

아래와 같이 왼쪽에 자신의 프로그램, 오른쪽에는 드래그할 파일을 담고있는 폴더를 열었다고 치자.

예시 이미지

오른쪽 폴더에있는 파일을 내 프로그램의 아래부분에 띄워져있는 ImGui윈도우로 드래그해서 어떠한 이벤트를 발생시키려한다. 단순히 프로그램에 드랍이아니라 프로그램에 렌더중인 또다른 ImGui윈도우에다 드랍하려는 경우이다.

저 ImGui윈도우는 마우스커서가 해당윈도우에 올려져있는지(Hover) 체크하는 함수를 제공해준다. 그러면 단순하게 저 ImGui윈도우를 렌더시키는 함수에서 호버체크만 해주고 그 여부에 따라 이벤트를 실행시키면 된다고 생각했다.(일단은)
왜냐하면 어차피 파일을 드래그한다음 드랍할때 저 ImGui윈도우에다가 놓을테니 마우스커서가 당연히 ImGui윈도우 위에 있을거니까...  당연히 호버체크는 true가 리턴되고 이벤트를 실행할거라 생각했다.

 

하지만 결론은 되는 경우도 있고 안되는 경우도 있다. 

 

되는 경우는 내 프로그램이 활성화가 되어있는상태에서 폴더의 이미지를 드래그하는 경우. (즉,폴더를 마우스클릭안하고 파일만 드래그앤드랍)

 

안되는 경우는 폴더가 활성화 되어있는 상태에서 폴더의 이미지를 드래그하는 경우.

 

즉 파일을 드래그앤드랍하기전에 '어떤 윈도우가 활성화되어있는가?'에 따라서 결과가 달라진다. 여기서 '어떤 윈도우'의

윈도우란 저런 폴더같은것들도 말한다. 즉, 어디에 포커스가 되어있는 상태에서 드래그앤드랍을 하느냐.. 라는 말이다.

 

활성화가 되어있다는것은 마우스로 클릭한다던가 알트탭으로 고르던가하면 타이틀바 색이 변하고 제일 앞에 렌더되면서 키보드입력을 받을수있는 상태(포커스가 되어있는 상태)를 말한다. 

포커스가 되어있어야 호버체크를 정상적으로 수행할 수 있다. 아마 내부적으로 마우스커서위치를 이용해서 체크를 할 거 같은데, 포커스가 안되어있으면 WndProc에서 마우스입력메시지를 제대로 받지 않는다. 

그렇기 때문에 폴더가 활성화되어있는 상태(프로그램은 포커스 X)에서 파일 드랍을 하더라도 WM_DROPFILES는 수행되지만, 호버체크는 FALSE를 리턴하고 내가 원하는 이벤트를 수행하지 않는다. 

어느 경우든 WM_DROPFILES는 수행되긴하기때문에 그냥 자신의 프로그램 아무데나 드래그앤드랍 해도되는거면 신경안써도 될것같다. 다만 나는 특정 ImGui윈도우에 드래그앤드랍하기를 원했기 때문에 해결해야했다.

 

해결법은 간단했다. 강제로 윈도우를 활성화시키면 된다. 함수 하나면 충분했다.

어떤 경우든 WM_DROPFILES는 수행되기 때문에 마지막에 SetForegroundWindow(hWnd) 함수를 추가해서 강제로 윈도우를 활성화시켰다. 그러면 메시지입력들을 다시 잘 받을 수 있기 때문에 정상적으로 잘 수행된다.

물론 문제가 생기자마자 바로 해결했던건 아니고 몇번 삽질하다가 해결한거긴하다.

 

덕분에 WINAPI에 좀 더 공부할수있는 계기가 되기도 했어서 나쁘지 않았던거같다. 물론 새발의 피정도로 공부한정도다.아마 복잡한 윈도우 응용프로그램을 만든다면 핸들러를 자유롭게 사용할 수 있어야할텐데 그럴려면 윈도우라는 운영체제에서의 시스템메시지큐,메시지큐,비큐, 스레드의 메시지처리 등 머리속으로 정리가 잘 되어야 가능할 것 같다..