현재 개인 프로젝트에서

입력 된 값만큼 텍스트 박스를 생성을 해야 하는 작업을 하다가 동적으로 텍스트 박스를 생성하는데 꽤나 긴 시간이 소비되어

다음에 같은 작업을 할 때 시간을 좀 더 덜어내고자 글을 쓰게 되었다.





작업 해야 할 프로그램 화면 구성은 아래의 이미지와 같다.



  





먼저 해당 객체에는

public List<TextBox> userNameBoxList; 와 같은 List 컨테이너를 통해서 TextBox를 저장했다.

TextBox를 그려줄 canvas가 필요했고 CNV_tb_cnv라는 변수명으로 Canvas를 생성했다.







Canvas.SetTop

Canvas.SetLeft


위의 두 함수와 간단한 수식을 통해서 그려줄 위치를 설정하였고. 리스트 원소에 접근하여 width, height를 설정하였고

CNV_tb_cnv의 Children으로 추가하여주었다.



참조코드 :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
            int startX = 100;
            int startY = 50;
 
            int maxCntinLine = 2;
            double offsetX = IMG_UserMap.Width / 2;
            double offsetY = 30 + IMG_UserMap.Height / 6 ;
 
            for ( int i = 0; i < maxUser; ++i)
            {
                userNameBoxList.Add(new TextBox());
                
                // 위치
                Canvas.SetLeft(userNameBoxList[i], startX + (i % maxCntinLine) * offsetX);
                Canvas.SetTop(userNameBoxList[i], startY + (i / 2* offsetY);
 
                // 높이 / 너비

userNameBoxList[i].Width = IMG_UserMap.Width / 4;
                userNameBoxList[i].Height = IMG_UserMap.Height/ 6;
 
                CNV_tb_cnv.Children.Add(userNameBoxList[i]);
                
 
 
            }

cs






1. 델리게이트 (delegate )



- 사용자 정의형

- 대리자

- C, C++의 함수포인터와 동일한 기능


어떤 일을 대신 해주는 사람 이라고 생각하자.



예시)


// 매개변수로 string type의 값을받으며 반환은 하지 않는 함수를 대리하는 Log_Event라는 이름을 갖는자료형을 정의

public delegate void Log_Event(string msg);


// func라는 함수가 있다. string을 입력받으며 입력받은 값을 콘솔창에 출력하여 준다.

public void func( string msg )

{

    Console.WriteLine("{ 0 }", msg); 

}


// Log_Event형 log_msg라는 변수를 생성.

public Log_Event log_msg;


// log_msg가 func를 앞으로 대신하여 줄 것이다.

log_msg = func;


// func의 기능을 대신하여 준다. 따라서 입력값을 콘솔창에 출력해준다.

log_msg("text");



- delegate는 반드시 메서드를 참조 시켜야 한다.

- 또한 정의한 형식에 맞아야 한다.



- += 연산자를 통하여 1개의 delegate 변수에 여러 메서드를 참조 시킬 수 있으며 호출은 
  참조 순서대로 호출 된다. 이것을 델리게이트 체인 이라고 한다.

- -= 연산자를 통해 참조했던 함수를 더이상 참조하지 않겠다는 의미로 사용 할 수 있다.











2. 이벤트 ( Event )


- 델리게이트를 통하여 구현


- 하나의 이벤트는 여러개의 작업들이 추가 될 수 있어야 한다.


- 어떤 사건이 일어났을때 특정 함수나 작업을 수행 하는 것.


- 이벤트는 Delegate를 캡슐화 시킨것이라고 생각하자!!


사용 절차 )


1. 델리게이트 선언


2. 클래스 내에 선언한 델리게이트의 선언 앞에 event 한정자를 수식


3. 메소드 형식과 일치하는 이벤트 핸들러 작성


4. 인스턴스 생성 후 3번에서 작성한 이벤트 핸들러 등록


5. 이벤트 발생 -> 이벤트 핸들러 호출





3. 델리게이트 와 이벤트 차이 



Delegate는 클래스 외부에서 임의로 직접 사용 가능

Event는 외부에서 직접 이벤트를 임의로 일으킬 수 없음.





2019.02.16


WPF의 Mainwindow에서 버튼을 누르면 기존 창이 가려지고, 

새로운 창이 나와야 하는 작업을 하는 과정에서 생각보다 시간을 많이 쓰게 되었다.

내가 같은 실수를 하지 않기 위해,

나와 같은 어려움을 겪은 분들을 위해 글을 남긴다.




1. DLL 생성



DLL 추가하고자 하는 솔루션에서 마우스 오른쪽 버튼을 눌러 [ 새 프로젝트 추가 ]를 누르고 [ 클래스 라이브러리 ] 를 생성 한다.




생성된 클래스라이브러리에서 나는 wpf 사용자 컨트롤을 사용 할 것이니 사용자 정의 컨트롤(WPF)를 눌러 생성한다.



시작 프로젝트의 참조에서 마우스 오른쪽 버튼을 눌러 [ 참조 추가 ]로 들어간다.



해당 DLL을 체크하고 확인을 누른다. 이후에 생성한 DLL 프로젝트에서 마우스 오른쪽 버튼을 눌러 시작프로젝트로 설정하여 주고

빌드하여 준다. 그렇게 하면 bin\debug 경로에 DLL 파일이 생성된다.


그런데 만약 빌드가 되지 않고 아래 이미지와 같은 에러가 뜬다면



위에서 참조추가 했던것과 마찬가지로 system.xaml을 참조추가하면 해결된다.



이제 DLL은 생성이 완료되었다. 메인윈도우에서 해당 버튼을 눌렀을때 방금 만들었던 사용자컨트롤 창을 띄우게 하면된다.




나는 유저 컨트롤이 생성되는 시점에서 Visibility를 hidden으로 두고 메인윈도우에서 해당 창을 띄우는 버튼을 눌렀을때 창이 visible 상태가 될수있도록 Show 함수를 작성하였다. 이제 메인에 사용자 컨트롤을 컨트롤 할 수 있는 변수를 생성하고 메인 canvas에 해당 childwindow를 add해주면 된다. 아래의

이미지를 참고하자









끝!


오늘 WPF 관련 프로젝트를 진행하는 과정에서 컨트롤 속성창이 나오지 않아 당황을 했다.






바로 이 친구가 사라졌다. 


이 친구는 F4 Key를 입력 하거나


혹은



보기 -> 속성 창을 통해서 다시 활성화 시킬 수 있다.




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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
        void OldDeleteLog()
        {
            // 경로 + 형식을 통하여 폴더 내 해당 파일들을 추출하기 위한 변수
            string path = savePath + "*.log";
            // 핸들을 통하여 path에 해당하는지 검사
            _finddata_t fd;
            intptr_t handle = _findfirst(path.c_str(), &fd);
            // 파일이 없다.
            if (-1 == handle) return;
            int result = 0;
            // 경로를 통해 파일 이름을 가져온다.
            do
            {
                int year = 0, month = 0, day = 0;
                char *pt1 = nullptr, *pt2 = nullptr, temp[16= { 0 };
                char yearCopy[4= { 0 }, monthCopy[2= { 0 }, dayCopy[2= { 0 };
                // 파일이름에서 20으로 시작하는 부분(연도)을 가르킨다.
                pt1 = strstr(fd.name, "20");
                pt2 = strstr(fd.name, ".log");
                // 파일 포맷에서 날짜 추출 X_X_yyyy-mm-dd.log에서 "yyyy-mm-dd"를 추출한다.
                // 20으로 시작하는 부분을 copy하고 .log로 시작하는 부분을 자른다.
                // 현재 "yyyy-mm-dd" 상태
                memcpy(temp, pt1, 10);
                memcpy(yearCopy, &temp, 4);
                memcpy(monthCopy, &temp[5], 2);
                memcpy(dayCopy, &temp[8], 2);
                // 날짜를 문자열 -> 정수로 변환
                year = atoi(yearCopy);
                month = atoi(monthCopy);
                day = atoi(dayCopy);
                // tm_st = 뺄 기준이 되는 날짜 변수 ( 현재 날짜 )
                // tm_nd = 뺄 날짜 ( 파일 날짜 )
                time_t tm_st, tm_nd;
                struct tm fileDay;
                memset(&fileDay, 0sizeof(tm));
                // 년도는 1900년부터 시작, 월은 0부터 시작이기에 
                // 년도 - 1900 / 월 - 1
                fileDay.tm_year = year - 1900;
                fileDay.tm_mon = month - 1 ;
                fileDay.tm_mday = day;
                
                tm_st = mktime(&fileDay);
                time(&tm_nd);
                // 날짜 차이를 구하고 tm_day에 일수를 set
                double d_diff = difftime(tm_nd, tm_st);
                int tm_day = static_cast<int>(d_diff / (60 * 60 * 24));
                
                // 날짜의 차이가 save_day와 같거나 크면 
                // 해당 경로의 Log 파일을 삭제
                if (tm_day >= save_day)
                {
                    // 경로 + 파일 이름 지우기
                    char deleteFilePath[100];
                    ZeroMemory(deleteFilePath, sizeof(deleteFilePath));
                    strcat_s(deleteFilePath, sizeof(deleteFilePath), savePath.c_str() );
                    strcat_s(deleteFilePath, sizeof(deleteFilePath), fd.name );
                    // 해당 경로의 파일 지우기
                    remove( deleteFilePath );
                }
                // 해당 날짜 - saveDay가 크면 해당 파일 삭제.
                result = _findnext(handle, &fd);
            } while (-1 != result);
            _findclose(handle);
        }
cs




섀도잉

int num = 10; (전역변수) void A(){ int num = 20; Console.WriteLine( num );}

위와 같이 코딩을 하였을 때, 콘솔 창에 출력되는 것은 함수 A에서 생성된 num의 값인 20이다.

이와 같이 특정 영역에서 이름이 겹쳐져 다른 변수를 가리는 것을 섀도잉 이라고 한다!


하이딩

class Program { class Parent { public int variable = 273; } class Child : Parent { public string variable = "shadowing"; } static void Main(string[] args) { Child child = new Child(); Console.WriteLine(child.variable); } }

하이딩과는 달리 부모 클래스와 자식클래스 간에 동일한 이름으로 변수를 생성하여 사용하는 상황이다.

Main문을 보았을때 과연 무엇이 출력 될 것이라고 생각하는가?

정답은 ? " Shadowing "이 출력 된다. 섀도잉과 비슷하게 가장 가까운 클래스 내부의 문자열 변수를 사용한다고 생각하면 된다.

하이딩을 사용하는 것은 정상적인 상속을 막으므로 되도록이면 변수명을 다르게하여 하이딩 상황이 일어나지 않도록 하는 것이 좋겠다.






//< 함수 >

public static IEnumerable<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
{
if (obj != null)
{
if (obj is T)
yield return obj as T;
foreach (DependencyObject child in LogicalTreeHelper.GetChildren(obj).OfType<DependencyObject>())
foreach (T c in FindLogicalChildren<T>(child))
yield return c;
}
}
//< 호출 부분 >
foreach (var a in FindLogicalChildren<ContentControl>(this))
{
if (dic.ContainsKey(a.Name))
{
if (a is GroupBox)
{
GroupBox grb = (GroupBox)a;
grb.Header = dic[a.Name];
}
else if (a is TabItem)
{
TabItem cvs = (TabItem)a;
cvs.Header = dic[a.Name];
}
else if (a.Content is String)
{
a.Content = dic[a.Name];
}
}
}

자식 컨트롤을 재귀적으로 탐색하여 리턴하여주는 함수를 통하여 모든 버튼을 반환한다.
그러나 Canvas와 TabItem의 Text는 Content가 아닌 Header를 쓰기 때문에 따로 검사를 해주어야 한다.
이 방식을 이용하여 이미 개발되어 있는 프로젝트의 각 컨트롤 추출 및 입력으로 다국어 지원을 쉽게 해결 할 수있었다.






+ Recent posts