"저장 폴더를 지정하세요" 같은 기능은 업무용 프로그램이라면 거의 빠지지 않습니다. 엑셀 일괄 변환기, 로그 내보내기, 이미지 일괄 처리 같은 도구를 만들다 보면 결과물을 어디에 떨굴지 사용자에게 물어봐야 하니까요. WinForms에서는 방법이 크게 두 가지인데, 기본 내장된 FolderBrowserDialog와 Windows API Code Pack의 CommonOpenFileDialog입니다. VB.NET에서도 클래스는 똑같으니 문법만 바꿔 쓰면 됩니다.

기본 내장 FolderBrowserDialog

별도 설치 없이 바로 쓸 수 있는 게 장점입니다. using으로 감싸 두면 창을 닫은 뒤 자원 정리까지 알아서 됩니다. ShowNewFolderButton을 켜면 대화상자 안에서 새 폴더를 만들 수 있고, RootFolder로 트리의 시작 위치를 정합니다. 아래 예제는 "내 컴퓨터"부터 보여 주고, 취소하면 빈 문자열을 돌려줍니다.

광고
private string FolderBrowser()
{
    using (FolderBrowserDialog dlg = new FolderBrowserDialog())
    {
        dlg.ShowNewFolderButton = true;
        dlg.RootFolder = Environment.SpecialFolder.MyComputer;
        if(dlg.ShowDialog() == DialogResult.OK)
        {
            //string path = dlg.SelectedPath;
            return dlg.SelectedPath;
        }
    }
    return "";
}

문제는 이 창이 오래된 트리 형태라 쓰기가 불편하다는 점입니다. 주소창에 경로를 붙여 넣을 수도 없고, 폴더 깊이가 깊으면 한참 펼쳐 내려가야 하죠. 사용자 입장에서 답답하다는 말을 듣기 쉬운 부분입니다.

파일 열기 창처럼 폴더 고르기 - CommonOpenFileDialog

그래서 대안으로 많이 쓰는 게 CommonOpenFileDialog입니다. 파일 열기 창과 똑같은 모양에서 폴더를 선택하게 해 줍니다. 원래 마이크로소프트가 만든 Windows API Code Pack에 들어 있던 클래스인데, 지금은 공식 관리가 끝났고 자유 사용 라이선스였던 덕분에 Aybe라는 개발자가 GitHub에서 이어서 관리하고 있습니다. 패키지 이름이 Microsoft.WindowsAPICodePack으로 시작하는 이유가 그 때문입니다.

설치는 두 가지 중 하나로 합니다.

  • 예전 방식: Windows API Code Pack for Microsoft .NET Framework(http://archive.msdn.microsoft.com/WindowsAPICodePack/)를 받아 Microsoft.WindowsAPICodePack.dll, Microsoft.WindowsAPICodePack.Shell.dll 두 어셈블리를 참조에 추가
  • NuGet 방식: 도구 > NuGet 패키지 관리자 > 솔루션용 NuGet 패키지 관리에서 WindowsAPICodePack-Shell을 찾아 설치

요즘은 NuGet 쪽이 훨씬 편합니다. 사용법은 IsFolderPicker만 켜 주면 끝이고, 선택 결과는 이름과 달리 FileName 속성에 들어옵니다. 처음 쓸 때 SelectedPath 같은 속성을 찾다가 헤매기 쉬운 부분이에요.

var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
    this.Path = dialog.FileName;
}

배포할 때는 실행 파일 옆에 다음 파일을 같이 넣어야 합니다. 개발 PC에서는 잘 되다가 사용자 PC에서 실행하자마자 죽는다면 대부분 이 DLL이 빠진 경우입니다.

  • Microsoft.WindowsAPICodePack.dll
  • Microsoft.WindowsAPICodePack.Shell.dll
  • Microsoft.WindowsAPICodePack.Shell.xml
  • Microsoft.WindowsAPICodePack.xml

고른 폴더를 탐색기로 열어 주기

작업이 끝난 뒤 결과 폴더를 바로 띄워 주면 사용자 반응이 꽤 좋습니다. 한 줄이면 됩니다.

System.Diagnostics.Process.Start("explorer.exe", path);

파일 저장 대화상자 SaveFileDialog

폴더가 아니라 저장할 파일 이름까지 받아야 한다면 SaveFileDialog를 씁니다. Filter는 "표시 이름|패턴"을 |로 이어 붙이는 형식이고, DefaultExt는 사용자가 확장자를 안 적었을 때 붙일 기본값입니다.

public bool DoSaveFileDialog(Image image)
{
    string filePath = "";
    using (SaveFileDialog dlg = new SaveFileDialog())
    {
        dlg.Title = "이미지 저장";//창 위에 뜨는 타이틀
        dlg.FileName = GenerateBaseFilename();
        dlg.Filter = "PNG 이미지 (*.png)|*.png|JPG 이미지 (*.jpg)|*.jpg|BMP 이미지 (*.bmp)|*.bmp|GIF 이미지 (*.gif)|*.gif|모든 파일 (*.*)|*.*";//확장자 선택
        dlg.DefaultExt = "png";
        dlg.InitialDirectory = "";

        //showDialog의 리턴값이 OK 일 때
        if (dlg.ShowDialog() == DialogResult.OK)
        {
            filePath = dlg.FileName;
        } else
        {
            // 취소한 경우
            return false;
        }
    }

    if (filePath.Trim().Length > 1)
    {
        MessageBox.Show("저장 되었습니다.");
        return true;
    }
    else
    {
        // 경로를 잘못 지정한 경우
        MessageBox.Show("저장경로를 설정해주세요");
    }
    return false;
}

이 예제는 대화상자 사용 흐름을 보여 주는 뼈대라서 그대로 쓰면 안 됩니다. GenerateBaseFilename()은 기본 파일명을 만들어 주는 별도 함수로 직접 구현해야 하고, 무엇보다 받은 image를 실제로 저장하는 코드가 없습니다. "저장 되었습니다" 메시지 앞에 image.Save(filePath)처럼 선택한 확장자에 맞춰 저장하는 줄을 넣어야 메시지가 거짓말이 되지 않습니다. FilterIndex 값을 보면 사용자가 어떤 형식을 골랐는지 알 수 있으니, 그 값으로 ImageFormat을 고르는 식으로 확장하면 됩니다.

참고: CSharp 개발/폴더 선택 창 (astrawiki)