WPF 팁

Form Designer 프로젝트 #6 - Tool Window (콘트롤 추가/삭제) - 완료! 안떠니 평점: 10.0/10 (4명 참여) 조회: 5148
거의 한달이 흘렀네요. 과연 몇분이나 제 강좌를 기다리고 계셨을까요? ㅎㅎㅎ
아무도 관심을 갖지 않았다면, 무척 슬프겠죠? ㅎㅎ 댓글이나 평점을 투척하는 배려를~~~
인터넷에서 제일 무섭고 슬픈게 악플이 아닌 무플이라고 하죠 ^^

자 그럼, 툴 윈도우를 시작하겠습니다. 며칠전에 화면 캡처를 올렸죠. 아래 그림처럼요.




우선 위의 그림처럼, 간단히 디자인했습니다.

콤보 박스가 data binding 을 하면, width 가 자동으로 셋팅되던데...끄는걸 ..모르겠네요.
일단..몇시간 허비한 걸루...충분해서..그냥 스킵하고 다음 단계로~~수작업으로 폭을 맞췄습니다.

위에 윈도우의 크기를 변경하면, 안에 있는 콤보박스의 폭도 같이 따라서 변경되던데요.
다른 콘트롤은 가만히 있는데, 왜 위에 콤보박스만 폭이 전체 윈도우의 폭 변경에 따라 움직이는지 몰겠네요.
아시는분 계시면, 댓글 주세요 ^^

위의 콘트롤 버튼을 클릭하면, 각각에 해당하는 콘트롤을 폼 디자인을 하는 윈도우에 생성,등록하는 구조입니다.
그리고, 그 윈도우에서 [DEL] 키를 누르면, 현재 adorner 가 (즉 포커스를 가진) 있는 콘트롤(들)을 삭제합니다.

이 기능을 구현하기 위해서, 여기 질문게시판에 질문도 올려보고, 구글링도 해 봤습니다.
구글링을 통해서, WPF 가 VS 에 있는 폼 디자이너를 개발자용으로 제공하고 있다는 글을 발겼했고,
비슷한 시점에 질문게시판에 어느 분께서 자료실에 가보면 찾는게 있을꺼다라고 댓글을 주셨습니다.

자료실에 보니, 폼 디자이너, 라는게 떡하니 있더군요. 만드신 분은 제가 활동하는 델마당(델파이 개발자 동호회),
에서도 활동하시는 분이였구요. 델마당에서 그 분이 폼 디자이너를 만들었다는 글을 봤는데,
여기 게시판에 소스를 공개하신 줄은 몰랐네요.

그 분의 소스를 분석하고 데모를 둘러본 결과, 이 분의 소스가 없었다면, 이 프로젝트를 완성할 수 있었을까??
라는 자문을 하게 되더군요. 700 라인 정도로 길지도 짧지도 않은 소스였지만, 바닥부터 그렇게 코딩할려면,
아마도 저는 6개월 정도의 시간이 걸리지 않았을까 합니다. 진심으로 감사~~~

소스를 분석하고, 데모를 이리저리 굴려본 결과, 우선 동적으로 생성/삭제하는 기능은 없었구요.
따라서, 동적으로 생성된 콘트롤을 관리하는 리스트를 추가해서 콘트롤이 여러개 선택된 경우 삭제도 가능하게 개선시켰습니다.

그리고, [Shift] 키를 이용해서 여러개의 콘트롤을 선택하는 경우, 사소한 버그를 발견했습니다.
여러개를 선택해 놓고, 다시 [Shift] 키를 누른 채로 선택을 해제하는 경우, 현재 클릭한 콘트롤이 해제되는게 아니라,
[Shift] 키를 누르고 선택할 때 가장 첫번째 선택된 콘트롤이 해제가 되는 버그였습니다.

버그의 이유는 아래 코드에서 보여드리고, 수정된 코드도 같이 보여드리겠습니다.
버그의 수정과 추가 개선된 기능은 원제작자인 틱톡님께 소스를 보내드리고, 여기 게시판에 공개를 허락받겠습니다.

우선, 제가 추가한 코드부터 시작하겠습니다.
버튼을 추가하는 메서드를 만들었습니다. 코드는 아래와 같이,

        public void AddButton()
        {
            Button btn = new Button();

            btn.Width = 70;
            btn.Height = 30;
            btn.Content = "Button" + getLastIndex("Button").ToString();
            btn.Name = btn.Content.ToString();

            Canvas.SetLeft(btn, 100);               // Very important lines
            Canvas.SetTop(btn, 100);               // Very important lines

            target.Children.Add(btn);
            target.RegisterName(btn.Name, btn);

            btn.PreviewMouseDown += this.MouseDownProc;
            btn.PreviewKeyDown += this.KeyDownProc;
        }

위의 코드가 실행될 때마다 새로운 버튼을 생성시켜야 하기 때문에, 동적으로 생성된 이름은 항상

Button<인덱스>

이렇게 생성이 되어야 합니다. 매번 다른 이름으로 생성이 되어야, 사용자가 삭제를 원할 때 현재 선택된 콘트를을
콘트롤의 이름을 찾을 수 있겠죠. 그래서, 콘트롤 생성할 때마다 마지막 인덱스를 구해오는 함수를 만들었습니다.

위의 코드에서 2~3일 동안 삽질한 부분이 있습니다. 바로 "Very important lines" 라고 주석을 붙힌 부분이죠.
동적으로 생성한 콘트롤이 윈도우에 보여지지를 않는 문제였습니다. 정말 모르겠더라구요.
그래서, 결국 이 부분을 해결하지 못해서, 원제작자이신 틱톡님께 이메일로 질문을 드려봤습니다.

하지만, 틱톡님도 WPF/C# 코딩을 안하신지 5년 정도 되셨고, 현재 테스트해 볼 환경도 아니라고 하셨습니다.
그렇게 좌절한 채로...이리 저리...이 코드 저 코드를 시도해 보다가, 위의 코드를 넣어봤습니다.

그랬더니, 귀신처럼, 윈도우에 버튼이 나타나더군요. 동적으로 콘트롤을 생성시키고, Margine 이 아닌,
Canvas 에서의 위치를 저런 식으로 지정을 해야, 해당 콘트롤이 Canvas 위에 그려지는 방식이죠.

아직도..긴가 민가 하지만, 결론은 위의 코드 없이는 콘트롤이 Canvas 에 그려지지 않는다, 입니다.
정말....멘붕이죠...이것 때문에 거의 2~3일을 의자에 꼭 붙어서 일어나지도 못했습니다.


그리고, 위의 코드 뿐만이 아니라, 디자인 윈도우의 Canvas 인 MainCanvas 에 Children.Add 를 이용해서,
등록하고 RegisterName 을 이용해서 이름도 등록해야 합니다. 그렇게 해서, 동적 생성된 콘트롤이 완성되는거죠.

동적으로 생성된 콘트롤의 마우스다운 이벤트와 키다운 이벤트는 [Shift], [Del] 키를 받기 위해서,
그리고, 마우스에 의한 이동, 크기 변경을 위해서 공통의 이벤트를 등록해 줍니다.

아래는 콘트롤의 작명을 위해서 마지막 인덱스를 가져오는 함수입니다.


        public int getLastIndex(string ControlName)
        {
            int iCount = 0;
            int iLastIndex = 1;
            int iMaxIndex = 1;

            foreach (UIElement each in target.Children)
            {
                if ((each as Control).Name.IndexOf(ControlName) == 0)
                {
                    iCount++;
                    iLastIndex = Convert.ToInt32((each as Control).Name.Substring(ControlName.Length));
                    if (iMaxIndex < iLastIndex)
                    {
                        iMaxIndex = iLastIndex;
                    }
                }               
            }
            if (iCount == 0)
            {
                iMaxIndex = 0;
            }
            return (iMaxIndex + 1);
        }


target 은 디자인 윈도우의 Canvas 가 canvasDesigner 라는 객체에 인자로 전달된 것입니다.
target 으로 디자인 윈도우에 콘트롤을 생성하고 삭제/이동 등등의 작업을 하는거죠.

이 target 을 루프로 돌면서 그 자식들의 이름을 검색합니다.
가령, Button 을 인자로 전달하면 Button 이 몇개 있는지를 조사하고, 가장 큰값에 + 1 을 해서 리턴을 합니다.
그러면, 중간에 작업하다가 삭제된 Button 의 인덱스를 건너뛸 수 있겠죠. 중요하고 가장 기본적인 함수입니다.

아래는 특정 콘트롤(들)이 선택된 상태에서, [Del] 키가 눌려서, 선택된 콘트롤(들)을 삭제하는 코드입니다.

       public void DeleteElements()
        {
            foreach (String eachUI in selection.selectedElements)
            {
               foreach (Selector eachSL in selection.selectedList)
               {
                   if (eachSL.Uid == eachUI)
                   {
                       selection.Delete(eachSL);
                       //selector = null;
                       break;
                   }
               }
              target.Children.Remove(target.FindName(eachUI) as UIElement);
              target.UnregisterName(eachUI);              
            }
        }

selection.selectedElements 는 선택된 콘트롤(들)의 이름을 보관하고 있는 리스트 객체입니다.
이 리스트를 돌면서 현재 adorner 로 선택된 콘트롤(들)과 일치하는지 확인합니다.
adorner 가 할당된 콘트롤은 selection.selectedLit 에 모아져 있습니다.

발견이 되면, target 에서 해당 이름을 갖는 자식을 찾아서 삭제시키고, 이름도 UnregisterName 으로 제거합니다.

아래는 콘트롤이 사용자에 의해 선택이 되면, 작은 사각형을 각 꼭지점에 그려주기 위한 객체입니다.
이것은 WPF 에서는 adorner 라는 객체로 제공하고, 이것을 상속 받아서 추가 코드를 작성해야 합니다.

제가 추가한 부분은 동적으로 생성된 콘트롤(들)의 삭제를 위해서 식별자 용도로 속성을 추가한 것입니다.
콘트롤이 삭제될 때 adorner 도 삭제가 되어야 하기 때문이죠.

틱톡님께서는 adorner 를 selector 라는 이름으로 상속을 하셨고, 저는 Uid 를 추가했습니다. 아래와 같이..

 

    public class Selector : Adorner
    {
        <생략....>
        public string Uid = "";                

        <생략.....>
   
        public Selector(Selection sel, UIElement adornedElement) : base(adornedElement)
        {
            selection = sel;
            target = adornedElement;
            Uid = (adornedElement as Control).Name;                                    // Uid !!
            AdornerLayer.GetAdornerLayer(adornedElement).Add(this);
        }
    }



그리고, 이 selector 의 instance 는 사용자에 의해 동적으로 콘트롤이 생성이 되고, 마우스 클릭이 왔을 때,
selection.add 에 의해 한개씩 생성이 됩니다. selection 은 selector 를 관리하고 다중 선택된 콘트롤을 관리하죠.

selection 의 선언을 보면, selector 를 관리하는 selectedList 라는 객체가 있고, 다중 선택된 콘트롤을 관리하기 위해서,
제가 추가한 selectedElements 라는 리스트 객체가 있습니다. 전자는 Selector 형을 보관하는 리스트 객체이고,
후자는 String 형을 보관하는 리스트 객체입니다. 코드는 다음과 같습니다.


 

public class Selection
    {
        public List<Selector> selectedList;
        public List<String> selectedElements;

       <중간생략>

        public Selection()
        {
            selectedList = new List<Selector>();
            selectedElements = new List<String>();
            thumbs = new SizeThumbCollection();
        }

        public int IndexOf(UIElement elem)
        {
            int result = 0;
            foreach (Selector selector in selectedList)
                if (selector.AdornedElement == elem) return result;

            return -1;
        }


        // 이미 add 되어 있고 shift 키가 눌렸다면 delete 해준다.
        // add 안되어 있다면 add 하고 selector 를 리턴한다.
        public Selector Add(UIElement elem)
        {
            bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
            bool ctrlDown = (Keyboard.Modifiers & ModifierKeys.Control) != 0;

            int ind = IndexOf(elem);

            // 키보드에서 shift 키가 안눌렸으면 리스트에 있는 애들 모두를 clear 해준다..
            // 선택된 것에 MouseDown 이면 clear 하지 않는다.
            // If the selected is not in the list,
            // remove all adorners from the list and add a new adorner to the selected.

            if (shiftDown == false && (ind < 0)) Clear();


            if (thumbsTarget != null)
            {
                thumbsTarget.RemoveSizeThumbs();
                thumbsTarget = null;
            }

            Selector selector = null;
            if (ind < 0)  // if it is not in the list.
            {
                selector = new Selector(this, elem);  // pass the selection and the selected UIElement.
                selectedList.Add(selector);
                selectedElements.Add((elem as Control).Name);    // 새로 추가된 코드
            }
            else          // if it is already in the list.
            {
                // ind is always 0. So it always indicates the very first one of the list.
                // Bug #1: Fix it! And Fixed on Mar/01/2013.
                // selector = selectedList.ElementAt<Selector>(ind);

                foreach (Selector each in selectedList)
                {
                    if (each.Uid == (elem as Control).Name) selector = each;
                }            

                // 이미 add 되어 있고 shift 키가 눌렸다면 delete 해준다.
                // Bug #1: The control only having the mouse should be deleted.
                if (shiftDown)
                {
                    Delete(selector);
                    selector = null;
                }
            }

            if (selectedList.Count == 1)
                LocateSizeThumbs(selectedList.ElementAt<Selector>(0));
            else
            {
                if (thumbsTarget != null) thumbsTarget.RemoveSizeThumbs();
                thumbsTarget = null;
            }

            return selector;
        }
   

원래 코드는

selector = selectedList.ElementAt<Selector>(ind);
   
이 코드는 선택된 콘트롤이 한개 이상일 경우 항상 ind 가 IndexO 함수로 부터 0 을 받아오기 때문에,
다중 선택된 콘트롤에서 항상 처음 선택된 콘트롤을 가리키게 되어 있습니다. 그래서 수정한 코드가,

 foreach (Selector each in selectedList)
 {
      if (each.Uid == (elem as Control).Name) selector = each;
 }           

입니다. selector.Uid 에 보관한 콘트롤 이름과 현재 해제시키려고 선택된 콘트롤의 이름과 같은지를 확인합니다.


이렇게 해서, 동적으로 콘트롤을 생성시키고, 삭제하는 기능을 추가하였습니다.
1개 이상 선택되었을 때 한꺼번에 삭제하는 기능도 잘 작동하네요.

제가 추가한 코드는 얼마 안되는 분량이지만, 완성하는데 꽤 많은 시간이 걸렸네요 ㅠㅠㅠ
다시 한번 틱톡님께 감사의 말씀을 드립니다. 이 소스가 없었다면, 이렇게 빨리 끝내지도 못했죠..

지금까지 내용 중에서 질문이 있으면 댓글 남기세요.
전체 소스 코드는 강의 맨 마지막 글에 첨부하고, 자료실에 올려두겠습니다.

코드가 정리되면, 다시 와서 올리도록 하겠습니다.

<끝>





















 

태그 : Add control list manager remove
작성자 정보
안떠니
Level 28
 [EXP.46/50]

메일:  비공개
글등록 +12 444 덧글등록 +3 246
자기소개
회사에서 WPF C# 으로 프로그램을 만들라고 하네요 ^^;
글 공유하기 |
  tweet facebook
2013-02-19 오전 9:09:26
나도한마디
사용자
피아소            [2013-02-26]
Level 12
 [EXP.5/40]
강좌가 아직 안올라 왔군요~~ 잘 안되시고 있나봐요..ㅠㅠ
사용자
안떠니            [2013-03-05]
Level 28
 [EXP.46/50]
잘 안되고 있는 와중에 다른 일이 생겨서 다른거 하고 있었습니다.
그러다. 지난 금요일부터 삽질 다시 시작했는데요.
겨우 오늘 끝냈네요 ^^;

동적으로 콘트롤 생성시키고, 삭제시키고 하는게 나름 힘들었네요.
여기 자료실에 틱톡님이 올리신 Designer.cs 에 동적 콘트롤 추가/삭제를
구현했습니다.

그리고, adorner 를 삭제하는 부분에서 약간의 버그가 있어서 그걸
수정했구요.

자세한 내용은 강좌 게시물에서 언급하겠습니다.

휴.....다행이네요. 끝마쳐서요 ^^; WPF/C# 책 한권 없이...
구글링과 게시판 질/답, 그리고, 틱톡님의 공개자료로 후다닥 ㄷㄷㄷㄷ
사용자
지송닷넷            [2013-03-19]
Level 99
 [EXP.만랩]
비쥬얼트리 라는 걸 이용하시면 좀더 컨트롤에 접근이 쉽지 않을까 싶습니다. ^^
사용자
천생이            [2013-12-02]
Level 30
 [EXP.24/50]
전체 소스좀 볼 수 없을까요..ㅠ
태그로 엮인글
[C#.NET Q&A] ListBox 에 등록된 아이템의 정렬 질문[1]  필승불패
[C#.NET Q&A] 메소드로 생성한 데이터 테이블을 데이터 셋에 담는 방법이 있을까요[3]+9  가리워진길
[ASP.NET Q&A] OnClientClick 에서 버튼 비활성화 후에 onclick 실행[2]  Belbo
[C#.NET Q&A] 윈폼 툴박스에 혹시 이런기능 있나요?[2]  DevHong
[C#.NET Q&A] Devexpress 의 Grid Control 에서 sql 의 where 절? 을 할 수 있나요?[2]+2  바보쿠우
[C#.NET Q&A] DataGridView.Rows.Add 질문[1]+1 파일첨부 필승불패
[C#.NET Q&A] UltraGrid 사용중인데 ROW를 추가하는 법이 있을까요?[2]+2  신입사원
[C#.NET Q&A] Thread 와 UI Control 문제[1]  라팍스
[C#.NET Q&A] List<int[]>의 Remove 질문 드립니다[1]+4  현시키
[C#.NET Q&A] c# 에서의 레퍼런스 개념을 잘모르겠습니다[2]  엿장수
글리스트
CheckBox ListBox 샘플[1] 파일첨부 이재웅
RadioButton ListBox 샘플 파일첨부 이재웅
UniformGrid ListBox 샘플 파일첨부 이재웅
Horizontal ListBox 샘플 파일첨부 이재웅
Vertical ListBox 샘플[2] 파일첨부 이재웅
Form Designer 프로젝트 #7 - Tool Window (콘트롤 속성 지정/변경) - 완료![1]+1  안떠니
 ★현재글->   Form Designer 프로젝트 #6 - Tool Window (콘트롤 추가/삭제) - 완료![3]+1  안떠니
Form Designer 프로젝트 #5 - TreeView Manager  안떠니
Form Designer 프로젝트 #4 - TreeView + XML [1]  안떠니
Form Designer 프로젝트 #3 - TreeView 삭제 / 이름변경[2]+2  안떠니
Form Designer 프로젝트 #2 - TreeView 노드 추가[1]  안떠니
Form Designer 프로젝트 #1 - 프로젝트 관리 화면[4]+1  안떠니
실행파일에서 config 파일 변경하기[1]+3  킴언어
계산기[3]  sa2랑
WPF 성능관리  sa2랑
[RE] WPF Performance Suite  sa2랑
WPF에서 내부에서 작업한 내용을 UI에 올릴때 Dispatcher 클래스 사용[1]  지유니