어떠한 데이터들을 트리 형태의 계층적 표현을 하려 하는데 이전에는 무료 컴포넌트(있는지 안찾아 봤음.ㅠ)나
자바스크립트를 이용해서 개발 하셨을 것인데... ASP.NET 2.0에는 TreeView컨트롤이 있어 손쉽게 트리형태로 표현이 가능합니다.
이 TreeView를 연습삼아 테스트 해보니 꽤 괜찮은듯 합니다만 TreeView에 데이터를 바인딩 하기 위한 DataSource컨트롤은 XML형태나 사이트맵 형태의 소스만을 선택하도록 되어있습니다.
데이터가 고정적이고 자주 변화가 없는 형태라면 XML로 해도 되지만 웹이란게 여러 사용자가 추가,수정,삭제 하기 때문에 XML을 선택하기란 좀 무리가 있어 보입니다.
그럼.. DataBase를 이용하면 되지 않을까?? 오브코쓰~!!
하지만 무턱대고 일반 DataBase를 TreeView의 DataSource에 넣고 실행하면 아래의 친절한 에러씨가 마중나옵니다.
HierarchicalDataBoundControl은IHierarchicalDataSource또는IHierarchicalEnumerable을 구현하는 데이터 소스만 허용합니다.
대충 보면 계층적인 DataSource를 원하다는 뜻인데 TreeView맘은 이해하지만 대충이라도 표현해주면 안되겠니?? ㅡㅡ; 그래서...눈 감고 생각을 했습니다. 어떻게 하면 일반 DataBase의 데이터를 트리 형태로 표현할 수 있을까... 그러다 문득...떠오른 것이 계층형 게시판~!! 계층형 게시판의 소스는 너무나도 많고 DataBase의 디자인도 여러가지입니다.
하지만 저는 태오사이트를 너무나도 사랑하는 나머지 태오님 강좌의 그림이...뇌리를 강하게 스쳤습니다.
그래~!! 트리형태로 보여줄 데이터를 눈에 보이는 그대로 정렬이 가능하고 계층을 표현할 Depth만 있으면 되겠네??
위와 같은 형태의 데이터이고 idx는 기본키입니다.
thread는 눈에 보이는 트리의 순서입니다. 즉, thread를 정렬하고 순차적으로 읽기만 하면 됩니다.
groupid는 나중에 추가, 수정, 삭제를 위해서 필자가 필요로 해서 넣은 것 이고 depth는 트리에서 표현될 계층입니다.
일단 쿼리는 Select * From [테이블명] Order By thread로 하면 됩니다. 자 그럼.. ASP.NET 페이지를 구성해보도록 하겠습니다.
Visual Studio 2005를 열고 새로운 웹사이트나 웹 응용프로그램으로 프로젝트를 하나 만들고 Default.aspx 디자인 모드에서
옆의 그림처럼 TreeView 컨트롤을 하나 드래그해서 올려 놓습니다.
자동서식을 보면 여러가지 서식이 있지만.. 개인적으로 MSDN 서식이 맘에 듭니다.
첨에는 MSDN처럼 나오지만 "선표시"를 체크하면 그림처럼 선도 표시됩니다.
이제 위 그림에서 봤던 데이터를 끌어 오도록 하겠습니다.
뭘로?? 맘대로... 난..DataSet을 사용한다. 아무이유없어~!!
protectedvoidPage_Load(objectsender,EventArgse)
{
SqlConnection con =newSqlConnection("server=;uid=sa;pwd=;database=;");
SqlCommand cmd =newSqlCommand();
cmd.Connection = con;
cmd.CommandText ="select * from category order by thread";
con.Open();
SqlDataAdapter da =newSqlDataAdapter(cmd);
DataSetds =newDataSet();
da.Fill(ds);
}
연결 문자열의 정보는 각자 시스템에 맞게 넣어주고 테이블은 category라는 테이블을 사용합니다.
이제 DataSet에 담긴 데이터를 TreeView 컨트롤에 표현해주는 일만 남았는데 이게 생각보다 쉽지 않았습니다.
일단 표현 방식을 간략하게 설명하자면 TreeView 컨트롤에 Node를 추가할 수 있는데 그 형태는 TreeNode 객체여야 하구요.
그 TreeNode형태로 데이터들을 표현하고 그 TreeNode를 TreeView에 추가시켜주면 됩니다.
그러기 위해선 TreeNode가 여러개 있어야 하며 그 노드는 루트노드(가장 상위노드), 이전노드, 현재노드입니다.루트노드는 가장 상위에 보일 "프로그램"이고 그 루트노드의 자식 노드로 아래의 노드들이 채워지면 됩니다.
그리고 무엇보다 중요한건 depth의 표현인데... 곰곰히 생각해 보면 depth에도 3가지 규칙?? 형태??가 있습니다.
제가가 생각 못한 다른 규칙이 있을 수도 있지만 데이터를 보고 생각끝에 얻은 결론이고 현재까지는 문제가 없었습니다.
1. 이전 데이터의 depth가 현재 데이터의 depth보다 작다면 "이전 노드가 현재 노드의 부모 노드가 된다."
데이터에서 보면 ASP.NET의 depth는 2이고 ASP.NET 1.0의 depth는 3이다. 즉, ASP.NET이 부모 노드가 되는 것입니다.
2. 이전 데이터의 depth와 현재 데이터의 depth가 같다면 "이전 노드의 부모노드가 현재 노드의 부모노드이다."ASP.NET 1.0과 ASP.NET 2.0의 depth는 같다. 여기서 ASP.NET 2.0의 부모노드는 ASP.NET 1.0의 부모노드가 됩니다.
3. 이전 데이터의 depth가 현재 데이터의 depth보다 크다면 이건 두 depth의 차이(gap)로 루프를 돌면서 찾아야 한다.
.NET Framework 2.0의 depth는 3인데 자바의 depth는 1이다. 그럼 두 depth의 gap이 2이고 반복문을 통하여 부모노드를 찾으면 되는데요 찾는 구문을 보면 간단합니다.
privateTreeNodefindParentNode(TreeNodenode,intgap)
{
TreeNodetmpNode = node;
for(inti = 0; i <= gap; i++)
{
tmpNode = tmpNode.Parent;
}
returntmpNode;
}
인자로는 이전 노드와 depth의 gap이고 현재 노드가 아니라이전 노드인 점에 주의해야 합니다.
이렇게 찾은 부모 노드 즉, TreeNode객체에 현재 노드를 자식 노드로 추가시켜주면 됩니다.
ParentNode.ChildNodes.Add(Node);
위의 구문처럼 자식 노드로 추가시켜주면 됩니다.
이제 TreeNode의 표현 코드를 전부 보도록 하겠습니다. 특별한 코드도 없고 위의 3가지 규칙에 맞춰서 작성된 코드입니다. 이는 버그도 있을 수 있으며 깔끔한 코드가 아닐 수도 있습니다. 더 좋은 방법과 깔끔한 코드가 있다면 피드백 부탁요~!!
TreeView1.Nodes.Clear();
TreeNoderootNode =newTreeNode();
TreeNodeparentNode;
TreeNodecurrentNode;
TreeNodepreviousNode = rootNode;
intpreviousDepth = 0;
intcurrentDepth = 0;
foreach(DataRowrowinds.Tables[0].Rows)
{
currentDepth =int.Parse(row[4].ToString());
currentNode =newTreeNode();//현재 노드 생성
currentNode.Expanded =true;//노드를 확장(펼쳐보여지게 한다.)
currentNode.Text = row[1].ToString();
currentNode.Value = row[2].ToString() +"^"+ row[3].ToString();
if(currentDepth > 0)
{
if(previousDepth < currentDepth)//이전 depth보다 현재 depth가 크면...
previousNode.ChildNodes.Add(currentNode);//이전 노드가 부모 노드가 된다.
elseif(previousDepth == currentDepth)
previousNode.Parent.ChildNodes.Add(currentNode);//같은 depth상의 노드라면 이전 노드의 부모 노드가 현재 노드의 부모 노드이다.
elseif(previousDepth > currentDepth)
findParentNode(previousNode, previousDepth - currentDepth).ChildNodes.Add(currentNode);
//현재 depth가 이전 depth보다 작다면 현재 노드의 부모 노드를 찾는다.
previousDepth = currentDepth;//이전 depth를 할당
previousNode = currentNode;//이전 노드 할당
}
else//루트 노드 생성
{
rootNode = currentNode;
parentNode = rootNode;
previousNode = parentNode;
previousDepth = currentDepth;
}
}
TreeView1.Nodes.Add(rootNode);
약간의 주석이 있으니 코드를 보는데 크게 불편함이 없을 것입니다. TreeNode에서 주의할 점은 Value가 같으면 안됩니다.
DataSet을 만든 코드와 위의 코드를 비하인드에 추가하고 실행을 시켜보겠습니다.
아주 깔끔하게 표현되었지만 단지 선들이 제각각 떨어진게 맘에 안듭니다. 선의 이미지도 변경 가능하니 이쁘게 변경해도 됩니다.
노드 추가,삭제,수정의 코드는 다음에 기회가 되면 포스트에 적어 보겠습니다. 단지, 추가, 수정의 단점은 중간에 노드가 낀다면 뒤의 thread의 값을 모두 변경해줘야 한다는 것인데 그러한 이유로 groupid가 있고 태오님의 계층형 강좌를 참고한다면 무리없이 추가,삭제,수정도 가능하리라 봅니다. 보너스로 제가 기능 테스트를 했던 화면도 간략히 올려보면
이상 언제나 별 영향가 없는 포스트만 올리는 마리였습니다. ^^;