RSS구독하기:SUBSCRIBE TO RSS FEED
즐겨찾기추가:ADD FAVORITE
글쓰기:POST
관리자:ADMINISTRATOR

텍스트 박스에 값을 입력하지 않으면 배경에 특정 메시지를 보여주고, 값을 입력하면 보이지 않게 해달라는 요청을 받았습니다.

그런데!!

텍스트박스는 OnPaint를 기본적으로 쉽게 사용할 수 없습니다. SetStyle 메서드에서 UserPaint 플래그를 설정하면 가능하지만, 글씨 같은 부분도 모두 처리해 줘야되기 때문에 여간 번거로운게 아닙니다. 제가 원하는건 오직 배경에 뭔가 처리!’ 그것 뿐이라구요..ㅠㅡㅠ

 

그래서 WndProc 를 재정의해서 이벤트를 받아오는 방법을 사용했습니다. 주의라고 까진 아니지만, 유심히 보실 부분은 WM_KILLFOCUS 도 처리해주고 있는것인데요. 텍스트 박스를 멀티라인 지원으로 해놓으면, WM_PAINT 가 발생을 하지 않더군요. 그래서 WM_PAINT 에서도 처리를 해주었습니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace TextBoxPaintSample.Controls
{
    public class TextBoxExt : TextBox
    {
        private string waterMarkText = string.Empty; // 워터마크로 사용할 문자열
        private Color waterMarkColor = Color.Gray;   // 워터마크로 사용할 문자색

        protected override void WndProc(ref Message m)
        {
            // base.WndProc 중복 호출을 피하기 위해서
            bool isCallAlready = false;

            // WM_PAINT 메세지를 받아서 처리
            if (m.Msg == 0x000F) // WM_PAINT = 0x000F
            {
                // 원래 처리해야될 로직을 먼저 호출해서 처리해줌
                base.WndProc(ref m);
                isCallAlready = true;

                DrawWaterMarkText();
            }
            // Multiline == true 일때는 포커스 빠질때 WM_PAINT가 발생 안하므로
            else if (m.Msg == 0x0008 && this.Multiline) // WM_KILLFOCUS = 0x0008
            {
                DrawWaterMarkText();
            }

            if (false == isCallAlready)
                base.WndProc(ref m);
        }

        // 텍스트박스의 크기를 계산해서 워터마크를 그려줌
        private void DrawWaterMarkText()
        {
            if (string.IsNullOrEmpty(this.Text) &&
                false == string.IsNullOrEmpty(this.WaterMarkText) &&
                this.IsHandleCreated &&
                false == this.Focused &&
                this.Visible)
            {
                using (Graphics g = Graphics.FromHwnd(this.Handle))
                {
                    // 텍스트의 vertical 정렬을 하기 위한 계산들
                    StringFormat sf = new StringFormat();
                    float textHeight = g.MeasureString(this.WaterMarkText, this.Font, this.Width, sf).Height;
                    float textY = ((float)this.Height - textHeight) / (float)2.0;
                    RectangleF bounds = new RectangleF(
                        0, textY, (float)this.Width, (float)this.Height - (textY * (float)2.0));

                    g.DrawString(this.WaterMarkText, this.Font, new SolidBrush(this.WaterMarkColor), bounds, sf);
                }
            }
        }

        public string WaterMarkText
        {
            get { return waterMarkText; }
            set { waterMarkText = value; }
        }

        public Color WaterMarkColor
        {
            get { return waterMarkColor; }
            set { waterMarkColor = value; }
        }

    }
}


실행 결과
사용자 삽입 이미지

이러한 코드를 바탕으로 텍스트 박스의 배경으로 이미지를 넣는다던지 하는 처리도 가능하리라 생각됩니다.

소스코드 전체를 첨부합니다. VS2005에서 작성 및 테스트되었습니다.


ps. 사실, 이 포스트의 내용은 제가 원래 작성하고자 했던 내용은 아닙니다만, 문제해결의 과정을, 혹은 진짜 이 내용이 필요하신분을 위해서 먼저 작성하였습니다. 다음 포스트에서는 TextBox를 상속받을 수 없는경우, 즉 다른 사람이 만든 TextBox에서 컨트롤만 얻어올 수 있을때의 대처 방법을 포스팅 하겠습니다.


2009/04/08 21:23 2009/04/08 21:23
http://lemonwidz.com/tc/trackback/23
kwangho  | 2009/04/28 16:44
오오~~ 훈스타고 왔는 데 워터마크 기능이되네요.
잘은 모르겠으나 재정의하는 거 같은데 신기하네요
지송  | 2010/07/15 00:06
안녕하세요. ^^;
다름이 아니오라. 위 워터마크효과를 이용해서 제 텍스트박스기능에 붙였는데...
다른창이 텍스트박스 위로 올라갔다 내려갔다를 몇차례 반복하면 잔상이 남게 되는 현상이 발견되어서
해결 방법을 찾아서 댓글 남겨드립니다.

TextRenderer.DrawText(g, waterMarkText, Font, Rectangle.Truncate(bounds), waterMarkColor, BackColor, TextFormatFlags.Default);

글을 이렇게 쓰시면 잔상이 생기진 않더라고요. ^^ 더운 여름 잘 보내시고요. 컨트롤 잘쓸께요.
유쾌한냐옹이  | 2012/02/28 09:33
훈스에서 퇴근5초전 님 추천받아왔는데 정말 유익하군요.

왔사 겁내 저한테 필요한 정보에욬ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
have a gd day 에용~
멋져부러
유쾌한냐옹이  | 2012/02/28 10:33
이미지를 올려놓을 경우 엄청 뻔쩍거리네요..;;ㅠㅠ
리플렉션을 통해서 타입을 컨트롤하는 것은 일반 개발자(컴파일러/프레임웍 등의 개발자가 아닌)에게는 거의 필요가 없거나, 사용을 권장하지 않는 기능입니다. 하지만, 실무를 접하다 보면 부득이하게 필요한 경우가 발생하기 마련이고, 제한적으로 사용하면 도움이 되기에 이러한 팁을 작성해 보았습니다.


// 테스트를 위한 클래스
public class IHavePrivateMember
{
    public IHavePrivateMember()
    {
        this.mySecretName = "Lemon";
    }

    private string mySecretName;
    private void PrintSecretName()
    {
        Console.WriteLine("My secret name is {0}.", this.MySecretName);
    }

    private string MySecretName
    {
        get { return this.mySecretName; }
    }
}


위와 같은 클래스의 private 멤버인 mySecretName 변수, MySecretName 프러퍼티, PrintSecretName 메서드에 접근해 보도록 하겠습니다.

IHavePrivateMember 클래스의 타입을 얻어오는 것에서부터 리플렉션이 시작됩니다. CLR에서 관리되는 모든 타입은 System.Type 클래스의 인스턴스인데, 이 클래스는 객체의 타입을 유지, 관리해주는 많은 멤버들을 가지고 있습니다. 그 중 GetMembers 메서드를 통해서 해당 타입의 멤버들을 MemberInfo 클래스로 가져올 수 있습니다.

MemberInfo[] privateMembers = typeof(IHavePrivateMember).GetMembers(
                BindingFlags.Instance | BindingFlags.NonPublic);


MemberInfo 클래스는 다음과 같은 상속 구조를 가지면서 다른 구체화된 멤버정보 클래스들의 베이스가 됩니다.

사용자 삽입 이미지
GetMembers는 위에서 보이는 MemberInfo를 상속받는 클래스들을 MemberInfo클래스의 배열로 리턴해줌으로써 MemberInfo.MemberType을 살펴보고 이를 적절히 캐스팅해서 사용할 수 있습니다.

if (privateMembers[i].MemberType == MemberTypes.Field)
{
}


이와 같이 타입을 확인후에 해당 클래스로 캐스팅하여 적절한 액션을 취하면 되겠지요. 필드를 대상으로 취할 수 있는 액션은 값을 가져오는 것이니깐 GetValue 정도의 메서드가 적절할 듯 싶습니다. 그런데, GetValue 메서드는 파라메터를 하나 요구하는데요, 이 멤버가 Static 멤버가 아닌, 인스턴스멤버이기 때문에 값을 가져오려면 반드시 인스턴스가 필요하기 때문입니다.

IHavePrivateMember secret = new IHavePrivateMember();
Console.WriteLine("멤버변수 {0} / 값 : {1}",
    privateMembers[i].Name,
    ((FieldInfo)privateMembers[i]).GetValue(secret)
);


이렇게 값을 얻어올 실제 인스턴스를 넘겨줍니다.

하나를 알면 열을 안다고 했던가요? 나머지 타입들도 같은 맥락으로 사용하실 수 있습니다. 메서드는 실행이니까 Invoke, 프러퍼티도 값을 가져오니까 GetValue 이런식으로 사용하시면 됩니다.

콘솔응용프로그램으로 실행되는 전체 소스코드는 아래와 같습니다.

using System;
using System.Reflection;

namespace AccessPrivateMemberSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IHavePrivateMember secret = new IHavePrivateMember();

            // 멤버로 검색할 조건을 인자로써 지정해줌
            MemberInfo[] privateMembers = typeof(IHavePrivateMember).GetMembers(
                BindingFlags.Instance | BindingFlags.NonPublic);

            for (int i = 0; i < privateMembers.Length; i++)
            {
                if (privateMembers[i].MemberType == MemberTypes.Field)
                {
                    Console.WriteLine("멤버변수 {0} / 값 : {1}",
                        privateMembers[i].Name,
                        ((FieldInfo)privateMembers[i]).GetValue(secret)
                    );
                }
                else if (privateMembers[i].MemberType == MemberTypes.Property)
                {
                    Console.WriteLine("프러퍼티 : {0} / 값 : {1}",
                        privateMembers[i].Name,
                        ((PropertyInfo)privateMembers[i]).GetValue(secret, null)
                    );
                }
                else if (privateMembers[i].MemberType == MemberTypes.Method)
                {
                    MethodInfo mi = privateMembers[i] as MethodInfo;
                    if (mi != null && mi.GetParameters().Length == 0)
                    {
                        Console.WriteLine("메서드 {0} 실행 ----------", mi.Name);
                        mi.Invoke(secret, null);
                        Console.WriteLine("메서드 {0} 실행 끝 --", mi.Name);
                    }
                }
            }
        }
    }

    // 테스트를 위한 클래스
    public class IHavePrivateMember
    {
        public IHavePrivateMember()
        {
            this.mySecretName = "Lemon";
        }

        private string mySecretName;
        private void PrintSecretName()
        {
            Console.WriteLine("My secret name is {0}.", this.MySecretName);
        }

        private string MySecretName
        {
            get { return this.mySecretName; }
        }
    }
}


결과
메서드 PrintSecretName 실행 ----------
My secret name is Lemon.
메서드 PrintSecretName 실행 끝 --
메서드 get_MySecretName 실행 ----------
메서드 get_MySecretName 실행 끝 --
메서드 Finalize 실행 ----------
메서드 Finalize 실행 끝 --
메서드 MemberwiseClone 실행 ----------
메서드 MemberwiseClone 실행 끝 --
프러퍼티 : MySecretName / 값 : Lemon
멤버변수 mySecretName / 값 : Lemon
계속하려면 아무 키나 누르십시오 . . .

2009/03/31 20:27 2009/03/31 20:27
http://lemonwidz.com/tc/trackback/21
C# Coding Standards and Best Programming Practices ( C# 코딩 표준과 좋은 프로그래밍 습관 )

이라는 제목의 닷넷 스파이더팀( http://www.dotnetspider.com )의 글을 번역한 글입니다.

작년 회사에서 팀원들 대상으로 세미나할때 사용했던 문서인데, 하드에 짱박혀있는것 보단, 많은 분들이 보셨으면 해서 올립니다.

이글의 내용중 '권고안'은 말그대로 Let's 의 의미이며, '역주'라고 작성된 이외의 모든 내용은 닷넷 스파이더 팀의 의견임을 미리 밝혀둡니다. (사실 제 의견과 좀 틀린 부분도 있어서요 =ㅂ=)

목차는 아래와 같습니다.

01. 서문
02. 저작권에 관해
03. 이력관리
04. 소개
05. 이문서의 목적(purpose)
06. 팀 내에서 표준을 만드는 방법
07. 명명규칙과 표준
08. 들여쓰기, 공백
09. 좋은 프로그래밍 습관
10. 아카텍처(Architecture)
11. ASP.NET
12. 주석
13. 예외처리

조금이나마 도움이 되었으면 합니다. (_ _)
2009/03/10 08:18 2009/03/10 08:18
http://lemonwidz.com/tc/trackback/16
꼬기얌얌얌  | 2009/03/10 09:26
좋은 자료 감사합니다..^^
(훈스닷넷에서 답변했던 내용인데 의외로 헷갈려하는분도 있을것 같아서 포스팅으로 옮깁니다.)


ref 키워드는 메서드의 인자로 사용할 수 있는데, 이를 사용하면 값형식이든, 참조형식이든 인자로 넘긴값을 메서드에서 사용 후, 변경된 값을 다시 그대로 받을 수 있습니다.
ref 키워드 없이 호출을 하게되면, 메서드에 복사본이 넘어가게되어 메서드 내에서 변경되더라도 메서드가 끝난 이후에는 원래값(넘길때 주었던)이 그대로 남아 있게 됩니다.

여기서 참조형식을 넘겨줄때 의문이 생기는데요, 참조형식은 원래 부터가 참조하는 포인터를 넘겨주기때문에 어짜피 메서드에서 값을 바꾸고 메서드 종료가 되더라도 변경된 값이 유지되는데, ref 키워드를 쓴다한들 참조형식에서는 무슨의미가 있냐? 라는 의문이 생깁니다.

위의 설명에서도 잠깐 나왔지만, 참조형식이 메서드의 매개변수로 넘어과정을 보자면

public class SampleClass { /* 클래스 내용 */ }
public static void Main()
{
    SampleClass sampleClass = new SampleClass();
    WorkWithSampleClass(sampleClass);
}
public static void WorkWithSampleClass(SampleClass obj)
{
}


이와 같은 코드가 있다고 할때, SampleClass sampleClass = new SampleClass(); 처럼 객체 생성이 되면, 아래 그림처럼 실제 객체의 내용은 힙에 생성되고, 그 힙을 가리키는 포인터가 스택에 생성됩니다.
사용자 삽입 이미지
그리고 WorkWithSampleClass(sampleClass); 를 통해 메서드가 호출될때는 아래 그림과 같이 스택에 있는 참조가 복사되어 메서드의 매개변수로 넘어갑니다.
사용자 삽입 이미지
당연히 복사되었으므로 포인터가 가리키는 힙의 객체는 같습니다. 따라서 메서드가 끝난다하더라도, 스택의 복사본만 파괴될뿐, 힙내의 객체는 여전하므로, 힙의 객체 원본을 수정한것은 메서드 호출이 끝나더라도 남아있게 되는것이죠.

그러나,
public class SampleClass { /* 클래스 내용 */ }
public static void Main()
{
   SampleClass sampleClass = new SampleClass();
   WorkWithSampleClass(sampleClass);
}
public static void WorkWithSampleClass(SampleClass obj)
{
    obj = new SampleClass();
}

위의 코드처럼 호출된 메서드 내에서 파라메터로 넘어온 obj 에 직접 다른 객체를 대입하면 어떻게 될까요?
아래 그림과 같은 변화가 메모리상에서 일어납니다.

사용자 삽입 이미지
아까, 메서드 호출시에 복사본이 넘어온다고 했고, 여기에 새로운 객체를 생성해서 대입했으니, 당연한 결과겠죠?

자, 이렇게 됬을때, 메서드가 종료되면? 네, 스택에 있는 복사본은 해당 스코프가 끝났기 때문에 소멸되고, 힙에 있던 객체역시 참조하는 곳이 없게 되므로, 가비지 수집의 대상이 됩니다. 한마디로 둘다 없어지는 거죠.

그렇기 때문에, 메서드가 끝나면, 원래있던 객체를 다시 참조하게 되는것입니다.



자, 이제 ref 키워드를 통해 넘기면..?
사용자 삽입 이미지
맨처음 나왔던, 그림과 동일합니다. 메서드의 매개변수로 스택에 있는 참조를 그대로 넘겨줍니다. 메서드 내에서도 이와 같은 메모리 구조를 유지하고 있기 때문에, 아까 나왔던 매개변수로 전달된 참조에 직접 다른 객체를 대입하게 되면 아래 처럼 됩니다.
사용자 삽입 이미지
이제 아까(ref 키워드를 사용하지 않았을때)와 다른 형세가 되었네요. 기존의 객체는 참조하는곳이 없어져서 가비지 수집의 대상이 되고, 새로 생성한 객체가 스택에 있는 참조의 참조대상이 됩니다.
당연히 메서드 종료 후에도 참조는 새로는 생성한 객체를 가지키게 되겠죠.

간단한 내용이지만, 설명이 굉장히 길어졌는데요. 요약하자면..

ref 키워드로 참조형식을 넘겨주면 참조형식에 직접 대입하여 다른 객체로 바꿀 수 있다.

(아래는 훈스닷넷에 답글을 달면서 만든 샘플 소스입니다.)
2009/03/05 11:10 2009/03/05 11:10
http://lemonwidz.com/tc/trackback/14
HOONS  | 2009/03/05 13:10
좋은글 잘 보았습니다(^^)
레몬  | 2009/03/05 19:29
어익후.. 제 블로그 첫번째 리플의 주인공이시네요^^;;
감사합니다~
anydeveloper  | 2011/02/23 22:57
다소 혼동스러운 개념이었는데, 정말 명확하게 잘 정리해 주셨네요. 좋은 내용 공유해 주신 것에 대해 진심으로 감사드립니다.^^
?? 를 기준으로 좌측값이 null 이라면 우측값을, null 이 아니라면 좌측값을 반환한다.

SQL문의 ISNULL과 같은 기능을 수행한다.

string value = null;
string result = value ?? "value is null";
Console.WriteLine(result);


아래의 코드와 동일하다.

string value = null;
string result = value == null ? "value is null" : value;
Console.WriteLine(result);


결과는 둘다

value is null

.
.
.

c#을 2년동안 나름 열심히 한다고 했는데.. 이걸 이제서야 알게 되다니.. 부끄럽다..ㅠㅡㅠ
isnull 같은게 있으면 좋겠다고 늘 생각했었는데.. 후..
2009/03/02 09:23 2009/03/02 09:23
,
http://lemonwidz.com/tc/trackback/13
from.Nyaonge's Home  2011/01/20 09:33
?? 연산자가 있는지 지금 처음 알았네~ ?? 를 기준으로 좌측값이 null 이라면 우측값을, null 이 아니라면 좌측값을 반환한다. SQL문의 ISNULL과 같은 기능을 수행한다. view plaincopy to clipboardprint? string v
지송  | 2009/04/17 20:06
오~ ... 저도 첨보는건데요. 항상 삼항연산자를 통해 반환 했는데

멋진게 있네요... 역시 레몬님 멋진글 잘봤습니다 ^^

주말 즐겁게 보내세요 ^^;
원저자 : Syed Mehroz Alam
원제목 : My First Data Application in Silverlight 2
원링크 : http://www.codeproject.com/KB/silverlight/MySilverlightDataApp.aspx
번  역 : 레몬(http://www.lemonwidz.com)
소  스 : DataApplication.zip

목차

소개

몇 주 전부터 실버라이트를 사용하기 시작했데, 정말 놀랍다. WPF의 강력한 표현력과 C#의 능력을 겸한 실버라이트는 정말 강력한 툴이 아닐까 생각해본다. 이 포스팅에서는 데이터베이스로 부터 데이터를 가져와서 실버라이트 응용 프로그램에 적용시키는 것을 다룰것이다. 실버라이트와 ASP.NET의 공통점에 대해서도 다룰것이기 때문에 ASP.NET 개발을 하고 있는 개발자이고 실버라이트에 대해 알고 싶은 개발자또한 이 포스팅을 보는것을 추천한다.



다룰 내용

이 포스팅을 통해 비지니스 어플리케이션을 제작하고자 한다. 비지니스 어플리케이션은 대부분 데이터(데이터베이스에서 가져온)를 다루기 때문에, 실버라이트에서 데이터가 어떻게 표현되는지를 살펴보기로 하자. Northwind 데이터베이스를 예제로 한다. LINQ 클래스를 생성하고, WCF 서비스를 사용하여 데이터를 받아올것이고, 최종적으로 리스트박스와 데이터그리드(데이터템플릿이 딸린)에 뿌려보기로 하자. 우리가 만들 UI는 고객, 주문, 주문 상세가 있는 마스터-디테일 형태가 될것이다. 그리고 이 포스팅에 소개되는 코드는 실버라이트2 RC1 을 기준으로 작성되었다. (역주:번역하면서 한글 실버라이트2 정식으로 테스트 하였습니다.)



예제 프로그램의 실행

예제 프로젝트를 실행하기위해 조금 해야할것이 있다. 기본적으로 솔루션에는 시작 프로젝트가 없기때문에, 솔루션의 설정 윈도우에서 수작업으로 DataApplication.Web 을 시작 프로젝트로 설정해 준다. (역주:이부분은 뭔가 이해가 안되는데, 제공되는 솔루션에는 이미 DataApplication.Web이 시작 프로젝트로 설정되어 있다.)



ASP.NET 프로그래머가 알아둘것

ASP.NET 프로그래머라면, ASP.NET 과는 다르게 실버라이트는 서버가 아닌 클라이언트에서 C#코드가 실행된다는것을 알아두자. 클라이언트라는 말에 자바스크립트라고 생각할 수도 있는데, 아니다. 실버라이트로 코딩함에 있어서 가장 좋은 방법은 자바스크립트를 사용하지 않고 클라이언트 코딩(C#)으로 모두 다 다루는것이다. (적어도 나는 자바스크립트를 잘 다루지 못하니깐)



왜 WCF를 사용하나? 그냥 DB에 접근하면 안되나?

음, 간단하게 말하자면 C# 코드가 클라이언트에서 돌아가고, 클라이언트에서는 데이터베이스에 직접 연결할 수 없기때문이다. 우리의 실버라이트 프로젝트에는 DataSet이나 DataSource 같은 것이 없다는것을 알아두자. 그리고 System.Data 네임스페이스에는 즐겨쓰던 클래스들이 빠져있다. 하지만, WCF 서비스 같은 좋은 것들이 포함되어 있기도 하다. 이 포스팅은 WCF 서비스를 통해 데이터를 가져오는 방법을 보여준다.



시작해보자

비주얼 스튜디오로 실버라이트 어플리케이션을 만들기 위해, 이곳에서 실버라이트 툴을 다운로드받아서 설치하자. 설치후에 Silverlight 응용 프로그램과 Sliverlight 클래스 라이브러리가 추가된것을 볼 수 있다. 여기서 "Silverlight 응용 프로그램" 을 선택하고 이름을 DataApplication 으로하여 시작하자.

사용자 삽입 이미지

확인하면 실버라이트 응용프로그램을 호스팅할 방법을 선택해야되는데, "ASP.NET 웹 응용프로젝트"를 선택하여 ASP.NET 서버측에서 LINQ 클래스와 WCF서비스를 만들 수 있도록 하자.

사용자 삽입 이미지
여기까지 제대로 했다면 두개의 프로젝트가 추가된 솔루션이 열린다. 그중 DataApplication 프로젝트는 클라이언트에서 돌아갈 실버라이트 프로젝트이고, DataApplication.Web 프로젝트는 서버측의 ASP.NET 프로젝트다.



LINQ 클래스 생성

Northwind 데이터베이스는 가벼우면서도 여러가지 상황에 잘 맞기때문에 쓸모가 많다. (여기서 받을 수 있다.) MDF 파일에는 SQLExpress로 접근할 수 있는 프로젝트도 포함되어있다. LINQ 데이터 클래스를 생성하기위해 ASP.NET 응용 프로그램에서 LINQ to SQL 클래스 추가를 선택하도록 하자. (역주:instNwnd.sql 파일만 실행시켜서 DB생성해도 무관하다.)

사용자 삽입 이미지
이제 서버탐색기에서 Northwind 데이터베이스로 새로운 연결을 하나 만들고(SQLExpress든 SQL Server든 상관없다. 자신이 가진걸로 하자.), Customers, Orders, OrderDetails 테이블을 LINQ 디자이너로 드래그하자. 여기서 중요한 점은 실버라이트 응용프로그램으로 데이터를 전송할 수 있도록 하기 위해서는 LINQ로 생성되는 데이터의 직렬화가 가능하도록 해주어야 한다는것이다. 직렬화가 가능하도록 설정하기 위해서 LINQ 디자이너의 빈공간을 클릭한 후 속성창에서 직렬화모드(Serialization Mode)를 Unidirectional 로 설정하도록 하자.

사용자 삽입 이미지



실버라이트 WCF 서비스 생성

이제 데이터를 받을 수 있는 서비스를 추가하자. 실버라이트2 베타2 이전에는 WCF 서비스를 사용하기 위해서는 몇가지 꼼수가 필요했었다. 그러나 다행히도 실버라이트2 베타2 이상의 버전에서는 "Silverlight 사용 WCF 서비스" 라는 템플릿이 제공된다. 자, 새항목추가 - Silverlight를 선택하고 DataService 라는 이름으로 "Sliverlight 사용 WCF 서비스"를 ASP.NET 프로젝트에 추가하자.

사용자 삽입 이미지
생성한 서비스에 세가지 메서드를 작성할건데, 하나는 모든 고객을 리턴하는것이고, 다른 하나는 한 고객의 주문을 리턴하는것이며, 나머지 하나는 특정 주문의 주문상세내역을 리턴해주는것이다. 이 메서드는 반드시 [OperationContract] 특성이 있다는것을 알아두자. (이 특성은 ASMX 서비스의 [WebMethod]와 유사하다.) 아주 간단한 LINQ를 이용해 데이터를 가져오는 것을 구현하였다. 아래 코드를 DataService.svc.cs 파일에 추가하자. (역주:이후 나오는 코드중 Customer, Order, OrderDetail 테이블은 실제 모두 마지막에 's'가 붙어있다. 원저자의 테이블과 약간 틀린듯하다. 번역본에 소개되는 코드에는 모두 's' 를 붙여 놓았다.)

[OperationContract]
public List<Customers> GetCustomers()
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return datacontext.Customers.ToList();
}

[OperationContract]
public List<Orders> GetOrders(string customerID)
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return (from order in datacontext.Orders
            where order.CustomerID == customerID
            select order).ToList();
}

[OperationContract]
public List<Order_Details> GetOrderDetails(int orderID)
{
    DataClasses1DataContext datacontext = new DataClasses1DataContext();
    return (from orderdetail in datacontext.Order_Details
            where orderdetail.OrderID == orderID
            select orderdetail).ToList();
}



실버라이트 프로젝트에 서비스 참조 추가

서버측의 ASP.NET 프로젝트에서 할일은 이걸로 끝이다. 데이터베이스에서 데이터를 얻기 위해 LINQ 클래스를 만들었고, 이 LINQ 객체를 전달해주기 위해서 WCF 서비스를 만들었다. 이제, 클라이언트측의 실버라이트에서 데이터를 사용할 준비가 끝난 셈이다. 이제 실제 사용을 위해 실버라이트 프로젝트인 DataApplication 프로젝트에 서비스 참조를 추가하자. 참조 - 서비스 참조 추가를 실행하고 팝업창에서 검색버튼을 누르면 방금 우리가 만들었던 WCF 서비스를 검색해준다.

사용자 삽입 이미지



UI 만들기

실버라이트 페이지, 컨트롤은 레이아웃을 담당하는 XAML 파일과 xaml.cs 파일의 코드 비하인드 파일로 구성된다. 간단하게보면 UI를 구성하기위한 *.aspx 파일과 각종 처리로직과 이벤트 핸들러 등이 있는 *.aspx.cs 파일로 구성되는 ASP.NET 과 유사하다. 자, 우리의 응용 프로그램에도 기본적인 레이아웃을 구성해 보자.



DataGrid 컨트롤을 사용하기위해 어셈블리 추가

데이터를 보여주기위해서 DataGrid 컨트롤을 사용하려고 하는데, 실버라이트에는 기본적으로 DataGrid 컨트롤의 참조가 추가되어 있지 않다. 직접 추가해주자. 이 과정은 ASP.NET 에서 사용자정의 컨트롤을 추가하는 방법과 거의 같다. DLL 참조를 추가하고 aspx 페이지에 태그를 등록했던것을 상기해보도록 하자. 실버라이트에서는 참조 - 참조추가를 클릭하고 목록에서 System.Windows.Controls.Data 를 선택하기만 하면 된다. (이 어셈블리에 DataGrid 컨트롤이 포함되어 있다.)

사용자 삽입 이미지
참조를 추가한 다음, XAML 코드에 추가한 참조의 네임스페이스를 등록해 주어야한다. 아래의 네임스페이스 선언을 Page.xaml 파일에 추가하자.

xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

사용자 삽입 이미지



UI 레이아웃

이제 우리가 할것은, LayoutRoot 라는 이름의 Grid 를 만들고 3개의 행을 가지도록 한다. 첫번째 행은 어플리케이션 제목을 위해 사용한다.(width=50) 세번째 행은 상태바를 위해 사용한다.(width=20) 그리고 가운데 행은 메인데이터를 위해 사용한다. (width=*, 남는 공간을 전부 사용) LayoutRoot 그리드의 첫번째 행에 TextBlock를 추가하고 마지막 행에 txtStatus 라는 이름의 빈 TextBlock을 추가한다. LayoutRoot 그리드의 가운데 행(컨텐트 홀더로 사용하기로 한)에 ContentRoot 라는 이름의 2행 2열로 구성된 또 다른 그리드를 추가한다. 왼쪽 열은 너비 200이고 오른쪽은 나머지 전체를 차지 하도록 하고, 행들은 각각 60%, 40%의 비율을 가지도록 한다. ContentRoot 그리드의 왼쪽 열에는 ListBox 를 아래 행까지 합쳐서 배치하고, 오른쪽 열에는 위, 아래 행 두곳 각각의 DataGrid 를 배치하는데, 위쪽은 주문 데이터를 위한 그리드이고, 아래쪽은 주문상세를 위한 그리드이다. 말로 줄줄 풀어써서 짜증났다면 이 문장들을 XAML화한 코드가 아래에 있다. Page.xaml 파일에 추가하도록 하자.

<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;
    assembly=System.Windows.Controls" 
    x:Class="DataApplication.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;
        assembly=System.Windows.Controls.Data"
    Width="Auto" Height="Auto">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="55" x:Name="HeaderRow" />
            <RowDefinition Height="*" x:Name="ContentRow"/>
            <RowDefinition Height="20" x:Name="FooterRow"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <!-- Heading -->
        <TextBlock x:Name="txtHeader" Grid.Row="0" 
                   FontSize="20" Margin="5,5" Foreground="Blue"
                   Text="My First Data Application in Silverlight">
        </TextBlock>

        <!-- A textblock in the footer to be used as an Status bar -->
        <TextBlock x:Name="txtStatus" Grid.Row="2" 
               FontSize="10" Margin="5,0" Foreground="Red">
        </TextBlock>

        <!-- Content Holder -->
        <Grid x:Name="ContentGrid" Grid.Row="1" Margin="5">
            <Grid.RowDefinitions>
                <RowDefinition Height=".6*" />
                <RowDefinition Height=".4*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- Listbox for displaying customers -->
            <ListBox x:Name="lstCustomers" Grid.Column="0" Grid.RowSpan="2"
                     DisplayMemberPath="ContactName"
                     Loaded="lstCustomers_Loaded"
                     SelectionChanged="lstCustomers_SelectionChanged">
            </ListBox>

            <!-- DataGrid for displaying orders of a customer 
                (with autogenerated columns) -->
            <data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1" 
                           AutoGenerateColumns="True"
                           SelectionChanged="dgOrders_SelectionChanged">
            </data:DataGrid>

            <!-- DataGrid for displaying orderdetais for an order -->
            <data:DataGrid x:Name="dgOrderDetails" Grid.Row="1" Grid.Column="1" 
                           AutoGenerateColumns="True"
                           AutoGeneratingColumn="dgOrderDetails_AutoGeneratingColumn">
            </data:DataGrid>

        </Grid>

    </Grid>
</UserControl>

WPF 의 레이아웃에 관해서는 코드 프로젝트와 여러 다른 사이트에서 많이 다루고 있기 때문에 굳이 자세히 설명하지는 않겠다.(Sacha Barber의 이런 것 이 있다.) 우리는 ListBox와 DataGrid에 대해 훓어 보는 편이 낮다.



ListBox

lstCustomers 라고 명명한 리스트박스는 데이터베이스의 고객을을 보여주기위해 사용한다. Loaded 이벤트를 등록해서 바인딩을 할것이다. 리스트박스는 object 소스가 바인딩되었을때 각 items 컬렉션의 object.ToString() 을 해당 값으로 보여준다는것을 알아두자. 다른 값을 보여주려면 세가지 방법을사용할수 있다.

  • object.ToString() 메서드를 오버라이드 한다. (이 포스팅에서는 이렇게 구현하지 않는다.)
  • Data Template 를 정의한다. (이 방법이 제일 유연한 접근방법이다. 나중에 DataGrid의 컬럼을 직접 정의하면서 간단하게 살펴보겠지만, 지금은 이 방법을 사용하지 않는다.)
  • ListBox의 DisplayMemberPath 프러퍼티에 보여줄 객체의 프러퍼티를 설정한다. (아주 간단한 방법이다. 지금, 이 방법으로 구현해 보겠다.)

ListBox에 바인딩된 Customer 객체의 ContactName 프러퍼티를 보여줄 것이기 때문에, DisplayMemberPath = "ContactName" 라고 설정한다. 또한, DataGrid에 선택된 고객의 주문을 보여줄수 있도록 하기위해 SelectionChanged 이벤트도 등록해 주도록 하자.



DataGrids

이제는 DataGrid 를 설정하는것이 좀더 수월해졌다. 바인딘된 데이터에 따라서 자동으로 컬럼을 생성하도록 설정하면 되기 때문이다. 그리고, dgOrderDetails 그리드에 AutoGeneratingColumns 이벤트를 등록해주면 컬럼이 자동으로 생성될때 필요치 않는 컬럼을 제거하는 로직을 구현할 수 있는데, 이렇게 하는것이 특정 칼럼을 수월하게 제거하는 일반적인 방법이다. 그리고 자동생성말고 직접 컬럼을 설정하는것은 나중에 다시 알아보기로 하고, 일단은 자동생성으로 간단하게 구현해 보자.



코드 작성

ListBox 처리

ListBox에는 lstCustomers 라고 명명하고, Loaded 이벤트에서 우리가 보여줄 고객에 대한 정보를 보여주자. 실버라이트의 대부분의 서비스 호출은 비동기적으로 일어날 수 있기때문에 데이터가 바인딩되는 시점에 콜백함수를 등록하여 데이터 바인딩 결과에 대한 처리를 해주면 좋다. 여기서는 데이터 로딩과 관련된 메세지를 텍스트로 뿌려줄것이다. (처음에 LayoutGrid의 마지막행에 만들어 두었던 txtStatus 라는 이름의 텍스트 박스를 사용하기로 한다.)

private void lstCustomers_Loaded(object sender, RoutedEventArgs e)
{
    DataServiceClient svc = new DataServiceClient();
    this.txtStatus.Text = "Loading customers...";
    svc.GetCustomersCompleted += new
      EventHandler<GetCustomersCompletedEventArgs>(svc_GetCustomersCompleted);
    svc.GetCustomersAsync();
}

void svc_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
    if (e.Error == null)
    {
        this.lstCustomers.ItemsSource = e.Result;
        this.txtStatus.Text = string.Empty;
    }
    else
    {
        this.txtStatus.Text =
            "Error occurred while loading customers from database";
    }
}



고객에 대한 주문 보여주기

이제, ListBox에서 특정 고객을 선택하면 해당 고객에 대한 주문건을 보여주는 코드를 작성해 보자. ListBox의 SelectionChanged 이벤트 핸들러에서 아까 만들어 두었던 WCF 서비스를 호출하여 데이터를 가져오고, 이를 dgOrders 그리드에 바인딩시켜서 보여주자. 이번 구현에서는 간단한 구현을 위해 익명메서드를 사용해보기로 하자.

private void lstCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Customers selectedCustomer = this.lstCustomers.SelectedItem as Customers;
    if (selectedCustomer != null)
    {
        DataServiceClient svc = new DataServiceClient();
        this.txtStatus.Text = "Loading orders...";
        svc.GetOrdersCompleted +=
            delegate(object eventSender, GetOrdersCompletedEventArgs eventArgs)
            {
                if (eventArgs.Error == null)
                {
                    this.dgOrders.ItemsSource = eventArgs.Result;
                    this.txtStatus.Text = string.Empty;
                }
                else
                {
                    this.txtStatus.Text =
                        "Error occurred while loading orders from database";
                }
            };
        svc.GetOrdersAsync(selectedCustomer.CustomerID);
    }
}



주문에 대한 상세주문정보 보여주기

ListBox의 SelectionChanged 이벤트와 유사하게 dgOrders에도 SelectionChanged 이벤트를 등록하고 코드를 작성하자. 이번에는 람다 표현식을 사용해서 구현해 보겠다.

private void dgOrders_SelectionChanged(object sender, EventArgs e)
{
    Orders selectedOrder = this.dgOrders.SelectedItem as Orders;
    if (selectedOrder != null)
    {
        DataServiceClient svc = new DataServiceClient();
        this.txtStatus.Text = "Loading order details...";
        svc.GetOrderDetailsCompleted +=
            (eventSender, eventArgs) =>
            {
                if (eventArgs.Error == null)
                {
                    this.dgOrderDetails.ItemsSource = eventArgs.Result;
                    this.txtStatus.Text = string.Empty;
                }
                else
                {
                    this.txtStatus.Text =
                        "Error occurred while loading order details from database";
                }
            };
        svc.GetOrderDetailsAsync(selectedOrder.OrderID);
    }
}



dgOrderDetails 에서 일부 자동생성된 칼럼 제거하기

아까 작성한 XAML에서 우리는 DataGrid의 AutoGenerateColumns 속성을 true 로 설정함으로써 바인딩된 데이터에 따라서 컬럼을 생성하도록 하였다. 이제 이렇게 생성된 컬럼중에서 dgOrderDetails 그리드의 OrderID 컬럼을 제거해보자. 아까 말했다시피 AutoGeneratingColumns 이벤트 핸들러를 통해서 가능하다.

private void dgOrderDetails_AutoGeneratingColumn(object sender,
    DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.Column.Header.ToString() == "OrderID")
        e.Column.Visibility = Visibility.Collapsed;
}



중간점검.. 프로젝트 실행

어느정도 볼만한 프로그램이 만들어진것 같다. 실행해서 이것저것 마구 건드려보자. 고객, 주문, 주문상세 셋의 선택이 바뀔때 연관되어 바뀌는 관계도 보고, 그리드 자체의 수정기능, 컬럼헤더클릭으로 정렬기능, 헤더의 컬럼 사이즈 변경, 브라우저 크기에 따른 동적 크기 계산 등등.. 원하는건 뭐든지 해보라. 멋지지 않은가? 자, 이제 DataGrid 의 컬럼과 템플릿에 대해 알아보자.

사용자 삽입 이미지



컬럼 정의

실버라이트의 컬럼 설정방법은 ASP.NET의 방법과 유사하다. DataGrid는 세가지 종류의 컬럼을 설정할 수 있다.

  • DataGridTextBoxColumn - 보여주기만 하거나, 수정도 가능한 TextBlock 형태의 데이터. 바인딩된 객체의 어떤 프러퍼티를 보여줄것인지를 DisplayMemberPath 를 통해 설정해줘야 한다.
  • DataGridCheckBoxColumn - 불린, 널러블불린값의 체크박스의 형태. 읽기전용으로 설정하거나, 수정할 수 있도록 설정할 수도 있다.
  • DataGridTemplateColumn - ASP.NET의 TemplateColumn 처럼 강력한 기능을 제공해준다. DataTemplate 을 정의해서 원하는 컨트롤을 해당 칼럼에 설정할 수있다. DataTemplate에 대한 좀 더 자세한 내용은 MSDN 의 이곳에서 확인하도록 하자.

칼럼들에대해 좀더 자세히 알고 싶으면 Scott Morris 의 블로그를 방문해보자. 꽤 좋은 포스팅을 해놓았다.

자, 아제 실제 응용프로그램에 적용해 보도록하자. 간단한 구현을 위해 네개의 칼럼만 정의하겠다. OrderID, EmployeeID 에는 DataGridTextBoxColumn을 사용하고, OrderDate에는 DataGridTemplateColumn 을 사용할건데, CellTemplate 에는 TextBlock을, CellEditingTemplate에는 DatePicker를 사용해보자. 아, 마지막으로 Frieght 칼럼에도 TemplateColumn을 사용하는데 거기에는 데이터 표현을 위해 TextBlock을 넣고 값 수정을 위해 Slider를 넣어서 값의 증감을 조절할 수 있도록 하겠다. 근데, DataTemplate 에는 하나의 컨트롤만 넣을 수 있도록 되어 있으므로, StackPanel을 만들어 이 패널에 TextBlock과 Slider를 넣은뒤에 DataTemplate에 넣기로 하자.

변경된 dgOrders 부분의 코드는 아래와 같다.

<!-- DataGrid for displaying orders of a customer -->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1" 
   AutoGenerateColumns="False"
   SelectionChanged="dgOrders_SelectionChanged">
    <data:DataGrid.Columns>
        <!-- OrderID text column -->
        <data:DataGridTextColumn Header="Order ID" Binding="{Binding OrderID}" />

        <!-- EmployeeID text column -->
        <data:DataGridTextColumn Header="Employee ID" Binding="{Binding EmployeeID}" />

        <!-- OrderDate template column -->
        <data:DataGridTemplateColumn Header="Order Date" Width="150">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding OrderDate}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
            <data:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <basics:DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellEditingTemplate>
        </data:DataGridTemplateColumn>

        <!-- Freight template column -->
        <data:DataGridTemplateColumn Header="Freight" Width="150">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Freight}"></TextBlock>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
            <data:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Freight}" Width="50" />
                        <Slider Value="{Binding Freight, Mode=TwoWay}" Width="100"
                                Minimum="0" Maximum="500" />
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellEditingTemplate>
        </data:DataGridTemplateColumn>
    </data:DataGrid.Columns>
</data:DataGrid>

같은 방식으로 DataTemplate 를 ListBox에도 사용할 수 있다. In a similar way, we can use DataTemplates for our ListBox. 우리가 가져올 데이터중에 비트맵이미지를 리턴해주는 PictureProperty가 있다고 한다면, 아래와 같은 방식으로 ListBox에 이미지를 보여줄 수도 있다.

<ListBox x:Name="lstCustomer">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding NameProperty}"></TextBlock>
                <Image Source="{Binding PictureProperty}"></Image>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

위 코드는 이렇게도 할수있다라는 샘플에 불과하니깐 우리 응용프로그램에는 적용시키지 말자. (우리가 가져오는 데이터에는 이미지가 없다.)



두번째 실행

다시 프로젝트를 실행해보자. OrderDate 칼럼을 더블클릭해서 DataPicker가 어떻게 나타나는지 보라. Freight 칼럼의 Slider를 조절하여 값을 조절해보고, 여러개의 컨트롤들이 어떻게 결합되어 하나의 칼럼에 나타나는지 유심히 보라. 알아채고 있었는지 모르겠지만, 지금 우리가 이렇게 칼럼을 수정해서 컨트롤을 집어넣은것은 모두 레이아웃코드에서 해결했다. *.cs 파일은 코딩도 하지 않았는데 말이다. 이게 바로 WPF 프레임웍의 장점이 아니겠는가!

사용자 삽입 이미지




DB에 변경데이터 적용하기

음, DB에 데이터를 다시 적용하는것은 이번 포스팅의 범위에서는 좀 벗어난다. 하지만 여러분이 WCF 서비스가 서버와 클라이언트사이에서 통신하는 것을 알고 있으면, 서비스쪽에 데이터를 저장하는 몇가지 함수를 만들고, 그 함수를 호출해서 사용한다면, 실버라이트에서도 쉽게 데이터 작성도 가능하도록 구현할 수 있다고 본다. 그리드에서 바인딩된 값이 변경될때 변경된값은 서버의 값이 아니라, 그리드에 바인딩된 DataContext 라는 것을 기억하자. 실제 DB에 변경을 하고 싶다면, ASP.NET 프로젝트의 WCF 서비스를 이용해서 변경된 데이터를 보내서 DB에 업데이트하도록 해야할 것이다. Ronnie Saurenmann의 동영상이(여기) 두개있는데, 그는 헬퍼 클래스를 이용해서 데이터를 마치 DataSet 처럼 다루고 있다. 변경된 데이터 관리라든지, 원본데이터라든지, 업데이트시에 변경된 데이터만 보내도록 한다든지 하는 방법을 보여주고 있다. 이 동영상을 꼭 한번 보기를 권장한다.



결론

이걸로 끝이다. 나는 이 포스팅을 통해 얼마나 간편하게 데이터관리 응용프로그램을 만들수있는지를 보여주고 싶었다. 배웠던것을 다시 한번 보자면, LINQ로 데이터엑세스 레이어를 만들었고, ASP.NET 프로젝트의 WCF 서비스를 통해 접근할 수 있도록 하였다. 그리고 실버라이트를 통해 클라이언트 측에서 데이터를 가져왔고, 이를 몇몇 컨트롤들을 사용해서 보여주었다.이 포스팅이 여러분들의 실버라이트 응용프로그램 제작의 동기가 될 수 있기를 바란다. 언제나 즐거운 실러라이트 프로그래밍을 하기를..



문서 이력

  • 2008년 08월 12일 - 기사 게시
  • 2008년 10월 19일 - Silverlight 2 RC1에 맞도록 업데이트
  • 2009년 01월 30일 - 한글로 번역되어 포스팅됨




라이센스

이 포스팅에 관련된 모든 파일과 소스코드는 The Code Project Open License (CPOL)를 따릅니다.



저자에 관해

Syed Mehroz AlamSyed Mehroz Alam has done his Bachelors in Computer and Information Systems Engineering in 2007. He loves logical challenges and has written certain logic games and mathematical applications in C/C#/VB in his early age which can be found at: http://www.geocities.com/smehrozalam/myprogs.html
He writes his blog at http://smehrozalam.wordpress.com
Any questions, comments or suggestions are always welcomed at smehrozalam at yahoo dot com.

변역

이 글의 원저자에게 양해를 구하고 번역하였습니다. 전문 번역가도 하니고 학습을 목적으로 번역을 했기때문에 서툰 표현이 많을 수 있습니다. 번역본을 퍼가실때는 출처를 남겨주시기 바랍니다.

2009/01/29 08:41 2009/01/29 08:41
http://lemonwidz.com/tc/trackback/8
CLR Via C# 2nd Edition 을 읽는 도중 흥미로운 부분이 있어 기록해 놓는다.

요지는 관리 코드인 경우 IL -> JIT -> Native Code 단계로서 최종적으로 C++과 같은 Native 코드를 생산해 내지만, 중간 단계인 JIT 에서 속도의 저하가 일어난다 라고 사람들은 알고 있다. 하지만, JIT 의 경우 Native 코드로 컴파일할때 현재 실행환경(CPU, 32, 64비트 환경 여부 등)을 정확하게 파악하고 있으므로, 항상 해당 환경에 최적화된 Native Code 를 생산해준다..... 라는것! 일리 있어 보이는데?

믿기 힘들겠지만 필자를 포함한 많은 사람들은 비관리 어플리케이션보다 관리되는 어플리게이션이 더 빠르다고 생각한다. 이에 대한 근거는 많이 있다. 예를 들면, JIT 컴파일러는 IL코드를 네이티브 코드로 컴파일할 때 어플리케이션의 실행 관경에 대해서 비관리 컴파일러보다 훨씬 더 자세하고 정확하게 알고 있다. 다음은 관리 코드가 비관리 코드보다 성능이 우수할 수 있는 몇 가지 근거이다.

  • 컴파일 시에 JIT 컴파일러는 실행 환경이 펜티엄4 CPU 라는 것을 알 수 있고 또 펜티엄4 CPU라는 것이 확인되면 이에 최적화된 지시어를 이용해서 네이티브 코드를 생성함으로써 성능을 향상시킬 수 있다.

  • JIT 컴파일러는 특정 상황의 테스트 값 혹은 논리 연산의 결과를 실행 전에 이미 정확히 알 수 있다. 다음의 코드가 이런 예이다.

    if(numberOfCPUs > 1) {
    //...
    }

    이 경우 JIT 컴파일러를 호스팅하고 있는 시스템의 CPU가 하나라면 KIT 컴파일러는 해당 if 블록이 할상 실행되지 않을 것을 알 것이며, 따라서 전체 if 문을 네이티브 코드로 변황하지 않을 것이다. 이 경우 어플리케이션의 실행 코드는 호스팅하는 운영체계에 최적화된 코드이며 좀 더 작고 좀 더 빠른 네이티브 코드를 생성하게 된다.

  • CLR은 어플리케이션의 실행 패턴을 프로파일(profile)할 수 있으며 이에 따라서 어플리케이션 실행중에 IL 코드를 네이티브 코드로 다시 컴파일할 수 있다. 재컴파일된 네이티브 코드는 프로파일된 실행 패턴을 바탕으로 코드의 분기를 최적화하기 위해서 재정열된다.
- CLR Via C# 2nd Edition (Jeffrey Richter 저 / 송기수 역) 에서 발췌
2008/11/30 22:21 2008/11/30 22:21
http://lemonwidz.com/tc/trackback/5
이전 블로그의 글을 옮겼습니다.


DB를 사용하고 나면 꼭꼭 닫으라고 ADO.NET 관련 서적, 강좌들에 나와있다.


그런데 오늘 직장분들과 이야기 하는도중 DB에 접근이 필요할때 마다 매번 열고 닫고 한다면 그 과정에서 시간이 오래 걸리니까 시간적으로 손해가 아니냐? 차라리 연결을 열어두고 사용하지 않는다고 판단이 될때 닫아주는것이 속도 면에서 좋지 않겠느냐? 라는 이야기를 듣게 되었다.


일리 있는 말이다. 그래서 구글링해서 몇가지 정보를 찾았고 MSDN에서 명쾌한 답변을 해주었다.


http://msdn2.microsoft.com/ko-kr/library/8xx3tyca(VS.80).aspx


요점은..


  • DB연결에는 여러가지 복잡한 과정을 거쳐야 하므로 시간이 많이 소요된다.
  • ADO.NET에서는 '연결풀링'이라는 최적화 기법을 사용하여 DB와의 연결을 관리한다.
  • 연결풀링은 일종의 캐시 형태로 연결에 관련된 정보를 저장하여 놓는다.
  • 연결풀링의 '풀러'가 실제 연결에 대한 제어권을 가지며 연결 요청이 있을때마다 풀러가 가지고 있는 정보를 요청받은 쪽에 넘겨준다.
  • 연결풀링은 원한다면 직접 제어할 수 있다.
  • 개발자가 연결을 닫지 않으면 연결풀링은 일정시간마다 검색하여 닫아주는 작업을 하는데, 이때 시간이 많이 소요될 수 있다.
    ( 따라서 연결은 꼭 닫아 주어라. )
  • 이와 같은 '연결풀링'의 사용은(기본값이다) 응용프로그램의 성능을 대폭 향상 시킬 수 있다.
2007/11/21 15:05 2007/11/21 15:05
http://lemonwidz.com/tc/trackback/10
이전 블로그의 글을 옮겼습니다.


나는 평소에 C#을 '씨샵' 이라고 발음했다. 그런데 직장 선임분과 Ajax 발음에 관한 이야기를 주고 받는 중에 C#도 외국에서는 '씨샵'이 아닌 다른 발음으로 발음 한다고 들은적이 있는것 같다는 얘기를 해서 지식인에게 물어 봤다-_-;

'#' 이라는 글자를 두고 외국에서는 두가지 의미가 있다.

음악적인 의미에서는 sharp 으로 쓰인다.

중량, 혹은 화폐단위의 의미에서 pound 로 쓰인다.

의문의 가졌던것 처럼 '#'은 두가지 뜻을 내포하고있었고 거기에 발음까지 달랐다.

이 시점에서 궁금증이 증폭하여 구글링을 시작했다-_-;


구글링을 시작하자마자 궁금증의 종지부를 찍을 만한 사이트를 발견했다-ㅅ-;;;;;;;;

 MSDN Visual C# 언어 참조 - 한국어 ( http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cscon/html/vcoriCStartPage.asp )


위 사이트에는 분명히 '시 샵으로 발음'이라고 맨처음에 소개하고있다-_-;; 그것도 공식 MSDN 페이지이니 뭐라 할말도 없다-_-;;;;

구글링 중에 많은 외국 포럼에서 C# and C sharp are same? , C sharp or C pound? 등등의 쓰레드도 몇개 봤다. 대답은 C sharp였다.


여기서 마지막으로 쐐기를 박기위해(-_-) MSDN 공식 비디오를 찾아서 직접 발음을 들어 보았다.

THE MSDN SHOW ( http://msdn.microsoft.com/archive/en-us/theshow/Viewer/player.asp?folder=Episode008&file=100k.asx )


결과는 당연히(?) '씨샵'이라고 발음하였고, 더구나 이 비디오의 대본중에서 눈에 띄는 것이 있었는데,

ROBERT HESS: To continue on with our focus on understanding the .NET Architecture, we will today focus on the new C# (that's "C Sharp", not "C Pound"!) programming language. But first, of course, let's go check in with Erica and the news.


 

후.. 음악에서의 '#'은 '올림'을 뜻한다. 말그대로 C#은 C를 한단계 올린 언어라고 생각해서, 혹은 그렇게 만들고 싶어서 C Sharp이라고 부르게 된것이 아닐까하고 문득 생각해 봤다.


뭐.. 끝났다-_-; 여기서 재미난 놀이는 끝이다-_-;; 다시 하던 공부나 마저해야겠다.......-_-;;

2007/11/13 20:36 2007/11/13 20:36
http://lemonwidz.com/tc/trackback/9
전체 (23)
사진이야기 (4)
프로그래밍 (18)
  1. Nyaonge's Home  2011
    [C#] ?? 연산자(물음표 두개)
  1. 2012/03 (1)
  2. 2011/12 (2)
  3. 2009/07 (1)
  4. 2009/04 (1)
  5. 2009/03 (9)