일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- lck
- 돋보기
- 히오스
- 탐색기
- 리그오브레전드
- 그림판
- Chromium Embedded Framework
- 메모장
- HGC
- 게임의 심리학
- 히어로즈오브더스톰
- c#
- NuGet
- Xamarin
- 압축프로그램
- 롤
- ogn
- lol
- winmm.dll
- .NET
- Facebook developer
- 리뷰
- OAuth 2.0
- 음악플레이어
- Today
- Total
1인 콘텐츠 개발실
[c#] 간단한 유틸을 만들어보자! - [DIY 탐색기] 편 본문
해당 포스팅은 C#을 이용하여 간단한 유틸리티 프로그램을 개발하면서, 그 안에 쓰이는 라이브러리나 알고리즘(로직) 등을 다뤄보고 학습하는 포스팅입니다. 이 글을 통해 기존에 있던 프로그램과 비교하면서 해당 프로그램의 쓰임새, 동작 방식 등을 분석하거나 조금은 다른 관점으로 접근하여 생각해 보는것이 이 글과 앞으로 있을 포스팅의 목표입니다.
# 개요
이번 유틸리티 프로그램은 우리가 알게 모르게 사용하고 있는 [탐색기]입니다. 가령 예를 들어 [내 컴퓨터]나 [내 문서]를 들어가 그 안에 있는 파일을 실행하거나 다른 폴더를 들어가는 작업을 종종 하는데, 이 때 열리는 폴더 창이 [파일 탐색기] 프로그램입니다. 작업 프로세스에서는 explorer.exe로 실행되고 있는것이 바로 [탐색기]입니다.
윈도우 10의 파일 탐색기 모습
윈도우를 사용하기 이전의 Dos나 리눅스/유닉스에서는 원하는 파일을 실행하기 위해서는 그에 맞는 커맨드와 파일 경로를 키보드로 입력해서 실행했지만, 윈도우에서는 GUI(그래픽 사용자 인터페이스)가 강조되면서 컴퓨터에 저장되어 있는 파일이나 폴더들이 아이콘 형태의 이미지로 직관적으로 표현되고, 사용자는 이를 손쉽게 찾고 실행할 수 있게 되었습니다. 하지만 예전에 비해 직관성이 높아졌다고는 해도 저장공간이 천문학적으로 방대해진만큼 다루는 데이터가 많아져서 오히려 원하는 파일을 못찾을때도 있고, 검색을 해서 겨우겨우 찾는 경우가 허다합니다. 그래서 초기의 탐색기는 사용자가 인지하기 쉬운 직관성과 접근성이 강조되었다면, 오늘날의 탐색기는 파일 검색, 보안 등등의 기능이 강조된 형태라고 볼 수 있습니다.
기본으로 제공하는 탐색기외에도 여러 탐색기 프로그램이 있는데 그 중 개인적으로는 검색 기능이 강화된 Everything이라는 프로그램을 유용하게 사용하고 있습니다. Everything은 한 번 색인 작업을 마치면 그 이후 파일이나 폴더를 타이핑 하는 순간마다 실시간으로 찾아줄 만큼 검색 속도가 빠르기 때문에 많은 파일을 다루는 사용자가 유용하고 간편하게 쓸 수 있는 탐색기 프로그램이라고 생각합니다.
Everything 프로그램의 모습
[DIY 탐색기] 역시 Everything의 검색 기능까지는 아니지만 키워드를 통해 기본적인 검색을 할 수 있도록 구현해보았습니다.
# [DIY 탐색기] 기능
[DIY 탐색기]를 실행하면 좌측에는 계층구조(트리)로 표시된 항목들을 통해 전체 폴더들을 탐색할 수 있으며, 우측에는 해당 폴더내에 존재하는 데이터들을 리스트 형태로 열거되어 데이터의 이름, 수정날짜, 유형, 크기 등의 정보를 볼 수 있습니다.
[좌측에 전체 폴더들을 트리 형태로 보여주기]
private void Form1_Load(object sender, EventArgs e) { m_curPath = "Root"; label1.Text = m_curPath; TreeNode root = treeView1.Nodes.Add(m_curPath); string[] drives = Directory.GetLogicalDrives(); // 모든 논리적으로 구분되어 있는 드라이브들을 읽어들임 foreach (string drive in drives) { DriveInfo di = new DriveInfo(drive); if (di.IsReady) // 드라이브가 준비되었는지 여부 판단. 이 조건으로 CD R/W 드라이브 등을 제외시킴. { TreeNode node = root.Nodes.Add(drive); node.Nodes.Add("\\"); // 파일 경로 사이사이에 "\"를 추가함. } } } private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e) // 트리뷰에서 +버튼으로 항목을 펼치기 직전에 수행할 작업 { try { if (e.Node.Nodes.Count == 1 && e.Node.Nodes[0].Text.Equals("\\")) { e.Node.Nodes.Clear(); string path = e.Node.FullPath.Substring(e.Node.FullPath.IndexOf("\\") + 1); string[] directories = Directory.GetDirectories(path); // 현재 경로에서 존재하고 있는 폴더들을 하위 노드에 추가 foreach (string directory in directories) { TreeNode newNode = e.Node.Nodes.Add(directory.Substring(directory.LastIndexOf("\\") + 1)); newNode.Nodes.Add("\\" ); } } } catch (Exception ex) { Console.WriteLine("treeView1_BeforeExpand : " + ex.Message); } }
Directory.GetLogicalDrives()를 통해 현재 셋팅되어 있는 모든 드라이브를 읽어옵니다. 읽어온 드라이브 목록 중에 준비된 드라이브(로컬 디스크)를 트리뷰에 추가합니다. 6번째 줄에서 TreeView에 Root 트리를 추가함과 동시에 TreeNode 인스턴스가 생성이 되고, 생성된 인스턴스에 자식 노드를 붙이면 자동으로 TreeView에도 추가가 됩니다.
[우측에 하위 폴더&파일들을 리스트 형태로 보여주기]
private void ViewDirectoryList(string path) { if (m_thread != null && m_thread.IsAlive) // 리스트를 다시 출력해야 하므로 검색 중이라면 강제로 중단시킴 m_thread.Abort(); string curPath = path; if (path.IndexOf("Root\\") == 0) // 앞에 붙은 "Root\"는 실제 파일경로가 아니기 때문에 path에 있으면 빼준다. { curPath = path.Substring(path.IndexOf("\\") + 1); label1.Text = (curPath.Length > 4) ? curPath.Remove(curPath.IndexOf("\\") + 1, 1) : curPath; m_curPath = label1.Text; } else { label1.Text = path; m_curPath = path; } try { listView1.Items.Clear(); // 전에 있던 목록들을 지우고 새로 나열함. string[] directories = Directory.GetDirectories(curPath); foreach (string directory in directories) // 폴더 나열 { DirectoryInfo info = new DirectoryInfo(directory); ListViewItem item = new ListViewItem(new string[] { info.Name, info.LastWriteTime.ToString(), "파일 폴더", "" // 폴더의 상세 내용 표시. }); listView1.Items.Add(item); } string[] files = Directory.GetFiles(curPath); foreach (string file in files) // 파일 나열 { FileInfo info = new FileInfo(file); ListViewItem item = new ListViewItem(new string[] { info.Name, info.LastWriteTime.ToString(), info.Extension, ((info.Length/1000)+1).ToString()+"KB" // 파일의 상세 내용 표시. }); listView1.Items.Add(item); } } catch (Exception ex) { Console.WriteLine("ViewDirectoryList : " + ex.Message); } } private void SelectTreeView(TreeNode node) { if (node.FullPath == null) { Console.WriteLine("empth node.FullPath"); return; } string path = node.FullPath; ViewDirectoryList(path); } private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) // 트리뷰에서 +버튼으로 항목을 펼친 후에 수행할 작업 { SelectTreeView(e.Node); } private void button1_Click(object sender, EventArgs e) // [상위 디렉토리 설정] 버튼으로 경로를 지정할 때 수행할 작업 { FolderBrowserDialog fbd = new FolderBrowserDialog(); if (fbd.ShowDialog() == DialogResult.OK) { m_curPath = fbd.SelectedPath; Console.WriteLine(m_curPath); label1.Text = m_curPath; ViewDirectoryList(m_curPath); } }
리스트뷰에서 데이터가 상세히 표시되는 경우가 2가지 있습니다. 첫번째는 트리뷰에서 항목을 선택했을때이고, 두번째는 [상위 디렉토리 설정] 버튼을 통해 디렉토리를 선택했을 때입니다. 이 두가지 경우 모두 ViewDirectoryList를 거치도록 처리하여 디렉토리에 존재하는 폴더와 파일의 상세한 내용(이름, 수정한 날짜, 유형, 크기)을 출력합니다.
SelectTreeView에서는 현재 TreeNode가 루트 노드인 0 Level에서부터 어떤 경로를 통해 해당 노드까지 도달하는지 node.FullPath값을 통해 알 수 있습니다. 앞서 자식 노드를 추가할 때 파일경로와 같은 방식으로 중간중간 "\"를 붙여줘서 FullPath의 값이 파일 경로와 같은 형식이기 때문에 별 다른 처리 없이 실제 파일 경로처럼 사용할 수 있습니다.
[검색 기능]
private void button2_Click(object sender, EventArgs e) { if(m_thread != null && m_thread.IsAlive) // 새로 검색해야 하므로 이미 검색 중이라면 강제로 중단시킴. m_thread.Abort(); m_curPath = label1.Text; DirectoryInfo rootDirInfo = new DirectoryInfo(m_curPath); string searchFiles = textBox1.Text; m_thread = new Thread(delegate () // 새로운 쓰레드를 생성하여 사용. { WalkDirectoryTree(rootDirInfo, searchFiles); }); m_thread.Start(); } private void WalkDirectoryTree(DirectoryInfo dirInfo, string searchFiles) { listView1.Items.Find(searchFiles, true); FileInfo[] files = null; DirectoryInfo[] subDirs = null; try { files = dirInfo.GetFiles(searchFiles + "*.*"); // 검색 단어가 앞 부분과 일치한 폴더와 파일을 검색. } catch (UnauthorizedAccessException e) { Console.WriteLine(e.Message); } catch (DirectoryNotFoundException e) { Console.WriteLine(e.Message); } if (files != null) { DirectoryInfo tempDirInfo = new DirectoryInfo(m_curPath); if (dirInfo.ToString() == tempDirInfo.ToString()) listView1.Items.Clear(); foreach (FileInfo fi in files) { ListViewItem item = new ListViewItem(new string[] { fi.FullName, fi.LastWriteTime.ToString(), fi.Extension, ((fi.Length/1000)+1).ToString()+"KB" // 검색으로 찾은 폴더와 파일은 모든 하위 폴더의 파일까지 // 표시하기 때문에 전체 경로를 표시하여 구분함. }); listView1.Items.Add(item); } subDirs = dirInfo.GetDirectories(); foreach (DirectoryInfo di in subDirs) { WalkDirectoryTree(di, searchFiles); } } }
텍스트박스에서 검색어를 입력받으면 해당 검색어를 접두어로 하여 일치하는 데이터를 검색합니다. 예를들어 검색어로 "The"라고 입력했을때 "The~"로 시작하는, 가령 "The Folder", "The File", "Then", "There" 등등의 폴더와 파일을 리스트로 출력합니다.
따로 효과적인 검색 알고리즘을 사용한것이 아니기에 실제 검색 속도는 일반 윈도우 탐색기와 비슷한 속도로 보여집니다. 그래서 간혹 검색 시간이 무한정 길어질 수 있기 때문에 검색 작업은 메인 스레드에서 수행하는것 보다 새로운 스레드를 생성하여 수행하는것이 좋습니다. 파라미터가 두 개 이상 존재하는 함수의 스레드는 10번째 줄처럼 델리게이트를 사용하여 스레드를 생성할 수 있습니다.
[리스트 항목 더블 클릭시 외부 프로그램 실행]
private void listView1_DoubleClick(object sender, EventArgs e) { if (listView1.SelectedItems.Count == 1) { string processPath; if (listView1.SelectedItems[0].Text.IndexOf("\\") > 0) processPath = listView1.SelectedItems[0].Text; // 검색으로 모든 경로가 표시된 경우는 그대로 사용. else processPath = m_curPath + "\\" + listView1.SelectedItems[0].Text; // 트리뷰를 통해서 폴더를 읽어온 경우에는 현재 경로와 파일 이름을 합쳐서 사용. Process.Start("explorer.exe", processPath); // 윈도우의 탐색기를 통해 파일을 실행하는 것과 같은 결과 } }
# 완성 및 마무리
- [DIY 탐색기] 소스 파일 : https://github.com/hyunil-stdlib/SimpleExplorer
(소스코드와 실행 파일을 받으실 수 있습니다.)
[DIY 탐색기] 프로그램 구동 모습
[DIY 탐색기]의 검색 기능을 통해 입력한 키워드로 시작하는 파일과 폴더를 검색
[DIY 탐색기]의 하위 폴더&파일 리스트에서 더블 클릭하여 실행 가능
사실 이번 [DIY 탐색기]를 다루면서 이진 탐색 트리, AVL트리 등과 같이 트리에 대한 검색 알고리즘 내용을 포함하고 싶었으나, 짤막하게 다루게 되면 어줍게 다루어 내용이 애매하게 될 것 같아 생략하였습니다. 이 글에서는 생략하였지만, 해당 내용들을 검색해서 찾아보시면 꽤 유용한 정보를 얻으실 수 있으니 한번 찾아보시는 것을 추천합니다.
어쨌든 포스팅은 이것으로 마치고, 다음에 올릴 포스팅도 기대해주시면 감사하겠습니다.
# 참조
- Everything 프로그램 : http://www.voidtools.com/
- 마이크로소프트 Developer Network (msdn) : https://msdn.microsoft.com/ko-kr/library/618ayhy6.aspx
- DirectoryInfo의 getFiles 메서드를 사용한 파일 검색 로직 : https://msdn.microsoft.com/ko-kr/library/bb513869.aspx
'개발&제작 > 프로그램' 카테고리의 다른 글
[c#] 간단한 유틸을 만들어보자! - [DIY 음악플레이어] 편 (2) | 2017.05.22 |
---|---|
[c#] 간단한 유틸을 만들어보자! - [DIY 압축기] 편 (0) | 2017.05.11 |
[c#] 간단한 유틸을 만들어보자! - [DIY 그림판] 편 (4) | 2017.04.14 |
[c#] 간단한 유틸을 만들어보자! - [DIY 돋보기] 편 (1) | 2017.04.06 |
[c#] 간단한 유틸을 만들어보자! - [DIY 메모장] 편 (0) | 2017.04.01 |