'분류 전체보기'에 해당되는 글 189건

  1. 2009.11.01 C# 코딩 연습, 객체의 상속과 포함 (이벤트 코드 생성기의 HistoryManager 클래스의 소스 분석을 겸하여)
  2. 2009.11.01 Midnight Peach의 필드 테스트와 개봉 박두
  3. 2009.10.30 이벤트 코드 생성기 1.4 업데이트와 설치, 그리고 소스
  4. 2009.07.20 자동 업데이터 (HAU, Http Auto Updater) 1.2 업데이트
  5. 2009.06.21 중첩된 사용자 정의 컨트롤의 DesignMode 속성은 동작하지 않음
  6. 2009.06.18 멀티 패널 컨트롤
  7. 2009.06.17 닷넷 응용프로그램의 리소소를 자동으로 번역하고 동기화 하기
  8. 2009.06.17 DBML 커스터마이징 작업을 관리하는 VS 애드인
  9. 2009.05.20 C# 코딩 연습, 멀티 쓰레드와 이벤트
  10. 2009.05.07 비주얼 스튜디오 디버거에 관련된 특성들

C# 코딩 연습, 객체의 상속과 포함 (이벤트 코드 생성기의 HistoryManager 클래스의 소스 분석을 겸하여)

C# 코딩 연습 2009. 11. 1. 23:41

졸작 이벤트 코드 생성기 1.4 버전에 추가된 히스토리 기능은 이전에 생성한 이벤트 이름과 매개변수 쌍을 기억하는 기능입니다.

이 기능을 어떤 식으로 구현하는 것이 좋을까요?

아마도 클래스가 있는 언어를 사용한다면 히스토리를 관리하는 클래스를 만들어 관련된 모든 로직을 캡슐화하는 것이 정석일 것입니다.

구체적으로 이 클래스가 할 일을 정리해 본다면,

  1. 히스토리를 추가한다. 다만 추가할 수 있는 갯수에는 제한이 있어 최대값에 도달하면 선입선출 식으로 제거된다.
  2. 추가된 히스토리를 삭제할 필요는 없다.
  3. 히스토리 목록을 저장소(여기에서는 파일 시스템)에 저장하고 저장소에서 불러온다.
  4. 윈폼의 BindingSource 객체의 DataSource로 지정될 수 있다. (즉 히스토리 목록을 열거할 수 있다.)
  5. 프로그램 전반에 걸쳐 단 한 개의 인스턴스가 생성된다.

각각의 요구사항에 대한 해결 방법을 (간단한 것 부터) 찾아봅시다.

5 <- 이건 그냥 싱글톤 패턴으로 바로 해결이 가능하겠네요.

4 <- ‘열거’ 라는 말에서 착상할 수 있듯이 IEnumerable<T> 인터페이스를 구현하면 역시 간단하게 해결할 수 있겠습니다.

3 <- 히스토리 목록이 직렬화가 가능하도록 하면 되겠습니다. Serializable 특성을 붙이거나 ISerializable 인터페이스를 구현하거나 직접 (역)직렬화 로직을 구현하는 방법이 있겠습니다.

여기서는 닷넷 프레임웍 3.0에 추가된 DataContract와 DataMember 특성을 사용하겠습니다.


이제 제일 중요한 1번과 2번이 남았는데요.

직관적으로 결국 이 클래스는 바로 히스토리의 목록을 조작하는 클래스라는 것을 알 수 있는데, 그렇다면 문제는 히스토리의 목록을 어떻게 표현하느냐로 좁혀집니다.

즉, 히스토리의 목록으로 어떤 자료구조를 사용하느냐, 닷넷 프레임웍의 용어로 이야기하자면 어떤 컬렉션을 선택하느냐의 문제에 다름 아닙니다.

(자료구조와 컬렉션에 대해서는 졸고를 참고하십시오.)


그런데 사실 이 문제는 별로 고민할 필요가 없는 것이, 이 프로그램처럼 윈폼의 데이터바인딩 메커니즘과 함께 사용하는 경우라면 거의 대부분의 경우 List<T> 혹은 BindingList<T>가 정답이 될 것입니다.

(드문 경우긴 하지만 IList<T>나 IBindingList<T> 인터페이스를 구현한 클래스를 직접 만들어 사용하는 경우도 있긴 합니다.)

물론 List<T>는 윈폼 뿐만 아니라 닷넷 베이스 클래스 라이브러리를 통틀어 가장 많이 사용되는 자료구조가 아닐까 싶은데요.

STL과 자바에서는 ‘벡터’라고 불리는 이 가변배열은 자료구조의 기본이면서도 정말 유용합니다.

혹시 아직도 습관적으로 배열이나 ArrayList를 사용하고 있다면 List<T>를 대신 사용할 것을 고려해 보시기 바랍니다.


히스토리의 목록으로 List<T>를 사용하기로 결정했다면 이제 마지막 문제, 오늘의 주제이기도 한 문제가 남았습니다.

List<T> 클래스를 이용하여 히스토리 목록을 구현할텐데, 어떤 방식으로 이용하느냐는 것입니다.


OOP에 있어서 한 클래스가 다른 클래스를 이용(재사용)하는 방법에는 두 가지가 있습니다.

이른 바 is-a, has-a 관계라고도 일컬어지는 상속포함(또는 합성 혹은 위임)이 그것입니다.

상속은 한 클래스가 다른 클래스의 (private 멤버를 제외한) 멤버를 그대로 물려받는 것이며

반면에 포함은 한 클래스가 다른 클래스의 인스턴스를 멤버로 가지고 있는 경우를 말합니다.

물론 상속과 포함은 각각의 장단이 있긴 때문에 상황에 따라 선택을 해야할 문제이긴 하지만, OOP 언어의 대가들은 대체로 포함 방식을 사용할 것을 권장하고 있습니다.

그 근거 중 가장 대표적인 것이 상속의 경우에는 두 클래스 간에 (보다) 강력한 결합이 발생한다는 것인데, 예를 들면 이렇습니다.

보시다시피 A 클래스의 Foo 메서드가 Foo2로 이름이 변경되면 B.Foo()를 호출하던 코드는 이제 수정을 하여야 합니다.

반면에 포함의 경우에는 이런 문제가 발생하지 않습니다.

A 클래스가 변경되더라도 B 클래스에서 A에 대한 위임 부분만 바꾸면 되므로, B 클래스를 사용하는 입장에서는 변함이 없습니다.


워밍업은 이 정도로 하고 이제부터는 본격적으로 히스토리를 관리하는 클래스를 만들어 보도록 합시다.

클래스의 이름을 HistoryManager 라고 하고, List<T>를 포함하는 형태로 한다면 아래와 같은 클래스를 선언할 수 있겠네요.

아, 아직 지네릭 타입인 T를 결정하지 않았네요.

히스토리의 목록이라고 했으니까 이 T는 히스토리 아이템 타입이 될 것입니다. 히스토리 아이템 타입은 아래와 같이 정의하겠습니다.

이벤트의 이름과 매개변수를 각각 string 형으로 가지고 있는 단순한 클래스이고요.

직렬화를 위해 DataContract 특성과 DataMember 특성을 가지고 있습니다.

이제 HistoryManager의 정의는 다음과 같이 바뀌겠네요.


프로그램 전체에 걸쳐 단 하나의 인스턴스만 생성할테니, 싱글톤 패턴을 추가해봅시다.

싱글톤 패턴은 워낙에 잘 알려져 있으니 따로 이야기하지 않아도 될테구요.

리스트에 저장할 최대 갯수인 MaxHistoryItems와 파일에 저장하기 위한 파일명을 상수로 정의한 것을 기억하여 주십시오.

IsolatedStorageHelper 클래스는 격리된 저장소에 대한 파일 입출력 작업을 담당하는 헬퍼 클래스인데 구체적인 구현은 이벤트 코드 생성기의 소스를 참조하시기 바랍니다. 여기서는 그냥 로컬 파일시스템에서 데이터를 읽어오는 코드 정도로만 생각하셔도 무방합니다.


히스토리 아이템을 추가하는 메서드는 아래와 같이 구현할 수 있습니다.

리스트의 갯수가 최대값에 도달하면 마지막 아이템을 삭제하고, 새로운 아이템을 제일 앞에 삽입하고 있습니다.

여기서 List<T>와 관련하여 한 가지 사족을 달자면, List<T>에는 원소를 추가하는 메서드가 두 개가 있습니다.

Add와 Insert가 그것인데요. Add는 내부 배열의 마지막에 원소를 덧붙이는 반면에, Insert는 배열 가운데에 원소를 삽입합니다.

(그래서 Insert에는 삽입할 위치를 나타내는 인자가 하나 더 있습니다.)

문제는 Insert를 통해 원소가 삽입되면, 내부 배열에서 인덱스가 그 이후인 모든 원소들이 한 칸식 뒤로 밀려나게 된다는 것입니다.

여기서 뒤로 밀려난다는 것은 기술적으로 말하자면, 객체의 이동(즉 메모리의 복사)을 의미하는데, 그 갯수가 많다면 이는 성능상의 오버헤드가 될 수 있습니다.

예를 들어 원소가 만 개인 리스트의 0번째에 삽입을 한다면 만 개의 객체를 이동하여야 하는 것입니다.


그리고 List<T>의 삭제의 경우에도 마지막 원소가 아닌 중간의 원소를 지우면 이후 원소들은 한 칸식 앞으로 이동하는 문제가 있습니다.


결국 위 코드에서는 Insert를 사용하나 Add를 사용하나 이론적으로는 같은 성능을 낸다고 할 수 있겠습니다.


여기서 한 가지 상속과 포함에 관하여 생각해 볼 것이 있습니다.

만일 HistoryManager 클래스가 List<T>를 상속하는 구조였다면 이 코드는 아마도 아래와 같이 작성되었을 것입니다.

Add 메서드의 시그니처가 상속받은 List<T>의 메서드에 맞추어 변경되고, (List<T>.Add가 가상 메서드가 아니기 때문에 new 키워드를 붙여줘야 합니다.)

Count 나 Insert 같은 속성이나 메서드를 가지고 있지 않으므로 상속받은 List<T>의 것을 사용하고 있습니다.

(참고로 HistoryManager를 사용하는 클래스는 Add 메서드 만을 알고 있긴 때문에, 실제로는 Add가 아니라 Insert 이지만 이름은 Add로 하였습니다.)


포함 대신 상속을 사용하면, 히스토리를 추가하는 것은 이런 식으로 가능하겠지만, 2번 요구사항인 ‘삭제를 할 수 없다’ 에서 문제가 생기게 됩니다.

포함의 경우라면, HistoryManager 매니저에 Remove 메서드를 작성하지 않는 한 당연히 외부에서는 이를 호출할 수 없습니다.

하지만 상속이라면, HistoryManager 클래스는 상속받은 List<T>의 모든 공용 속성과 메서드를 노출하므로 HistoryManager의 사용자가 List<T>의 Remove 메서드를 호출하는 것을 막을 방법이 없습니다.

그래서 다음과 같이 런타임에 예외를 발생시키는 어색한 코드라도 추가할 수 밖에 없습니다.


이번에는 히스토리 목록을 파일로 저장하는 (직력화) 하는 코드를 살펴봅시다.

IsolatedStorageHelper 클래스 덕분에 코드가 가능합니다.

여기서 한 가지 주의할 점은, 직렬화의 대상이 되는 객체는 HistoryManager 객체 자체가 아니라 그 필드인 히스토리 목록 (List<HistoryItem> _list) 객체라는 것입니다.

따라서 HistoryManager에는 DataContract 특성을 붙일 필요가 없으며, List<T>의 지네릭 타입인 HistoryItem에 이 특성을 붙여야 한다는 것입니다.

(List<T> 클래스에는 닷넷 프레임웍 내부에서 이미 이 특성이 붙어 있습니다.)


마지막으로 HistoryManager 객체를 열거가능하게 해 봅시다.

제일 위 스크린 샷에서 히스토리 콤보박스의 데이터소스로 HistoryManager 인스턴스를 지정하는 코드를 간략하게 적으면 아래와 같습니다.

물론 이 코드는 런타임에 예외를 발생시킵니다.

HistoryManager가 히스토리 목록을 열거하는 기능을 가지고 있지 않기 때문입니다.


그럼 HistoryManager에 히스토리 목록을 열거하는 기능을 어떻게 추가할 수 있을까요?

간단하게는 필드로 가지고 있는 히스토리 목록을 외부로 바로 노출하는 것입니다.

그럼 위 코드는 아래와 같이 바꾸면 됩니다.

물론 동작은 하겠지만, 이 코드는 OOP의 기본 원칙 중 하나인 캡슐화를 위배해버렸습니다.

즉 히스토리 목록이 외부로 직접 노출되었기 때문에, 이를 통해 외부에서 히스토리 목록에 아이템을 제거하거나 하는 일을 할 수 있게 된 것입니다.


히스토리 목록을 노출하는 것 보다 좋은 방법은 HistoryManager 클래스 자체를 열거 가능한 형으로 만드는 것입니다.

즉 HistoryManager가 IEnumerable<HistoryItem> 인터페이스를 구현하도록 한 후 IEnumerable<HistoryItem>의 GetEnumerator 메서드를 적절하게 구현하는 것입니다.

지네릭 버전이 아닌 IEnumerable 인터페이스는 구현하지 않고, 지네릭 버전의 IEnumerable 인터페이스만 구현하기 위해서는 위 코드 처럼 두 개의  GetEnumerator 메서드를 구현을 하여야 하는데, 둘 중 하나는 암시적으로, 나머지 하나는 명시적으로 구현을 하여야 합니다. (인터페이스의 명시적 구현에 관해서는 역시 졸고를 참고하십시오.)


그럼 이제 HistoryManager는 아래와 같이 콤보박스의 데이터소스로 바로 사용될 수 있습니다.

 


이번 C# 코딩 연습에서는 OOP의 기본 이론 중 하나인 클래스의 상속과 합성에 대해서 다루어 보았습니다.

아시다시피 C#은 (기본적으로) OOP 언어입니다. 그래서 OOP에 대한 깊이 있는 이해가 C#을 제대로 활용하기 위한 첩경이 됩니다.

OOP를 이해하는 데 이 글이 미약한 도움이나마 되셨기를 바랍니다.


우리는 모두 한국의 개발자입니다.

그래서 이 글을 읽는 분의 경쟁력이 곧 이 글을 쓴 저의 경쟁력이기도 합니다.

장인(匠人)으로서의 자부심을 가지고 피나는 용맹정진을 할 것을 (스스로에게) 다짐하고, 또 (여러분에게) 당부합니다.


연습문제

HistoryManager 클래스에 아래와 같은 상황에서 발생할 이벤트를 추가해 보시기 바랍니다.

  1. 히스토리 목록이 추가되려고 함 –> 히스토리 목록의 원소 갯수를 알려주어야 함
  2. 히스토리 목록이 추가되었음 –> 히스토리 목록의 원소 갯수를 알려주어야 함
  3. 히스토리 목록이 파일 시스템에 저장되었음 –> 저장된 파일명을 알려주어야 함

이왕이면 이벤트 코드 생성기를 사용해 보십시오.


연습문제 정답 :

:

Midnight Peach의 필드 테스트와 개봉 박두

Midnight Peach 2009. 11. 1. 04:31

[주의]
이 글은 MP의 현재 버전 (3.X)에 맞지 않습니다.

단순 참고용으로만 활용하십시오

2009.7.20 일이 마지막 포스팅 날짜니, 근 백일 만의 포스팅이 되네요.

물론 짐작하시다시피, 화끈한 프로젝트에 한 발 수준이 아니라 온 몸을 푸욱 담그고 있었던 게 백일 간의 은둔의 변명이 맞습니다.

 

그 동안 로피스 플러스라는 제품을 만들었었는데요. 대형 로펌 (물론 개인 법률 사무소도)의 거의 모든 법률 업무를 처리할 수 있는 일종의 ERP 솔루션입니다.

개발 과정에서 프로그래밍에 대하여 좀 더 알게 된 부분도 많고, 몇 가지 디자인 패턴과 라이브러리를 개발하기도 하는 등, 나름 뜻 깊은 프로젝트였습니다만, 그 중 가장 큰 의미가 있는 것은 아무래도 Midnight Peach의 필드 테스트를 성공적으로 수행한 것이 아닌가 싶습니다.

 

테이블이 백여개 정도 되는 제법 규모가 있는 솔루션임에도 불구하고 LINQ와 MP를 사용하니, 데이터베이스 핸들링 작업, 즉 데이터 액세스 레이어는 날로 먹다시피 했습니다.

예를 들어 아래는 사건 검색 컨트롤 입니다.

각각 AND 조건으로 결합되는 14가지의 검색 조건이 있습니다. 그리고 결과는 8개의 테이블에서 뽑아 오는데, 이 중 일부는 두 단계 혹은 세 단계의 조인을 거쳐야 합니다.

LINQ를 사용할 수 없다면, 각각의 검색 조건을 매개변수로 받아 동적으로 쿼리를 조립하는 프로시저(동적 쿼리 프로시저)를 만드는 게 최선(어쩌면 유일?)일 것 같은데요.

아마도 이 프로시저의 길이는 족히 몇 백 라인, 어쩌면 천 라인이 넘을 것 같습니다.

 

이런 끔찍한 동적 쿼리 프로시저는 생각만 해도 예전 기억이 떠올라 경기를 일으킬 분이 많으실 것 같은데요, 굳이 상처를 헤집어 소금을 뿌리는 심정으로 문제점들을 이야기해보자면,

  1. 유지보수가 극히 어렵습니다. 특히 작성자가 다른 사람이라면 불가능에 가깝다고도 할 수 있습니다.
  2. 코드의 가독성이 무척 낮아, 코드 파악이 힘듭니다.
  3. 동적으로 쿼리를 조립하는 형태이므로, 컴파일 타임의 체크가 불가능합니다. 특히나 인텔리센스의 도움을 받을 수 없기 때문에 몇 천 라인 중에 오타 한 글자만 있더라도 실행 전까지는 알 수 없습니다.
  4. 디버깅이 불가능하거나 극히 어렵습니다.
  5. 저장 프로시저임에도 불구하고 실행계획을 저장할 수 없기 때문에, 동적 쿼리가 아닌 프로시저나 parameterized 쿼리에 비해 성능이 좋지 않습니다.
  6. 따로 방비를 하지 않으면 SQL injection 공격에 취약합니다.

 

하지만 이 모든 문제가 LINQ를 사용하면 해결이 됩니다.

물론 LINQ 자체가 가진 장단이 있고, LINQ에 대한 각 개발자의 호오가 분명 있을 테지만, 적어도 위에서 말한 말 그대로 ‘끔찍한’ 동적 쿼리 프로시저를 만들지 않아도 된다면, LINQ는 충분히 고려해볼 만한 가치가 있는 기술이라는 생각이 듭니다.

 

참고로, 단순 비교하기에는 무리가 있겠지만, 위의 검색 메서드를 MP가 생성한 프레임웍의 도움을 받아 LINQ로 구현하는 데 한 시간이 채 걸리지 않았습니다. 라인 수도 70 라인 정도로 짧았고요.

(사족이지만, 이는 제가 평생 만든 메서드 중 가장 긴 것인데, 사실 70 라인도 개발자가 직관적으로 코드를 장악하기에는 긴 편입니다.)

 

DataReader를 이용한 저수준(수동) 구현, 형식 있는 (혹은 없는) 데이터셋의 사용, 데이터셋 대신 비지니스 오브젝트 (엔터티)를 사용하되 Data Access Application Block과 같은 라이브러리나 NetTiers 같은 코드 생성기의 도움을 받는 방법, CSLA.NET과 같은 프레임웍의 도입, NHibernate 같은 ORM 툴 까지, 그간 데이터 액세스 레이어를 구축하는 데 있어 참 많은 기술들을 사용하여 보았습니다.

하지만 이번 프로젝트에서의 적용을 통해 저는 LINQ가 이 모든 기술의 정점에 있다는 확신을 가지게 되었습니다.

정점에 있다니까 ‘유사한 것 중 최고’ 라는 느낌이 드는데, LINQ는 다른 기술들과 본질적으로 다릅니다. 그래서 ‘더 좋다’가 아니라 패러다임 자체가 다르다고 표현해야 되지 않을까 싶습니다.

 

아주 추상화시켜서 이야기하자면, 프로그램 언어는 데이터와 이에 대한 연산으로 이루어져 있습니다.

이 중 데이터는 메모리의 휘발성 때문에 저장소에 영속화(persistent) 되어야 할 필요가 있는데, 이 저장소에는 여러가지가 있습니다.

우리가 필드에서 개발하는 대부분의 응용 프로그램들은 DBMS를 저장소로 사용하지만, 이는 파일 시스템이 될 수도 있고, 네트웍을 통해 들어오는 스트림이 될 수도 있습니다.

그렇다면 프로그램이 데이터를 연산하기 위해서는 저장소에 있는 데이터를 메모리로 가져와야 하는데(쿼리), 저장소가 무엇이냐에 따라 이 쿼리 방법이 너무나 상이하고, 또 프로그램 언어 자체에는 이 기능이 없다는 것이 문제가 됩니다.

(예컨데, DataSet을 통해 DB의 데이터를 가져오는 것은 C#의 기능이 아닙니다. FileStream 라이브러리를 통해 파일의 데이터를 가져오듯이, DataSet 클래스 라이브러리를 통해 DB에 접근하는 것이지요)

 

LINQ의 목표는 이 상이한 데이터 쿼리 방법들을 언어 자체의 기능으로 통합하는 것이고, LINQ의 정식 명칭인 Language INtegrated Query가 의미하는 바도 바로 이것입니다.

그리고 C#의 설계자인 Anders Hejlsberg가 LINQ에 대해 인터뷰하면서 ‘(LINQ는) 쿼리와 집합(역주:데이터) 연산을 닷넷 언어의 주요 구성 요소로 만든다’고 한 말도 이런 맥락에서 이해할 수 있겠습니다.

(http://channel9.msdn.com/posts/scobleizer/Anders-Hejlsberg-LINQ/)

 

필드 테스트를 통해 MP의 안정성이라던가 사용편의성 같은 것들을 체크해 보았으니, 이제 MP를 발표할 때가 된 것 같습니다.

여태까지는 블로그에만 포스팅을 하고 외부에는 일절 알리지 않고 있는데, 블로그에도 아직 MP의 기능 반 정도가 소개되지 않았습니다.

모든 기능에 대한 매뉴얼과 MP를 사용한 샘플 애플리케이션 정도만 준비되면 공식적으로 발표를 할 생각입니다.

기대해 주세요.

:

이벤트 코드 생성기 1.4 업데이트와 설치, 그리고 소스

Dev Tools/이벤트 코드 생성기 2009. 10. 30. 00:14

이벤트 코드 생성기에 대한 설명은 아래 링크를 참조하십시오.

C# 코딩 연습 - 대리자와 이벤트

이벤트 코드 생성기 1.1 업데이트

이벤트 코드 생성기 1.3 업데이트

 

이벤트 코드 생성기가 1.4로 업데이트 되었습니다.

 

[설치]

이번 버전 부터는 클릭원스를 통해 배포가 되게 됩니다.

따라서 서버에 최신 버전이 게시되면 자동으로 업데이트가 일어나게 됩니다.

설치 URL은 다음과 같습니다.

http://ldev.homeip.net/publish/DevTools.EventStuffGenerator/publish.htm

 

[업데이트 내역]

이벤트 이름과 매개변수의 히스토리가 저장

프로그램을 다시 시작하더라도 최근 20개 까지의 목록이 유지되므로 동일한 이벤트 코드 생성을 편리하게 할 수 있습니다.

 

이벤트 매개변수 파싱 방법 향상

이전 버전에서는 매개변수를 파싱할 때, 타입명과 변수명은 스페이스, 또 각각의 매개변수는 개행문자로만 구분할 수 있었지만, 이제는 쉼표나 여러 개의 스페이스, 개행문자로도 구분할 수 있습니다.

즉,

int a1

int a2

라고 지정해야만 했던 포맷을 이제는

int a1, int a2

와 같이 지정할 수도 있습니다.

이 기능은 특히, 한번 생성한 이벤트 코드를 수정하는 경우에 유용하게 사용할 수 있습니다.

예를 들어 위 스크린샷과 같이 GenerateButtonClicked 라는 이벤트를 생성하여 사용한다고 합시다.

개발이 진행됨에 따라 이 이벤트의 매개 변수에 새로운 매개변수 (int dummmy)를 추가하여야 한다면, 이전에는 template 부터 argument 까지의 매개변수를 다시 입력하여야 했지만, 이제는 이전에 생성된 코드 중,

등 에서 Template template, string generatedCode, bool successed, string eventName, string argument 부분을 복사한 후, 여기에 int dummy 를 추가하는 식으로 좀 더 효율적인 작업이 가능합니다.

 

파일 저장소로 격리된 저장소 사용

이벤트 이름과 매개변수의 히스토리와 커스텀 템플릿을 일반 파일 시스템이 아닌 격리된 저장소에 저장합니다.

따라서 비스타 이상의 운영체제에서 발생하는 보안 문제 (퍼미션)가 해결되었습니다.

또한 클릭원스를 통해 매번 업데이트를 받더라도, 이전과 동일한 격리된 저장소를 사용하는 로직이 추가되어 있어, 사용자의 사용 환경이 그대로 유지됩니다. (소스를 참고하십시오)

 

[소스]

실제 개발을 진행하고 있는 상태의 소스를 공개용으로 일부 수정하여 첨부하였습니다.

다른 요구사항은 없지만, DevExpress 사의 .Net Winform Controls 라는 써드 파티 컨트롤 라이브러리를 사용하고 있으므로 이 제품은 설치되어 있어야 합니다.

평가판은 여기에서 다운로드 받을 수 있습니다.

:

자동 업데이터 (HAU, Http Auto Updater) 1.2 업데이트

HAU 2009. 7. 20. 12:59

남은 시간을 표시하는 기능이 추가되었습니다.


AutoUpdater 컴퍼넌트의 UpdateProgressChanged 이벤트 핸들러의 매개 변수로 TransferingInfo 가 넘어오는데, 여기에 RemainingSeconds 라는 프로퍼티가 추가되었습니다.

이를 사용하는 예는 다음과 같습니다.


AutoUpdater 컴퍼너트의 소스 파일과 샘플 애플리케이션, 업데이트 목록 편집기의 소스는 아래 링크에서 받으실 수 있습니다.

:

중첩된 사용자 정의 컨트롤의 DesignMode 속성은 동작하지 않음

RTFM/Winform 2009. 6. 21. 09:14

윈폼의 디자인 타임을 지원하는 Component.DesignMode 라는 속성이 있습니다.

이름이 의미하는 대로 이 컴퍼넌트가 현재 디자인 타임에 있는지 여부를 알 수 있어, 특히 컨트롤이나 폼의 Load 이벤트 핸들러에서 많이 사용됩니다.

예를 들어 폼이 로드되고 나서 어떤 작업을 해야 한다면 아래와 같은 코드를 작성할 수 있습니다.

(주 : Load 이벤트의 핸들러를 작성하지 않고 OnLoad 메서드를 오버라이드 하는 이유에 대해서는 졸고를 참고하십시오.)

(주의 주 : 폼의 경우라면 Load 이벤트 보다 Shown 이벤트를 사용할 것을 추천합니다. Shown 이벤트는 Load 이벤트가 발생한 후 폼이 화면에 표시된 시점에 발생합니다. 참고로 컨트롤에는 Shown 이벤트가 없습니다.)


코드에서 보듯이 Load 이벤트 핸들러에서는 디자인 타임인지 여부를 검사할 필요가 있습니다.

왜냐면 Load 이벤트가 발생하는 시점이 폼이 로드될 때인데, 비주얼 스튜디오에서 폼을 열 때도 폼이 로드된 것이기 때문에 이 이벤트가 발생하기 때문입니다.

여기서 ‘어떤 작업’이 실제로 어떤 작업이냐에 따라서 문제가 발생할 수 있습니다.

만약에 ‘어떤 작업’이 DB에서 데이터를 로드하거나 하드 디스크의 특정 위치에 있는 파일을 읽는 것이라면, 이는 디자인 타임에서는 수행될 수 없거나 수행되지 않아야 할 작업입니다.

위 코드에서 조건문을 뺀 상태에서 디자인 타임에서 할 수 없는 작업을 하면 예외가 발생되고, 비주얼 스튜디오에서 폼을 열 수 없게 됩니다.

비주얼 스튜디오의 버그라고 포기하기 전에 혹 위와 같은 문제가 아닌지 살펴보면 어쩌면 쉽게 문제가 해결될 수도 있습니다.

(물론 비주얼 스튜디오에서 폼을 열지 못하는 원인에는 수 십, 어쩌면 수 백 가지가 있겠는데, 이러한 상황에 대한 각각의 해결법을 하나씩 익혀 가는게 바로 경험이라는 것이겠지요.)


그런데 문제는 이 DesignMode 속성이 중첩된 사용자 정의 컨트롤에서는 동작을 하지 않는다는 것입니다.

코드를 보면서 이야기해 보지요.

사용자 정의 컨트롤을 하나 만들고 그 이름을 InnerTextViewer라고 합시다.

내부에 텍스트 박스를 하나 올리고, 배경색은 빨간 색으로 지정하였습니다.

이름이 의미하듯이 이 컨트롤은 텍스트 파일을 읽어서 표시하는 일을 합니다.

컨트롤이 로드(정확하게는 이 컨트롤이 속한 폼이 로드)되면 실행 디렉토리에 있는 a.txt 파일을 읽는 코드는 다음과 같겠네요.

이제 폼을 하나 만들고 이 사용자 정의 컨트롤을 올려 봅니다.

(아, 사용자 정의 컨트롤을 작성하거나 수정하면 빌드를 하여야 반영되는 건 아시죠?)

문제 없이 올라가고 실행하면 동작도 잘 합니다.

문제는 지금 부터입니다.

새로운 사용자 정의 컨트롤을 만들고 이름을 OuterTextViewer 라고 합시다.

그리고 앞에서 만든 InnerTextViewer 컨트롤을 도구상자에서 끌어 옵니다.

이번에는 배경색은 파란색으로 지정하였습니다.

(역시 빌드를 한 후에) 이제 위 폼에서 InnerTextViewer를 지우고 OuterTextViewer를 올려봅시다.

친숙한 화면이 나타났습니다. 절대 그냥 닫지 마시고 어떤 예외인지를 찬찬히 보시기 바랍니다.

FileNotFoundException 이고 1.txt 파일이 없다는 말 같습니다.

무슨 말인지 감이 오실 겁니다.

런 타임이 아닌 디자인 타임 임에도 불구하고 위 InnerTextViewer의 코드가 실행이 된 것입니다.

그렇다면 그 말은 위 코드의 ‘if (DesignMode)’ 에서 DesignMode 값이 false 라는 말이 되겠습니다.

위 코드에다 아래와 같이 메시지 박스를 출력하는 코드를 추가하여 직접 값을 볼 수도 있습니다.

정리하자면, 사용자 정의 컨트롤에 포함된 사용자 정의 컨트롤의 DesignMode 속성은 기대한 것처럼 동작하지 않는다는 것입니다.

‘중첩된 사용자 정의 컨트롤이 사용되는 경우가 있을까’ 하고 대수롭지 않게 생각할 수도 있지만, 규모가 제법 있고 코드가 구조화된 프로젝트에서라면 필요한 경우가 제법 있습니다.


그렇다면 이 문제를 어떻게 해결하면 좋을까요?

먼저 Load 이벤트에서 디자인 타임에서 할 수 없는 작업을 빼는 방법이 있겠네요.

위 예에서라면 파일을 읽는 부분을 별도의 메서드로 만들고, 폼이 로드되었을 때 이를 수동으로 호출하도록 하는 것입니다.

하지만 사용자 정의 컨트롤이 사용된 모든 폼의 로드 시점을 추적하여야 하기 때문에, 전체적인 프로그램의 복잡도를 증가시키는 요인이 될 수 있으므로 별로 좋은 방법은 아닌 것 같습니다.


또 다른 방법은 전역 변수 개념으로 플래그를 하나 만들어서 디자인 타임에서 이를 체크하는 것입니다.

코드를 보면서 이야기 해봅시다.

아래와 같이 Program 클래스에 정적 속성을 추가하고, 이 값을 응용프로그램이 실행되기 전에 true로 설정합니다.

그리고 InnerTextViewer의 Load 이벤트 핸들러에서는 DesignMode 속성 대신 이 플래그를 검사합니다.

빌드하고 나면 이제 폼에 OuterTextViewer를 올릴 수 있습니다.



소스 파일 :

:

멀티 패널 컨트롤

RTFM/Winform 2009. 6. 18. 18:22

http://www.codeproject.com/KB/cs/multipanelcontrol.aspx

 

폼을 구성하다 보면, 여러 개의 패널을 준비하여 놓고 특정 조건에 따라서 이 중 한 개만을 보여줘야 하는 경우가 종종 있습니다.

현재까지 발표된 닷넷 프레임웍에는 (디자인 타임을 지원하며) 이러한 기능을 제공하는 컨트롤은 없습니다.

그래서 대안으로,

1. 탭 컨트롤을 이용하는데, 선택되지 않은 나머지 탭을 감추는 등을 로직이 런타임에 실행되어야 합니다.

2. 패널 컨트롤을 이용할 수도 있는데, 위와 같은 문제와 더불어, 디자인 타임에는 모든 패널을 표시할 수 밖에 없어 폼의 레이아웃이 런타임과 달라 집니다.

등의 방법을 사용할 수 있는데, 이 아티클 필자의 말처럼 아주 ugly 합니다.

 

이 아티클에서는 디자인 타임을 지원하는 멀티 패널 컨트롤을 소개합니다.

스샷을 보시면 하나의 MultiPanel 컨트롤 안에 세 개의 페이지가 추가된 것을 볼 수 있는데, 폼 디자이너에서는 각각의 페이지를 별도로 표시하고 있습니다.

 

사용법은 무척 간단합니다.

디자인 타임에서 MultiPanel 컨트롤의 스마트 태그나 Document Online 창을 이용해 적절히 페이지를 구성한 후, 코드에서는 MultiPanel.SelectedPage 속성을 읽거나 쓰면 됩니다.

 

이 자체로도 충분히 유용한 컨트롤이지만, 선택된 페이지가 아닌 모든 페이지에 접근하는 방법이 없어 소스를 약간 수정해 보겠습니다.

각각의 페이지에 접근하기 위해서 인덱서를 사용할 것인데, 총 3개의 오버로드를 제공하겠습니다.

이를 사용하는 코드는 다음과 같습니다.

 

원 제작자의 소스는 제일 위 원문 링크에서 받으시면 되고, 제가 수정한 소스는 아래 링크에서 받을실 수 있습니다.

:

닷넷 응용프로그램의 리소소를 자동으로 번역하고 동기화 하기

RTFM/Visual Studio 2009. 6. 17. 15:12

http://www.codeproject.com/KB/locale/autotranslatesynchronize.aspx

 

지역화된 닷넷 응용프로그램을 만드는 표준적인 방법은 리소스 파일을 사용하는 것입니다.

비주얼 스튜디오는 그 이름 처럼 리소스 파일도 비주얼하게 지원하기 때문에 전반적으로는 지역화 작업이 힘들지가 않지만, 필드에서 작업을 하다 보면 이 역시 몇 가지 문제가 있긴 합니다.

이 기사는 리소스 파일의 자동 번역과 동기화 기능을 제공하여 지역화 작업을 좀 더 편리하게 하는 툴에 대한 설명을 하고 있습니다.

 

1. 자동 번역

마이크로 소프트와 구글을 자동 번역 기능을 사용합니다.

물론 ‘번역’이라고 할 만한 작업은 아니지만, 그래도 기계적인 작업의 상당 부분을 줄여주는 의미가 있다고 하겠습니다. (예컨데 수 십 개의 리소스 파일에 “Yes” 라는 문자열이 수 백개 있다고 생각을 해 보십시오.)

 

2. 동기화

MyResource.resx 파일과 MyResource.ko-KR.resx 파일에 각각 Yes와 No 라는 문자열이 있다고 합시다.

시간이 지나 MyResource.resx 파일에 OK 라는 항목이 추가되었습니다. 그렇다면 MyResource.resx와 MyResource.ko-KR.resx 사이에는 불일치가 생기게 되어, MyResource.ko-KR.resx 파일을 수동으로 고쳐야 합니다.

이 툴에서는 MyResource.resx 파일과 MyResource.ko-KR.resx 파일의 동기화 기능을 제공합니다.

:

DBML 커스터마이징 작업을 관리하는 VS 애드인

RTFM/Visual Studio 2009. 6. 17. 14:57

http://www.codeproject.com/KB/database/SqlMetalPlus.aspx

 

LINQ to SQL에서 DB 스키마가 변경되면 항상 새로운 DBML 파일을 만들어야 합니다.

이 때 DBML 파일에 대한 수정이 필요하다면 (예를 들어 특정 테이블의 이름을 변경한다거나, 테이블 혹은 컬럼의 속성을 바꾸는 등), DBML 파일을 새로 생성할 때 마다 이러한 작업을 매번 해주어야 합니다.

이 툴은 이러한 반복 작업을 자동화 합니다.

설치하면 비주얼 스튜디오에서 두 가지 컨텍스트 메뉴가 추가되는데,

1. Apply Customization

   현재 DBML 파일에 미리 정의된 커스터마이징 작업을 수행합니다.

2. Refresh

   SQL 서버에서 새로운 스키마를 로딩한 후 위 작업을 자동으로 수행합니다.

:

C# 코딩 연습, 멀티 쓰레드와 이벤트

C# 코딩 연습 2009. 5. 20. 00:28

이번 C# 코딩 연습은 .NET Tip of The Day에 포스팅 된 이벤트의 올바른 호출이라는 기사에 관한 주해 형식으로 작성해 보았습니다.


위 포스트를 보면, 멀티 쓰레드 환경에서는

와 같이 이벤트를 호출해서는 안되고,

와 같이 코드를 작성해야 한다며 그 이유가 ‘간략’하게 설명이 되어 있습니다.


이번 C# 코딩 연습은 이 ‘간략’한 설명에 대한 설명입니다.

:

비주얼 스튜디오 디버거에 관련된 특성들

RTFM/Visual Studio 2009. 5. 7. 15:09

http://www.codeproject.com/articles/35133/Visual-Studio-debugger-related-attributes-cheat-sheet.aspx

 

image

비주얼 스튜디오에서 디버깅 시 디버깅 작업을 제어하는 특성들을 소개합니다.

저자의 말로는 70% 개발자가 이러한 특성이 있는지도 모르고, 그 중 95%의 개발자는 자신이 무얼 하는지, 이 특성들을 어떻게 사용해야 하는지를 모른다고 합니다.

 

MP와 같은 코드 생성기에서 유용하게 사용할 수 있을 것 같습니다.

: