이번에 제작해 볼 유틸리티는 윈폼에서 유용하게 사용될 만한 유틸리티입니다.
윈폼에서 Panel 이나 Groubox , Form 같은 컨트롤들을 가리켜 자식 컨트롤을 포함할 수 있는
컨테이너(Container) 컨트롤 이라고 합니다.
이러한 컨테이너 컨트롤이 가진 모든 자식 컨트롤을 가져오는 기능를 가진 유틸을 제작해 보겠습니다.
아래와 같은 폼이 있다고 가정합니다.

그림에서 폼에는 총 6개의 컨트롤이 있습니다.
즉 폼(Form)이라는 부모 컨트롤 하위에 총 6개 컨트롤이 있습니다.
더 엄밀히 말하자면 폼(Form) 이라는 부모 컨트롤 하위에는 GroupBox, Panel 이라는 두 개의 컨트롤과 하나의
Button 컨트롤이 있고, GroupBox1 부모 컨트롤은 하나의 TextBox 컨트롤을 가지며 Panel 부모 컨트롤은
GroupBox2 컨트롤을 가지며 또 다시 GroupBox2 컨트롤은 Button2라는 컨트롤을 자식으로 가지고 있습니다.
즉 다음과 같은 계층적 구조로 컨트롤들이 배치되어 있는 것입니다.
Form
- GroupBox1
- TextBox1
- Panel
- GroupBox2
- Button2
- Button1
* System.Windows.Forms.Control.Controls 속성
윈도우 폼의 컨트롤들은 기본적으로 System.Windows.Forms.Control 클래스로 부터 파생된(상속받은)
클래스입니다. 따라서 Control 클래스의 Controls 라는 속성을 통해 자식 컨트롤들을 반환 받을 수 있습니다.
그러나 이 Controls 속성은 계층적 구조를 지원하지 않습니다. 즉 바로 밑의 자식 컨트롤만 반환하게 됩니다.
위 그림에서 폼에는 총 6개의 컨트롤이 있지만 폼(Form)의 바로 밑 계층에는 3개의 컨트롤만 있습니다.
위 그림과 같이 폼이 구성되었을 때 다음과 같은 코드를 작성하여 폼(Form)의 자식 컨트롤의 갯수를 불러오면
총 3개라는 결과가 나옵니다.
MessageBox.Show("자식 컨트롤 갯수: " + this.Controls.Count); |

결과적으로 모든 계층의 컨트롤들을 다 가져오려고 한다면 다른 방법을 사용해야 한다는 것입니다.
이번에 제작할 유틸의 기능은 이와같이 계층적으로 구성된 컨테이너 컨트롤의 모든 자식 컨트롤을 반환하는
기능을 가지고 있습니다.
* 재귀호출을 통한 구현
이를 구현하기 위한 방법도 여러가지가 있을 수 있겠네요.
우선, 계층적 구조.. 하면 생각나는 재귀호출 기법을 통해 구현해 보도록 합니다.
static Control[] GetAllControlsUsingRecursive(Control containerControl)
{
List<Control> allControls = new List<Control>();
foreach (Control control in containerControl.Controls) { //자식 컨트롤을 컬렉션에 추가한다
allControls.Add(control);
//만일 자식 컨트롤이 또 다른 자식 컨트롤을 가지고 있다면…
if (control.Controls.Count > 0)
{
//자신을 재귀적으로 호출한다
allControls.AddRange(GetAllControlsUsingRecursive(control));
}
}
//모든 컨트롤을 반환한다
return allControls.ToArray();
} |
코드는 자식의 자식(또 자식의 자식...) 이라는 계층적 구조를 모두 훓어가기 위한 재귀 호출 기법을 사용하고 있습니다. 이렇게 하여 폼의 모든 컨트롤이 반환될 수 있습니다.
이 코드를 테스트 해 보겠습니다. 클라이언트 코드를 다음과 같이 작성하고 결과를 보겠습니다.
//폼 자신(this)을 매개변수로 전달
Control[] controls = GetAllControlsUsingRecursive(this);
//자식 컨트롤 갯수
MessageBox.Show("자식 컨트롤 갯수: " + controls.Length);
//자식 컨트롤을 하나씩 출력해 본다
foreach (Control control in controls)
{
MessageBox.Show(control.ToString());
} |

결과를 보면 폼이라는 컨테이너 컨트롤 하위 모든 계층의 컨트롤이 반환되었음을 확인할 수 있습니다.
* 두 개의 컬렉션을 이용한 구현
앞서, 재귀호출을 통해 컨테이너 컨트롤의 모든 자식 컨트롤을 반환할 수 있었는데요..
사실 재귀호출은 성능 이슈로 조금 꺼려하기도 합니다.
이번에는 완전 똑 같은 일을 하지만 재귀호출을 사용하지 않고 두 개의 컬렉션을 이용해서 구현해 보도록 합니다.
참고로 이 구현 방법은 'C#을 이용한 윈도우 폼 프로그래밍(저자:크리스셀즈)' 이라는 책에서 제시한 코드를
닷넷 2.0의 지네릭 버전으로 변경한 코드입니다. 아래 코드를 보시죠..
static Control[] GetAllControls(Control containerControl)
{
List<Control> allControls = new List<Control>();
Queue<Control.ControlCollection> queue = new Queue<Control.ControlCollection>();
queue.Enqueue(containerControl.Controls);
while (queue.Count > 0)
{
Control.ControlCollection controls
= (Control.ControlCollection)queue.Dequeue();
if (controls == null || controls.Count == 0) continue;
foreach (Control control in controls)
{
allControls.Add(control);
queue.Enqueue(control.Controls);
}
}
return allControls.ToArray();
} |
앞서 예제와 동일하게 리스트 컬렉션은 모든 컨트롤을 담기 위한 컬렉션이며
큐(Queue) 컬렉션이 바로 이전의 재귀호출 부분을 대신하는 컬렉션입니다.
큐에 바로 밑 계층의 자식 컨트롤들을 담고(Enqueue) 리스트에 추가한 뒤 빼고(Dequeue) 다시 자식컨트롤이 있을경우 큐에 삽입하고.. 를 반복하면서 모든 계층을 돌아 다니는 로직입니다.
이상 두 개의 메서드는 완전 동일한 기능을 제공하며 입맛에 따라 골라 쓰시면 되겠습니당~!~