"뭐... 엑셀 처럼 해주세요..."
대략 뷁스럽기 이를데 없는 경우다. 이야기가 좀 옆으로 샜지만 닷넷과 엑셀의 연동에서 골치거리 중 하나가 이놈의 Excel.exe 프로세스가 잘 죽지 않는다는 것이다. Excel.exe 프로세스를 확실하게 죽이는 방법에 대해 썰을 좀 풀어볼까 한다. (죽인다는 표현이 좀 쌀벌하다...)
다음은 엑셀을 액세스하는 전형적인 간단한 C# 코드이다. 뭐 설명할 필요도 없이 Sheet1의 A1 셀에 Hello Excel Interop 이란 문자열을 박아 넣는 코드로서 매우 잘 작동한다.
위 코드의 문제는 작업을 모두 마쳤음에도 불구하 Excel.exe 프로세스가 죽지 않는다는 것이다. 작업 관리자로 현재 수행중인 프로세스를 살펴보면 떡하니 Excel.exe가 버티고 있다. Quit() 메쏘드까지 호출해 줬는데도 말이다!
비슷한 코드를 VB 6.0이나 VBS(Visual Basic Script)로 수행해보면 메쏘드를 종료하는 시점에 Excel.exe 프로세스는 제깍 제깍 죽는다. 그런데 동등한 코드를 닷넷 환경에서 수행시키면 메쏘드 호출이 끝났어도 죽지 않는다. 원인은 COM 객체와 닷넷의 가비지 컬렉션이 서로 궁합이 잘 맞지 않는다는 것이며 Excel 프로세스가 종료하기 위해서는 생성된 모든 엑셀 COM 객체들(Application, Workbook Sheet 등등)이 해제(release)되어야 하기 때문이다.
닷넷에서 엑셀 객체들과 같은 COM 객체를 액세스 할 때는 항상 RCW(Runtime Callable Wrapper)를 통해서 액세스를 한다는 것은 알고 있을 것이다. 이 RCW는 Finalizer를 정의하고 있고 이 Finalizer가 COM 객체를 해제하도록 구성되어 있다. 따라서 엑셀 프로세스가 종료되는 시점은 다음(next) 가비지 컬렉션이 수행된 후가 될 것이다. 이것도 좀 문제가 있는 것이... 비록 가비지 컬렉션이 수행되더라도 곧바로 RCW의 Finalizer가 호출된다는 보장이 없다. Finalizer 메쏘드는 별도의 Finalizer 만을 위한 쓰레드가 호출하도록 되어 있기 때문에 언젠가는 호출되겠지만 그 언젠가가 당췌 언제인지는 모르는 것이다.
Dispose 패턴이 그러하듯이 명시적으로 엑셀 객체를 해제해 주면 되지 않을까? 그렇다. 명시적으로 엑셀 객체들을 해제 해주면 엑셀 프로세스는 곧바로 종료하게 된다. 요것이 포인트가 되겠다. COM 객체를 다룰 때는 두 가지만 알면 된다. 북치기 박치기~~ ^__^
웃자고 해본 소리고... COM 객체를 닷넷에서 다루고자 한다면 참조한 모든 COM 객체는 죄다 명시적으로 해제를 하면 된다. COM 객체를 해제하는 구체적인 방법은 System.Runtime.InteropServices 네임스페이스의 Marshal 클래스가 제공하는 ReleaseComObject 메쏘드를 호출하면 된다.
위 코드를 어떻게 수정하면 될까? 뇌리를 스치는 것은 app, workbook, sheet, range 변수에 대해 ReleaseComObject 를 호출하면 되지 않을까? 자... 그렇다면 위 메쏘드의 마지막에 다음 코드를 추가하고 Excel.exe 가 종료되나 살펴보자.
테스트를 수행해 보면 예상과 달리 Excel.exe 프로세스는 종료되지 않는다. 왜일까? 필자가 붉은 색으로 강조하고 침 튀기며 외쳐댄 "참조한 COM 객체를 명시적으로 해제"를 수행 했는데 왜 Excel.exe 프로세스가 종료되지 않았을까? 둘 중 하나다. 필자가 구라를 쳤던가 아니면 코드에서 해제되지 않은 COM 객체 참조가 있던지.
당근 필자가 구라를 친 것은 아니다. 해제되지 않은 COM 객체 참조가 있기 때문이다. 메쏘드의 두 번째 라인을 잘 살펴보자. Application 객체의 Workbooks 속성의 Add 메쏘드를 호출하고 있다. 이 코드를 좀 풀어서 써 보자면 다음과 같다.
이제 감이 오는가? Application 객체의 Workbooks 속성이 또 다른 Workbooks 라는 COM 컬렉션 객체를 반환하고 있고, 이 COM 객체에 대한 참조는 암시적으로 이루어지고 있음을 알아야 한다.
그렇다면 명시적으로 COM 객체를 해제해 주려면 엑셀에 접근하는 코드 자체를 다음과 같이 수정해주어야 한다.
이 코드는 확실이 메쏘드 종료와 더불어 Excel.exe 프로세스를 종료시켜 준다.
VB 6.0이나 VBS 등으로 동등한 코드를 만들면 위와 같이 명시적인 해제가 없더라도 엑셀 프로세스는 잘 죽곤 한다. 왜 일까? 그건 VB 6.0 이나 VBS의 런타임이 메쏘드의 scope를 벗어나면 메쏘드에서 사용된 로컬 변수와 암시적으로 사용된 변수(app.WorkBooks.Add 의 경우 처럼)에 대해 해제 코드를 모두 호출해 주기 때문이다.
닷넷은 왜 VB 6.0 처럼 하지 못하는 것일까? 닷넷 개발팀이 닭대가리들이라서? 닷넷 개발팀이 닭대가리라면 나는 접시물에 코박고 죽어야할 운명이다... 근본적인 원인은 닷넷의 가비지 컬렉션이 그 이유라고 보면 되겠다. 가비지 컬렉션은 많은 장점을 가지고 있지만 COM 객체 접근이나 데이터베이스 연결(connection)과 같이 unmanaged 자원에 접근하는 경우 역 기능을 가지곤 한다.
보다 많은 코드를 작성해야 하고 ReleaseComObject 도 호출하는 위와 같은 방법 말고 Excel.exe를 우아하게 종료하는 방법은 없을까? 우아하지 않은 방법은 있다. 강제로 가비지 컬렉션을 수행하고 RCW의 Finalizer 메쏘드가 호출되도록 하면 Excel.exe가 종료되긴 한다.
위와 같은 방법은 벼룩 잡을려고 초가삼간 태우는 격이 되겠다. 그 누구도 GC.Collect 호출을 권장하지 않는다. 가비지 컬렉션은 닷넷 CLR이 판단하여 스스로 수행하도록 하는 것이 가장 좋다. 그래도 위와 같이 GC.Collect와 GC.WaitForPendingFinalizer 호출을 수행하여 Excel 연동의 결과로 수행된 Excel.exe 프로세스를 종료시키는 방법이 있다는 것만 알아 두자.
마지막으로 당부할 것은 COM 객체를 반복문 안에서 참조하는 경우를 조심해야 한다. 반복문(for, while 등) 내에서 COM 객체의 참조를 얻었다면 해제 역시 반복문 내에서 이루어져야 한다는 점이다.
왜 반복문 안에서 얻은 COM 참조를 반복문 내에서 해제해야 하는 가에 대해서는 상세히 설명하지 않겠다. 잘 생각해 보기를 바란다.
프로젝트를 수행하거나 예제코드를 비주얼 스튜디오(Visual Studio)로 작성할 때 항상 프로젝트를 만들곤 한다. 필자는 간단한 예제 코드는 텍스트 에디터와 커맨드 라인을 사용하지만 웹 어플리케이션이나 COM+ 관련 DLL 및 EXE를 작성할 때는 죽으나 사나 비주얼 스튜디오의 능력을 빌리곤 한다. 그런데 매번 프로젝트를 만들 때마다 지겹도록 반복하는 작업이 있다. AssemblyInfo.cs 파일의 내용을 수정하고, 각 .cs 파일의 맨 위에 소스 코드 주석을 다는 등등의 일반적인 작업 뿐만 아니라, COM+ DLL 프로젝트라면 System.EnterpriseServices를 매번 참조해야 하는 등등의 작업이다. 이러한 작업은 반복적일 뿐만 아니라 매우 지겨운 작업임에 분명하다.
이러한 반복적이고 지루한 작업을 없애는 방법은 이런 작업들이 이미 적용된 프로젝트 템플릿이 있으면 될 것이다. 이미 비주얼 스튜디오 내에는 다양(?)한 프로젝트 템플릿이 존재하지만 대부분 매우 일반적인 템플릿들만이 제공되고 있어서 나만의 템플릿, 혹은 프로젝트 팀에서 사용할 공통 프로젝트 템플릿은 아님에 분명하다.
비주얼 스튜디오 2005 이전 버전에서 커스텀 템플릿을 만드는 것이 가능했다. 하지만 이놈의 것이 MSDN 등에 거의 문서화 되어 있지 않기 때문에 작성이 쉽지 않았다. 비주얼 스튜디오 2005에서는 존재하는 프로젝트를 템플릿으로 작성해주는 "Export Template" 마법사를 제공한다. 이 마법사는 현재 열려있는 프로젝트 중 하나를 템플릿 형태로 저장해 준다.
화면1. Export Template Wizard
커스텀 템플릿을 작성하는 방법은 매우 쉽다. 비주얼 스튜디오에서 작성하고자 하는 프로젝트 템플릿을 생성한다. 이 프로젝트에 템플릿에서 가져야 하는 프로젝트 속성, 참조 등을 설정하고 소스 코드 역시 템플릿에서 설정하고자 하는 형태로 바꾼다. 예를 들자면, 소스 코맨트나 템플릿에서 필요로 하는 클래스를 추가하고 기본 특성(attribute), 메쏘드 등을 추가 한다. 필수는 아니지만 컴파일이 되는가 확인하는 것도 좋다. 그리고 나서 파일 메뉴의 "Export Template" 메뉴를 선택한다. 그러면 화면1과 같은 마법사가 나타날 것이며 생성된 프로젝트를 템플릿으로 만들어 준다. 구체적인 예제와 단계별 작업 요령은 다음 글들을 참조하기 바란다. (같은 내용을 반복해 글을 쓰자니 귀차니즘이... -_-;)
MSDN Magazine의 글이 가장 상세하게 템플릿을 설명하고 있으므로 이 문서를 집중적으로 읽어 보면 될 것이다. 마지막 문서는 비록 DirectX 프로젝트의 템플릿을 만드는 예제이지만 한글 문서라는 장점이 있다(필자는 이 글을 읽어 보진 않았다).
템플릿은 SI 프로젝트를 수행하는데 매우 유용하게 사용할 수 있다. 간단한 웹 어플리케이션 프로젝트라면 하나의 웹 프로젝트를 여러 개발 팀원이 VSS 를 통해 공유하겠지만, 여러 COM+ DLL을 작성해야 하는 프로젝트이거나 커다란 규모의 웹 어플리케이션이라면 한두 개의 비주얼 스튜디오 프로젝트로는 어림도 없게 된다. 이런 경우 개발자들은 수시(?)로 프로젝트를 생성해야 하는데, 매번 프로젝트 표준(반드시 참조해야 할 어셈블리, 커맨트 규칙 등)에 맞추어 프로젝트를 생성하자면 매우 귀찮을 뿐더러, 초보 개발자들에게는 커다란 짐이 될 수도 있다. 이럴 때 개발 팀에서 사용하는 표준 프로젝트 템플릿이 있다면, 반복적인 작업을 줄여 줄 수 있을 뿐만 아니라 새로이 팀에 참가한 개발자나 초보 개발자들이 시작점을 잡기 용이해 질 것은 분명하다.
프로젝트 템플릿 뿐만 아니라 아이템 템플릿은 더욱 유용한 경우가 많다. 어플리케이션을 작성하다 보면, 동일한 유형의 화면들을 많이 작성하기 마련이다. 이 때 하나의 유형에 대해 기본 적인 코드를 작성해 놓고, 이 소스 파일을 템플릿으로 작성해 놓으면 매우 편리하다. 동일한 유형을 또 다시 작업할 때는 반복해야 할 코드는 확연히 줄어 들 것이다.
템플릿에서 주의해야 할 사항은, 템플릿이 기본으로 제공하는 코드에 버그가 존재한다면 치명적일 수 있다. 개발팀 내의 모든 개발자들이 템플릿에서 제공하는 코드를 기본으로 프로그램을 작성하기 때문에 템플릿 코드에 문제가 있다면 이 템플릿을 사용하는 모든 코드가 문제를 안고 시작하는 셈이 되기 때문이다. 필자도 1998년에 참여했던 프로젝트에서 비슷한 문제를 겪었었다. 그 때는 수천 개의 화면을 7-8개의 화면 유형으로 나누고 각 화면 유형에 대한 예제 코드 템플릿을 제공했었다. 40여명의 개발자들 이들 화면 유형 코드를 Copy & Paste 방식으로 자신의 코드에 가져다 썼었다. 이렇게 개발이 한창 진행 중이다가, 템플릿으로 제공된 예제 코드에 버그가 발견되었다. 덕택에 40여명의 개발자들은 자신이 몇달 동안 작성한 수 십개의 화면에 대해 하루 온종일 코드를 수정해야 했으며, 이런 사건은 프로젝트 개발 기간 내에 3-4 번 반복되곤 하였다. 템플릿으로 제공되는 코드에 버그가 존재하지 않는가 항상 주의해야 할 것이며, 템플릿 자체에 너무 많은 코드를 넣으려고 하는 것 역시 좋지 않다. 대신 클래스 라이브러리와 클래스 상속을 이용한다면, 코드를 복사하여 사용하는(템플릿 역시 코드 복사와 동등한 코드 재사용 법이다) 것 보다 좀 더 유연하게 버그에 대처할 수 있을 것이다.
비주얼 스튜디오 2005에서는 사용자 정의 템플릿을 손쉽게 작성할 수 있도록 Export Template 마법사를 제공하며, 이를 통해 팀 단위 개발에서 사용 가능한 프로젝트 템플릿과 아이템 템플릿을 작성할 수 있다. 비주얼 스튜디오 2003까지 제공되던 엔터프라이즈 템플릿이 사라진 이유도 2005 버전에서 손쉽게 사용자 정의 템플릿을 작성할 수 있기 때문이다. 개인이 사용할 템플릿이라면 그 정도가 덜하겠지만, 개발 팀 내에서 여러 개발자가 사용할 템플릿이라면 템플릿이 제공하는 코드에 버그가 없도록 세심한 주의를 필요로 하며, 템플릿에 다량의 코드를 넣는 것 보다는 베이스 클래스를 DLL 형태로 제공하는 것이 더 유연한 코드 재사용 모델임을 숙지할 필요가 있겠다.
출처 : Tong - dodari79님의 .NET FrameWork통
2005년 9월, ASP.NET 팀은 "Atlas"라는 코드명의 ASP.NET에 도입된 새 기능에 대한 첫 CTP(Community Technology Preview) 버전을 발표했습니다.
Microsoft .NET Framework 2.0에 적용되는 이 확장 기능을 사용하여 개발자는 브라우저와 서버의 기능을 모두 활용하여 풍부한 기능의 대화형 웹 사이트를 보다 쉽게 만들 수 있습니다.
Atlas에서 제공하는 풍부한 개발 환경을 흔히 AJAX(Asynchronous JavaScript and XML)라고 하며, 이는 그 동안 사용된 여러 가지 기술의 이름을 결합하여 만든 비교적 새로운 머리글자어입니다. 요즘에 사용되는 브라우저에는 JavaScript에서 서버를 호출하는 데 사용할 수 있는 XMLHttpRequest 개체가 포함되어 있는데, 이 개체를 사용하면 웹 페이지에서 사용자가 입력한 내용에 반응하여 전체 페이지를 새로 고치지 않고도 대역 외 작업을 수행할 수 있습니다. 이 개념 자체는 단순하지만 AJAX 라이브러리는 서버와 통신하고 웹 서비스에서 반환되는 XML을 처리하기 위해 클라이언트 쪽 JavaScript를 작성해야 하는 고된 작업을 크게 줄여줄 수 있습니다.
AJAX를 통해 해결하려는 일반적인 문제들은 대개 HTTP 프로토콜 자체의 특성에서 비롯됩니다. HTTP는 브라우저가 웹 서버와 통신하여 웹 페이지를 가져오고 데이터를 웹 서버에서 받아 다시 게시하기 위해 사용하는 표준 프로토콜로서, 상태를 저장하지 않는 방식의 프로토콜이므로 페이지를 새로 고치기 전에는 사용자의 입력이 서버의 코드에 전달되지 않습니다. 따라서 일반적인 사용자 환경에서는 상태 정보를 서버로 보내기 위해 전체 페이지를 새로 고쳐야 합니다. 사용자가 페이지에 입력한 내용은 서버에서 처리된 후 HTML 형식으로 복원되어 브라우저로 다시 보내집니다.
ASP.NET에서는 이러한 프로세스를 화면에 보이지 않는 폼 필드를 사용하여 관리합니다. 페이지의 일부만 업데이트되더라도 전체 페이지의 HTML이 전송되므로 브라우저 화면은 일시적으로 비어 있게 됩니다. 또한 페이지의 내용을 새로 고치기 위해 브라우저에서 새 내용을 받아 렌더링하는 동안에는 사용자가 웹 페이지와 상호 작용할 수 없습니다. AJAX는 이러한 환경을 개선하기 위해 전체 페이지를 새로 고치지 않고도 서버를 호출하여 웹 서비스를 실행할 수 있는 XMLHttpRequest 개체를 사용합니다. 이 개체를 사용하면 수신한 XML을 기반으로 페이지의 업데이트할 부분을 JavaScript에서 직접 수정할 수 있습니다. 사용자는 페이지가 업데이트되고 있는지 알아챌 수 없으며, 작업은 대역 외 방식으로 백그라운드에서 비동기로 실행되므로 그 동안에도 계속해서 웹 페이지를 읽거나 상호 작용할 수 있습니다.
ASP.NET의 Atlas 기능을 단지 클라이언트 중심의 웹 응용 프로그램을 만들기 위한 또 하나의 AJAX 스크립트 라이브러리로 생각해서는 안 됩니다. Atlas는 .NET Framework 2.0을 기반으로 만들어졌으며 클라이언트 쪽 JavaScript와 XMLHttpRequest 개체의 기능을 보다 잘 활용할 수 있도록 지원합니다. 또한 기존 ASP.NET 응용 프로그램뿐 아니라 Atlas 컨트롤과 서비스에서 사용하는 클라이언트 스크립트 라이브러리도 손쉽게 향상시킬 수 있는 서버 기반 기능을 갖추고 있습니다. 그림 1의 Atlas 아키텍처 다이어그램은 Atlas가 클라이언트와 서버 모두를 확장하며, 기능이 더욱 다양하고 응답이 보다 빠른 브라우저 간 웹 응용 프로그램을 만들 수 있는 광범위한 개발 기술로 간주되어야 함을 보여 줍니다.
Atlas를 활용할 수 있는 시나리오는 JavaScript를 비동기식으로 호출하여 웹 페이지의 일부를 업데이트하는 것에 한정되지 않으며 그 동안 구현하기 힘들었던 다양한 클라이언트 환경을 만드는 데 사용할 수 있습니다. 예를 들어 영화 관련 데이터로 구성된 웹 응용 프로그램이 있다고 가정해 보겠습니다. 사용자가 이 응용 프로그램을 사용하여 특정 배우를 검색할 수도 있습니다. 모든 배우 이름을 하나의 드롭다운 목록으로 만들어 제공하는 것은 비현실적이므로 선택의 폭을 좁힐 수 있는 다른 방법을 찾아야 합니다. 예를 들어 배우 이름의 첫 번째 글자를 선택하도록 사용자에게 요청하고 이 글자로 서버에 요청하여 해당 글자가 포함된 목록을 제공하면 처리가 보다 수월해지지만 사용자 입장에서는 귀찮은 일이 됩니다. 또 다른 방법으로, 배우의 이름 중 일부만 입력하여 검색할 수 있는 입력란을 제공할 수도 있습니다. 그러면 서버에서 사용자가 입력한 데이터를 기준으로 검색의 범위를 좁힐 수 있을 것입니다. 첫 번째 방법보다는 낫지만, 이 방법 역시 여전히 개선의 여지가 있습니다. Atlas를 사용하면 사용자의 입력에 따라 동적으로 반응하는 입력란을 제공하여 브라우저에서 전체 페이지를 새로 고칠 때까지 기다리지 않고도 검색 범위를 좁힐 수 있습니다. 그림 2는 Atlas를 사용하여 사용자의 입력에 따라 피드백을 제공하는 자동 완성 동작을 추가한 모습을 보여 줍니다.
Atlas CTP는 atlas.asp.net (영문)에서 다운로드할 수 있습니다. Atlas CTP를 설치하면 C# .NET 및 Visual Basic .NET용 추가 웹 사이트 템플릿이 Microsoft Visual Web Developer™에 추가되므로 Visual Web Developer에서 파일, 새로 만들기, 웹 사이트를 차례로 클릭하여 웹 사이트 프로젝트를 새로 만들 때 그림 3과 같은 대화 상자가 표시됩니다. Atlas 기반의 ASP.NET 기능을 사용하기 위해, Atlas 웹 사이트에는 웹 응용 프로그램을 구성하는 업데이트된 web.config 파일과 Microsoft.Web.Atlas.dll이 포함됩니다. 현재 릴리스의 경우 Microsoft.Web.Atlas.dll은 응용 프로그램 전체에서 사용할 수 있는 로컬 어셈블리로 응용 프로그램의 bin 디렉터리에 배치됩니다.
Atlas 기반 응용 프로그램은 Atlas를 별도로 설치할 필요 없이 개발 컴퓨터에 있는 파일을 ASP.NET 2.0이 있는 서버로 복사하여 쉽게 배포할 수 있습니다. Atlas는 시스템 수준이 아니라 응용 프로그램 수준에서 설치되므로 다음 버전의 CTP가 제공되면서 기능이 발전하고 변경될 경우 이전 버전의 Atlas가 있는 컴퓨터에서 새 버전을 사용할 수 있습니다. 따라서 시스템 수준으로 설치하는 경우보다 더욱 유연하게 마이그레이션할 수 있습니다.
그림 1의 Atlas 아키텍처 다이어그램에서 가장 먼저 주목해야 할 사실은 Atlas가 클라이언트와 서버 양쪽 모두에 적용된다는 점입니다. ASP.NET 2.0에 몇 가지 클라이언트 기능이 추가되었지만 이는 Atlas의 적용 범위에 미치지 못합니다. 아키텍처 다이어그램의 오른쪽을 보면 Atlas의 서버 기능이 ASP.NET 2.0을 기반으로 이를 확장하고 있음을 알 수 있습니다. Atlas는 브라우저에서 서버 기반 데이터와 서비스에 액세스할 수 있는 새로운 기능뿐만 아니라 새로운 서버 컨트롤 집합도 제공합니다.
다이어그램 왼쪽에는 서버 기능과는 별도로
그림 4는 웹 응용 프로그램의 일반적인 클라이언트-서버 상호 작용 방식을 보여 줍니다. 먼저 브라우저에서 웹 페이지를 요청하고 사용자는 이 페이지와 상호 작용합니다. 사용자의 작업을 위해 서버의 데이터가 필요하면 전체 페이지가 새로 고쳐지며
그림 4 일반적인 클라이언트-서버 상호 작용그림 5는 전체 페이지를 새로 고칠 필요가 없는 Atlas의 클라이언트-서버 상호 작용 방식을 보여 줍니다. 이 방식에서는 처음 HTML을 가져온 후 서버를 다시 호출할 경우 XML, JSON(JavaScript Object Notation) 또는 HTML 조각으로 업데이트된 데이터를 가져와 페이지를 증분 업데이트합니다. 웹 서비스를 호출하거나 페이지 변경 내용을 가져오기 위한 호출이 비동기식으로 백그라운드에서 수행되므로 사용자는 작업을 일시 중단할 필요가 없습니다. 이러한 비동기식 호출에서는 서버로의 다음 번 다시 게시 작업을 위해 업데이트된 보기 상태 정보를 관리하므로 페이지를 완전히 새로 고쳐야 할 경우 페이지의 정확한 상태를 서버에 전달합니다.
Atlas의 클라이언트 스크립트 라이브러리는 분리된 몇 개의 고유한 조각으로 브라우저에 전달됩니다. 스크립트 핵심은 나머지 라이브러리의 기반이 되는 하위 계층을 구성하며 최하단에는 브라우저 호환성 계층이 자리잡게 됩니다. Atlas의 주요 특징은 AJAX의 주요 요소를 지원하는 최신 브라우저에서만 Atlas가 실행된다는 점입니다. CTP 빌드를 지원하는 브라우저로는 Mozilla Firefox, Apple Safari 및 Microsoft Internet Explorer가 있습니다. 브라우저 호환성 계층은 브라우저의 차이에 신경 쓰지 않고 보다 편리하게 스크립트를 작성할 수 있게 해 주는 추상화 계층입니다. 이 계층을 통해 브라우저의 구현 방식에 의한 차이는 감춰지며 브라우저의 업데이트나 새 버전 발표에 맞춰 Atlas 지원도 쉽게 확장될 수 있습니다. 요청하는 브라우저의 종류에 따라 호환성 계층의 브라우저별 부분 중 어떤 부분이 사용될지 자동으로 결정되며 추상화 계층에 상위 수준의 코드가 미리 작성되어 있으므로 브라우저 구현에 따라 다르게 코딩할 필요가 없습니다.
호환성 계층의 최상단에는 핵심 형식 시스템이 있습니다. 형식 시스템은 JavaScript에서 개체 지향 방식을 사용하기 위한 것으로, JavaScript 개발자는 이 시스템을 사용하여 네임스페이스를 만들고 이 네임스페이스에 클래스를 추가할 수 있으며 개체 상속도 시뮬레이트할 수 있습니다. 또한 인터페이스, 대리자, 열거형을 지원하므로 C# 같은 개체 지향 프로그래밍 언어를 사용하는 서버에서의 코드 개발과 JavaScript를 사용하는 클라이언트 코드 작성 간의 전환이 수월합니다.
형식 시스템을 기반으로 구축된 기본 클래스 라이브러리 계층은 클라이언트 스크립트 라이브러리의 핵심을 완성합니다. 개념 자체가 .NET Framework를 모방한 것이므로 일부 형식은 이미 사용자에게 친숙할 것입니다. 예를 들어 JavaScript에서 자연스럽게 이벤트를 멀티캐스팅할 수 있도록 지원하는 Event 개체가 있으며 StringBuilder 개체도 있습니다. 또한 JSON과 XML 데이터에 대한 지원뿐 아니라 개체의 직렬화(serialization)도 지원합니다. 기본 클래스 라이브러리에는 브라우저의 XMLHttpRequest 개체에 대한 추상화를 제공하는 WebRequest와 WebResponse 클래스도 있습니다. 이는 .NET Framework의 System.Net 네임스페이스에 있는 개체와 상당히 유사합니다. 그림 6의 코드는 Atlas 스크립트 핵심을 사용하여 JavaScript로 두 개의 간단한 형식을 만드는 방법을 보여 줍니다. 첫 번째 형식인 Movie는 영화의 제목과 장르를 보여주는 두 속성, 그리고 결과를 문자열로 반환하는 toString 메서드를 지원합니다. 두 번째 형식인 Drama는 Movie 형식을 확장하고 toString 메서드를 다시 정의합니다.
Movie와 Drama 형식을 사용하는 페이지는 그림 7에서 볼 수 있습니다. 이 페이지는 먼저 형식이 정의되어 있는 .js 파일을 참조합니다. 이 파일은 Atlas ScriptManager 컨트롤에 포함되어 있습니다. 그런 다음 Click 처리기에서 Movie와 Drama 형식의 인스턴스를 만들고 이 인스턴스의 toString 메서드를 호출합니다. 사용되는 코드는 동적 JavaScript이지만 상속 동작은 개체 지향 프로그래밍 언어를 사용할 때와 동일합니다. 현재 발표된 Atlas의 또 다른 장점은 클라이언트 스크립트 라이브러리의 디버그 버전이 포함되어 디버깅과 문제 해결이 더 쉽다는 것입니다. JavaScript 디버깅은 항상 번거로운 작업이었으므로 이러한 지원은 많은 수고를 덜어줄 수 있습니다.
AppDomain은 관리 코드의 하위 프로세스를 격리합니다. 이것은 각 AppDomain에 고유의 상태 집합이 있다는 의미입니다. 하나의 AppDomain에 있는 확인할 수 있는 코드는 호스팅 환경에서 작성된 인터페이스가 상호 작용을 허용하지 않는한 다른 AppDomain에 있는 코드나 데이터를 손상시키지 않습니다. 이것이 어떤 방식으로 작동하는지 알아 봅시다. C# 및 Visual Basic .NET 컴파일러에서 기본적으로 작성되는 형식이 안전한 확인할 수 있는 코드는 어떤 방법으로도 메모리에 액세스할 수 없습니다. 각 명령은 형식이 안전한 방식으로 메모리에 액세스하도록 확인 규칙 집합을 사용하여 런타임으로 검사됩니다. 따라서 런타임 검사을 통해 확인할 수 있는 코드를 실행할 때 AppDomain의 격리를 보장하며 확인할 수 없는 코드의 실행을 막을 수 잇습니다.
호스트는 이러한 격리를 통해 코드를 동일한 프로세스에서 다른 신뢰 수준으로 안전하게 실행할 수 있습니다. 낮은 신뢰 코드는 신뢰할 수 있는 호스트 코드 또는 다른 낮은 신뢰 코드와는 별도로 AppDomain에서 실행할 수 있습니다. 낮은 신뢰 코드의 호스트에 필요한 AppDomain 수는 해당 호스트의 격리 의미에 따라 다릅니다. 예를 들어 Internet Explorer는 관리 컨트롤에 대해 사이트당 하나의 AppDomain을 작성합니다. 한 사이트의 컨트롤은 동일한 AppDomain에서 상호 작용할 수 있지만 다른 사이트의 컨트롤을 간섭하거나 악의적으로 이용할 수 없습니다.
Atlas 아키텍처의 클라이언트 스크립트 핵심을 구성하는 계층 위에는 구성 요소 모델과 컨트롤 계층이 있습니다. 스크립트 라이브러리의 이 계층은 하부의 스크립트 핵심을 기반으로 구축되지만 클라이언트에서는 별도로 렌더링됩니다. 스크립트를 작성할 때 구성 요소 계층을 포함하지 않고 JavaScript 형식 시스템과 기본 클래스 라이브러리를 직접 사용할 수도 있지만, 이렇게 하면 Atlas에서 제공되는 클라이언트 구성 요소에 액세스할 수 없으며 브라우저로 보내지는 페이지 마크업에 포함되는 새로운 선언적 요소 집합인 xml-script를 사용할 수 없게 됩니다. xml-script 요소는 다음과 같은 새로운 형식 값을 사용하는 스크립트 태그 안에 포함됩니다.
<script type="text/xml-script">
마크업에서 추가적인 요소 집합을 사용하기 위한 가장 기본적인 방법은 스크립트 태그를 사용하는 것입니다. 브라우저는 스크립트 요소를 인식하지만 text/xml-script 형식을 처리할 수는 없습니다. 스크립트 태그 자체에 포함된 요소는 Atlas 스크립트 라이브러리에서 처리될 수 있으며 마크업은 클라이언트 스크립트 라이브러리의 구성 요소 계층에서 처리됩니다. xml-script는 클라이언트에서 구문 분석되어 구성 요소와 컨트롤의 인스턴스를 만듭니다. xml-script에는 정의하는 구성 요소와 컨트롤에 대한 속성 설정이 포함될 수 있으며 페이지의 다른 부분에 있는 HTML 요소와의 바인딩도 선언될 수 있습니다. 또한 xml-script 요소에서 웹 서비스 리소스를 선언한 다음 마크업의 다른 부분에서 이를 데이터 소스로 참조할 수도 있습니다. 그림 8의 예제 페이지는 xml-script를 사용하여 마우스 포인터로 연도를 가리킬 때 해당 연도와 관련된 영화 제목이 팝업 요소로 표시되도록 선언적으로 설정하고 있습니다.
그림 8의 페이지에서는 연도를 표시하기 위해 DIV 요소를, 영화 제목을 표시하기 위해 SPAN 요소를 사용하지만 숨겨지도록 선언했습니다. xml-script의 popupBehavior는 영화 제목과 연결되어 있으며 해당 연도와 연결된 hoverBehavior에 의해 실행됩니다. popupBehavior에 대한 코드는 Atlas 스크립트 라이브러리의 컨트롤과 구성 요소 계층에 있습니다. xml-script는 일반적으로 페이지에 포함되는 JavaScript에 비해 더 쉽게 분석할 수 있으며, 특히 다수의 브라우저 구현을 처리하기 위해 코드에서 팩터링을 시작할 때 이러한 특징이 두드러지게 나타납니다. 그림 8의 xml-script와 같은 선언적 구문은 개발 도구를 사용하여 쉽게 만들고 사용할 수 있으며 풍부한 기능의 사용자 환경을 가능하게 하는 xml-script는 페이지가 실행될 때 Atlas 서버 컨트롤에 의해 만들어집니다. Atlas 응용 프로그램에서 사용되는 대다수의 xml-script는 .aspx 파일에 전혀 존재하지 않으며 개발자가 직접 코딩할 필요도 거의 없습니다.
Atlas CTP에서 제공하는 다양한 동작은 사용자 환경을 개선하는 데 사용됩니다. 진행률 동작은 백그라운드에 보류되어 있는 작업에 대한 정보를 제공할 수 있으며 클릭, 가리키기, 팝업 동작은 다양한 사용자 상호 작용을 가능하게 합니다. 이러한 동작은 xml-script를 사용하여 선언적 방식으로 페이지의 HTML 요소와 쉽게 연결할 수 있습니다. 동작 자체는 JavaScript로 구현되기 때문에 더 복잡한 동작도 가능하지만, 페이지에서 동작을 사용할 때는 xml-script를 사용할 수 있습니다.
Atlas CTP에 포함된 서버 컨트롤을 사용하면 페이지를 다시 게시할 때 일시적으로 상호 작용이 중단되는 것을 방지할 수 있습니다. 즉, 서버 컨트롤이 백그라운드에서 렌더링을 업데이트하는 동안 사용자는 웹 페이지와 계속 상호 작용할 수 있습니다. 이러한 기능의 주역은 함께 작동하는 두 개의 서버 컨트롤입니다. 이러한 두 서버 컨트롤을 기존 페이지에 추가하면 상당한 기능 향상을 얻을 수 있습니다. 첫 번째 컨트롤인 ScriptManager 컨트롤은 클라이언트에서 서버로의 다시 게시 동작을 변경하며 두 번째 컨트롤인 UpdatePanel 컨트롤은 변경 작업을 위해 서버에서 페이지의 수명 주기를 관리합니다.
ScriptManager 컨트롤은 Atlas 기능을 사용할 모든 페이지에 포함되어야 합니다. 이 컨트롤은 클라이언트로 보내는 JavaScript를 제어하는 역할을 하는데, 서버 컨트롤은 클라이언트에 JavaScript를 제공할 수 있으며 이 동작을 제어하기 위해 ScriptManager 컨트롤을 이용합니다. ScriptManager 컨트롤은 구현을 위해 새로운 IScriptComponent 인터페이스를 이용하며 xml-script 요소와 연결되는 구성 요소 스크립트 라이브러리도 지원합니다.
다음과 같이 ScriptManager 컨트롤의 EnablePartialRendering 속성을 true로 설정하면 클라이언트에서 서버로의 다시 게시 동작이 새롭게 변경됩니다.
<atlas:ScriptManager EnablePartialRendering="true" runat="server" />
이 코드를 통해 사용자 환경을 중단하지 않고도 서버로 다시 게시를 요청할 수 있게 됩니다. 요청 간의 제어 정보를 보존하는 데 필요한 보기 상태 정보는 부분적 렌더링 요청을 위해 유지됩니다. 그리고 새로 고쳐지거나 수정되는 부분에 대한 HTML은 브라우저 DOM(Document Object Model)과 상호 작용하는 JavaScript에서 업데이트됩니다. ASP.NET 페이지에서 부분 업데이트를 허용해야 하는 영역은 UpdatePanel 컨트롤을 사용하여 지정됩니다.
UpdatePanel 컨트롤은 페이지의 나머지 부분과는 별개로 업데이트되어야 하는 영역을 ScriptManager 컨트롤에 알리는 역할을 합니다. 사용자가 브라우저에서 수행한 작업으로 인해 페이지의 일부를 다시 게시해야 할 경우, 폼 데이터가 게시되고 페이지 수명 주기가 서버에서 실행되기 시작합니다. 이때 스크립트는 백그라운드에서 비동기식으로 다시 게시 작업을 초기화하므로 페이지가 계속 사용자에게 표시됩니다. 서버에서는 클라이언트에서 게시한 보기 상태 데이터를 바탕으로 컨트롤 상태를 복원합니다. 렌더링 단계에 접어들면 ScriptManager 컨트롤은 UpdatePanel 영역을 새로 고쳐 브라우저로 돌려 보내기 위해 이 영역에 대한 렌더링을 격리시킵니다. 또한 페이지의 보기 상태 데이터가 수집되고 응답의 일부로 HTML이 전송됩니다. 그런 다음 브라우저의 스크립트에서 UpdatePanel의 이전 내용을 해당하는 새 HTML로 바꿉니다.
UpdatePanel 컨트롤에는 다음과 같이 Triggers와 ContentTemplate에 대한 요소가 포함될 수 있습니다.
<atlas:UpdatePanel ID="UpdatePanel1" runat="server"> <Triggers> ... </Triggers> <ContentTemplate> ... </ContentTemplate> </atlas:UpdatePanel>
ContentTemplate 내의 영역은 ScriptManager 컨트롤이 비동기식으로 요청된 다시 게시 작업을 처리할 때 새로 고쳐집니다. Triggers 요소에는 ControlEventTrigger 및 ControlValueTrigger 요소가 포함될 수 있으며, 웹 페이지 개발자는 Triggers 요소를 사용하여 어떤 내용이 변경되었을 때 영역이 업데이트될 것인지 지정할 수 있습니다. 이렇게 하면 UpdatePanel 컨트롤에 직접 포함되지 않은 외부 컨트롤에 의해서도 변경이 이루어지도록 할 수 있습니다. 또한 간단한 선언을 사용하여 페이지의 동작과 UpdatePanel 컨트롤을 제어하고 가져온 새 데이터를 표시할 수 있습니다.
한 페이지에 여러 개의 UpdatePanel 컨트롤을 배치하고, 각 컨트롤을 업데이트하도록 하는 여러 Triggers를 추가할 수 있습니다. UpdatePanel 컨트롤의 내용은 특정 사용자 입력에 응답하는 데 필요한 최소한의 범위로 축소할 수 있습니다. UpdatePanel 컨트롤을 사용하면 기존의 ASP.NET 페이지를 크게 변경하지 않고도 사용자의 입력에 보다 빠르게 응답하도록 만들 수 있습니다.
웹 응용 프로그램은 서비스 지향 아키텍처를 중심으로 만들어집니다. 대화형 응용 프로그램을 가능하게 하는 핵심은 브라우저에서 서비스에 액세스할 수 있는 기능으로, Atlas에서 제공하는 서비스에는 두 가지 종류가 있습니다. ScriptManager 컨트롤은 다음과 같이 웹 서비스 참조를 위해 자동으로 생성되는 프록시를 사용합니다.
<atlas:ScriptManager EnablePartialRendering="true" runat="server"> <Services> <atlas:ServiceReference GenerateProxy="true" Path="~/nominees.aspx" Type="Custom" </Services> </atlas:ScriptManager>
이렇게 하면 클라이언트 쪽 구성 요소가 스크립트에서 웹 서비스를 직접 호출할 수 있습니다. 웹 서비스는 컨트롤에 바인딩되어 더욱 풍부한 동작을 지원합니다. 예를 들어 웹 서비스를 사용하여 관련된 정보를 검색하도록 xml-script에서 AutoCompleteBehavior를 정의할 수 있습니다(그림 9 참조).
동작은 페이지의 요소에 연결되어 해당 요소의 동작을 확장하며 .aspx 마크업에 설정되면 extender로 참조됩니다. AutoCompleteBehavior는 AutoCompleteExtender 컨트롤을 통해 요소와 연결될 수 있습니다. xml-script를 직접 작성하는 대신 extender가 서버의 컨트롤과 연결됩니다. 그런 다음 적절한 xml-script를 렌더링하여 클라이언트 쪽 동작을 가져옴으로써 컨트롤 동작이 확장됩니다. 웹 서비스를 호출할 때는 호출과 반환에 대한 결과가 대개 XML로 전달됩니다. 그러나 Atlas는 웹 서비스의 데이터를 JSON 형식으로 serialize할 수 있는 기능도 제공하므로 이를 통해 XML 사용으로 인한 일부 오버헤드를 줄일 수 있습니다. JSON 데이터는 브라우저에서 JavaScript 개체로 직접 deserialize될 수 있습니다. 이 밖에도 Atlas는 서버에서 사용되는 보다 복잡한 .NET의 관리되는 형식을 브라우저에서 JavaScript 개체로 serialize할 수 있는 기능을 지원하므로 브라우저에서 웹 서비스에 액세스하는 작업이 간단해집니다.
브라우저에서 사용할 수 있는 웹 서비스는 응용 프로그램의 일부인 사용자 지정 웹 서비스뿐만 아니라 ASP.NET 응용 프로그램 서비스까지 다양합니다. Atlas는 다음과 같이 JavaScript에서 폼 인증 서비스를 직접 사용할 수 있는 기능도 제공합니다.
Sys.Services.AuthenticationService.login( username, password, completionFunction);
사용자가 로그인 자격 증명을 제공할 때 HTML이 동적으로 변경될 수 있으므로 사용자는 로그인 페이지로 리디렉션된 후 원래 페이지로 되돌아가지 않아도 됩니다. .aspx 페이지에서 사용되는 프로필 데이터는 웹 서비스 호출을 통해서도 사용할 수 있으며 JavaScript 개체를 통해 서버에 저장된 프로필 데이터를 저장하거나 가져오는 기능도 제공됩니다.
응용 프로그램에서 사용하는 웹 서비스가 언제나 같은 호스트 서버에 있는 것은 아닙니다. 호스트 서버뿐 아니라 실제로 웹 서비스가 같은 도메인에 있어야 한다는 규칙도 없습니다. 그러나 브라우저는 페이지의 원래 출처가 아닌 도메인에 대해서는 XmlHttpRequest를 사용하는 호출을 차단합니다. 자식 요청을 시작하는 숨겨진 IFrame 개체를 사용하여 이 제한을 피하는 몇 가지 교묘한 방법이 있기는 하지만 실행하기가 상당히 번거롭습니다. Atlas에서는 웹 서비스 브리징을 제공하여 이 문제를 해결합니다. 웹 서비스 브리징을 사용하면 클라이언트가 다른 도메인으로 보내는 웹 서비스 호출을 시작할 수 있습니다. 이 호출은 Atlas 응용 프로그램으로 보내지며, 여기서 대상 서버로 요청을 프록시한 후 serialize하여 클라이언트로 결과를 돌려 보냅니다. 물론 Atlas는 IFrame 기술을 사용하여 다른 도메인과 직접 통신하는 기능도 제공합니다.
Atlas는 풍부한 기능을 갖춘 웹 응용 프로그램을 만들기 위한 다양한 기능을 제공합니다. 클라이언트 스크립트 라이브러리는 JavaScript를 작성하는 작업을 간소화하며 JavaScript를 작성할 때 개체 지향적 접근 방식을 사용하기 위한 구문을 제공합니다. 웹 서비스 기능을 사용하면 원격 및 로컬 서비스에 쉽게 액세스할 수 있습니다. 또한 복잡한 형식을 serialize하여 클라이언트와 서버에서 다양한 형식을 쉽게 이용할 수 있습니다. 서버 컨트롤 역시 클라이언트 스크립트 라이브러리를 이용하며 이를 통해 기존 응용 프로그램과 새 응용 프로그램에서 일반적으로 발생하는 '사용 대기를 위한 일시 중지'를 상당히 줄일 수 있습니다.
CTP 빌드는 업데이트, 변경, 새 기능 추가 등이 이루어져 거의 두 달에 한 번씩 새로 발표되고 있습니다. 궁극적으로 Atlas는 Visual Studio의 디자인 환경에서 사용할 수 있는 기능을 포함하여 다음 릴리스의 .NET Framework에 통합될 예정입니다. 근래에 Microsoft는 현재 사용 중인 사이트에 Atlas를 배포하여 웹 응용 프로그램에서 이용할 수 있는, 사용권이 제한된 버전의 Atlas를 발표했습니다. 자세한 내용을 보거나 최신 Atlas CTP를 다운로드하려면 atlas.asp.net (영문)을 참조하십시오.
Taeyo's ASP.NET
| ||||||||||
|
Point start_point = ...
IntPtr lParamStartPoint = (IntPtr) BND.Windows.Helper.MakeParam(start_point.X, start_point.Y);
// 1. 마우스 이동
System.Windows.Forms.Message mouse_move_to_start = System.Windows.Forms.Message.Create(target_window, BND.Windows.Messages.WM_MOUSEMOVE, (IntPtr) BND.Windows.MouseMasks.MK_NULL, lParamStartPoint);
BND.Windows.User32.PostMessage(mouse_move_to_start);
// 2. 마우스 누름
System.Windows.Forms.Message mouse_down = System.Windows.Forms.Message.Create(target_window, BND.Windows.Messages.WM_LBUTTONDOWN, (IntPtr) BND.Windows.MouseMasks.MK_NULL, lParamStartPoint);
BND.Windows.User32.PostMessage(mouse_down);
-------------------------
//
// MakeParam
//
public static int MakeParam(int LoWord, int HiWord)
{
return (((HiWord & 0x000000FF) << 16) | (LoWord & 0x000000FF));
}
-------------------------
[DllImport("User32")] public extern static bool PostMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
public static bool PostMessage(System.Windows.Forms.Message m)
{
return PostMessage(m.HWnd, m.Msg, m.WParam, m.LParam);
}
--------------------------
public class Messages
{
public const int WM_NULL = 0x0000;
public const int WM_CREATE = 0x0001;
public const int WM_DESTROY = 0x0002;
public const int WM_MOVE = 0x0003;
public const int WM_SIZE = 0x0005;
public const int WM_ACTIVATE = 0x0006;
public const int WM_SETFOCUS = 0x0007;
public const int WM_KILLFOCUS = 0x0008;
public const int WM_ENABLE = 0x000A;
public const int WM_SETREDRAW = 0x000B;
public const int WM_SETTEXT = 0x000C;
public const int WM_GETTEXT = 0x000D;
public const int WM_GETTEXTLENGTH = 0x000E;
public const int WM_PAINT = 0x000F;
public const int WM_CLOSE = 0x0010;
public const int WM_QUERYENDSESSION = 0x0011;
public const int WM_QUERYOPEN = 0x0013;
public const int WM_ENDSESSION = 0x0016;
public const int WM_QUIT = 0x0012;
public const int WM_ERASEBKGND = 0x0014;
public const int WM_SYSCOLORCHANGE = 0x0015;
public const int WM_SHOWWINDOW = 0x0018;
public const int WM_WININICHANGE = 0x001A;
public const int WM_SETTINGCHANGE = 0x001A;
public const int WM_DEVMODECHANGE = 0x001B;
public const int WM_ACTIVATEAPP = 0x001C;
public const int WM_FONTCHANGE = 0x001D;
public const int WM_TIMECHANGE = 0x001E;
public const int WM_CANCELMODE = 0x001F;
public const int WM_SETCURSOR = 0x0020;
public const int WM_MOUSEACTIVATE = 0x0021;
public const int WM_CHILDACTIVATE = 0x0022;
public const int WM_QUEUESYNC = 0x0023;
public const int WM_GETMINMAXINFO = 0x0024;
public const int WM_PAINTICON = 0x0026;
public const int WM_ICONERASEBKGND = 0x0027;
public const int WM_NEXTDLGCTL = 0x0028;
public const int WM_SPOOLERSTATUS = 0x002A;
public const int WM_DRAWITEM = 0x002B;
public const int WM_MEASUREITEM = 0x002C;
public const int WM_DELETEITEM = 0x002D;
public const int WM_VKEYTOITEM = 0x002E;
public const int WM_CHARTOITEM = 0x002F;
public const int WM_SETFONT = 0x0030;
public const int WM_GETFONT = 0x0031;
public const int WM_SETHOTKEY = 0x0032;
public const int WM_GETHOTKEY = 0x0033;
public const int WM_QUERYDRAGICON = 0x0037;
public const int WM_COMPAREITEM = 0x0039;
public const int WM_GETOBJECT = 0x003D;
public const int WM_COMPACTING = 0x0041;
public const int WM_COMMNOTIFY = 0x0044;
public const int WM_WINDOWPOSCHANGING = 0x0046;
public const int WM_WINDOWPOSCHANGED = 0x0047;
public const int WM_POWER = 0x0048;
public const int WM_COPYDATA = 0x004A;
public const int WM_CANCELJOURNAL = 0x004B;
public const int WM_NOTIFY = 0x004E;
public const int WM_INPUTLANGCHANGEREQUEST = 0x0050;
public const int WM_INPUTLANGCHANGE = 0x0051;
public const int WM_TCARD = 0x0052;
public const int WM_HELP = 0x0053;
public const int WM_USERCHANGED = 0x0054;
public const int WM_NOTIFYFORMAT = 0x0055;
public const int WM_CONTEXTMENU = 0x007B;
public const int WM_STYLECHANGING = 0x007C;
public const int WM_STYLECHANGED = 0x007D;
public const int WM_DISPLAYCHANGE = 0x007E;
public const int WM_GETICON = 0x007F;
public const int WM_SETICON = 0x0080;
public const int WM_NCCREATE = 0x0081;
public const int WM_NCDESTROY = 0x0082;
public const int WM_NCCALCSIZE = 0x0083;
public const int WM_NCHITTEST = 0x0084;
public const int WM_NCPAINT = 0x0085;
public const int WM_NCACTIVATE = 0x0086;
public const int WM_GETDLGCODE = 0x0087;
public const int WM_SYNCPAINT = 0x0088;
public const int WM_NCMOUSEMOVE = 0x00A0;
public const int WM_NCLBUTTONDOWN = 0x00A1;
public const int WM_NCLBUTTONUP = 0x00A2;
public const int WM_NCLBUTTONDBLCLK = 0x00A3;
public const int WM_NCRBUTTONDOWN = 0x00A4;
public const int WM_NCRBUTTONUP = 0x00A5;
public const int WM_NCRBUTTONDBLCLK = 0x00A6;
public const int WM_NCMBUTTONDOWN = 0x00A7;
public const int WM_NCMBUTTONUP = 0x00A8;
public const int WM_NCMBUTTONDBLCLK = 0x00A9;
public const int WM_NCXBUTTONDOWN = 0x00AB;
public const int WM_NCXBUTTONUP = 0x00AC;
public const int WM_NCXBUTTONDBLCLK = 0x00AD;
public const int WM_INPUT = 0x00FF;
public const int WM_KEYFIRST = 0x0100;
public const int WM_KEYDOWN = 0x0100;
public const int WM_KEYUP = 0x0101;
public const int WM_CHAR = 0x0102;
public const int WM_DEADCHAR = 0x0103;
public const int WM_SYSKEYDOWN = 0x0104;
public const int WM_SYSKEYUP = 0x0105;
public const int WM_SYSCHAR = 0x0106;
public const int WM_SYSDEADCHAR = 0x0107;
public const int WM_UNICHAR = 0x0109;
public const int WM_KEYLAST_NT501 = 0x0109;
public const int UNICODE_NOCHAR = 0xFFFF;
public const int WM_KEYLAST_PRE501 = 0x0108;
public const int WM_IME_STARTCOMPOSITION = 0x010D;
public const int WM_IME_ENDCOMPOSITION = 0x010E;
public const int WM_IME_COMPOSITION = 0x010F;
public const int WM_IME_KEYLAST = 0x010F;
public const int WM_INITDIALOG = 0x0110;
public const int WM_COMMAND = 0x0111;
public const int WM_SYSCOMMAND = 0x0112;
public const int WM_TIMER = 0x0113;
public const int WM_HSCROLL = 0x0114;
public const int WM_VSCROLL = 0x0115;
public const int WM_INITMENU = 0x0116;
public const int WM_INITMENUPOPUP = 0x0117;
public const int WM_MENUSELECT = 0x011F;
public const int WM_MENUCHAR = 0x0120;
public const int WM_ENTERIDLE = 0x0121;
public const int WM_MENURBUTTONUP = 0x0122;
public const int WM_MENUDRAG = 0x0123;
public const int WM_MENUGETOBJECT = 0x0124;
public const int WM_UNINITMENUPOPUP = 0x0125;
public const int WM_MENUCOMMAND = 0x0126;
public const int WM_CHANGEUISTATE = 0x0127;
public const int WM_UPDATEUISTATE = 0x0128;
public const int WM_QUERYUISTATE = 0x0129;
public const int WM_CTLCOLORMSGBOX = 0x0132;
public const int WM_CTLCOLOREDIT = 0x0133;
public const int WM_CTLCOLORLISTBOX = 0x0134;
public const int WM_CTLCOLORBTN = 0x0135;
public const int WM_CTLCOLORDLG = 0x0136;
public const int WM_CTLCOLORSCROLLBAR = 0x0137;
public const int WM_CTLCOLORSTATIC = 0x0138;
public const int WM_MOUSEFIRST = 0x0200;
public const int WM_MOUSEMOVE = 0x0200;
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public const int WM_LBUTTONDBLCLK = 0x0203;
public const int WM_RBUTTONDOWN = 0x0204;
public const int WM_RBUTTONUP = 0x0205;
public const int WM_RBUTTONDBLCLK = 0x0206;
public const int WM_MBUTTONDOWN = 0x0207;
public const int WM_MBUTTONUP = 0x0208;
public const int WM_MBUTTONDBLCLK = 0x0209;
public const int WM_MOUSEWHEEL = 0x020A;
public const int WM_XBUTTONDOWN = 0x020B;
public const int WM_XBUTTONUP = 0x020C;
public const int WM_XBUTTONDBLCLK = 0x020D;
public const int WM_MOUSELAST_5 = 0x020D;
public const int WM_MOUSELAST_4 = 0x020A;
public const int WM_MOUSELAST_PRE_4 = 0x0209;
public const int WM_PARENTNOTIFY = 0x0210;
public const int WM_ENTERMENULOOP = 0x0211;
public const int WM_EXITMENULOOP = 0x0212;
public const int WM_NEXTMENU = 0x0213;
public const int WM_SIZING = 0x0214;
public const int WM_CAPTURECHANGED = 0x0215;
public const int WM_MOVING = 0x0216;
public const int WM_POWERBROADCAST = 0x0218;
public const int WM_DEVICECHANGE = 0x0219;
public const int WM_MDICREATE = 0x0220;
public const int WM_MDIDESTROY = 0x0221;
public const int WM_MDIACTIVATE = 0x0222;
public const int WM_MDIRESTORE = 0x0223;
public const int WM_MDINEXT = 0x0224;
public const int WM_MDIMAXIMIZE = 0x0225;
public const int WM_MDITILE = 0x0226;
public const int WM_MDICASCADE = 0x0227;
public const int WM_MDIICONARRANGE = 0x0228;
public const int WM_MDIGETACTIVE = 0x0229;
public const int WM_MDISETMENU = 0x0230;
public const int WM_ENTERSIZEMOVE = 0x0231;
public const int WM_EXITSIZEMOVE = 0x0232;
public const int WM_DROPFILES = 0x0233;
public const int WM_MDIREFRESHMENU = 0x0234;
public const int WM_IME_SETCONTEXT = 0x0281;
public const int WM_IME_NOTIFY = 0x0282;
public const int WM_IME_CONTROL = 0x0283;
public const int WM_IME_COMPOSITIONFULL = 0x0284;
public const int WM_IME_SELECT = 0x0285;
public const int WM_IME_CHAR = 0x0286;
public const int WM_IME_REQUEST = 0x0288;
public const int WM_IME_KEYDOWN = 0x0290;
public const int WM_IME_KEYUP = 0x0291;
public const int WM_MOUSEHOVER = 0x02A1;
public const int WM_MOUSELEAVE = 0x02A3;
public const int WM_NCMOUSEHOVER = 0x02A0;
public const int WM_NCMOUSELEAVE = 0x02A2;
public const int WM_WTSSESSION_CHANGE = 0x02B1;
public const int WM_TABLET_FIRST = 0x02c0;
public const int WM_TABLET_LAST = 0x02df;
public const int WM_CUT = 0x0300;
public const int WM_COPY = 0x0301;
public const int WM_PASTE = 0x0302;
public const int WM_CLEAR = 0x0303;
public const int WM_UNDO = 0x0304;
public const int WM_RENDERFORMAT = 0x0305;
public const int WM_RENDERALLFORMATS = 0x0306;
public const int WM_DESTROYCLIPBOARD = 0x0307;
public const int WM_DRAWCLIPBOARD = 0x0308;
public const int WM_PAINTCLIPBOARD = 0x0309;
public const int WM_VSCROLLCLIPBOARD = 0x030A;
public const int WM_SIZECLIPBOARD = 0x030B;
public const int WM_ASKCBFORMATNAME = 0x030C;
public const int WM_CHANGECBCHAIN = 0x030D;
public const int WM_HSCROLLCLIPBOARD = 0x030E;
public const int WM_QUERYNEWPALETTE = 0x030F;
public const int WM_PALETTEISCHANGING = 0x0310;
public const int WM_PALETTECHANGED = 0x0311;
public const int WM_HOTKEY = 0x0312;
public const int WM_PRINT = 0x0317;
public const int WM_PRINTCLIENT = 0x0318;
public const int WM_APPCOMMAND = 0x0319;
public const int WM_THEMECHANGED = 0x031A;
public const int WM_HANDHELDFIRST = 0x0358;
public const int WM_HANDHELDLAST = 0x035F;
public const int WM_AFXFIRST = 0x0360;
public const int WM_AFXLAST = 0x037F;
public const int WM_PENWINFIRST = 0x0380;
public const int WM_PENWINLAST = 0x038F;
public const int WM_APP = 0x8000;
public const int WM_USER = 0x0400;
public const int EM_GETSEL = 0x00B0;
public const int EM_SETSEL = 0x00B1;
public const int EM_GETRECT = 0x00B2;
public const int EM_SETRECT = 0x00B3;
public const int EM_SETRECTNP = 0x00B4;
public const int EM_SCROLL = 0x00B5;
public const int EM_LINESCROLL = 0x00B6;
public const int EM_SCROLLCARET = 0x00B7;
public const int EM_GETMODIFY = 0x00B8;
public const int EM_SETMODIFY = 0x00B9;
public const int EM_GETLINECOUNT = 0x00BA;
public const int EM_LINEINDEX = 0x00BB;
public const int EM_SETHANDLE = 0x00BC;
public const int EM_GETHANDLE = 0x00BD;
public const int EM_GETTHUMB = 0x00BE;
public const int EM_LINELENGTH = 0x00C1;
public const int EM_REPLACESEL = 0x00C2;
public const int EM_GETLINE = 0x00C4;
public const int EM_LIMITTEXT = 0x00C5;
public const int EM_CANUNDO = 0x00C6;
public const int EM_UNDO = 0x00C7;
public const int EM_FMTLINES = 0x00C8;
public const int EM_LINEFROMCHAR = 0x00C9;
public const int EM_SETTABSTOPS = 0x00CB;
public const int EM_SETPASSWORDCHAR = 0x00CC;
public const int EM_EMPTYUNDOBUFFER = 0x00CD;
public const int EM_GETFIRSTVISIBLELINE = 0x00CE;
public const int EM_SETREADONLY = 0x00CF;
public const int EM_SETWORDBREAKPROC = 0x00D0;
public const int EM_GETWORDBREAKPROC = 0x00D1;
public const int EM_GETPASSWORDCHAR = 0x00D2;
public const int EM_SETMARGINS = 0x00D3;
public const int EM_GETMARGINS = 0x00D4;
public const int EM_SETLIMITTEXT = EM_LIMITTEXT;
public const int EM_GETLIMITTEXT = 0x00D5;
public const int EM_POSFROMCHAR = 0x00D6;
public const int EM_CHARFROMPOS = 0x00D7;
public const int EM_SETIMESTATUS = 0x00D8;
public const int EM_GETIMESTATUS = 0x00D9;
public const int BM_GETCHECK= 0x00F0;
public const int BM_SETCHECK= 0x00F1;
public const int BM_GETSTATE= 0x00F2;
public const int BM_SETSTATE= 0x00F3;
public const int BM_SETSTYLE= 0x00F4;
public const int BM_CLICK = 0x00F5;
public const int BM_GETIMAGE= 0x00F6;
public const int BM_SETIMAGE= 0x00F7;
public const int STM_SETICON = 0x0170;
public const int STM_GETICON = 0x0171;
public const int STM_SETIMAGE = 0x0172;
public const int STM_GETIMAGE = 0x0173;
public const int STM_MSGMAX = 0x0174;
public const int DM_GETDEFID = (WM_USER+0);
public const int DM_SETDEFID = (WM_USER+1);
public const int DM_REPOSITION = (WM_USER+2);
public const int LB_ADDSTRING = 0x0180;
public const int LB_INSERTSTRING = 0x0181;
public const int LB_DELETESTRING = 0x0182;
public const int LB_SELITEMRANGEEX= 0x0183;
public const int LB_RESETCONTENT = 0x0184;
public const int LB_SETSEL = 0x0185;
public const int LB_SETCURSEL = 0x0186;
public const int LB_GETSEL = 0x0187;
public const int LB_GETCURSEL = 0x0188;
public const int LB_GETTEXT = 0x0189;
public const int LB_GETTEXTLEN = 0x018A;
public const int LB_GETCOUNT = 0x018B;
public const int LB_SELECTSTRING = 0x018C;
public const int LB_DIR = 0x018D;
public const int LB_GETTOPINDEX = 0x018E;
public const int LB_FINDSTRING = 0x018F;
public const int LB_GETSELCOUNT = 0x0190;
public const int LB_GETSELITEMS = 0x0191;
public const int LB_SETTABSTOPS = 0x0192;
public const int LB_GETHORIZONTALEXTENT = 0x0193;
public const int LB_SETHORIZONTALEXTENT = 0x0194;
public const int LB_SETCOLUMNWIDTH = 0x0195;
public const int LB_ADDFILE = 0x0196;
public const int LB_SETTOPINDEX = 0x0197;
public const int LB_GETITEMRECT = 0x0198;
public const int LB_GETITEMDATA = 0x0199;
public const int LB_SETITEMDATA = 0x019A;
public const int LB_SELITEMRANGE = 0x019B;
public const int LB_SETANCHORINDEX = 0x019C;
public const int LB_GETANCHORINDEX = 0x019D;
public const int LB_SETCARETINDEX = 0x019E;
public const int LB_GETCARETINDEX = 0x019F;
public const int LB_SETITEMHEIGHT = 0x01A0;
public const int LB_GETITEMHEIGHT = 0x01A1;
public const int LB_FINDSTRINGEXACT = 0x01A2;
public const int LB_SETLOCALE = 0x01A5;
public const int LB_GETLOCALE = 0x01A6;
public const int LB_SETCOUNT = 0x01A7;
public const int LB_INITSTORAGE = 0x01A8;
public const int LB_ITEMFROMPOINT = 0x01A9;
public const int LB_MULTIPLEADDSTRING = 0x01B1;
public const int LB_GETLISTBOXINFO= 0x01B2;
public const int LB_MSGMAX_501 = 0x01B3;
public const int LB_MSGMAX_WCE4 = 0x01B1;
public const int LB_MSGMAX_4 = 0x01B0;
public const int LB_MSGMAX_PRE4 = 0x01A8;
public const int CB_GETEDITSEL = 0x0140;
public const int CB_LIMITTEXT = 0x0141;
public const int CB_SETEDITSEL = 0x0142;
public const int CB_ADDSTRING = 0x0143;
public const int CB_DELETESTRING = 0x0144;
public const int CB_DIR = 0x0145;
public const int CB_GETCOUNT = 0x0146;
public const int CB_GETCURSEL = 0x0147;
public const int CB_GETLBTEXT = 0x0148;
public const int CB_GETLBTEXTLEN = 0x0149;
public const int CB_INSERTSTRING = 0x014A;
public const int CB_RESETCONTENT = 0x014B;
public const int CB_FINDSTRING = 0x014C;
public const int CB_SELECTSTRING = 0x014D;
public const int CB_SETCURSEL = 0x014E;
public const int CB_SHOWDROPDOWN = 0x014F;
public const int CB_GETITEMDATA = 0x0150;
public const int CB_SETITEMDATA = 0x0151;
public const int CB_GETDROPPEDCONTROLRECT = 0x0152;
public const int CB_SETITEMHEIGHT = 0x0153;
public const int CB_GETITEMHEIGHT = 0x0154;
public const int CB_SETEXTENDEDUI = 0x0155;
public const int CB_GETEXTENDEDUI = 0x0156;
public const int CB_GETDROPPEDSTATE = 0x0157;
public const int CB_FINDSTRINGEXACT = 0x0158;
public const int CB_SETLOCALE = 0x0159;
public const int CB_GETLOCALE = 0x015A;
public const int CB_GETTOPINDEX = 0x015B;
public const int CB_SETTOPINDEX = 0x015C;
public const int CB_GETHORIZONTALEXTENT = 0x015d;
public const int CB_SETHORIZONTALEXTENT = 0x015e;
public const int CB_GETDROPPEDWIDTH = 0x015f;
public const int CB_SETDROPPEDWIDTH = 0x0160;
public const int CB_INITSTORAGE = 0x0161;
public const int CB_MULTIPLEADDSTRING = 0x0163;
public const int CB_GETCOMBOBOXINFO = 0x0164;
public const int CB_MSGMAX_501 = 0x0165;
public const int CB_MSGMAX_WCE400 = 0x0163;
public const int CB_MSGMAX_400 = 0x0162;
public const int CB_MSGMAX_PRE400 = 0x015B;
public const int SBM_SETPOS = 0x00E0;
public const int SBM_GETPOS = 0x00E1;
public const int SBM_SETRANGE = 0x00E2;
public const int SBM_SETRANGEREDRAW = 0x00E6;
public const int SBM_GETRANGE = 0x00E3;
public const int SBM_ENABLE_ARROWS = 0x00E4;
public const int SBM_SETSCROLLINFO = 0x00E9;
public const int SBM_GETSCROLLINFO = 0x00EA;
public const int SBM_GETSCROLLBARINFO= 0x00EB;
public const int LVM_FIRST = 0x1000;// ListView messages
public const int TV_FIRST = 0x1100;// TreeView messages
public const int HDM_FIRST = 0x1200;// Header messages
public const int TCM_FIRST = 0x1300;// Tab control messages
public const int PGM_FIRST = 0x1400;// Pager control messages
public const int ECM_FIRST = 0x1500;// Edit control messages
public const int BCM_FIRST = 0x1600;// Button control messages
public const int CBM_FIRST = 0x1700;// Combobox control messages
public const int CCM_FIRST = 0x2000;// Common control shared messages
public const int CCM_LAST =(CCM_FIRST + 0x200);
public const int CCM_SETBKCOLOR = (CCM_FIRST + 1);
public const int CCM_SETCOLORSCHEME = (CCM_FIRST + 2);
public const int CCM_GETCOLORSCHEME = (CCM_FIRST + 3);
public const int CCM_GETDROPTARGET = (CCM_FIRST + 4);
public const int CCM_SETUNICODEFORMAT = (CCM_FIRST + 5);
public const int CCM_GETUNICODEFORMAT = (CCM_FIRST + 6);
public const int CCM_SETVERSION = (CCM_FIRST + 0x7);
public const int CCM_GETVERSION = (CCM_FIRST + 0x8);
public const int CCM_SETNOTIFYWINDOW = (CCM_FIRST + 0x9);
public const int CCM_SETWINDOWTHEME = (CCM_FIRST + 0xb);
public const int CCM_DPISCALE = (CCM_FIRST + 0xc);
public const int HDM_GETITEMCOUNT = (HDM_FIRST + 0);
public const int HDM_INSERTITEMA = (HDM_FIRST + 1);
public const int HDM_INSERTITEMW = (HDM_FIRST + 10);
public const int HDM_DELETEITEM = (HDM_FIRST + 2);
public const int HDM_GETITEMA = (HDM_FIRST + 3);
public const int HDM_GETITEMW = (HDM_FIRST + 11);
public const int HDM_SETITEMA = (HDM_FIRST + 4);
public const int HDM_SETITEMW = (HDM_FIRST + 12);
public const int HDM_LAYOUT = (HDM_FIRST + 5);
public const int HDM_HITTEST = (HDM_FIRST + 6);
public const int HDM_GETITEMRECT = (HDM_FIRST + 7);
public const int HDM_SETIMAGELIST = (HDM_FIRST + 8);
public const int HDM_GETIMAGELIST = (HDM_FIRST + 9);
public const int HDM_ORDERTOINDEX = (HDM_FIRST + 15);
public const int HDM_CREATEDRAGIMAGE = (HDM_FIRST + 16);
public const int HDM_GETORDERARRAY = (HDM_FIRST + 17);
public const int HDM_SETORDERARRAY = (HDM_FIRST + 18);
public const int HDM_SETHOTDIVIDER = (HDM_FIRST + 19);
public const int HDM_SETBITMAPMARGIN = (HDM_FIRST + 20);
public const int HDM_GETBITMAPMARGIN = (HDM_FIRST + 21);
public const int HDM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int HDM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int HDM_SETFILTERCHANGETIMEOUT = (HDM_FIRST+22);
public const int HDM_EDITFILTER = (HDM_FIRST+23);
public const int HDM_CLEARFILTER = (HDM_FIRST+24);
public const int TB_ENABLEBUTTON = (WM_USER + 1);
public const int TB_CHECKBUTTON = (WM_USER + 2);
public const int TB_PRESSBUTTON = (WM_USER + 3);
public const int TB_HIDEBUTTON = (WM_USER + 4);
public const int TB_INDETERMINATE = (WM_USER + 5);
public const int TB_MARKBUTTON = (WM_USER + 6);
public const int TB_ISBUTTONENABLED = (WM_USER + 9);
public const int TB_ISBUTTONCHECKED = (WM_USER + 10);
public const int TB_ISBUTTONPRESSED = (WM_USER + 11);
public const int TB_ISBUTTONHIDDEN = (WM_USER + 12);
public const int TB_ISBUTTONINDETERMINATE = (WM_USER + 13);
public const int TB_ISBUTTONHIGHLIGHTED = (WM_USER + 14);
public const int TB_SETSTATE = (WM_USER + 17);
public const int TB_GETSTATE = (WM_USER + 18);
public const int TB_ADDBITMAP = (WM_USER + 19);
public const int TB_ADDBUTTONSA = (WM_USER + 20);
public const int TB_INSERTBUTTONA = (WM_USER + 21);
public const int TB_ADDBUTTONS = (WM_USER + 20);
public const int TB_INSERTBUTTON = (WM_USER + 21);
public const int TB_DELETEBUTTON = (WM_USER + 22);
public const int TB_GETBUTTON = (WM_USER + 23);
public const int TB_BUTTONCOUNT = (WM_USER + 24);
public const int TB_COMMANDTOINDEX = (WM_USER + 25);
public const int TB_SAVERESTOREA = (WM_USER + 26);
public const int TB_SAVERESTOREW = (WM_USER + 76);
public const int TB_CUSTOMIZE = (WM_USER + 27);
public const int TB_ADDSTRINGA = (WM_USER + 28);
public const int TB_ADDSTRINGW = (WM_USER + 77);
public const int TB_GETITEMRECT = (WM_USER + 29);
public const int TB_BUTTONSTRUCTSIZE = (WM_USER + 30);
public const int TB_SETBUTTONSIZE = (WM_USER + 31);
public const int TB_SETBITMAPSIZE = (WM_USER + 32);
public const int TB_AUTOSIZE = (WM_USER + 33);
public const int TB_GETTOOLTIPS = (WM_USER + 35);
public const int TB_SETTOOLTIPS = (WM_USER + 36);
public const int TB_SETPARENT = (WM_USER + 37);
public const int TB_SETROWS = (WM_USER + 39);
public const int TB_GETROWS = (WM_USER + 40);
public const int TB_SETCMDID = (WM_USER + 42);
public const int TB_CHANGEBITMAP = (WM_USER + 43);
public const int TB_GETBITMAP = (WM_USER + 44);
public const int TB_GETBUTTONTEXTA = (WM_USER + 45);
public const int TB_GETBUTTONTEXTW = (WM_USER + 75);
public const int TB_REPLACEBITMAP = (WM_USER + 46);
public const int TB_SETINDENT = (WM_USER + 47);
public const int TB_SETIMAGELIST = (WM_USER + 48);
public const int TB_GETIMAGELIST = (WM_USER + 49);
public const int TB_LOADIMAGES = (WM_USER + 50);
public const int TB_GETRECT = (WM_USER + 51);
public const int TB_SETHOTIMAGELIST = (WM_USER + 52);
public const int TB_GETHOTIMAGELIST = (WM_USER + 53);
public const int TB_SETDISABLEDIMAGELIST = (WM_USER + 54);
public const int TB_GETDISABLEDIMAGELIST = (WM_USER + 55);
public const int TB_SETSTYLE = (WM_USER + 56);
public const int TB_GETSTYLE = (WM_USER + 57);
public const int TB_GETBUTTONSIZE = (WM_USER + 58);
public const int TB_SETBUTTONWIDTH = (WM_USER + 59);
public const int TB_SETMAXTEXTROWS = (WM_USER + 60);
public const int TB_GETTEXTROWS = (WM_USER + 61);
public const int TB_GETOBJECT = (WM_USER + 62);
public const int TB_GETHOTITEM = (WM_USER + 71);
public const int TB_SETHOTITEM = (WM_USER + 72);
public const int TB_SETANCHORHIGHLIGHT = (WM_USER + 73);
public const int TB_GETANCHORHIGHLIGHT = (WM_USER + 74);
public const int TB_MAPACCELERATORA = (WM_USER + 78);
public const int TB_GETINSERTMARK = (WM_USER + 79);
public const int TB_SETINSERTMARK = (WM_USER + 80);
public const int TB_INSERTMARKHITTEST = (WM_USER + 81);
public const int TB_MOVEBUTTON = (WM_USER + 82);
public const int TB_GETMAXSIZE = (WM_USER + 83);
public const int TB_SETEXTENDEDSTYLE = (WM_USER + 84);
public const int TB_GETEXTENDEDSTYLE = (WM_USER + 85);
public const int TB_GETPADDING = (WM_USER + 86);
public const int TB_SETPADDING = (WM_USER + 87);
public const int TB_SETINSERTMARKCOLOR = (WM_USER + 88);
public const int TB_GETINSERTMARKCOLOR = (WM_USER + 89);
public const int TB_SETCOLORSCHEME = CCM_SETCOLORSCHEME;
public const int TB_GETCOLORSCHEME = CCM_GETCOLORSCHEME;
public const int TB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int TB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int TB_MAPACCELERATORW = (WM_USER + 90);
public const int TB_GETBITMAPFLAGS = (WM_USER + 41);
public const int TB_GETBUTTONINFOW = (WM_USER + 63);
public const int TB_SETBUTTONINFOW = (WM_USER + 64);
public const int TB_GETBUTTONINFOA = (WM_USER + 65);
public const int TB_SETBUTTONINFOA = (WM_USER + 66);
public const int TB_INSERTBUTTONW = (WM_USER + 67);
public const int TB_ADDBUTTONSW = (WM_USER + 68);
public const int TB_HITTEST = (WM_USER + 69);
public const int TB_SETDRAWTEXTFLAGS = (WM_USER + 70);
public const int TB_GETSTRINGW = (WM_USER + 91);
public const int TB_GETSTRINGA = (WM_USER + 92);
public const int TB_GETMETRICS = (WM_USER + 101);
public const int TB_SETMETRICS = (WM_USER + 102);
public const int TB_SETWINDOWTHEME = CCM_SETWINDOWTHEME;
public const int RB_INSERTBANDA = (WM_USER + 1);
public const int RB_DELETEBAND = (WM_USER + 2);
public const int RB_GETBARINFO = (WM_USER + 3);
public const int RB_SETBARINFO = (WM_USER + 4);
public const int RB_GETBANDINFO = (WM_USER + 5);
public const int RB_SETBANDINFOA = (WM_USER + 6);
public const int RB_SETPARENT = (WM_USER + 7);
public const int RB_HITTEST = (WM_USER + 8);
public const int RB_GETRECT = (WM_USER + 9);
public const int RB_INSERTBANDW = (WM_USER + 10);
public const int RB_SETBANDINFOW = (WM_USER + 11);
public const int RB_GETBANDCOUNT = (WM_USER + 12);
public const int RB_GETROWCOUNT = (WM_USER + 13);
public const int RB_GETROWHEIGHT = (WM_USER + 14);
public const int RB_IDTOINDEX = (WM_USER + 16);
public const int RB_GETTOOLTIPS = (WM_USER + 17);
public const int RB_SETTOOLTIPS = (WM_USER + 18);
public const int RB_SETBKCOLOR = (WM_USER + 19);
public const int RB_GETBKCOLOR = (WM_USER + 20);
public const int RB_SETTEXTCOLOR = (WM_USER + 21);
public const int RB_GETTEXTCOLOR = (WM_USER + 22);
public const int RB_SIZETORECT = (WM_USER + 23);
public const int RB_SETCOLORSCHEME = CCM_SETCOLORSCHEME;
public const int RB_GETCOLORSCHEME = CCM_GETCOLORSCHEME;
public const int RB_BEGINDRAG = (WM_USER + 24);
public const int RB_ENDDRAG = (WM_USER + 25);
public const int RB_DRAGMOVE = (WM_USER + 26);
public const int RB_GETBARHEIGHT = (WM_USER + 27);
public const int RB_GETBANDINFOW = (WM_USER + 28);
public const int RB_GETBANDINFOA = (WM_USER + 29);
public const int RB_MINIMIZEBAND = (WM_USER + 30);
public const int RB_MAXIMIZEBAND = (WM_USER + 31);
public const int RB_GETDROPTARGET = (CCM_GETDROPTARGET);
public const int RB_GETBANDBORDERS = (WM_USER + 34);
public const int RB_SHOWBAND = (WM_USER + 35);
public const int RB_SETPALETTE = (WM_USER + 37);
public const int RB_GETPALETTE = (WM_USER + 38);
public const int RB_MOVEBAND = (WM_USER + 39);
public const int RB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int RB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int RB_GETBANDMARGINS = (WM_USER + 40);
public const int RB_SETWINDOWTHEME = CCM_SETWINDOWTHEME;
public const int RB_PUSHCHEVRON = (WM_USER + 43);
public const int TTM_ACTIVATE = (WM_USER + 1);
public const int TTM_SETDELAYTIME = (WM_USER + 3);
public const int TTM_ADDTOOLA = (WM_USER + 4);
public const int TTM_ADDTOOLW = (WM_USER + 50);
public const int TTM_DELTOOLA = (WM_USER + 5);
public const int TTM_DELTOOLW = (WM_USER + 51);
public const int TTM_NEWTOOLRECTA = (WM_USER + 6);
public const int TTM_NEWTOOLRECTW = (WM_USER + 52);
public const int TTM_RELAYEVENT = (WM_USER + 7);
public const int TTM_GETTOOLINFOA = (WM_USER + 8);
public const int TTM_GETTOOLINFOW = (WM_USER + 53);
public const int TTM_SETTOOLINFOA = (WM_USER + 9);
public const int TTM_SETTOOLINFOW = (WM_USER + 54);
public const int TTM_HITTESTA = (WM_USER +10);
public const int TTM_HITTESTW = (WM_USER +55);
public const int TTM_GETTEXTA = (WM_USER +11);
public const int TTM_GETTEXTW = (WM_USER +56);
public const int TTM_UPDATETIPTEXTA = (WM_USER +12);
public const int TTM_UPDATETIPTEXTW = (WM_USER +57);
public const int TTM_GETTOOLCOUNT = (WM_USER +13);
public const int TTM_ENUMTOOLSA = (WM_USER +14);
public const int TTM_ENUMTOOLSW = (WM_USER +58);
public const int TTM_GETCURRENTTOOLA = (WM_USER + 15);
public const int TTM_GETCURRENTTOOLW = (WM_USER + 59);
public const int TTM_WINDOWFROMPOINT = (WM_USER + 16);
public const int TTM_TRACKACTIVATE = (WM_USER + 17);
public const int TTM_TRACKPOSITION = (WM_USER + 18);
public const int TTM_SETTIPBKCOLOR = (WM_USER + 19);
public const int TTM_SETTIPTEXTCOLOR = (WM_USER + 20);
public const int TTM_GETDELAYTIME = (WM_USER + 21);
public const int TTM_GETTIPBKCOLOR = (WM_USER + 22);
public const int TTM_GETTIPTEXTCOLOR = (WM_USER + 23);
public const int TTM_SETMAXTIPWIDTH = (WM_USER + 24);
public const int TTM_GETMAXTIPWIDTH = (WM_USER + 25);
public const int TTM_SETMARGIN = (WM_USER + 26);
public const int TTM_GETMARGIN = (WM_USER + 27);
public const int TTM_POP = (WM_USER + 28);
public const int TTM_UPDATE = (WM_USER + 29);
public const int TTM_GETBUBBLESIZE = (WM_USER + 30);
public const int TTM_ADJUSTRECT = (WM_USER + 31);
public const int TTM_SETTITLEA = (WM_USER + 32);
public const int TTM_SETTITLEW = (WM_USER + 33);
public const int TTM_POPUP = (WM_USER + 34);
public const int TTM_GETTITLE = (WM_USER + 35);
public const int TTM_SETWINDOWTHEME = CCM_SETWINDOWTHEME;
public const int SB_SETTEXTA = (WM_USER+1);
public const int SB_SETTEXTW = (WM_USER+11);
public const int SB_GETTEXTA = (WM_USER+2);
public const int SB_GETTEXTW = (WM_USER+13);
public const int SB_GETTEXTLENGTHA = (WM_USER+3);
public const int SB_GETTEXTLENGTHW = (WM_USER+12);
public const int SB_SETPARTS = (WM_USER+4);
public const int SB_GETPARTS = (WM_USER+6);
public const int SB_GETBORDERS = (WM_USER+7);
public const int SB_SETMINHEIGHT = (WM_USER+8);
public const int SB_SIMPLE = (WM_USER+9);
public const int SB_GETRECT = (WM_USER+10);
public const int SB_ISSIMPLE = (WM_USER+14);
public const int SB_SETICON = (WM_USER+15);
public const int SB_SETTIPTEXTA = (WM_USER+16);
public const int SB_SETTIPTEXTW = (WM_USER+17);
public const int SB_GETTIPTEXTA = (WM_USER+18);
public const int SB_GETTIPTEXTW = (WM_USER+19);
public const int SB_GETICON = (WM_USER+20);
public const int SB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int SB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int SB_SETBKCOLOR = CCM_SETBKCOLOR;
public const int SB_SIMPLEID = 0x00ff;
public const int TBM_GETPOS = (WM_USER);
public const int TBM_GETRANGEMIN = (WM_USER+1);
public const int TBM_GETRANGEMAX = (WM_USER+2);
public const int TBM_GETTIC = (WM_USER+3);
public const int TBM_SETTIC = (WM_USER+4);
public const int TBM_SETPOS = (WM_USER+5);
public const int TBM_SETRANGE = (WM_USER+6);
public const int TBM_SETRANGEMIN = (WM_USER+7);
public const int TBM_SETRANGEMAX = (WM_USER+8);
public const int TBM_CLEARTICS = (WM_USER+9);
public const int TBM_SETSEL = (WM_USER+10);
public const int TBM_SETSELSTART = (WM_USER+11);
public const int TBM_SETSELEND = (WM_USER+12);
public const int TBM_GETPTICS = (WM_USER+14);
public const int TBM_GETTICPOS = (WM_USER+15);
public const int TBM_GETNUMTICS = (WM_USER+16);
public const int TBM_GETSELSTART = (WM_USER+17);
public const int TBM_GETSELEND = (WM_USER+18);
public const int TBM_CLEARSEL = (WM_USER+19);
public const int TBM_SETTICFREQ = (WM_USER+20);
public const int TBM_SETPAGESIZE = (WM_USER+21);
public const int TBM_GETPAGESIZE = (WM_USER+22);
public const int TBM_SETLINESIZE = (WM_USER+23);
public const int TBM_GETLINESIZE = (WM_USER+24);
public const int TBM_GETTHUMBRECT = (WM_USER+25);
public const int TBM_GETCHANNELRECT = (WM_USER+26);
public const int TBM_SETTHUMBLENGTH = (WM_USER+27);
public const int TBM_GETTHUMBLENGTH = (WM_USER+28);
public const int TBM_SETTOOLTIPS = (WM_USER+29);
public const int TBM_GETTOOLTIPS = (WM_USER+30);
public const int TBM_SETTIPSIDE = (WM_USER+31);
public const int TBM_SETBUDDY = (WM_USER+32);
public const int TBM_GETBUDDY = (WM_USER+33);
public const int TBM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int TBM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int DL_BEGINDRAG = (WM_USER+133);
public const int DL_DRAGGING = (WM_USER+134);
public const int DL_DROPPED = (WM_USER+135);
public const int DL_CANCELDRAG = (WM_USER+136);
public const int UDM_SETRANGE = (WM_USER+101);
public const int UDM_GETRANGE = (WM_USER+102);
public const int UDM_SETPOS = (WM_USER+103);
public const int UDM_GETPOS = (WM_USER+104);
public const int UDM_SETBUDDY = (WM_USER+105);
public const int UDM_GETBUDDY = (WM_USER+106);
public const int UDM_SETACCEL = (WM_USER+107);
public const int UDM_GETACCEL = (WM_USER+108);
public const int UDM_SETBASE = (WM_USER+109);
public const int UDM_GETBASE = (WM_USER+110);
public const int UDM_SETRANGE32 = (WM_USER+111);
public const int UDM_GETRANGE32 = (WM_USER+112);
public const int UDM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int UDM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int UDM_SETPOS32 = (WM_USER+113);
public const int UDM_GETPOS32 = (WM_USER+114);
public const int PBM_SETRANGE = (WM_USER+1);
public const int PBM_SETPOS = (WM_USER+2);
public const int PBM_DELTAPOS = (WM_USER+3);
public const int PBM_SETSTEP = (WM_USER+4);
public const int PBM_STEPIT = (WM_USER+5);
public const int PBM_SETRANGE32 = (WM_USER+6);
public const int PBM_GETRANGE = (WM_USER+7);
public const int PBM_GETPOS = (WM_USER+8);
public const int PBM_SETBARCOLOR = (WM_USER+9);
public const int PBM_SETBKCOLOR = CCM_SETBKCOLOR;
public const int HKM_SETHOTKEY = (WM_USER+1);
public const int HKM_GETHOTKEY = (WM_USER+2);
public const int HKM_SETRULES = (WM_USER+3);
public const int LVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int LVM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int LVM_GETBKCOLOR = (LVM_FIRST + 0);
public const int LVM_SETBKCOLOR = (LVM_FIRST + 1);
public const int LVM_GETIMAGELIST = (LVM_FIRST + 2);
public const int LVM_SETIMAGELIST = (LVM_FIRST + 3);
public const int LVM_GETITEMCOUNT = (LVM_FIRST + 4);
public const int LVM_GETITEMA = (LVM_FIRST + 5);
public const int LVM_GETITEMW = (LVM_FIRST + 75);
public const int LVM_SETITEMA = (LVM_FIRST + 6);
public const int LVM_SETITEMW = (LVM_FIRST + 76);
public const int LVM_INSERTITEMA = (LVM_FIRST + 7);
public const int LVM_INSERTITEMW = (LVM_FIRST + 77);
public const int LVM_DELETEITEM = (LVM_FIRST + 8);
public const int LVM_DELETEALLITEMS = (LVM_FIRST + 9);
public const int LVM_GETCALLBACKMASK = (LVM_FIRST + 10);
public const int LVM_SETCALLBACKMASK = (LVM_FIRST + 11);
public const int LVM_FINDITEMA = (LVM_FIRST + 13);
public const int LVM_FINDITEMW = (LVM_FIRST + 83);
public const int LVM_GETITEMRECT = (LVM_FIRST + 14);
public const int LVM_SETITEMPOSITION = (LVM_FIRST + 15);
public const int LVM_GETITEMPOSITION = (LVM_FIRST + 16);
public const int LVM_GETSTRINGWIDTHA = (LVM_FIRST + 17);
public const int LVM_GETSTRINGWIDTHW = (LVM_FIRST + 87);
public const int LVM_HITTEST = (LVM_FIRST + 18);
public const int LVM_ENSUREVISIBLE = (LVM_FIRST + 19);
public const int LVM_SCROLL = (LVM_FIRST + 20);
public const int LVM_REDRAWITEMS = (LVM_FIRST + 21);
public const int LVM_ARRANGE = (LVM_FIRST + 22);
public const int LVM_EDITLABELA = (LVM_FIRST + 23);
public const int LVM_EDITLABELW = (LVM_FIRST + 118);
public const int LVM_GETEDITCONTROL = (LVM_FIRST + 24);
public const int LVM_GETCOLUMNA = (LVM_FIRST + 25);
public const int LVM_GETCOLUMNW = (LVM_FIRST + 95);
public const int LVM_SETCOLUMNA = (LVM_FIRST + 26);
public const int LVM_SETCOLUMNW = (LVM_FIRST + 96);
public const int LVM_INSERTCOLUMNA = (LVM_FIRST + 27);
public const int LVM_INSERTCOLUMNW = (LVM_FIRST + 97);
public const int LVM_DELETECOLUMN = (LVM_FIRST + 28);
public const int LVM_GETCOLUMNWIDTH = (LVM_FIRST + 29);
public const int LVM_SETCOLUMNWIDTH = (LVM_FIRST + 30);
public const int LVM_CREATEDRAGIMAGE = (LVM_FIRST + 33);
public const int LVM_GETVIEWRECT = (LVM_FIRST + 34);
public const int LVM_GETTEXTCOLOR = (LVM_FIRST + 35);
public const int LVM_SETTEXTCOLOR = (LVM_FIRST + 36);
public const int LVM_GETTEXTBKCOLOR = (LVM_FIRST + 37);
public const int LVM_SETTEXTBKCOLOR = (LVM_FIRST + 38);
public const int LVM_GETTOPINDEX = (LVM_FIRST + 39);
public const int LVM_GETCOUNTPERPAGE = (LVM_FIRST + 40);
public const int LVM_GETORIGIN = (LVM_FIRST + 41);
public const int LVM_UPDATE = (LVM_FIRST + 42);
public const int LVM_SETITEMSTATE = (LVM_FIRST + 43);
public const int LVM_GETITEMSTATE = (LVM_FIRST + 44);
public const int LVM_GETITEMTEXTA = (LVM_FIRST + 45);
public const int LVM_GETITEMTEXTW = (LVM_FIRST + 115);
public const int LVM_SETITEMTEXTA = (LVM_FIRST + 46);
public const int LVM_SETITEMTEXTW = (LVM_FIRST + 116);
public const int LVM_SETITEMCOUNT = (LVM_FIRST + 47);
public const int LVM_SORTITEMS = (LVM_FIRST + 48);
public const int LVM_SETITEMPOSITION32 = (LVM_FIRST + 49);
public const int LVM_GETSELECTEDCOUNT = (LVM_FIRST + 50);
public const int LVM_GETITEMSPACING = (LVM_FIRST + 51);
public const int LVM_GETISEARCHSTRINGA = (LVM_FIRST + 52);
public const int LVM_GETISEARCHSTRINGW = (LVM_FIRST + 117);
public const int LVM_SETICONSPACING = (LVM_FIRST + 53);
public const int LVM_SETEXTENDEDLISTVIEWSTYLE = (LVM_FIRST + 54);
public const int LVM_GETEXTENDEDLISTVIEWSTYLE = (LVM_FIRST + 55);
public const int LVM_GETSUBITEMRECT = (LVM_FIRST + 56);
public const int LVM_SUBITEMHITTEST = (LVM_FIRST + 57);
public const int LVM_SETCOLUMNORDERARRAY = (LVM_FIRST + 58);
public const int LVM_GETCOLUMNORDERARRAY = (LVM_FIRST + 59);
public const int LVM_SETHOTITEM = (LVM_FIRST + 60);
public const int LVM_GETHOTITEM = (LVM_FIRST + 61);
public const int LVM_SETHOTCURSOR = (LVM_FIRST + 62);
public const int LVM_GETHOTCURSOR = (LVM_FIRST + 63);
public const int LVM_APPROXIMATEVIEWRECT = (LVM_FIRST + 64);
public const int LVM_SETWORKAREAS = (LVM_FIRST + 65);
public const int LVM_GETWORKAREAS = (LVM_FIRST + 70);
public const int LVM_GETNUMBEROFWORKAREAS = (LVM_FIRST + 73);
public const int LVM_GETSELECTIONMARK = (LVM_FIRST + 66);
public const int LVM_SETSELECTIONMARK = (LVM_FIRST + 67);
public const int LVM_SETHOVERTIME = (LVM_FIRST + 71);
public const int LVM_GETHOVERTIME = (LVM_FIRST + 72);
public const int LVM_SETTOOLTIPS = (LVM_FIRST + 74);
public const int LVM_GETTOOLTIPS = (LVM_FIRST + 78);
public const int LVM_SORTITEMSEX = (LVM_FIRST + 81);
public const int LVM_SETBKIMAGEA = (LVM_FIRST + 68);
public const int LVM_SETBKIMAGEW = (LVM_FIRST + 138);
public const int LVM_GETBKIMAGEA = (LVM_FIRST + 69);
public const int LVM_GETBKIMAGEW = (LVM_FIRST + 139);
public const int LVM_SETSELECTEDCOLUMN = (LVM_FIRST + 140);
public const int LVM_SETTILEWIDTH = (LVM_FIRST + 141);
public const int LVM_SETVIEW = (LVM_FIRST + 142);
public const int LVM_GETVIEW = (LVM_FIRST + 143);
public const int LVM_INSERTGROUP = (LVM_FIRST + 145);
public const int LVM_SETGROUPINFO = (LVM_FIRST + 147);
public const int LVM_GETGROUPINFO = (LVM_FIRST + 149);
public const int LVM_REMOVEGROUP = (LVM_FIRST + 150);
public const int LVM_MOVEGROUP = (LVM_FIRST + 151);
public const int LVM_MOVEITEMTOGROUP = (LVM_FIRST + 154);
public const int LVM_SETGROUPMETRICS = (LVM_FIRST + 155);
public const int LVM_GETGROUPMETRICS = (LVM_FIRST + 156);
public const int LVM_ENABLEGROUPVIEW = (LVM_FIRST + 157);
public const int LVM_SORTGROUPS = (LVM_FIRST + 158);
public const int LVM_INSERTGROUPSORTED = (LVM_FIRST + 159);
public const int LVM_REMOVEALLGROUPS = (LVM_FIRST + 160);
public const int LVM_HASGROUP = (LVM_FIRST + 161);
public const int LVM_SETTILEVIEWINFO = (LVM_FIRST + 162);
public const int LVM_GETTILEVIEWINFO = (LVM_FIRST + 163);
public const int LVM_SETTILEINFO = (LVM_FIRST + 164);
public const int LVM_GETTILEINFO = (LVM_FIRST + 165);
public const int LVM_SETINSERTMARK = (LVM_FIRST + 166);
public const int LVM_GETINSERTMARK = (LVM_FIRST + 167);
public const int LVM_INSERTMARKHITTEST = (LVM_FIRST + 168);
public const int LVM_GETINSERTMARKRECT = (LVM_FIRST + 169);
public const int LVM_SETINSERTMARKCOLOR = (LVM_FIRST + 170);
public const int LVM_GETINSERTMARKCOLOR = (LVM_FIRST + 171);
public const int LVM_SETINFOTIP = (LVM_FIRST + 173);
public const int LVM_GETSELECTEDCOLUMN = (LVM_FIRST + 174);
public const int LVM_ISGROUPVIEWENABLED = (LVM_FIRST + 175);
public const int LVM_GETOUTLINECOLOR = (LVM_FIRST + 176);
public const int LVM_SETOUTLINECOLOR = (LVM_FIRST + 177);
public const int LVM_CANCELEDITLABEL = (LVM_FIRST + 179);
public const int LVM_MAPINDEXTOID = (LVM_FIRST + 180);
public const int LVM_MAPIDTOINDEX = (LVM_FIRST + 181);
public const int TVM_INSERTITEMA = (TV_FIRST + 0);
public const int TVM_INSERTITEMW = (TV_FIRST + 50);
public const int TVM_DELETEITEM = (TV_FIRST + 1);
public const int TVM_EXPAND = (TV_FIRST + 2);
public const int TVM_GETITEMRECT = (TV_FIRST + 4);
public const int TVM_GETCOUNT = (TV_FIRST + 5);
public const int TVM_GETINDENT = (TV_FIRST + 6);
public const int TVM_SETINDENT = (TV_FIRST + 7);
public const int TVM_GETIMAGELIST = (TV_FIRST + 8);
public const int TVM_SETIMAGELIST = (TV_FIRST + 9);
public const int TVM_GETNEXTITEM = (TV_FIRST + 10);
public const int TVM_SELECTITEM = (TV_FIRST + 11);
public const int TVM_GETITEMA = (TV_FIRST + 12);
public const int TVM_GETITEMW = (TV_FIRST + 62);
public const int TVM_SETITEMA = (TV_FIRST + 13);
public const int TVM_SETITEMW = (TV_FIRST + 63);
public const int TVM_EDITLABELA = (TV_FIRST + 14);
public const int TVM_EDITLABELW = (TV_FIRST + 65);
public const int TVM_GETEDITCONTROL = (TV_FIRST + 15);
public const int TVM_GETVISIBLECOUNT = (TV_FIRST + 16);
public const int TVM_HITTEST = (TV_FIRST + 17);
public const int TVM_CREATEDRAGIMAGE = (TV_FIRST + 18);
public const int TVM_SORTCHILDREN = (TV_FIRST + 19);
public const int TVM_ENSUREVISIBLE = (TV_FIRST + 20);
public const int TVM_SORTCHILDRENCB = (TV_FIRST + 21);
public const int TVM_ENDEDITLABELNOW = (TV_FIRST + 22);
public const int TVM_GETISEARCHSTRINGA = (TV_FIRST + 23);
public const int TVM_GETISEARCHSTRINGW = (TV_FIRST + 64);
public const int TVM_SETTOOLTIPS = (TV_FIRST + 24);
public const int TVM_GETTOOLTIPS = (TV_FIRST + 25);
public const int TVM_SETINSERTMARK = (TV_FIRST + 26);
public const int TVM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int TVM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int TVM_SETITEMHEIGHT = (TV_FIRST + 27);
public const int TVM_GETITEMHEIGHT = (TV_FIRST + 28);
public const int TVM_SETBKCOLOR = (TV_FIRST + 29);
public const int TVM_SETTEXTCOLOR = (TV_FIRST + 30);
public const int TVM_GETBKCOLOR = (TV_FIRST + 31);
public const int TVM_GETTEXTCOLOR = (TV_FIRST + 32);
public const int TVM_SETSCROLLTIME = (TV_FIRST + 33);
public const int TVM_GETSCROLLTIME = (TV_FIRST + 34);
public const int TVM_SETINSERTMARKCOLOR = (TV_FIRST + 37);
public const int TVM_GETINSERTMARKCOLOR = (TV_FIRST + 38);
public const int TVM_GETITEMSTATE = (TV_FIRST + 39);
public const int TVM_SETLINECOLOR = (TV_FIRST + 40);
public const int TVM_GETLINECOLOR = (TV_FIRST + 41);
public const int TVM_MAPACCIDTOHTREEITEM = (TV_FIRST + 42);
public const int TVM_MAPHTREEITEMTOACCID = (TV_FIRST + 43);
public const int CBEM_INSERTITEMA = (WM_USER + 1);
public const int CBEM_SETIMAGELIST = (WM_USER + 2);
public const int CBEM_GETIMAGELIST = (WM_USER + 3);
public const int CBEM_GETITEMA = (WM_USER + 4);
public const int CBEM_SETITEMA = (WM_USER + 5);
public const int CBEM_DELETEITEM = CB_DELETESTRING;
public const int CBEM_GETCOMBOCONTROL = (WM_USER + 6);
public const int CBEM_GETEDITCONTROL = (WM_USER + 7);
public const int CBEM_SETEXTENDEDSTYLE = (WM_USER + 14);
public const int CBEM_GETEXTENDEDSTYLE = (WM_USER + 9);
public const int CBEM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int CBEM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int CBEM_SETEXSTYLE = (WM_USER + 8);
public const int CBEM_GETEXSTYLE = (WM_USER + 9);
public const int CBEM_HASEDITCHANGED = (WM_USER + 10);
public const int CBEM_INSERTITEMW = (WM_USER + 11);
public const int CBEM_SETITEMW = (WM_USER + 12);
public const int CBEM_GETITEMW = (WM_USER + 13);
public const int TCM_GETIMAGELIST = (TCM_FIRST + 2);
public const int TCM_SETIMAGELIST = (TCM_FIRST + 3);
public const int TCM_GETITEMCOUNT = (TCM_FIRST + 4);
public const int TCM_GETITEMA = (TCM_FIRST + 5);
public const int TCM_GETITEMW = (TCM_FIRST + 60);
public const int TCM_SETITEMA = (TCM_FIRST + 6);
public const int TCM_SETITEMW = (TCM_FIRST + 61);
public const int TCM_INSERTITEMA = (TCM_FIRST + 7);
public const int TCM_INSERTITEMW = (TCM_FIRST + 62);
public const int TCM_DELETEITEM = (TCM_FIRST + 8);
public const int TCM_DELETEALLITEMS = (TCM_FIRST + 9);
public const int TCM_GETITEMRECT = (TCM_FIRST + 10);
public const int TCM_GETCURSEL = (TCM_FIRST + 11);
public const int TCM_SETCURSEL = (TCM_FIRST + 12);
public const int TCM_HITTEST = (TCM_FIRST + 13);
public const int TCM_SETITEMEXTRA = (TCM_FIRST + 14);
public const int TCM_ADJUSTRECT = (TCM_FIRST + 40);
public const int TCM_SETITEMSIZE = (TCM_FIRST + 41);
public const int TCM_REMOVEIMAGE = (TCM_FIRST + 42);
public const int TCM_SETPADDING = (TCM_FIRST + 43);
public const int TCM_GETROWCOUNT = (TCM_FIRST + 44);
public const int TCM_GETTOOLTIPS = (TCM_FIRST + 45);
public const int TCM_SETTOOLTIPS = (TCM_FIRST + 46);
public const int TCM_GETCURFOCUS = (TCM_FIRST + 47);
public const int TCM_SETCURFOCUS = (TCM_FIRST + 48);
public const int TCM_SETMINTABWIDTH = (TCM_FIRST + 49);
public const int TCM_DESELECTALL = (TCM_FIRST + 50);
public const int TCM_HIGHLIGHTITEM = (TCM_FIRST + 51);
public const int TCM_SETEXTENDEDSTYLE = (TCM_FIRST + 52);
public const int TCM_GETEXTENDEDSTYLE = (TCM_FIRST + 53);
public const int TCM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int TCM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int ACM_OPENA = (WM_USER+100);
public const int ACM_OPENW = (WM_USER+103);
public const int ACM_PLAY = (WM_USER+101);
public const int ACM_STOP = (WM_USER+102);
public const int MCM_FIRST = 0x1000;
public const int MCM_GETCURSEL = (MCM_FIRST + 1);
public const int MCM_SETCURSEL = (MCM_FIRST + 2);
public const int MCM_GETMAXSELCOUNT = (MCM_FIRST + 3);
public const int MCM_SETMAXSELCOUNT = (MCM_FIRST + 4);
public const int MCM_GETSELRANGE = (MCM_FIRST + 5);
public const int MCM_SETSELRANGE = (MCM_FIRST + 6);
public const int MCM_GETMONTHRANGE = (MCM_FIRST + 7);
public const int MCM_SETDAYSTATE = (MCM_FIRST + 8);
public const int MCM_GETMINREQRECT = (MCM_FIRST + 9);
public const int MCM_SETCOLOR = (MCM_FIRST + 10);
public const int MCM_GETCOLOR = (MCM_FIRST + 11);
public const int MCM_SETTODAY = (MCM_FIRST + 12);
public const int MCM_GETTODAY = (MCM_FIRST + 13);
public const int MCM_HITTEST = (MCM_FIRST + 14);
public const int MCM_SETFIRSTDAYOFWEEK = (MCM_FIRST + 15);
public const int MCM_GETFIRSTDAYOFWEEK = (MCM_FIRST + 16);
public const int MCM_GETRANGE = (MCM_FIRST + 17);
public const int MCM_SETRANGE = (MCM_FIRST + 18);
public const int MCM_GETMONTHDELTA = (MCM_FIRST + 19);
public const int MCM_SETMONTHDELTA = (MCM_FIRST + 20);
public const int MCM_GETMAXTODAYWIDTH = (MCM_FIRST + 21);
public const int MCM_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT;
public const int MCM_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT;
public const int DTM_FIRST = 0x1000;
public const int DTM_GETSYSTEMTIME = (DTM_FIRST + 1);
public const int DTM_SETSYSTEMTIME = (DTM_FIRST + 2);
public const int DTM_GETRANGE = (DTM_FIRST + 3);
public const int DTM_SETRANGE = (DTM_FIRST + 4);
public const int DTM_SETFORMATA = (DTM_FIRST + 5);
public const int DTM_SETFORMATW = (DTM_FIRST + 50);
public const int DTM_SETMCCOLOR = (DTM_FIRST + 6);
public const int DTM_GETMCCOLOR = (DTM_FIRST + 7);
public const int DTM_GETMONTHCAL = (DTM_FIRST + 8);
public const int DTM_SETMCFONT = (DTM_FIRST + 9);
public const int DTM_GETMCFONT = (DTM_FIRST + 10);
public const int PGM_SETCHILD = (PGM_FIRST + 1);
public const int PGM_RECALCSIZE = (PGM_FIRST + 2);
public const int PGM_FORWARDMOUSE = (PGM_FIRST + 3);
public const int PGM_SETBKCOLOR = (PGM_FIRST + 4);
public const int PGM_GETBKCOLOR = (PGM_FIRST + 5);
public const int PGM_SETBORDER = (PGM_FIRST + 6);
public const int PGM_GETBORDER = (PGM_FIRST + 7);
public const int PGM_SETPOS = (PGM_FIRST + 8);
public const int PGM_GETPOS = (PGM_FIRST + 9);
public const int PGM_SETBUTTONSIZE = (PGM_FIRST + 10);
public const int PGM_GETBUTTONSIZE = (PGM_FIRST + 11);
public const int PGM_GETBUTTONSTATE = (PGM_FIRST + 12);
public const int PGM_GETDROPTARGET = CCM_GETDROPTARGET;
public const int BCM_GETIDEALSIZE = (BCM_FIRST + 0x0001);
public const int BCM_SETIMAGELIST = (BCM_FIRST + 0x0002);
public const int BCM_GETIMAGELIST = (BCM_FIRST + 0x0003);
public const int BCM_SETTEXTMARGIN = (BCM_FIRST + 0x0004);
public const int BCM_GETTEXTMARGIN = (BCM_FIRST + 0x0005);
public const int EM_SETCUEBANNER = (ECM_FIRST + 1);
public const int EM_GETCUEBANNER = (ECM_FIRST + 2);
public const int EM_SHOWBALLOONTIP = (ECM_FIRST + 3);
public const int EM_HIDEBALLOONTIP = (ECM_FIRST + 4);
public const int CB_SETMINVISIBLE = (CBM_FIRST + 1);
public const int CB_GETMINVISIBLE = (CBM_FIRST + 2);
public const int LM_HITTEST = (WM_USER + 0x300);
public const int LM_GETIDEALHEIGHT = (WM_USER + 0x301);
public const int LM_SETITEM = (WM_USER + 0x302);
public const int LM_GETITEM = (WM_USER + 0x303);
}
------------------------------
public class MouseMasks
{
public const int MK_NULL = 0x0000;
public const int MK_LBUTTON = 0x0001;
public const int MK_RBUTTON = 0x0002;
public const int MK_SHIFT = 0x0004;
public const int MK_CONTROL = 0x0008;
public const int MK_MBUTTON = 0x0010;
public const int MK_XBUTTON1 = 0x0020;
public const int MK_XBUTTON2 = 0x0040;
}
-----------------------------
이 정도면 가능할 것 같습니다.
조인에 대해 이제 찬찬히 이야기를 드려 보지요.
Join은 2개 이상의 테이블에서 1개의 테이블 집합을 생성해 질의 결과를 얻게 해주는 방법.
RDBMS는 정규화 과정을 거쳐 테이블을 나누어 설계 하게 됨.
이때 여러개의 테이블중 논리적인 테이블을 만들어 결과를 생성하게 하는 방법을 의미함.
조인의 종류는 5가지로 나뉘어 집니다.
1. INNER JOIN
2. OUTER JOIN
3. CROSS JOIN
4. FULL OUTER JOIN
5. SELF JOIN
조인의 방식은 3가지로 나뉘어 집니다.
1. Nested loop Join
2. Hash Join
3. Merge Join
종류에 대한 이야기니 그렇구나 하시면 되구요..
주로 사용되는 조인은? 맨 처음의 INNER JOIN 이라는 녀석 입니다.
위의 ID-성별 처럼 두개의 테이블을 합칠때 사용되는 녀석 이지요.
먼저 INNER JOIN에 대해 말을 드리지요.
1. INNER JOIN
두개의 관련된 키가 있는 테이블에서 Column의 값을 비교 후
Join 조건에 맞는 행만 검색합니다.
SQL서버의 기본 조인 방식 입니다.
SELECT * from authors
|
자 위의 조인문이 중요한 것은 아닙니다. 우선은 샘플만 보여 드린 거지요.
위의 두개 SELECT 구문은 데이터를 먼저 찬찬히 봐 보시라는 의미 이며..
세번째 쿼리가 JOIN 쿼리 입니다.
이제 INNER JOIN의 좀더 다른 샘플을 보도록 하지요.
ID-이름 테이블
ID | 이름 |
KONAN | 김대우 |
BAEZZANG | 이동혁 |
DEVIL | 은정범 |
이런 테이블이 있구요.
아울러.
ID - 정보 테이블
ID | 나이 | 성별 |
KONAN | 18 | 남 |
BAEZZANG | 45 | 남 |
의 식이라고 생각해 보세요 아시겠지요?
여기서!!! 가능한 모든 조합을 한번 뽑아 보도록 하지요.
ID-이름의 이름 | ID-이름의 ID | ID-정보의 ID | ID-정보의 나이 | ID-정보의 성별 |
김대우 | KONAN | KONAN | 18 | 남 |
김대우 | KONAN | BAEZZANG | 45 | 남 |
김대우 | KONAN | NULL | NULL | NULL |
이동혁 | BAEZZANG | KONAN | 18 | 남 |
이동혁 | BAEZZANG | BAEZZANG | 45 | 남 |
이동혁 | BAEZZANG | NULL | NULL | NULL |
은정범 | DEVIL | KONAN | 18 | 남 |
은정범 | DEVIL | BAEZZANG | 45 | 남 |
은정범 | DEVIL | NULL | NULL | NULL |
이런 식이 될겁니다.
그렇지요?
여기서!!!
결과들중!!! 바로 파란색으로 된 녀석들!!!!
ID-이름 테이블의 ID컬럼과 ID-정보 테이블의 ID 컬럼이 같은 녀석들이 의미 있는
값이라는 것이지요!!!! 다른 값들은 바로 쓰레기 값이다!!! 라는 의미 입니다.
나온 결과중 의미 있는 결과는!!!
ID-이름의 이름 | ID-이름의 ID | ID-정보의 ID | ID-정보의 나이 | ID-정보의 성별 |
김대우 | KONAN | KONAN | 18 | 남 |
이동혁 | BAEZZANG | BAEZZANG | 45 | 남 |
이런 식이 될겁니다.
이런식으로 생각해 보시면 조금 빨리 이해가 되시지요?
이렇게 여러 조합중 조건에 맞는 즉! 조건 컬럼이 같은 녀석을 뽑아 내는 것이 바로
JOIN 입니다.
따라서 이런 결과과 되지요.
이를 SQL문으로 생각해 본다면?
--실행 안됩니다. SELECT id-이름.이름, id-이름.ID, id-정보.id, id-정보.나이,id-정보.성별 FROM id-이름 INNER JOIN id-정보 ON id-이름.ID = id-정보.ID
|
이런 식이 되겠지요!! 조건은 바로 맨 아래줄의 ON 키워드 라는 것입니다.
자 상당히 많은 내용을 배우셨네요.
다음 내용은? INNER JOIN의 문법과 간단한 샘플들 입니다.
조인의 구현
각 테이블에서 하나의 컬럼을 사용하여 두개의 테이블을 연결하는 것
- 고려사항
- 연결하려는 컬럼은 조인에 포함된 각 테이블에 있는 데이터를 쉽게 일치시키거나 비교할 수 있어야 한다.
- ANSI SQL 문법 또는 SQL 서버 문법을 사용할 수 있다.
- 하나의 SELECT 문에서 동시에 ANSI SQL문법과 SQL서버 문법을 사용 할 수 없다.
- 두 테이블 모두에 존재하는 컬럼 이름을 참조하는 경우에는 반드시 table_name.column_name 형식을 따라야 한다.
조인의 구현 - ANSI SQL 문법
SELECT table_name.column_name [, table_name.column_name…]
FROM {table_name[join_type] JOIN table_name ON search_conditions}
WHERE [search_condition…]
- WHERE 절을 사용한 행의 선택에서 연결된 테이블을 구성한다.
형식
SELECT table_name.column_name[, table_name.column_name…]
FROM {table_name, table_name}
WHERE table_name.column_name join_operator table_name.column_name
- 컬럼 들의 값을 한 행씩 비교하여 비교 결과가 참일때 그 행을 나열한다.
- FROM절에 조인에 관련되는 모든 테이블을 나열하고 WHERE절에 어떤 행동들이 결과에 포함되어야 하는지를 명시한다.
WHERE 절에 사용할 수 있는 연산자들
=, >, <, >=, <=, <>
Inner 조인
두 테이블을 연결 조건에 맞는 행들만 포함하는 세번째 테이블로 연결한다.
내부 연결의 일반적 유형
- Equijoin
- 비교되는 컬럼의 값이 같을 경우에 연결이 이루어진다.
- 중복된 컬럼 정보를 만들게 된다.
- Natural join
- Equijoin 이 만들어 내는 결과 집합에서 중복된 컬럼의 데이터를 제거한다.
SELECT pub_name, title FROM titles INNER JOIN publishers ON titles.pub_id = publishers.pub_id
|
/* ANSI 조인 */ USE pubs SELECT authors.au_lname, authors.state, publishers.* FROM publishers INNER JOIN authors ON publishers.city = authors.city
|
/*T-SQL 조인*/ USE pubs SELECT authors.au_lname, authors.state, publishers.* FROM publishers, authors WHERE publishers.city = authors.city
|
북스 온라인 상에서는 T-SQL조인을 사용하기 보다는 ANSI 조인의 사용을 권하고 있습니다.
T-SQL조인은 사실 약간 모호할 가능성이 있기 때문 입니다.
추후 스터디를 위해서라도 가능하심 ANSI - SQL로 배우시길 바랍니다.
다음은 두번째의 OUTER JOIN 입니다.
LEFT 또는 RIGHT OUTER JOIN.
두 테이블에서 지정된 쪽인 LEFT 또는 RIGHT 쪽의
모든 결과를 보여준후 반대쪽에 대해는 매칭값이 없어도
보여주는 JOIN을 의미
조금 난해 하지요?
역시나 샘플을 보시면? 감이 빡빡 오실 겁니다.
위의 샘플과 마찬 가지로...
ID-이름 테이블
ID | 이름 |
KONAN | 김대우 |
BAEZZANG | 이동혁 |
DEVIL | 은정범 |
이런 테이블이 있구요.
아울러.
ID - 정보 테이블
ID | 나이 | 성별 |
KONAN | 18 | 남 |
BAEZZANG | 45 | 남 |
다시 가능한 모든 조합입니다.
ID-이름의 이름 | ID-이름의 ID | ID-정보의 ID | ID-정보의 나이 | ID-정보의 성별 |
김대우 | KONAN | KONAN | 18 | 남 |
김대우 | KONAN | BAEZZANG | 45 | 남 |
김대우 | KONAN | NULL | NULL | NULL |
이동혁 | BAEZZANG | KONAN | 18 | 남 |
이동혁 | BAEZZANG | BAEZZANG | 45 | 남 |
이동혁 | BAEZZANG | NULL | NULL | NULL |
은정범 | DEVIL | KONAN | 18 | 남 |
은정범 | DEVIL | BAEZZANG | 45 | 남 |
은정범 | DEVIL | NULL | NULL | NULL |
이때 INNER JOIN과 다른점은?
다시 가능한 모든 조합입니다.
ID-이름의 이름 | ID-이름의 ID | ID-정보의 ID | ID-정보의 나이 | ID-정보의 성별 |
김대우 | KONAN | KONAN | 18 | 남 |
이동혁 | BAEZZANG | KONAN | 18 | 남 |
이동혁 | BAEZZANG | BAEZZANG | 45 | 남 |
은정범 | DEVIL | NULL | NULL | NULL |
따라서 이런 결과과 됩니다.
즉!!!
FROM id-이름 LEFT OUTER JOIN id-정보
ON id-이름.ID = id-정보.ID
이런 LEFT OUTER JOIN 키워드가 있을 경우 JOIN의 왼쪽에 표시된 테이블인
id-이름 테이블은 오른쪽에 매칭 되는 결과가 없어도!!! 왼쪽 테이블을 우선 보여 준다는
의미 입니다.
그래서 ID-이름 테이블의 은정범 : DEVIL 은 매칭되는 ID-정보 테이블의 로우가 없어도
우선은 보여 준다는 의미 이지요.
이를 SQL문으로 생각해 본다면?
SELECT id-이름.이름, id-이름.ID, id-정보.id, id-정보.나이,id-정보.성별 FROM id-이름 LEFT OUTER JOIN id-정보 ON id-이름.ID = id-정보.ID
|
의 식이 된다. 이것이 LEFT OUTER JOIN 이다.
RIGHT OUTER JOIN 역시 같습니다. 오른쪽 테이블은 무조건 보이고 왼쪽은 NULL로
표기 한다는 의미 입니다. LEFT 와 같지요?
코난이의 경우 대부분 LEFT를 많은 사람이 사용하는 경우를 보았지..
RIGHT는 쓰시는 분이 거의 없더군요..
그렇다면!!! 이 LEFT OUTER JOIN은 주로 언제 사용하는가!!!
코난이의 경우 몇번 전자 상거래 구축시 사용한 경험이 있습니다.
바로 제품 - 판매량을 볼때 이지요.
특정 제품들의 리스트를 보기위해 아래의 표와 같은 데이터를 보려 합니다.
이런 테이블을 INNER JOIN해 볼때는? C 제품의 경우..
즉!! 하나도 팔리지 않은 제품도 분명히 있을 겁니다.
제품 | 판매량 |
A | 10 |
B | 20 |
C | NULL |
이때..!
C제품도 우선은 보이게 하고 판매량은 NULL로 보이더라도 우선은 보이고 싶을때
입니다. 그래서 LEFT OUTER JOIN을 사용하게 되는 것이지요..
비슷한 케이스가 상당히 많으니.. 이 LEFT OUTER JOIN도 알아두심 많은 도움 되실 겁니다.
이러한 OUTER JOIN의 구문 정보와 샘플 입니다.
Outer 조인
한 테이블에 있는 행에는 제한 조건을 가하지 않는
반면에 다른 테이블에 대해서는 행에 제한을 한다.
고려사항
- 관계된 테이블에서 일치하지 않는 outer 테이블의 모든 행을 보여준다.
- 두 테이블간에만 이루어질 수 있다.
- 기본키와 참조키가 동기화 되지 않았는지 등을 알아보는데 유용하게 사용된다.
SELECT title, stor_id, ord_num, qty, ord_date
|
다음은 CORSS JOIN 입니다.
CROSS JOIN
연관된 두개의 테이블에서 가능한 모든 조합을 찾는다.
SELECT au_fname, au_lname, pub_name |
자 역시 찬찬히 풀어서 말을 드릴 차례 이지요~~
위의 샘플과 마찬 가지로...
ID-이름 테이블
ID | 이름 |
KONAN | 김대우 |
BAEZZANG | 이동혁 |
DEVIL | 은정범 |
이런 테이블이 있구요.
아울러.
ID - 정보 테이블
ID | 나이 | 성별 |
KONAN | 18 | 남 |
BAEZZANG | 45 | 남 |
다시 가능한 모든 조합입니다.
ID-이름의 이름 | ID-이름의 ID | ID-정보의 ID | ID-정보의 나이 | ID-정보의 성별 |
김대우 | KONAN | KONAN | 18 | 남 |
김대우 | KONAN | BAEZZANG | 45 | 남 |
김대우 | KONAN | NULL | NULL | NULL |
이동혁 | BAEZZANG | KONAN | 18 | 남 |
이동혁 | BAEZZANG | BAEZZANG | 45 | 남 |
이동혁 | BAEZZANG | NULL | NULL | NULL |
은정범 | DEVIL | KONAN | 18 | 남 |
은정범 | DEVIL | BAEZZANG | 45 | 남 |
은정범 | DEVIL | NULL | NULL | NULL |
지금 보고 계신 이 모든 가능한 조합이? 바로 CORSS JOIN 의 결과 입니다.
자 여기엔 쓰레기 값이 상당히 많습니다.
LRFT OUTER JOIN에서 사용되는 맨 아래 줄을 제외 하더라도..
6개의 노란 셀로 표시된 로우는 모두 쓰레기 값이라는 것이지요.
그래도 CORSS JOIN에서는? 모두 보여 줍니다.
코난이의 경우 실무에서는 한번도 사용해 본적 없지만..
이런게 있다는 것은 알아 두시길 바랍니다.
다음은 FULL OUTER JOIN 입니다.
FULL OUTER JOIN
LEFT OUTER JOIN의 결과와 RIGHT OUTER JOIN의
결과를 표시한후 한번 중복되는 값(INNER JOIN의 값)의 중복을 제거한 값을 표시한다.
SELECT a.au_fname, a.au_lname, p.pub_name
|
이는? LEFT OUTER JOIN과 RIGHT OUTER JOIN을 실행 해 보심 아실 수 있습니다.
두 OUTER JOIN을 실행한후..
겹치는 부분인 INNER JOIN의 결과 부분이 두번 나오겠지요?
이 INNER JOIN의 결과를 한번 제외한 결과 라고 생각 하심 빠릅니다.
LEFT , RIGHT 조인을 실행 한다고 생각하심 빠르다는 의미 입니다.
코나니는 실무에서 사용해본 경험이 없습니다. - 거의 사용 안한다는 말이지요.
다음은 SELF JOIN 입니다.
SELF JOIN
사용하는 경우 :
1. 계층적인 구조를 테이블화 할 경우.
2. 한 테이블에서 일치하는 값을 찾고자 하는 경우.
아울러 반드시 테이블 Alias 명을 사용해 질의해야 한다.
판매 라는 테이블이 있다고 생각해 보세요..
우선 판매 라는 테이블을 별명으로 판매 a 라고 잡겠습니다.
판매a
구매자 ID | 물품 |
KONAN | 디아블로2 |
KONAN | 스타크래프트 |
BAEZZANG | 니드포스피드 |
DEVIL | 프리셀 |
DEVIL | 디아블로2 |
아울러 판매 b 라는 테이블이 또 있다고 생각 하지요.. 바로
구매자 ID | 물품 |
KONAN | 디아블로2 |
KONAN | 스타크래프트 |
BAEZZANG | 니드포스피드 |
DEVIL | 프리셀 |
DEVIL | 디아블로2 |
이렇게 같은 테이블 입니다.
구매자 ID | 물품 | 구매자 ID |
KONAN | 디아블로2 | DEVIL |
이런 식으로 찾기를 원한다고 생각해 보세요..
바로!!! 같은 물품을 구매한 다른 사람을 찾고 싶을때 입니다.
SELECT au_fname, au_lname, zip, city
|
이 쿼리를 먼저 실행해서 결과를 확인한후 Self-Join 의 쿼리를 실행해 보지요.
결과를 찬찬히 봐 보세요.. ZIP 코드가 같은 작가들이 있습니다.
이때 이를 찾을때 어떻게 찾으면 될까요?
SELECT au1.au_fname, au1.au_lname, au2.au_fname, au2.au_lname, au1.zip
|
이 쿼리는 오클랜드 지역에 사는 작가들중 zip 코드가 같은 사람이 있는가 하는 쿼리 이다.
즉, 오클랜드 지역에 같은 zip코드에 거주하는 사람이 있는가 하는 쿼리 이다.
코나니가 책회사 사장 이라면? 같은 지역에 거주하는 작가들 끼리 서로의 정보를
공유해 더 좋은 책을 쓰게 함 좋겠죠? 이럴 경우 사용 가능한 질의 입니다.
이제 SELF JOIN의 구문정보 입니다.
Self 조인
테이블의 행을 같은 테이블 안에 있는 다른 행과 연관시킨다.
- 비교되는 컬럼은 같은 자료형이어야 하고 여러 방법에 대해 비교 가능해야 한다.
- 같은 테이블을 조인하기 위해서는 하나의 테이블을 두개의 다른 논리적인 테이블로
참조 할 수 있도록 별명을 할당해야 한다.
ANSI SQL 문법
SELECT column_name, column_name [, column_name…]
FROM table_name alias [join_type] JOIN table_name alias
ON search_conditions
T - SQL 서버 문법
SELECT column_name, column_name [, column_name…]
FROM table_name alias, table_name alias [, table_name…]
WHERE alias.column_name join_operator alias.column_name
코나니의 경우 SELF 조인을 가끔 사용한 경험이 있습니다.
아울러 최근 많은 이슈가 되고 있는 CRM(Customer Relationship Management)
에서 종종 사용될 경우가 있으니.. 주의해 보심 많은 도움 되실 겁니다.
다음은 둘 이상의 테이블을 조인할 경우 입니다.
둘 이상의 테이블 조인
ASNI SQL 문법
SELECT table_name.column_name [, table_name.column_name…]
FROM table_name[join_type]
JOIN table_name ON search_conditions…[join_type]JOIN table_name ON search_conditions
WHERE [search_condition…]
T - SQL문법
SELECT table_name.column_name[, table_name.column_name…]
FROM table_name, table_name[, table_name…]
WHERE table_name.column_name join_operator
table_name.column_name
[AND table_name.column_name join_operator
table_name.column_name…]
ANSI 표준을 가능하면 보시고.. 간단히 ON 키워드에 추가추가 하심 됩니다.
/*ASNI 조인*/ SELECT stor_name, qty, title FROM titles INNER JOIN sales ON titles.title_id = sales.title_id INNER JOIN stores ON stores.stor_id = sales.stor_id /*T - SQL 조인*/ SELECT stor_name, qty, title FROM titles, sales, stores WHERE titles.title_id = sales.title_id AND stores.stor_id = sales.stor_id
|
끝으로 조인의 방식에 대한 이야기 입니다.
조인의 방식은 3가지로 나뉘어 집니다.
1. Nested loop Join
2. Hash Join
3. Merge Join
의 식입니다.
이는 조인이 내부적으로 사용하는 알고리즘에 대한 이야기 입니다.
이에 대한 내용은 알고리즘 이야기 인데..
코난이가 화일 시스템 시간에 배웠던 HASH의 이야기완 조금 틀리고..
MERGE 조인의 경우 제가 알고있는 MERGE에 대한 내용과 역시나 틀리 더군요.
아울러 Nested Loop는 루프를 돌며 조건 찾기로 아마 이해가 쉬우실 겁니다.
이에 대한 자료를 sqler의 Tip 게시판에 올려 두었으니 참고 하시길 바랍니다.
많은걸 배우 셨네요...
테이블 다이어 그램 생성하기....
간단한 정규화와 테이블에 대한 짧은 이야기...
조인의 다양한 방식들...
수고하셨네요...
그럼 다음 이야기인.. Sub Query에서 뵙지요.
9. JOIN을 이용한 테이블의 연결 문서의 끝입니다.
-----출처 http://sqler.pe.kr/sql2k/29.asp
1. Collation(정렬)설정하는 법
- 데이터베이스나 컬럼별로 정렬설정가능
- 대소문자구분해야함
2. 로컬접속의 의미
- 네트워크를 타고 밖으로 나갔다가 다시 들어오지 않는 네트워크 라이브러리를 무시함.
3. 멀티인스턴스
- 다중 서버의 존재가능하지만 하나의 서버를 사용하는것을 권장
- 스레드(thread)사용(하나의 프로세서 안에서 연결마다 새로운 스레드를 만드는 것이 훨씬 부하를 적게함)
4. SQL Server 2000에서 멀티 인스턴스를 지원하는 이유
- 개발과 테스트를 동시에 이용
- 클러스터링을 사용할 때 절대적 필요
5. 업그레이드
- 6.5업그레이드 : 업그레이드마법사사용
- 7.0업그레이드 : 자동으로 가능
(7.0과 2000버전은 호환성유지)
---------------------------------------------------------------
<2장 복습문제>
문제) 6.5에서 백업 받은 데이터베이스를 2000에서 바로 리스토어 할 수 있는가?
답) X, 업그레이드 마법사를 사용해야함
문제) 서비스를 시작하고 중지할 수 있는 모든 방법
답)서비스관리자,엔터프라이즈관리자,내컴퓨터/관리/서비스/net start/stop명령
문제) collation은 설치 후에 바꿀 수 있는가? 2000에서의 collation은 7.0과 비교해서 어떤점이 어떤 도구를 사용할 수 있는가?
답) 바꿀 수 있다. 7.0은 설치 시에 지정한 collation만을 사용할수 있지만, 2000은 데이터베이스 마다, 테이블 컬럼 마다 바꿀 수 있다.
문제) 설치후 클라이언트에서 서버로 접속이 되지 않는다. 이때 확인할 것들은 어떤 것이 있는가? 어떤도구를 사용할수있는가?
답) 프로토콜,서비이름,서비스시작여부,로컬서버접속여부 등을 확인한다. readpipe,makepipe,ping등을 사용한다.
문제) NT 4.0에 SQL을 설치하려면 사전에 요구되는것은?
답) 서비스팩5이상설치
---------------------------------------------------------------
< 3장 >
1. 온라인 설명서 사용하기
- 설명서를 참조하여 직접 설치해본다(설치과정)
2. 기본적인 SELECT 문
- SELECT 나열한 컬럼목록
- FROM 가져올테이블
- WHERE 가져올 행의 조건
3. 자료형 함수(책에 나왔던 함수만 시험에 나온대요)
INT 정수형
NUMERIC (= DECIMAL)
FLOAT 부동소수점값
DATETIME 8바이트 날짜와 시간
CHAR 8000자이하의 문자
VARCHAR
4. 사용자 정의 자료형
- 한컬럼에 대한 데이터 유형을 쉽게 사용하려할 때 적용
5. SELECT에서 자료형 바꾸기
- as 를 사용하여 컬럼이름을 바꿀수있다.
- CONVERT : 주어진자료형을 원하는 자료형으로 바꿈
- SUBSTRING : 문자자료형에 대한 길이만 줄여줌
6. 날짜에 대한 함수(이건 너무많아요~책참조)
- 함수
GETDATE () 현재날짜와 시간
DATEADD : datepart 부분에 number값을 더한다
DATEDIFF : 두날짜 사이의 datepart값
- 유형
ANSI yy.mm.dd
USA mm/dd/yy or mm-dd-yy
Japan yy/mm/dd
- datepart
7. 시스템함수(책에 나왔던 함수만 시험에 나온대요)
- ISNULL
- PARSENAME
8. NULL
- ANSI설정에서 널값에 대해 비교할 때 = 를 사용해서는 안된다. 반드시 IS NULL 또는 IS NOT NULL을 사용해야 한다.
9. 문자열/LIKE와 패턴 매칭
- _ : 문자가 와야 한다.
- % : 어떤것이라도 상관없다.
- []: []안에 있는 글자들
- [^]: ^다음에 있는 글자는 제외한 다른것이 와야한다.
10. WHERE 절을 사용할 때 고려 할 것
- *를 사용하지 말라.(작업의 용의를 위하여)
- 연산자 앞에는 컬럼만 오도록 하자(WHERE절에 사용하는 컬럼은 무조건 연산자 왼쪽에 두고 더 이상의 가공을 하지 않도록 하는 것이 좋다. 왜냐하면, NOT을 사용하지 말자는 것과 같다)
- 적절한 ()와 띄어쓰기
---------------------------------------------------------------
<3장 복습문제>
문제) 널값을 계산함수들에서 사용될 때 어떻게 처리되는가? 이때 어떤 경고 메시지가 나타나는가?
답) 널값은 계산함수에서 제외. ANSI WARNING이 설정되었을 때는 경고 메시지가 나타난다.
문제) 다음문장은 무슨 문제를 가지고 있는가?
SELECT 'TODAY IS' + GETDATE ()
정답) 자료형이 맞지 않다
SELECT 'TODAY IS' + CONVERT(VARCHAR(30),GETDATE (),102)
문제) float와 real을 사용하여 부동 소수점 데이터를 처리할 때 나타날 수 있는 문제점은 무엇인가?
답) float와 real은 한계 범위를 넘어가면 부정확한 연산을 한다. decimal 이나 numeric을 사용한다.
문제) WHERE절을 사용할 때 고려할 점은?
답)
1) *를 SELECT문에 사용하지 않도록 한다
2)컬럼이 나온 후에는 바로 연산자가 나오도록 한다
3)적절한 띄어쓰기, ()등을 사용한다.
---------------------------------------------------------------
<4장>
1. 정규화3가지
- 제 1정규화
- 제 2정규화
- 제 3정규화
- 비정규화
---------------------------------------------------------------<5장>
1. 연산함수
- AVG : 각각의 평균값
- COUNT : 각각의 개수
- COUNT (*): 선택된 모든행의 개수
- MAX : 최대값
- MIN : 최소값
- SUM : 각각의 합계
2. ISNULL - NULL 값을 다른값으로 바꾸기
3. GROUP BY / HAVING - 직계함수가 나오면 사용
- GROUP BY : 지정된 컬럼에 대한 그룹지정
- HAVING : 그룹함수에 걸어주는 조건(WHERE와 같은 기능이지만, 반드시 GROUP BY에는 HAVING를 사용해야 함)
4. COMPUTE / COMPUTE BY
ex) select type,title_id, price
from titles
order by type
compute avg(price) by type
= type에 의거해 가격의 평균을 구하고, type로 정렬해서 세부내역을 보여달라.
※ compute by 와 order by의 정렬순서가 만드시 같아야 함.
5. ROLLUP 과 CUBE
- GROUP BY 하단에 WITH ROLLUP(컬럼안에 자료를 그룹으로 묶어서 결과처리, 오른쪽에서 왼쪽으로 결과돌출)
- CUBE 는 ROLLUP 처리후 그 바로밑에 컬럼의 기준을 바꾸어 한번더 자료를 보여줌)
6. JOIN (정규화로 나누어진 테이블 혹은 컬럼을 다시 모음)
- INNER JOIN : 교집합과 같은 내용
- CROSS JOIN : 모든 자료를 곱한 결과
- OUTER JOIN : 기준에 맞추어(오른쪽,왼쪽) 조건에 맞는 데이터만 불러옴(기준은 그대로 존재하고 상대TABLE은 조건데이터만 불러옴, 조건에 맞지 않으면 모두 NULL값으로 나옴)
- SELF JOIN : 스스로의 자료를 조건에 맞게 불러오기 위해서 자기를 복사해서 자료를 돌출하되, 조건문에서 WHERE과 AND를 사용하여 중복되는것은 빼고 불러온다(<,>사용)
※ JOIN의 경우에는 ANSI문법을 그대로 사용할것을 권장함. bug의 주요 요인이 될수있음.
7. 하위질의(=부질의=subquery)
- select안에 또다시 select가 존재함. 안쪽 질의의 결과에 의해 수행됨.
- 하위질의(안쪽)질의만 수행해도 결과가 수행된다.
8. 상관하위질의(Correlated subquery)
- 상위질의에서 선택된 행이 안쪽 질의의 WHERE 절에 다시 참조됨.(바깥-안쪽-바깥)
9. IN / EXISTS
- ex) where title_id in(select title_id from sales)
= sales에서 title_id를 보여달라. 그리고 그 결과를 다시 title_id에 넣어서 보여달라.(괄호안의 항목과 일치하는 것이 있어야 참이 됨)
- DISTINCT : 내용이 중복된 것은 삭제
- ex) where exist(select * from titles .....)
= 괄호안에 어떤 것이든 결과만 돌려지만 참이 된다.
10. SELECT INTO
- 현재있는 테이블의 내용 전체나 일부를 선택하여 새로운 테이블을 만들 때 사용.
- 새로만들기 : ex) select * into sales_temp from sales
= sales로부터 sales_temp 테이블만듬.
- 삭제하기 : ex) drop table sales_temp
- ex) select * into #sales from sales(임시테이블)
ex) select * into ##sales from sales(프로그램닫을때까지만의 임시테이블)
11. UNION
- JOIN은 정규화된 테이블을 연결시키기 위한 반면,
UNION은 비정규화된 테이블을 연결시키기 위함.(자료형과 순서등이 맞으면 내용이 맞지않게 그냥 불러옴)
---------------------------------------------------------------
<5장 복습문제>
문제) JOIN을 걸면 데이터는 정렬되는가?
답) 정렬되지 않을수도 있다.정렬하려면 ORDER BY를 사용해야한다.
문제) 하위질의와 상관하위 질의의 차이는 무엇인가?
답) 하위질의-괄호안의 질의를 따로 실행시키면 수행되지만, 성관관계가 있는 하위질의는 그 자체만으로 수행되지 않는다. 상관관계있는 하위질의는 바깥(OUTER)질의의 결과가 안쪽(INNER)질의에 영향을 미치고 다시 그결과를 바깥질의에 돌려주기 때문이다.
문제) 같은 테이블을 둘로 나누었을 때(수평적으로)이를 연결시키는 문장은 무엇인가?
답) UNION
문제) 하위질의와 JOIN으로 모두 원하는 자료 처리 가능한 경우는 어느 것으로 처리하는 것이 일반적으로 나은가?
답) 하위질의보다 JOIN일 일반적으로 더 빠른 처리를 한다.
---------------------------------------------------------------
<6장>
1. 데이터베이스 개체 이름 붙이기
- 형식 :
server_name.datebase_name.owner_name.object_name
(소유자이름,테이블이름)
2. 테이블만들기
- ex) create table tempdb.dbo.emp1(id int,name char(10)
- ex) create table tempdb.dbo.emp1
(id int not null,name char(10) null)
= 널값허용여부지정하기
3. 테이블 수정하기
- 새로운컬럼추가
= 형식) alter table 내테이블
add 새컬럼 varchar(20) null
- 컬럼변경하기
= 형식) alter table 내테이블
alter column 새컬럼 varchar(40) null
- 컬럼삭제하기
= 형식) alter table 내테이블
drop column 새컬럼
- 테이블삭제하기
= 형식) drop table table_name
- 기본키설정하기
ex) create table pk_test (in int primary key)새테이블
ex) create table pk_test (in int not null)
go alter table a add
constraint pk_id primary key (id) 기존테이블
---------------------------------------------------------------
<6장 복습문제>
문제) 이미 데이터가 들어있는 테이블에 널을 허용하지 않는 새로운 컬럼을 추가할 수있는가? 어떻게 해야하나?
답) 할수있다. 디폴트 값을 설정하면 된다.
문제) 한글 테이블명/컬럼명을 사용해도 되는가?
답) 된다.
문제) 설치 할 때 사용한 자료 정렬은 모든 테이블을 만들때 다시는 병경할 수 없다(참,거짓)
답) 거짓, COLLATE을 사용하여 테이블을 만들때 변경할 수 있다.
문제) 데이터베이스 개체의 완전한 이름을 붙이는 규칙은 어떻게 되는가?
답) 서비이름, 데이터베이스이름, 소유자,개체이름이다.
문제) EM에서 만든 테이블의 스크립트는 어떻게 만들어 내는가?
답) EM/해당테이블/모든작업/SQL스크립트 생성으로 할 수 있다.
---------------------------------------------------------------
<7장>
1. INSERT
- 형식) INSERT 테이블
VALUES 컬럼에 들어갈 값
2. IDENTITY 와 DEFAULT
- IDENTITY : 데이터가 순차적으로 자동 들어가지는 값
- TIMESTAMP : 입력된 시간이 서버에 도장처럼 찍혀져있다.
3. INSERT...SELECT : SELECT...INTO
- 이미 존재하는 테이블에서 새로운 테이블을 만들어 데이터를 입력하고자 할때 사용.
4. DELETE
- 형식) DELETE 테이블명
5. 트랜잭션에 대한 기초개념
- 지운값을 다시 되돌릴때 사용
- ROLLBACK :되돌리기(취소)
- COMMIT : 그대로 저장
※ COMMIT 한후에 ROLLBACK는 안된다.
6. TRUNCATE TABLE
- 우리가 보이는 데이터는 똑같고, 대신 주소값만 삭제됨
7. UPDATE
8. 한UPDATE문은 같은 데이터를 두 번이상 UPDATE할 수없다.
---------------------------------------------------------------
<7장 복습문제>
문제) 계속적으로 증가하는 값을 사용하기 위해서는 어떤 속성을 사용하여야 하는가?
답) IDENTITY속성을 사용한다.
문제) 변경시킨 데이터를 되돌릴 수있는가?
답) 트랜잭션을 사용하면 된다. BEGIN TRAN / COMMIT TRAN을 사용.
문제) 이미존재하는 테이블에 다량의 데이터를 입력하려면 어떤 문장을 사용하여야 하는가?
답) INSERT...SELECT를 사용한다.
문제) DELETE 과 TRUNCATE TABLE의 차이점은 무엇인가?
답) DELETE은 WHERE절을 사용할 수 있으며 실제 데이터를 모두 로그에 기록하며 지우는데 비해, TRUN-CATE TABLE은 데이터를 모두 지우며(WHERE)절을 사용할 수 없다), 실제 행들을 하나씩 지우는 것이 아니라 전체를 지우기 때문에 빠르다.
---------------------------------------------------------------
<8장>
1. 데이터 무결성 3가지
영역무결성(entity) : 다른영역은 들어갈 수없다.
실체무결성(domain) : 나는유일하다. 둘이면 하나는 귀신~
참조무결성(referential) : 참조당하는것은 바뀔수없다.
2. 절차적방법/서술적방법
절차적 : 먼저정의하고, 다시 바인드하는 식으로 절차를 거쳐 정의하고 사용한다. 재활용 가능
서술적 : 테이블을 서술할 때 함께 서술됨. 재활용이 불가능하지만 문서화를 하기에 매우 쉽고, 읽기 좋다.
3. 컬럼에 대한 세가지 속성
NOT NULL(NN) 널을 허락하지 않음.NOT NULL, PR KEY
NO Duplicate(ND) 중복된값을 허락하지 않음.UNIQUE, PR KEY
NI Change(NC) 값의 변경을 허락하지 않는다.
(FOREIGN KEY로 참조당할 때)
4. NULL (P240 참조)
5. 고유제약(UNIQUE)
6. IDENTITY(P246 참조)
7. DEFAULT제약과 절차적 방법의 DEFAULT(247)
8. RULE과 CHECK 제약
9. 참조키
10. 제약중지시키기/기존 데이터 검사하지 않기
(Disable/Defer)
11. 무결성 강화 객체들을 사용할 때 고려할 사항
(256그림중요)
---------------------------------------------------------------
<8장 복습문제>
문제) 무결성을 강화시켜주는 구성 요소들을 나열해 보라
답) 자료형, 사용자정의 자료형,스토어드 프로시저,색인
제약(기본키,참조키,UNIQUE, DEFAULT),RULE,DEFAULT,트리거
문제) 기본키는 NN, ND, NC중에서 어떤것들을 지켜주는가?
답) NN, ND
문제) 참조키는 NN, ND, NC중에서 어떤것들을 지켜주는가?
답) NC
문제) 기본키와 유일 제약의 차이는 무엇인가?
답) 널값의 허용여부
문제) 7.0이후 널 값을 사용할 때 왜 주의해야 하는가?
답) ANSI92규정을 지키고자 변경된 것들이 많다.
예를 들어 널 + 문자는 널이다.
문제) 참조 키를 만들면 색인이 만들어 지는가?
답) 색인이 만들어지지 않는다
David Chappell
Chappell & Associates
2006년 9월
적용 대상:
Windows Vista
Windows Presentation Foundation
Microsoft .NET Framework 3.0
요약: WPF(Windows Presentation Foundation)의 기본적인 목표는 개발자가 세련되고 편리한 사용자 인터페이스를 만들 수 있도록 돕는 것입니다. 이 문서에서는 WPT 통합 플랫폼을 사용하여 디자이너가 사용자 인터페이스 개발 작업에 보다 적극적으로 참여하는 방법과 독립 실행형 응용 프로그램 및 브라우저 응용 프로그램을 만들기 위한 공통적인 프로그래밍 모델을 제공하는 방법에 대해 설명합니다.
Windows Presentation Foundation 설명
문제점 예시
문제점 해결: Windows Presentation Foundation의 기능
Windows Presentation Foundation 사용
Windows Presentation Foundation의 기술
Windows Presentation Foundation 적용
Windows Presentation Foundation 도구
개발자용: Visual Studio
디자이너용: Expression Interactive Designer
Windows Presentation Foundation 및 기타 Microsoft 기술
Windows Presentation Foundation과 Windows Forms
Windows Presentation Foundation과 Win32/MFC
Windows Presentation Foundation과 Direct3D
Windows Presentation Foundation과 AJAX/"Atlas"
Windows Presentation Foundation과 "WPF/E"
결론
저자 소개
기본적으로 기술은 주로 전문가들이 중요하게 생각하는 부분으로 대부분의 소프트웨어 전문가들은 응용 프로그램과 사용자 간의 상호 작용 방식보다는 응용 프로그램의 작동 방식에 훨씬 더 많은 관심을 가집니다. 그러나 응용 프로그램을 구입하는 실제 사용자에게는 사용자 인터페이스가 매우 중요합니다. 응용 프로그램의 인터페이스는 해당 소프트웨어에 대한 전체적인 사용자 환경에서 중요한 부분을 차지하며, 사용자에게 이러한 환경은 응용 프로그램 '그 자체'를 의미합니다. 더 나은 인터페이스를 통해 향상된 사용자 환경을 제공하면 생산성을 높이고 우수 고객을 더 많이 확보할 수 있으며 웹 사이트에서의 매출을 늘리는 등 다양한 효과를 얻을 수 있습니다.
이전에는 문자 기반 인터페이스만으로 충분했지만 오늘날의 사용자들은 그래픽 인터페이스에 익숙해졌으며 사용자 인터페이스에 대한 요구 사항은 계속해서 증가하고 있습니다. 그래픽과 미디어가 더욱 광범위하게 사용되고 있으며 웹의 발전은 소프트웨어와의 편리한 상호 작용을 기대하는 사용자 계층을 형성하게 되었습니다. 사용자들이 응용 프로그램을 사용하는 시간이 늘어날수록 해당 응용 프로그램의 인터페이스는 더욱 중요해집니다. 이렇듯 점점 높아지는 인터페이스 요구 사항에 부응하기 위해서는 사용자 인터페이스를 만드는 기술도 함께 발전해야 합니다.
WPF(Windows Presentation Foundation)의 목표는 Windows에 바로 이러한 고급 기능을 제공하는 것입니다. Microsoft .NET Framework 버전 3.0에 포함된 WPF를 사용하면 문서, 미디어, 2차원 및 3차원 그래픽, 애니메이션, 웹 특성 등을 포함하는 인터페이스를 만들 수 있습니다. WPF는 .NET Framework 3.0의 모든 구성 요소와 마찬가지로 Windows Vista, Windows XP 및 Windows Server 2003에서 사용할 수 있으며 Windows Vista와 함께 출시될 예정입니다. 이 백서에서는 WPF를 소개하고 WPF의 다양한 구성 요소에 대해 설명하며 이 기술을 통해 해결할 수 있는 문제점과 WPF가 제공하는 솔루션에 대해 살펴봅니다.
병원에서 환자를 진료하고 모니터링하는 데 사용할 새 응용 프로그램을 만든다고 가정해 보겠습니다. 새로 만들 응용 프로그램에는 다음과 같은 사용자 인터페이스가 필요할 수 있습니다.
이러한 요구 사항은 야심적이기는 하지만 충분히 실현 가능합니다. 필요한 정보를 적시에 적절한 방법으로 제공하는 사용자 인터페이스는 업무상 중요한 가치를 지닙니다. 앞에서 설명한 의료 응용 프로그램의 예에서는 적절한 사용자 인터페이스가 생명을 구할 수도 있습니다. 온라인 상점 또는 기타 고객 지향 응용 프로그램과 같은 일반적인 시나리오의 경우 강력한 사용자 환경을 제공하면 회사의 서비스를 경쟁사의 서비스와 차별화하여 매출 증진은 물론 회사의 브랜드 가치를 높일 수 있습니다. 중요한 점은 많은 수의 최신 응용 프로그램에서 그래픽, 미디어, 문서 및 기타 최신 사용자 환경의 요소를 통합하는 인터페이스를 제공함으로써 다양한 이점을 누릴 수 있다는 것입니다.
2006년 현재의 기술로 Windows에 이러한 유형의 인터페이스를 만들 수도 있지만 이러한 작업은 다음과 같은 주요 장애 요인으로 인해 실현하기 매우 어렵습니다.
강력한 기능을 제공하는 최신 사용자 인터페이스를 만드는 작업을 복잡하게 생각할 필요는 전혀 없습니다. 공통적인 기반을 사용하면 이러한 모든 문제를 해결하고 개발자에게 단일화된 접근 방식을 제공하는 동시에 디자이너도 작업에서 중요한 역할을 담당하도록 할 수 있습니다. 이것이 바로 WPF의 목표이며, 자세한 내용은 다음 섹션에서 설명합니다.
WPF가 제공하는 가장 중요한 세 가지 특징은 최신 사용자 인터페이스를 제공하기 위한 통합 플랫폼, 개발자와 디자이너가 공동으로 작업할 수 있는 환경, 그리고 Windows 사용자 인터페이스와 웹 브라우저 사용자 인터페이스를 개발하기 위한 공통의 기술입니다. 이 섹션에서는 이러한 세 가지 특징에 대해 설명합니다.
WPF를 사용하지 않는 환경에서 위 설명과 같은 Windows 사용자 인터페이스를 만들기 위해서는 여러 가지 기술을 함께 사용해야 합니다. 다음 표에서는 필요한 기술을 간략하게 보여 줍니다.
Windows Forms | Windows Forms/ GDI+ |
Windows Media Player | Direct3D | WPF | |
---|---|---|---|---|---|
그래픽 인터페이스(예: 폼 및 컨트롤) | X | X | |||
화면에 표시되는 문서 | X | X | |||
고정된 형식의 문서 | X | X | |||
이미지 | X | X | |||
비디오 및 오디오 | X | X | |||
2차원 그래픽 | X | X | |||
3차원 그래픽 | X | X |
개발자가 폼, 컨트롤 및 기타 Windows 그래픽 사용자 인터페이스의 일반적인 요소를 만들려면 대개 .NET Framework의 일부인 Windows Forms를 사용해야 합니다. 인터페이스에서 문서를 표시해야 할 경우 Windows Forms에서 부분적으로 지원되는 화면 표시 문서를 사용할 수 있지만 고정된 형식의 문서를 사용해야 할 경우에는 Adobe의 PDF 형식이 적합합니다. 이미지와 2차원 그래픽을 표시하려는 경우 개발자는 Windows Forms를 통해 액세스할 수 있는 고유한 프로그래밍 모델인 GDI+를 사용할 수 있습니다. 또한 비디오와 오디오 기능은 Windows Media Player를 통해 제공하고 3차원 그래픽 기능은 Windows 표준인 Direct3D를 통해 제공할 수 있습니다.
이렇게 복잡한 작업은 단지 이론적인 설명을 위한 것일 뿐이며 이를 실제로 실행하는 것은 적절하지 않다는 데 누구나 동의할 것입니다. 그러나 통합된 단일 솔루션인 WPF에서는 이 모든 것이 가능합니다. WPF가 설치된 컴퓨터에서 응용 프로그램을 만드는 개발자는 WPF 하나만으로 위에서 언급한 모든 문제를 해결할 수 있습니다. 즉, 이제는 여러 가지 독립적인 기술을 사용하는 대신 하나의 일관된 기반을 사용하여 사용자 인터페이스를 만들 수 있습니다.
물론 WPF가 이 표에 나와 있는 모든 기술을 대체하지는 않습니다. Windows Forms 응용 프로그램은 여전히 유용하게 활용될 것이며, WPF 환경에서도 일부 새 응용 프로그램에는 Windows Forms를 사용해야 할 수 있습니다. WPF를 Windows Forms와 상호 운용할 수 있다는 점은 주목할 만한 가치가 있습니다. 이에 대한 자세한 내용은 이 문서의 뒷부분에서 설명합니다. Windows Media Player도 여전히 독자적인 가치를 제공할 것이며 PDF 문서도 계속해서 사용될 것입니다. Direct3D 또한 게임이나 몇 가지 다른 유형의 응용 프로그램에서는 중요한 기술입니다. 실제로 WPF에서도 Direct3D를 통해 모든 렌더링 작업을 수행합니다.
그러나 WPF라는 단일 기술을 통해 광범위한 기능을 제공하면 최신 사용자 인터페이스를 훨씬 쉽게 구현할 수 있다는 장점이 있습니다. 다음 화면에서는 이러한 통합된 접근 방식의 이점을 쉽게 이해할 수 있도록 앞에서 설명한 WPF 기반 버전의 의료 응용 프로그램을 보여 줍니다.
그림 1. WPF 인터페이스에서는 이미지, 텍스트, 2D 그래픽, 3D 그래픽 등을 결합할 수 있습니다.
이 화면에서는 텍스트와 이미지를 비롯하여 2차원 그래픽 및 3차원 그래픽을 함께 볼 수 있습니다. 이 모든 인터페이스는 개발자가 GDI+ 또는 Direct3D와 같은 특수 그래픽 기술을 사용하는 코드를 작성할 필요 없이 WPF를 사용하여 생성되었습니다. WPF를 사용하면 다음 그림에서 볼 수 있는 초음파 진단 결과처럼 비디오를 표시하고 메모를 입력하는 기능을 구현할 수도 있습니다.
그림 2. WPF 인터페이스에는 비디오뿐만 아니라 사용자가 입력할 수 있는 텍스트 메모를 모두 포함할 수 있습니다.
WPF를 사용하면 읽기 편리한 방식으로 문서를 표시할 수 있습니다. 예를 들어 병원 응용 프로그램의 경우 의사가 환자의 처방전에 대한 기록을 조회하거나 관련 항목에 대한 최근의 의학 연구 자료에 액세스할 수 있습니다. 아래의 화면에서 볼 수 있는 것처럼 의사는 메모를 추가할 수 있습니다.
그림 3. WPF 인터페이스에서는 메모를 포함하여 여러 개의 열이 있는 문서를 표시할 수 있습니다.
이 화면에서 문서는 가독성이 뛰어난 열 형식으로 표시되고 사용자는 스크롤 방식이 아니라 문서를 한 페이지씩 이동할 수 있습니다. 화면을 보기 쉽게 표시하는 것도 WPF의 중요한 목표 중 하나입니다. 그러나 화면에 표시되는 문서보다 고정된 형식의 문서를 사용하는 것이 적절한 경우도 있습니다. 고정된 형식의 문서는 화면에서 볼 때나 인쇄했을 때 모양이 동일하기 때문에 항상 일관된 모양을 제공합니다. 이러한 형식의 문서를 정의하기 위해 Microsoft는 XPS(XML Paper Specification)를 만들었습니다. WPF는 개발자가 XPS 문서를 만들고 작업하는 데 사용할 수 있는 API(응용 프로그래밍 인터페이스) 그룹도 제공합니다.
그러나 최신 사용자 인터페이스를 만드는 것은 독립적인 여러 기술을 단순히 통합하는 것이 아니라 최신 그래픽 카드의 장점을 활용할 수 있다는 의미를 가지고 있습니다. 즉, WPF는 시스템의 GPU(Graphics Processing Unit)에 가능한 한 많은 작업 로드를 분산시킴으로써 GPU를 최대한 활용합니다. 또한 최신 인터페이스는 비트맵 그래픽의 한계에 제약을 받지 않아야 하므로 WPF는 화면의 크기와 해상도에 맞게 이미지 크기가 자동으로 조정되도록 벡터 그래픽 기술을 사용합니다. 따라서 개발자는 작은 모니터와 대형 TV 화면에 표시할 그래픽을 개별적으로 만들지 않고 WPF를 통해 이러한 크기 조정 작업을 자동으로 처리할 수 있습니다.
WPF는 사용자 인터페이스를 만드는 데 필요한 모든 기술을 하나의 기반에 집약함으로써 이러한 인터페이스를 만드는 개발자의 업무 부담을 크게 낮추어 줍니다. WPF를 사용하는 경우 개발자는 하나의 작업 환경만 파악하면 되기 때문에 응용 프로그램 개발 및 유지 관리에 소요되는 비용이 훨씬 저렴합니다. 또한 WPF를 사용하면 그래픽, 비디오 등의 다양한 요소가 포함된 인터페이스를 간단하게 구현할 수 있으므로 사용자가 Windows 응용 프로그램과 상호 작용하는 방식을 개선하고 업무 효율성을 높일 수 있습니다.
모든 기능을 갖춘 사용자 인터페이스를 만들기 위한 통합된 기술 기반을 제공하는 것은 매우 바람직합니다. 그러나 개발자가 외부의 도움 없이 이러한 강력한 기능을 제대로 활용하여 이해하기 쉽고 사용이 편리한 인터페이스를 만들 수 있을 것이라고 기대하는 것은 무리입니다. 특히 앞에서 설명한 병원 사례와 같이 포괄적이고 유용한 사용자 인터페이스를 만들기 위해서는 대부분의 소프트웨어 전문가들이 갖추지 못한 인터페이스 디자인 기술이 필요합니다. 소프트웨어 전문가들이 많은 수의 응용 프로그램을 단독으로 개발하고 있지만 최상의 사용자 인터페이스를 구현하기 위해서는 전문 인터페이스 디자이너의 도움이 필요합니다.
그러나 개발자와 디자이너는 업무 진행 방식이 서로 다르기 때문에 두 분야의 전문가가 공동으로 작업하는 데는 여러 가지 문제가 있습니다. 일반적으로 디자이너는 그래픽 도구를 사용하여 응용 프로그램에 표시할 화면 레이아웃의 정적 이미지를 만듭니다. 그런 다음 이러한 이미지를 개발자에게 전달하고, 개발자는 해당 이미지를 구현하는 코드를 작성합니다. 그러나 디자이너는 만들기 쉽더라도 개발자는 구현하기 어렵거나 전혀 구현할 수 없는 이미지도 있습니다. 예를 들어 개발자는 기술적인 한계, 촉박한 일정, 기술 부족, 사소한 오해 또는 단순한 의견 차이로 인해 디자이너의 의도를 완벽하게 구현하지 못할 수 있습니다. 따라서 최상의 인터페이스를 만들기 위해서는 독립적인 두 분야의 전문가가 인터페이스의 품질을 떨어뜨리지 않으면서 공동으로 작업할 수 있는 환경이 필요합니다.
WPF는 이러한 공동 작업이 가능하도록 XAML(eXtensible Application Markup Language)을 사용합니다. XAML은 사용자 인터페이스의 모양을 정확하게 정의하기 위해 Button, TextBox, Label 등 여러 XML 요소의 집합을 정의합니다. 일반적으로 XAML 요소는 다양한 옵션을 설정할 수 있도록 특성도 포함합니다. 예를 들어 다음의 간단한 XAML 코드는 "아니요"라는 단어가 표시되는 빨간색 단추를 만듭니다.
<Button Background="Red"> 아니요 </Button>
각 XAML 요소는 WPF 클래스에 해당하고 요소의 각 특성은 클래스의 해당 속성이나 이벤트를 갖습니다. 예를 들어 다음과 같은 C# 코드를 사용하여 동일한 빨간색 단추를 만들 수 있습니다.
Button btn = new Button(); btn.Background = Brushes.Red; btn.Content = "아니요";
XAML로 표현할 수 있는 모든 것을 코드를 사용하여 동일하게 작성할 수 있다면 XAML을 사용할 필요가 없다고 생각할 수도 있습니다. 그러나 XML 기반의 설명을 생성하고 사용하는 작성 도구를 사용하면 코드를 사용하여 동일한 작업을 할 때보다 훨씬 쉽게 작업할 수 있습니다. XAML은 사용이 편리한 도구 방식으로 사용자 인터페이스를 설명하기 때문에 개발자와 디자이너가 더 효율적으로 함께 작업할 수 있습니다. 다음 그림은 이러한 작업 과정을 보여 줍니다.
그림 4. 디자이너와 개발자의 공동 작업을 돕는 XAML
디자이너는 Microsoft Expression Interactive Designer와 같은 도구를 사용하여 사용자 인터페이스의 모양과 상호 작용 방식을 지정할 수 있습니다. 이 도구는 WPF 인터페이스의 모양과 느낌을 정의하는 용도로 사용되며 해당 인터페이스에 대한 설명을 XAML 표현으로 생성합니다. 예제로 사용된 간단한 단추만 포함하더라도 이 설명은 실제로 위에 나온 코드 조각보다 훨씬 복잡합니다. 개발자는 이 XAML 설명을 Microsoft Visual Studio와 같은 도구로 가져옵니다. 이렇게 하면 디자이너가 만든 정적 이미지를 토대로 인터페이스를 처음부터 다시 만들 필요 없이 인터페이스 정의 자체를 그대로 가져올 수 있습니다. 그런 다음 개발자는 이벤트 처리기와 같이 인터페이스에 필요한 코드 및 응용 프로그램에 필요한 기타 기능에 해당하는 코드를 작성합니다. 또한 응용 프로그램 인터페이스에 전체적으로 적용할 수 있는 스타일을 만들 수도 있으며, 이러한 스타일은 나중에 필요에 따라 사용자 지정할 수 있습니다.
디자이너와 개발자가 이러한 방식으로 함께 작업하면 디자이너가 만든 이미지를 토대로 개발자가 인터페이스를 구현할 경우 발생할 수 있는 변환 오류를 줄일 수 있을 뿐만 아니라 이러한 두 분야의 전문가가 반복 작업을 신속하게 수행하고 효과적으로 의견을 교환하면서 동시에 작업을 진행할 수 있습니다. 또한 두 환경 모두에서 동일한 빌드 시스템을 사용하므로 두 개발 환경 간에 WPF 응용 프로그램을 주고 받을 수도 있습니다. 3차원 인터페이스 요소를 만들기 위한 Electric Rain의 ZAM 3D와 같이 XAML로 정의된 인터페이스를 디자인하는 데 사용할 수 있는 특수한 도구도 있습니다.
더 나은 인터페이스를 사용하면 생산성을 높일 수 있을 뿐만 아니라 여러 가지 측면에서 업무상 매우 유용합니다. 그러나 효과적인 인터페이스를 만드는 데는 특히 WPF가 제공하는 다양한 환경의 경우 디자이너의 역할이 가장 중요합니다. XAML 및 XAML을 지원하는 도구의 기본적인 목표는 디자이너의 작업을 돕는 것입니다.
Windows 응용 프로그램에 사용할 효과적인 사용자 인터페이스를 만드는 것도 중요하지만 웹 기반 응용 프로그램에 사용할 효과적인 인터페이스를 만드는 것도 그만큼 중요합니다. 기본적으로 이러한 인터페이스는 웹 브라우저에서 제공합니다. 인터페이스를 만드는 가장 간단한 방법은 브라우저에 전달되는 모든 HTML을 브라우저가 수동적으로 표시하도록 하는 것입니다. 응답 성능이 더 뛰어난 브라우저 인터페이스에서는 대개 AJAX(Asynchronous JavaScript And XML)를 사용하여 JavaScript로 실행되는 논리를 제공합니다. Adobe Flash Player 또는 몇 가지 다른 기술을 사용하여 애니메이션, 비디오 등을 인터페이스에 사용할 수도 있습니다. 이와 같이 기능이 다양한 인터페이스 유형을 제공하는 웹 소프트웨어를 '다기능 인터넷 응용 프로그램'이라고도 합니다. 이 웹 소프트웨어를 사용하면 사용자 환경을 크게 향상시킬 수 있을 뿐만 아니라 더 많은 사용자의 참여를 유도하는 웹 응용 프로그램을 만들어 상당한 비즈니스 가치를 얻을 수 있습니다.
일반적으로 이러한 유형의 인터페이스를 구현하기 위해서는 기본적인 Windows 인터페이스에 사용되는 것과는 완전히 다른 기술을 사용해야 합니다. 결과적으로 개발자는 서로 다른 두 가지 작업, 즉 Windows 인터페이스를 개발하는 작업 또는 웹 인터페이스 개발하는 작업 중 하나에 초점을 두고 작업해야 합니다. 그러나 Windows에서 액세스할 다기능 인터넷 응용 프로그램을 만드는 데 이러한 양분된 방식으로 작업할 필요는 없으며 기본적인 Windows 인터페이스와 웹 브라우저 인터페이스 모두에 동일한 기술을 사용하지 못할 이유도 없습니다.
WPF를 사용하면 동일한 기술을 사용하여 Windows 인터페이스와 웹 브라우저 인터페이스 모두를 만드는 것이 가능합니다. 개발자는 WPF를 사용하여 Internet Explorer에서 실행되는 XBAP(XAML Browser Application)를 만들 수 있습니다. 실제로 동일한 코드를 사용하여 독립 실행형 WPF 응용 프로그램과 XBAP를 만들 수 있습니다. 예를 들어 아래의 화면에서는 독립 실행형 Windows 응용 프로그램으로 실행되는 재무 서비스 응용 프로그램을 보여 줍니다. 앞에서 설명한 병원 응용 프로그램과 마찬가지로 이 응용 프로그램도 텍스트, 이미지 및 다양한 종류의 그래픽을 함께 사용합니다. 이 화면에서는 시계 등의 가젯 및 응용 프로그램 창 주위에 반투명 테두리를 사용하는 Aero 테마가 적용된 Windows Vista 바탕 화면도 볼 수 있습니다.
그림 5. 재무 서비스 응용 프로그램을 독립 실행형 WPF 응용 프로그램으로 실행할 수 있습니다.
다음 그림에서는 이 인터페이스를 Internet Explorer에서 XBAP로 실행할 때의 모양을 보여 줍니다.
그림 6. 동일한 응용 프로그램을 XBAP로도 실행할 수 있습니다.
이 경우 인터페이스가 자체 창에서 실행되는 대신 브라우저에서 실행된다는 점만 다를 뿐 모든 기능은 동일합니다. 두 경우 모두에 동일한 코드를 사용하면 두 가지 유형의 인터페이스를 개별적으로 만드는 데 소요되는 것보다 작업량을 줄일 수 있습니다. 동일한 코드를 사용한다는 것은 동일한 개발자를 투입할 수 있음을 의미합니다. WPF를 사용하면 개발자를 Windows 인터페이스 개발자 또는 웹 인터페이스 개발자라는 분리된 두 개의 그룹으로 구분할 필요 없이 하나의 개발 리소스를 두 작업 모두에 활용할 수 있습니다.
동일한 기술을 사용하여 Windows 인터페이스와 웹 인터페이스를 만들면 응용 프로그램 작성자가 응용 프로그램의 인터페이스 유형을 미리 결정하지 않아도 된다는 또 하나의 장점이 있습니다. 즉, 대상 클라이언트가 XBAP를 실행하는 데 필요한 요구 사항을 충족하기만 하면 응용 프로그램에서는 거의 동일한 코드를 사용하여 Windows 인터페이스나 웹 인터페이스 또는 두 가지 인터페이스 모두를 제공할 수 있습니다.
XBAP는 필요할 때 웹 서버에서 다운로드되기 때문에 독립 실행형 Windows 응용 프로그램에 비해 더욱 엄격한 보안 요구 사항이 적용됩니다. 이러한 이유로 XBAP는 .NET Framework의 코드 액세스 보안에서 제공하는 보안 샌드박스(Security Sandbox)에서 실행됩니다. 또한 XBAP는 WPF가 설치되어 있는 Windows 시스템에서 Internet Explorer 버전 6 및 7에서만 실행됩니다. 이러한 요구 사항이 충족되면 인터넷 응용 프로그램이 독립 실행형 Windows 응용 프로그램과 동일한 기반을 활용하는 것이 가능합니다.
WPF로 해결할 수 있는 문제점만 알고 있어도 도움이 되지만 WPF가 이러한 문제점을 해결하는 방법을 이해한다면 더 큰 도움이 됩니다. 이 섹션에서는 WPF 기술 자체를 자세하게 살펴본 후 Windows 데스크톱 응용 프로그램, XBAP 및 XPS 문서에 WPF 기술이 적용되는 다양한 방식에 대해 알아봅니다.
WPF는 사용자 인터페이스를 만들기 위한 통합된 기반을 제공하지만 WPF에 포함된 기술을 문서, 이미지, 그래픽, 애니메이션 등과 같은 개별 단위로 나누어 살펴볼 수 있습니다. 이러한 모든 개별 단위는 다음에 설명할 WPF의 기본 응용 프로그램 모델에 의존합니다.
.NET Framework의 다른 구성 요소와 마찬가지로 WPF의 기능은 System.Windows 네임스페이스에 포함된 네임스페이스 그룹으로 나뉩니다. 모든 WPF 응용 프로그램의 기본 구조는 어느 기능을 사용하는지에 관계없이 거의 동일합니다. 일반적으로 응용 프로그램은 독립 실행형 Windows 응용 프로그램이나 XBAP에 관계없이 XAML 페이지 집합과 이들 페이지에 연결된 코드로 구성됩니다.
기본적으로 모든 응용 프로그램은 WPF의 표준 Application 클래스에서 상속됩니다. 이 클래스는 모든 응용 프로그램에서 유용하게 사용할 수 있는 공통 서비스를 제공하며, 여기에는 전체 응용 프로그램에 필요한 상태를 유지하는 서비스 및 응용 프로그램을 시작하는 Run, 응용 프로그램을 종료하는 Shutdown 등의 표준 메서드를 제공하는 서비스가 포함됩니다.
Application 개체는 Application 요소를 통해 XAML을 사용하여 만들거나 Application 클래스를 통해 코드를 사용하여 만들 수 있습니다. 이러한 특징은 사실상 WPF의 모든 요소에 적용되지만 이 문서에서는 간단히 설명하기 위해 XAML을 사용하는 방식만을 소개할 것입니다. 다음은 간단한 XAML을 보여 줍니다.
<Application xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Page1.xaml" x:Class="Example.SimpleApp"> . . . </Application>
이 정의에서는 먼저 WPF 네임스페이스를 이 요소의 기본값으로 지정한 다음 XAML 네임스페이스의 접두사를 정의합니다. XAML은 WPF에만 사용되는 것이 아니므로 이 두 네임스페이스는 서로 다릅니다. 다음으로 StartupUri 특성을 사용하여 응용 프로그램을 시작했을 때 로드하고 표시할 XAML 페이지의 이름을 나타냅니다. 마지막 특성인 Class는 이 Application과 관련된 코드가 들어 있는 클래스를 식별하는 데 사용됩니다. 앞에서 살펴본 것처럼 일반적으로 WPF 응용 프로그램에는 C# 또는 Visual Basic으로 작성된 코드와 XAML이 모두 들어 있으므로 이 클래스의 코드는 '코드 숨김' 파일에 저장됩니다. 여는 Application 태그 다음에는 이 응용 프로그램을 정의하는 데 필요한 나머지 XAML이 위치하고(여기에서는 생략됨), 마지막으로 Application 요소의 닫는 태그가 위치합니다.
모든 WPF 응용 프로그램은 동일한 루트 클래스에서 파생되지만 개발자는 다양한 옵션을 선택할 수 있습니다. 그 중 가장 중요한 옵션은 응용 프로그램에 일반적인 대화 상자 형식의 인터페이스를 사용할지 아니면 탐색 형식의 인터페이스를 사용할지 결정하는 것입니다. 대화 상자 형식의 인터페이스는 Windows 사용자라면 누구나 익숙한 단추 및 기타 요소를 제공합니다. 반면 탐색 형식의 인터페이스는 브라우저와 동작 방식이 거의 유사합니다. 예를 들어 탐색 형식의 인터페이스를 사용하면 대화 상자가 새 창에서 열리는 대신 새 페이지가 로드됩니다. 이와 같은 인터페이스는 페이지 그룹으로 구현되며, 각 페이지는 XAML에 정의된 사용자 인터페이스와 프로그래밍 언어로 표현된 논리로 구성됩니다. HTML로 정의된 브라우저 페이지와 마찬가지로 XAML은 페이지를 서로 연결하는 데 사용할 수 있는 Hyperlink 요소를 제공합니다. 사용자는 웹 기반 응용 프로그램의 페이지를 탐색할 때와 같은 방법으로 기록 목록을 통해 앞으로 또는 뒤로 이동하면서 여러 페이지를 탐색할 수 있습니다. 그러나 이것은 Windows 응용 프로그램이므로 혼동하지 말아야 합니다. 일반적으로 XBAP에서 이러한 유형의 인터페이스를 사용하지만, 필요에 따라 독립 실행형 Windows 응용 프로그램에서도 탐색 형식의 인터페이스를 통해 사용자와 상호 작용할 수 있습니다. 어떤 방식을 사용할지는 응용 프로그램을 만드는 개발자의 선택에 달려 있습니다.
응용 프로그램에서 어떤 인터페이스 스타일을 사용하든지 여부에 관계없이 응용 프로그램은 일반적으로 하나 이상의 창을 표시합니다. WPF는 창을 표시하기 위한 몇 가지 옵션을 제공합니다. 기본 Window 클래스에서는 창을 표시하고 숨기고 닫는 등의 기본적인 창 기능을 제공합니다. 일반적으로 탐색 형식의 인터페이스를 사용하지 않는 WPF 응용 프로그램에서 이 클래스를 사용합니다. NavigationWindow 클래스는 탐색 형식의 인터페이스를 사용하는 응용 프로그램에서 사용하며 기본 Window 클래스에 탐색 기능이 추가됩니다. 추가적인 탐색 기능으로는 응용 프로그램에서 새 페이지로 이동하는 데 사용할 수 있는 Navigate 메서드, 사용자의 탐색 기록을 저장하는 추적 정보 및 다양한 탐색 관련 이벤트가 있습니다.
WPF 응용 프로그램에서는 인터페이스의 다양한 요소를 구성하기 위해 '패널'을 사용하여 레이아웃을 지정합니다. 각 패널에는 단추, 텍스트 상자 등의 '컨트롤' 및 다른 패널이 자식 요소로 포함될 수 있습니다. 패널 종류마다 서로 다른 레이아웃 옵션을 제공합니다. 예를 들어, 이름에서 알 수 있듯이 DockPanel을 사용하면 자식 요소를 패널 가장자리에 도킹하여 배치할 수 있고, Grid를 사용하면 자식 요소를 눈금 위에 정확하게 배치할 수 있습니다. 이때 개발자는 눈금의 행 및 열 개수를 정의한 다음 자식 요소를 배치할 정확한 위치를 지정해야 합니다. Canvas를 사용하면 패널 경계 내의 아무 곳에나 자식 요소를 자유롭게 배치할 수 있습니다.
WPF는 다른 사용자 인터페이스 기술과 마찬가지로 다양한 컨트롤 집합을 제공합니다. 개발자는 사용자 지정 컨트롤을 만들 수도 있습니다. 표준 컨트롤 집합에는 Button, Label, TextBox, ListBox, Menu, Slider 및 기타 일반적인 사용자 인터페이스 디자인 요소가 포함됩니다. 또한 SpellCheck, PasswordBox, 잉크(예: Tablet PC)로 작업하는 데 필요한 컨트롤 등 보다 복잡한 컨트롤도 사용할 수 있습니다.
WPF 응용 프로그램에서는 일반적인 그래픽 인터페이스와 마찬가지로 마우스 이동 및 키 누르기 동작과 같이 사용자가 생성하는 이벤트를 컨트롤을 통해 포착하고 처리할 수 있습니다. 컨트롤 및 기타 사용자 인터페이스 요소는 XAML을 사용하여 완벽하게 지정할 수 있지만 이벤트는 코드에서 처리해야 합니다. 예를 들어 다음 XAML에서는 Canvas에 간단한 Button을 정의합니다.
<Canvas xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Example.CodeForCanvas"> <Button Click="Button_Click"> 여기를 클릭하십시오. </Button> </Canvas>
여는 Canvas 태그는 일반적인 WPF 및 XAML 네임스페이스 정의로 시작됩니다. 다음으로 이 XAML과 관련된 코드를 .NET Framework 네임스페이스 Example에 들어 있는 CodeForCanvas 클래스에서 찾을 수 있음이 지정되고, 그 다음에 "여기를 클릭하십시오."라는 화면 표시 텍스트를 지정하는 Button이 정의됩니다. 여는 Button 태그의 Click 특성은 이 단추의 click 이벤트가 Button_Click이라는 메서드를 사용하여 처리됨을 나타냅니다. 다음은 이 메서드의 코드입니다.
namespace Example { public partial class CodeForCanvas : Canvas { void Button_Click(object sender, RoutedEventArgs e) { Button btn = e.Source as Button; btn.Background = Brushes.Purple; } } }
네임스페이스 및 클래스의 이름은 위의 Canvas 태그에 지정된 것과 일치합니다. CodeForCanvas 클래스는 WPF에서 제공하는 기본 Canvas 클래스에서 상속되며 partial 클래스로 정의됩니다. Partial 클래스는 .NET Framework 버전 2.0에 새로 추가된 요소로서 개별적으로 정의된 코드를 단일 클래스로 결합하는 데 사용됩니다. 이 경우 XAML로 정의한 Canvas에서 생성되는 partial 클래스와 이 코드의 partial 클래스가 결합됩니다. 그 결과 단추가 포함된 캔버스를 표시하고 해당 단추의 이벤트도 처리할 수 있는 완전한 클래스가 생성됩니다.
이벤트를 처리하는 Button_Click 메서드는 CodeForCanvas 클래스 안에 선언됩니다. 이 메서드는 일반적인 .NET Framework 규칙에 따라 이벤트를 처리하지만 이벤트의 인수는 WPF로 정의된 RoutedEventArgs 클래스를 사용하여 전달됩니다. 이 클래스의 Source 속성은 이벤트를 생성한 Button에 대한 참조를 포함하며 메서드는 이 정보를 사용하여 단추의 색을 보라색으로 변경합니다.
이러한 간단한 예제에서 볼 수 있는 것처럼 WPF 사용자 인터페이스의 요소는 시각적 트리 형식으로 구성됩니다. 이 예제에서는 트리가 Canvas와 유일한 자식 요소인 Button으로 구성되지만 실제 WPF 응용 프로그램에서는 일반적으로 트리가 훨씬 더 복잡해집니다. 화면 표시 인터페이스를 실제로 만들려면 이 시각적 트리를 렌더링해야 합니다. WPF는 가능한 한 하드웨어 렌더링을 사용함으로써 응용 프로그램의 컴퓨터에 설치되어 있는 그래픽 카드를 통해 작업을 처리합니다. 그러나 컴퓨터의 그래픽 하드웨어로 작업을 처리할 수 없는 경우 WPF는 자체 소프트웨어를 사용하여 인터페이스를 렌더링합니다. 이러한 결정은 런타임에 WPF에서 내리기 때문에 개발자는 별도의 작업을 할 필요가 없습니다.
렌더링을 하드웨어 또는 소프트웨어에서 처리하는지 여부에 관계없이 WPF는 항상 유지 모드 그래픽이라는 방식을 사용합니다. 응용 프로그램 작성자가 일반적으로 XAML과 코드를 조합하여 시각적 트리의 모양을 정의하면 WPF 자체에 이 트리의 정보가 유지됩니다. 예를 들어 사용자가 창을 표시할 경우 응용 프로그램에서 창 전체 또는 일부를 다시 그릴 필요 없이 WPF에서 이 작업을 자체적으로 처리합니다. 트리를 구성하는 요소는 화면에 픽셀로 저장되지 않고 개체로 저장되기 때문에 WPF는 이러한 유형의 렌더링을 처리하는 데 충분한 정보를 항상 유지합니다. 창과 창에 포함된 컨트롤의 크기를 조정하는 경우에도 WPF는 자체적으로 모든 렌더링을 다시 수행할 수 있습니다. WPF는 선, 타원 등의 그래픽 형태를 파악하고 비트맵이 아니라 벡터 그래픽을 사용하기 때문에 이와 같은 충분한 정보를 사용하여 새로운 크기로 인터페이스를 다시 만들 수 있습니다.
몇 가지 사용자 인터페이스 요소의 모양을 한 번 정의한 다음 그 모양을 여러 번 다시 적용하면 편리한 경우가 있습니다. 예를 들어 HTML 페이지에서는 CSS(Cascading Style Sheet)를 사용하여 이러한 작업을 수행할 수 있습니다. WPF에서는 '스타일'이 이와 유사한 기능을 합니다. 이미 널리 사용되고 있는 CSS 스타일시트의 경우에서 볼 수 있듯이 스타일을 정의하는 기능을 매우 유용하게 사용할 수 있습니다. 예를 들어 스타일을 사용하면 디자이너와 개발자의 작업 영역을 보다 명확하게 구분할 수 있기 때문에 디자이너가 인터페이스에 일관된 모양을 만들면 개발자는 이러한 세부 사항에 신경쓰지 않아도 됩니다.
WPF 응용 프로그램 작성자는 XAML의 Style 요소를 사용하여 특정 항목의 모양에 대한 특징을 하나 이상 정의한 후 해당 스타일을 반복적으로 적용할 수 있습니다. 예를 들어 ButtonStyle이라는 스타일을 다음과 같이 정의할 수 있습니다.
<Style x:Key="ButtonStyle"> <Setter Property="Control.Background" Value="Red"/> <Setter Property="Control.FontSize" Value="16"/> </Style>
이 스타일을 사용하여 정의하는 모든 단추에는 빨간색 배경이 사용되고 글꼴 크기가 16으로 지정됩니다. 예를 들면 다음과 같습니다.
<Button Style="{StaticResource ButtonStyle}"> 여기를 클릭하십시오. </Button>
이 예제에 나와 있는 "StaticResource"의 형태에서 알 수 있듯이 일반적으로 WPF 스타일은 리소스로 정의됩니다. 리소스는 응용 프로그램 코드와는 별도로 정의되는 데이터입니다.
스타일은 이 예제에서 설명한 것보다 훨씬 다양한 기능을 제공합니다. 예를 들어 다른 스타일에서 스타일을 파생시킬 수 있습니다. 이 경우 원래의 설정을 상속하거나 필요에 따라 재정의할 수 있습니다. 스타일을 사용하면 대화형 동작의 일반적인 측면을 지정하는 '트리거'를 정의할 수도 있습니다. 예를 들어 마우스로 단추를 가리키면 단추 배경이 노란색으로 바뀌도록 스타일을 지정할 수 있습니다.
WPF는 '서식 파일' 기능도 지원합니다. 서식 파일은 스타일과 유사하며, 다음과 같은 두 가지 유형으로 나뉩니다.
응용 프로그램의 인터페이스 모양을 간단하게 정의할 수 있는 방법을 제공하면 응용 프로그램 작성자가 작업하는 데 큰 도움이 될 수 있습니다. WPF에서는 기본적으로 스타일과 서식 파일을 통해 인터페이스 모양을 보다 쉽게 정의할 수 있습니다.
대부분의 사용자 인터페이스에는 적어도 어느 정도의 텍스트가 표시되고, 다른 특성은 거의 없이 텍스트만 표시되는 사용자 인터페이스도 있습니다. 그러나 대다수의 사용자들은 화면에서 텍스트를 읽는 것보다 인쇄된 페이지를 읽는 것을 더 선호합니다. 사람들은 책이나 잡지에서 일반적으로 볼 수 있는 고품질의 문자 모양과 글자에 익숙해져 있기 때문에 화면에 표시되는 텍스트를 읽을 때는 왠지 모르게 불편함을 느끼는 것이 사실입니다.
WPF는 인쇄된 페이지를 읽는 것처럼 화면 표시 텍스트도 편하게 읽을 수 있도록 이러한 차이를 줄이는 것을 목표로 합니다. 이러한 목표의 일환으로 WPF에서는 기존 글꼴 라이브러리를 사용할 수 있도록 산업 표준의 OpenType 글꼴은 물론 최근에 정의된 ClearType 기술도 지원합니다. ClearType은 최신 모니터에서 각 픽셀을 구성하는 하위 요소를 개별적으로 다듬는 기법인 하위 픽셀 위치 조정을 통해 텍스트를 보다 매끄럽게 표시하는 기술입니다. WPF는 또한 Glyphs 클래스를 통한 텍스트 렌더링 기능을 부분적으로 지원합니다. 이 클래스는 XPS 문서에서 문자를 나타내는 데 사용되며, 자세한 내용은 이 문서의 뒷부분에서 설명합니다.
WPF는 가독성을 높이기 위해 문자 그룹을 연결된 단일 이미지로 대체하는 합자 방식도 지원합니다. 예를 들어 "ffi"라는 문자 그룹은 일반적으로 인쇄 페이지에서는 이 세 문자가 들어 있는 연결된 단일 합자로 대체됩니다. 이 합자를 화면 표시 텍스트에 사용하면 사용자는 사용된 세부적인 기술은 눈치채지 못한 채 인쇄된 페이지를 보는 것처럼 텍스트를 편하게 읽을 수 있습니다.
텍스트는 단추, 목록 및 사용자 인터페이스의 다른 여러 요소에 표시되므로 텍스트의 가독성을 높이는 것은 매우 바람직합니다. 그러나 텍스트는 문서에서와 같이 더 긴 단락 형식으로 표시되는 경우에 그 기능이 더 중요합니다. 따라서 화면 표시 가독성을 높이기 위해서는 문서가 표시되는 방법도 함께 개선해야 합니다. 이를 위해 WPF에서는 두 가지 유형의 문서, 즉 '고정' 문서와 '동적' 문서를 지원합니다.
고정 문서는 화면에서 렌더링할 때나 인쇄할 때의 모양이 동일합니다. 일부 양식, 법률 문서, 기타 출판물 유형 등 문서 모양을 항상 일관되게 유지해야 하는 경우에는 고정된 형식의 문서를 사용하는 것이 좋습니다. WPF에서 지원하는 고정된 형식의 문서는 XPS로 정의됩니다. XPS에 대한 자세한 내용은 이 문서의 뒷부분에서 설명합니다. 고정 문서의 내용은 XAML의 FixedDocument 요소를 사용하여 지정할 수 있습니다. 이 간단한 요소에는 PageContent 요소의 목록만 포함되고, 각 PageContent 요소에는 고정 문서에 포함된 각 페이지의 이름이 들어 있습니다. WPF에서는 DocumentViewer 컨트롤을 사용하여 고정 문서를 표시합니다. 이 컨트롤은 XPS 문서를 읽기 전용 모드로 표시하고, 사용자는 문서에서 뒤로 또는 앞으로 이동하거나 특정 텍스트를 검색하는 등의 작업을 할 수 있습니다.
고정 문서가 화면과 인쇄 용지 모두에서 사용할 수 있는 문서 유형이라면 동적 문서는 화면 표시 전용 문서입니다. 동적 문서는 문서 내용의 가독성을 최대한 높이기 위해 창 크기와 기타 요인에 따라 텍스트와 그래픽을 자동으로 조정하여 표시합니다. 고정 문서에 대한 설명을 통해 예상할 수 있듯이 동적 문서는 FlowDocument라는 XAML 요소를 사용하여 정의됩니다. 다음은 간단한 예제입니다.
<FlowDocument ColumnWidth="300" IsColumnWidthFlexible="True" IsHyphenationEnabled="True"> <Paragraph FontSize="12"> <Bold>WPF 설명</Bold> </Paragraph> <Paragraph FontSize="10"> WPF는 .NET Framework 3.0을 위한 사용자 인터페이스 기술입니다. WPF는 문서, 2차원 및 3차원 그래픽, 미디어 등을 지원할 뿐만 아니라 최신 사용자 인터페이스를 만들기 위한 통합된 기반을 제공합니다. 또한 동일한 방법과 동일한 코드를 사용하여 독립 실행형 Windows 응용 프로그램 및 브라우저 내에서 실행되는 응용 프로그램을 만들 수 있도록 지원합니다. </Paragraph> </FlowDocument>
이 문서는 너비가 300이상인 열에 표시되도록 지정됩니다. 이때 너비는 각 픽셀이 1/96인치인 '장치 독립적 픽셀' 단위로 측정됩니다. 그러나 문서 작성자는 바로 다음 줄에 IsColumnWidthFlexible 속성을 true로 설정하여 이 너비를 변경 가능하도록 설정했습니다. 이 설정은 이 문서를 표시하는 데 사용되는 열의 개수와 너비를 WPF에서 변경할 수 있도록 허용합니다. 예를 들어 이 문서가 표시되는 창의 너비를 사용자가 변경하면 WPF에서는 문서의 텍스트를 표시하는 데 사용되는 열의 개수와 너비를 적절하게 늘리거나 줄일 수 있습니다.
다음으로 이 문서에서는 하이픈을 사용하도록 IsHyphenationEnabled 속성이 true로 설정되었습니다. 바로 아래에는 이 문서에 포함된 두 개의 단락이 나옵니다. 각 단락의 텍스트는 글꼴 크기가 서로 다르게 설정된 두 개의 Paragraph 요소 안에 각각 들어 있고 첫 번째 단락의 텍스트는 굵게 표시되도록 설정되었습니다.
WPF에서는 가독성을 높이기 위한 몇 가지 FlowDocument 옵션을 추가적으로 정의합니다. 예를 들어 IsOptimalParagraphEnabled 속성을 true로 설정하면 WPF는 단락 전체에서 공백을 가능한 한 균등하게 사용합니다. 이 속성을 사용하면 가독성을 떨어뜨리는 "연속적인" 공백이 나타나는 것을 방지할 수 있습니다. 이 방법은 인쇄된 문서에 일반적으로 사용됩니다. 동적 문서에는 일반 텍스트나 잉크(Tablet PC에 해당)에 노트를 추가하는 것과 같이 메모를 사용할 수도 있습니다. 각 메모는 문서에서 메모가 연결되어 있는 내용을 식별하는 '앵커(anchor)'와 메모 내용 자체가 들어 있는 '카고(cargo)'로 구성됩니다.
WPF에서는 다음과 같은 몇 가지 컨트롤을 사용하여 FlowDocument를 표시할 수 있습니다.
디지털 방식으로 전달되는 정보가 증가함에 따라 화면 표시 가독성도 점차 그 중요성이 높아지고 있습니다. WPF는 동적 문서를 통해 정보를 보다 효과적으로 표시함으로써 더욱 편리한 Windows 사용자 환경을 제공합니다.
회사 로고, 노을 사진 등 이미지는 많은 사용자 인터페이스의 기본 요소입니다. WPF에서 이미지는 일반적으로 Image 컨트롤을 사용하여 표시됩니다. 예를 들어 JPEG 파일을 표시하려면 다음과 같은 XAML을 사용할 수 있습니다.
<Image Width="200" Source="C:\Documents and Settings\All Users\Documents\ My Pictures\Ava.jpg" />
이 예제에서는 이미지 너비를 200으로 설정합니다. 이 경우에도 장치 독립적 픽셀 단위가 사용됩니다. Source 특성은 이미지가 들어 있는 파일을 식별합니다.
이미지 파일에는 키워드, 사용자가 지정한 등급 등 이미지에 대한 정보를 나타내는 메타데이터가 들어 있으며 WPF 응용 프로그램에서는 이 정보를 읽고 기록할 수 있습니다. 회전하는 3차원 개체에 그림을 그리는 것처럼 더 재미있는 방법으로 이미지를 사용할 수도 있습니다. 이 문서에 포함된 간단한 예제에서는 가장 일반적인 이미지 사용 방법을 보여 주지만 WPF에서 이미지를 사용하는 방법은 이보다 훨씬 다양합니다.
WPF의 Image 컨트롤은 JPEG, BMP, TIFF, GIF, PNG 등의 다양한 형식뿐만 아니라 Windows Vista에 새롭게 추가된 Microsoft Windows Media Photo(WMPhoto) 형식의 이미지도 표시할 수 있습니다. 이미지 형식에 관계없이 WPF는 WIC(Windows Imaging Component)를 사용하여 이미지를 생성합니다. WIC는 위에서 언급한 모든 이미지 형식에 대한 코더/디코더(일반적으로 '코덱'이라고 함)뿐만 아니라 타사 코덱을 추가할 수 있는 프레임워크도 제공합니다.
네트워크 및 프로세서 속도가 빨라짐에 따라 비디오는 사용자가 소프트웨어와 상호 작용하는 방식에서 중요한 부분을 차지하게 되었습니다. 또한 사용자들은 컴퓨터에서 음악이나 기타 오디오를 듣는 데 많은 시간을 보내기도 합니다. 이러한 요구에 부응하기 위해 WPF는 비디오 및 오디오 기능을 기본적으로 제공합니다.
비디오 및 오디오 기능은 MediaElement 컨트롤을 통해 제공됩니다. 다음은 이 컨트롤을 사용하는 간단한 XAML 예제입니다.
<MediaElement Source="C:\Documents and Settings\All Users\Documents\ My Videos\Ruby.wmv" />
이 컨트롤은 WMV, MPEG 및 AVI 비디오와 다양한 형식의 오디오를 재생할 수 있습니다.
지난 20년 동안 Windows용 2차원 그래픽 작성자들은 GDI(Graphics Device Interface)와 그 후속 버전인 GDI+를 사용해왔습니다. 그러나 2차원 그래픽은 사용자 인터페이스 기술에 통합되어 있지 않기 때문에 Windows Forms 응용 프로그램에서도 별도의 네임스페이스를 통해 이 기능에 액세스해야 합니다. 3차원 그래픽의 경우에는 완전히 다른 기술인 Direct3D를 사용해야 하기 때문에 작업이 더 번거로웠습니다. 그러나 WPF를 사용하면 대부분의 응용 프로그램에서 이러한 복잡한 작업 과정을 단순화할 수 있습니다. 즉, 2차원 그래픽과 3차원 그래픽 모두를 XAML에서 직접 만들거나 WPF 라이브러리를 사용하여 프로시저 코드에서 만들 수 있습니다. 이러한 작업에 사용되는 요소는 WPF의 다른 모든 요소와 마찬가지로 응용 프로그램의 시각적 트리의 일부입니다.
2D 그래픽의 경우 WPF는 응용 프로그램에서 이미지를 만드는 데 사용할 수 있도록 다음과 같은 '모양' 그룹을 정의합니다
이 클래스를 사용하면 간단한 그래픽을 쉽게 만들 수 있습니다. 예를 들어 다음 XAML은 빨간색 타원을 그립니다.
<Ellipse Width="30" Height="10" Fill="Red" />
모양을 채우려면 '브러시'를 사용합니다. 위의 예제에서는 기본값인 단색 브러시를 사용하지만 WPF에서는 몇 가지 다른 옵션도 제공합니다. 예를 들어 다음 예제에서는 가로 방향으로 빨간색에서 노란색으로 바뀌는 색 그라데이션으로 채워진 사각형을 정의합니다.
<Rectangle Width="30" Height="10" Fill="HorizontalGradient Red Yellow" />
이 외에도 이미지, 비트맵 등을 사용하여 그리는 브러시, 수직 그라데이션 및 방사형 그라데이션을 비롯하여 여러 가지 다른 브러시를 사용할 수 있습니다. 이 문서에서 다루지는 않지만 모양에 '펜'을 사용하여 윤곽선의 색, 너비 및 스타일을 지정할 수도 있습니다.
WPF에서는 모든 요소가 공통의 기반에서 구현되므로 서로 다른 요소를 간단하게 결합할 수 있다는 점을 기억해 두십시오. 예를 들어 응용 프로그램에서는 Rectangle 안에 Image를 표시하고, Button 안에 Ellipse를 배치하는 등 다양한 방법으로 요소를 결합할 수 있습니다. 따라서 2D 그래픽을 3D 그래픽 및 다른 인터페이스 요소에 결합하는 것 역시 매우 간단합니다.
WPF는 모양 외에도 2차원 그래픽 작업에 사용할 수 있는 다른 클래스 그룹도 제공합니다. '기하 도형'이라고 하는 이러한 클래스는 모양과 여러모로 비슷합니다. Line, Rectangle, Ellipse 및 Path 같은 옵션을 제공하는 모양과 마찬가지로 이 클래스도 LineGeometry, RectangleGeometry, EllipseGeometry 및 PathGeometry 같은 옵션을 제공합니다. 일반적으로 모양은 실제 이미지를 그리는 데 사용되고 기하 도형은 주로 영역을 정의하는 데 사용된다는 것이 이 두 클래스 간의 가장 중요한 차이점입니다. 예를 들어 정사각형 이미지를 원 안에 맞추기 위해 잘라야 할 경우 EllipseGeometry 클래스를 사용하여 원의 경계를 지정할 수 있습니다. 마찬가지로 응용 프로그램에서 마우스 클릭이 감지된 영역과 같이 적중 테스트 영역을 정의해야 하는 경우에는 해당 영역에 기하 도형을 지정하면 됩니다.
이 섹션에서 설명한 모든 내용은 실제로 '시각적 레이어'라는 하위 수준의 인터페이스를 기반으로 구현된다는 점을 알아야 합니다. 이 레이어를 직접 사용하여 그래픽, 이미지 및 텍스트를 만들 수도 있습니다. 간단한 고성능 그래픽을 만들 때와 같이 레이어를 직접 사용하는 방법이 유용할 때도 있지만 대부분의 응용 프로그램에서는 WPF가 제공하는 모양 및 다른 상위 수준의 추상화를 사용합니다.
2차원 그래픽은 Windows 인터페이스의 일반적인 요소이기 때문에 WPF에서는 2차원 그래픽과 관련된 다양한 기술을 제공합니다. 반면, 3차원 그래픽은 뛰어난 데이터 시각화, 3D 차트, 제품 렌더링 등의 다양한 기능을 통해 상당한 가치를 제공할 수 있음에도 불구하고 아직까지는 2차원 그래픽만큼 일반화되지 않았습니다. 이전에는 3D로 작업하기 위해 주로 게임 개발자나 기타 전문 집단에서만 사용하는 고유한 기술을 사용해야 했습니다. 그러나 WPF는 3D 그래픽 기능을 표준 환경에 통합함으로써 이전과는 다른 새로운 작업 환경을 제공합니다.
WPF가 없는 경우 Windows 플랫폼에서는 Direct3D API를 사용하여 3D를 개발해야 합니다. WPF의 다른 요소와 마찬가지로 WPF의 3D 그래픽 기능은 기본적으로 Direct3D에 기반을 두고 있지만 개발자의 실제 작업 환경은 훨씬 단순해졌습니다. 이 문서의 뒷부분에서 설명하는 것처럼 WPF보다는 Direct3D를 사용하는 것이 더 적합한 경우도 있지만, Microsoft의 목표는 Windows 인터페이스에 사용할 대부분의 3D 작업에 WPF를 사용하도록 하는 것입니다.
WPF에서는 응용 프로그램에 Viewport3D 컨트롤을 사용하여 3D 그래픽을 표시합니다. 이 컨트롤은 기본적으로 응용 프로그램에서 나타내는 3차원 공간에 창을 제공합니다. Viewport3D 컨트롤은 WPF 인터페이스의 아무 곳에나 사용할 수 있기 때문에 3D 그래픽을 원하는 위치에 표시하는 것이 가능합니다.
3D 그래픽을 만들 경우 개발자는 하나 이상의 '모델'을 정의한 다음, 이러한 모델에 적용할 조명과 보기 방식을 지정합니다. 이 경우에도 XAML이나 코드 또는 두 방법을 함께 사용하여 모든 옵션을 지정할 수 있습니다. WPF에서는 모델의 모양을 정의할 수 있는 GeometryModel3D 클래스를 사용하여 모델을 정의합니다. 모델을 정의한 후에는 다양한 종류의 '재질'을 사용하여 모델의 모양을 제어할 수 있습니다. 예를 들어 SpecularMaterial 클래스는 표면에 광택 효과를 적용하지만 DiffuseMaterial 클래스는 이 효과를 적용하지 않습니다.
사용한 재질에 관계없이 다양한 방법으로 모델에 조명 효과를 적용할 수 있습니다. DirectionalLight 클래스는 특정 방향의 광원을 제공하고 AmbientLight 클래스는 전체적으로 균일한 조명을 제공합니다. 마지막으로 모델을 보는 방식을 정의하는 '카메라'를 지정합니다. 예를 들어 PerspectiveCamera를 사용하면 모델을 보는 거리와 원근감을 지정할 수 있고, OrthographicCamera를 사용하면 원근감만 제외하고 동일한 옵션을 지정할 수 있습니다. 즉, 이 경우에는 카메라에서 멀리 떨어진 개체도 더 작게 표시되지 않습니다.
XAML 또는 코드에서 직접 복잡한 3D 화면을 만드는 것은 쉽지 않습니다. 3D를 사용하는 대부분의 WPF 응용 프로그램에서 개발자는 그래픽 도구를 사용하여 필요한 정의를 생성하는 경우가 많습니다. 어떤 방법을 사용하든지 표준 사용자 인터페이스에 3D 그래픽을 사용하면 사용자에게 표시되는 화면의 품질을 크게 향상시킬 수 있습니다.
WPF에서는 개발자가 모양 및 기타 요소를 정의할 수 있을 뿐만 아니라 회전, 크기 변경 등의 작업을 통해 요소를 변환할 수도 있습니다. XAML에서는 RotateTransform 및 ScaleTransform 같은 요소를 사용하여 이러한 작업을 할 수 있습니다. 이러한 변환은 모든 사용자 인터페이스 요소에 적용할 수 있습니다. 다음은 변환 작업을 보여 주는 간단한 예제입니다.
</Button> <Button Content="여기를 클릭하십시오."> <Button.RenderTransform> <RotateTransform Angle="45" /> </Button.RenderTransform> </Button>
RotateTransform 요소는 단추를 45도 회전시킵니다. 이 방법으로 단추를 회전시키는 것은 그다지 유용하지 않지만 이러한 기능이 있다는 것만으로 WPF 디자인의 다양성을 짐작할 수 있습니다. 사용자 인터페이스의 여러 요소는 동일한 기술에 기반을 두고 있기 때문에 여러 가지 방법으로 결합될 수 있습니다.
WPF는 몇 가지 미리 정의된 효과를 제공합니다. 변환과 마찬가지로 이러한 효과도 Buttons, ComboBoxes 등 사용자 인터페이스의 다양한 요소에 적용할 수 있습니다. 미리 정의된 효과로는 인터페이스 요소를 흐릿하게 표시하는 흐리게 효과, 요소를 빛나게 만드는 외부 글로우 효과, 인터페이스 요소 뒤에 그림자를 추가하는 그림자 효과 등이 있습니다.
인터페이스의 요소를 움직이게 만드는 애니메이션을 사용하면 여러 가지로 매우 유용합니다. 예를 들어 단추를 클릭할 때 단추가 실제로 눌린 것처럼 아래로 내려갔다가 다시 올라오면 훨씬 실감나는 인터페이스를 연출할 수 있습니다. 이보다 복잡한 애니메이션을 사용하여 사용자의 주의를 끌거나 이야기를 들려주는 방법으로 사용자의 참여를 유도하는 인터페이스를 만들 수 있습니다. WPF의 애니메이션 기능은 이러한 모든 효과를 지원합니다.
변환과 마찬가지로 애니메이션을 단추, 모양, 이미지 등 사용자 인터페이스의 다양한 요소에 적용할 수 있습니다. 애니메이션은 시간 흐름에 따라 개체의 속성 중 하나 이상의 속성 값을 변경하여 만듭니다. 예를 들어 Ellipse의 Height 속성 값을 2초 동안 조금씩 줄이면 타원이 천천히 찌그러지는 것처럼 보일 수 있습니다.
서로 관련된 애니메이션 그룹을 정의하면 유용할 수 있습니다. WPF에서 제공하는 Storyboard 클래스를 사용하면 이러한 작업을 할 수 있습니다. 각 Storyboard는 하나 이상의 '시간 표시 막대'를 포함할 수 있고, 각 시간 표시 막대는 하나 이상의 애니메이션을 포함할 수 있습니다. 제공되는 다양한 시간 표시 막대를 사용하면 애니메이션을 순차적으로 실행하거나 병렬로 실행할 수 있습니다. 다음은 Ellipse를 찌그러뜨리는 애니메이션을 보여 주는 간단한 XAML 예제입니다.
<Ellipse Width="100" Height="50" Fill="Blue" Name="EllipseForSquashing"> . . . <Storyboard> <DoubleAnimation Storyboard.TargetName="EllipseForSquashing" Storyboard.TargetProperty="Height" From="50" To="25" Duration="0:0:2" /> </Storyboard> . . . </Ellipse>
앞에서 살펴본 예제에서와 마찬가지로 이 예제 역시 먼저 Ellipse를 정의합니다. 이 예제에서는 Name 속성도 함께 사용하여, 나중에 이 이름으로 Ellipse를 참조할 수 있도록 했습니다. 이 예제에는 몇 가지 세부 사항이 생략되었지만 XAML로 애니메이션을 정의하려면 Storyboard 요소가 반드시 있어야 합니다. 이 경우에는 Ellipse의 Height 속성이 Double 형식이기 때문에 Storyboard에 DoubleAnimation 요소가 들어 있습니다. 이 요소는 애니메이션을 만들 Ellipse의 이름, 변경할 속성 및 변경할 내용을 지정합니다. 이 예제에서는 Height의 값을 2초 동안 50에서 25로 변경합니다.
이 예제보다 훨씬 복잡한 애니메이션을 만들 수 있습니다. 예를 들어 애니메이션을 마우스 클릭 같은 이벤트를 통해 트리거하거나, 일시 중지했다가 다시 재생하거나, 지정한 횟수만큼(또는 계속) 반복하는 등 복잡한 애니메이션도 만들 수 있습니다. 애니메이션을 통해 개발자는 더 실감나고 사용하기 쉬우면서 보다 많은 기능을 제공하는 사용자 인터페이스를 만들 수 있습니다.
대부분의 사용자 인터페이스는 특정한 유형의 데이터를 표시합니다. 인터페이스 개발자는 데이터 바인딩을 통해 데이터를 더 쉽게 표시할 수 있습니다. 데이터 바인딩을 사용하면 WPF 컨트롤에 표시되는 요소를 해당 컨트롤 외부에 있는 데이터와 직접 연결할 수 있습니다. 예를 들어 WPF TextBox 컨트롤의 Text 속성 값을 이 응용 프로그램의 비즈니스 논리의 일부인 Employee 개체의 Name 속성에 바인딩할 수 있습니다. 두 속성 중 하나가 변경되면 바인딩된 다른 한 속성에 해당 변경 내용이 반영됩니다. 예를 들어 사용자가 TextBox에서 값을 변경하면 Employee 개체의 Name 속성에도 변경된 값이 적용되고, 그 반대의 경우도 마찬가지로 적용됩니다.
두 개체의 속성 간에 이러한 연결을 만들려면 WPF Binding 클래스를 사용해야 합니다. 다음은 이러한 바인딩을 보여 주는 간단한 XAML 예제입니다.
<TextBox . . . > <TextBox.Text> <Binding Path="Name" /> </TextBox.Text> </TextBox>
이 예제에서는 Binding 요소의 Path 특성을 사용하여 TextBox의 Text 속성을 바인딩할 대상 속성을 식별합니다. Path는 이 속성이 속해 있는 개체(런타임에 지정됨)가 C# 또는 Visual Basic 등의 언어로 정의된 CLR(공용 언어 런타임) 개체인 경우에 사용됩니다. WPF의 데이터 바인딩 기능을 사용하면 CLR 개체는 물론 Binding의 XPath 속성을 사용하여 XML 데이터에 직접 연결할 수도 있습니다. 이 옵션은 지정한 데이터를 참조하는 XML 문서에서 하나 이상의 노드를 선택하는 XPath 쿼리를 만듭니다.
더 복잡한 데이터 바인딩 옵션을 사용할 수도 있습니다. 예를 들어 목록 바인딩을 사용하면 표준 IEnumerable 인터페이스를 구현하는 CLR 개체를 통해 ListBox 컨트롤에 내용을 채울 수 있습니다. 필요한 경우 데이터를 표시하기 전에 필터링 또는 정렬을 적용할 수도 있습니다. WPF에서는 ADO.NET Dataset에 바인딩할 수 있지만 관계형 데이터베이스 관리 시스템의 데이터에 직접 바인딩하는 기능은 지원하지 않습니다. 어떤 데이터 바인딩 옵션을 사용하는지에 관계없이 기본적으로 데이터 바인딩은 최대한 간단하게 사용자 인터페이스에 데이터를 표시하는 데 필요한 기능입니다.
WPF 인터페이스의 가장 일반적인 사용자는 당연히 사람입니다. 그러나 사용자 인터페이스를 작동하는 주체가 사람이 아니라 다른 소프트웨어인 경우도 있습니다. WPF의 UI(사용자 인터페이스) 자동화를 사용하면 이러한 방식으로 인터페이스를 작동할 수 있습니다.
예를 들어 개발자가 인터페이스를 실행하는 자동화된 스크립트를 만들어야 한다고 가정해 보겠습니다. 이 경우 개발자는 UI 자동화 기능이 제공하는 프로그래밍 방식의 액세스를 통해 사람이 실행하는 것처럼 인터페이스를 작동시키는 스크립트를 만들 수 있습니다. UI 자동화는 인터페이스의 다양한 요소를 음성으로 읽어 주는 도구와 같은 내게 필요한 옵션 도구를 만드는 데도 유용합니다. UI 자동화를 사용하면 이러한 요소가 들어 있는 트리에서 각 요소를 프로그래밍 방식으로 하나씩 실행할 수 있기 때문에 이러한 종류의 도구를 만들 수 있는 것입니다.
WPF에서는 UI 자동화를 사용하기 위해 UI 자동화 트리를 만듭니다. 이 트리는 인터페이스의 각 요소를 나타내는 일련의 AutomationElement 개체로 구성됩니다. 이 트리의 루트는 Desktop이고, 열려 있는 각 응용 프로그램은 이 루트의 자식이 됩니다. 트리는 각 응용 프로그램으로 세분화되고 각 WPF 컨트롤은 AutomationElement 개체 하나로 표시되거나, 경우에 따라 둘 이상의 개체로 표시됩니다. 이 경우 프로그래밍 방식으로 인터페이스에 액세스할 수 있도록 사용자가 상호 작용할 수 있는 모든 요소가 고유한 AutomationElement로 나타납니다. 예를 들어 단추가 여러 개 포함된 컨트롤에는 컨트롤 자체와 각각 고유한 AutomationElement 개체로 표시되는 단추가 모두 있습니다. 이와 같이 세분화된 UI 자동화 트리를 만들면 테스트 스크립트, 내게 필요한 옵션 도구 등의 클라이언트 응용 프로그램이 인터페이스의 각 구성 요소를 사람이 실제 사용하는 것처럼 액세스할 수 있습니다.
UI 자동화가 WPF의 핵심적인 기능은 아니며 대다수의 일반 사용자들은 이 기능을 거의 사용하지 않을 수도 있습니다. 그러나 소프트웨어 테스트 담당자나 움직임이 불편한 사용자와 같이 이를 필요로 하는 사용자들에게는 '없어서는 안 될' 중요한 기능입니다. 다수의 사용자들이 사용하는 기능만 중요한 것은 아닙니다.
WPF에는 방대한 양의 기술이 포함되어 있습니다. 이러한 모든 기술이 사용자와의 상호 작용과 관련이 있지만 가장 많이 적용되는 세 가지 영역으로는 독립 실행형 WPF 응용 프로그램, XBAP 및 XPS 문서가 있습니다. 이 섹션에서는 이 세 가지 영역을 자세하게 살펴봅니다.
WPF를 사용하는 가장 일반적인 방법은 독립 실행형 응용 프로그램에 사용하는 것입니다. 독립 실행형 WPF 응용 프로그램은 다른 일반적인 Windows 응용 프로그램처럼 실행되며 웹 브라우저를 사용하지 않습니다. 이러한 이유로 독립 실행형 응용 프로그램에는 완전한 신뢰 수준이 부여되고 WPF의 모든 기능을 사용할 수 있습니다. 완전 신뢰란 독립 실행형 WPF 응용 프로그램이 컴퓨터에 설치되어 있는 WCF(Windows Communication Foundation) 등의 다른 서비스를 자유롭게 사용할 수 있음을 의미합니다.
다른 Windows 응용 프로그램과 마찬가지로 독립 실행형 WPF 응용 프로그램을 로컬 디스크 또는 네트워크 서버에서 설치할 수 있으며 .NET Framework의 기능인 ClickOnce를 사용하여 설치할 수도 있습니다. ClickOnce는 사용자가 Internet Explorer를 통해 Windows 응용 프로그램(WPF 응용 프로그램 포함)을 다운로드하여 설치한 후 해당 응용 프로그램의 업데이트가 릴리스될 때 이를 자동으로 설치할 수 있게 해 주는 간단한 방법을 제공합니다.
독립 실행형 WPF 응용 프로그램이 대부분의 기능을 제공하지만 모든 경우에 적합한 것은 아닙니다. 클라이언트를 Windows 응용 프로그램으로 실행하는 것보다 웹 브라우저에서 실행하는 것이 더 적절한 경우도 많이 있습니다. WPF는 이러한 클라이언트에서도 최신 사용자 인터페이스를 사용할 수 있도록 XBAP를 제공합니다.
다음 그림에서 볼 수 있는 것처럼 XBAP는 Internet Explorer에서 실행됩니다. XBAP는 ASP.NET, JSP(JavaServer Pages) 및 기타 웹 기술을 사용하여 만든 웹 응용 프로그램의 클라이언트로 동작합니다. XBAP는 HTTP 또는 SOAP를 사용하여 이러한 웹 응용 프로그램과 통신할 수 있습니다. XBAP는 서버 플랫폼에 관계없이 항상 ClickOnce를 통해 로드되며 이 과정에서 사용자에게 대화 상자나 메시지를 전혀 표시하지 않고 일반적인 웹 페이지처럼 로드됩니다. 이러한 이유로 XBAP는 시작 메뉴나 프로그램 추가/제거에 표시되지 않습니다.
그림 7. Internet Explorer에서 실행되는 XBAP
일반적으로 XBAP는 탐색 형식의 인터페이스를 제공하지만 이는 선택 사항입니다. 탐색 형식의 인터페이스를 사용하면 사용자에게 익숙한 기존의 웹 클라이언트처럼 응용 프로그램을 실행할 수 있습니다. Internet Explorer 7에서는 XBAP가 브라우저의 기본 앞으로/뒤로 단추를 사용하고, 사용자가 액세스하는 XAML 페이지는 브라우저의 기록 목록에 나타납니다. 그러나 Internet Explorer 6에서는 XBAP가 자체적인 앞으로/뒤로 단추와 별도의 기록 목록을 사용합니다. XBAP는 현재의 실행 환경을 자동으로 파악하여 적절한 방식으로 동작하기 때문에 개발자는 개별 브라우저별로 버전을 만들지 않아도 됩니다.
XBAP는 웹에서 로드되어 브라우저에서 실행되기 때문에 .NET Framework의 코드 액세스 보안 기능이 부여하는 제한된 신뢰 수준에서만 실행됩니다. 이러한 이유 때문에 독립 실행형 WPF 응용 프로그램에서 사용할 수 있는 기능 중 일부를 XBAP에서는 사용하지 못할 수 있습니다. 예를 들어 인터넷 영역에서 배포된 XBAP에서는 다음과 같은 기능을 사용할 수 없습니다.
앞에서 살펴본 것처럼 독립 실행형 WPF 응용 프로그램 및 XBAP 모두에 동일한 코드 베이스를 사용할 수 있습니다. 이를 위해 개발자는 XBAP에서 사용할 수 없는 모든 기능을 ifdef에 래핑하여 조건부 컴파일을 사용할 수 있습니다. XBAP 버전에서는 문서 표시, 2차원 및 3차원 그래픽 사용, 비디오와 오디오 재생 등 독립 실행형 응용 프로그램 버전에서 사용할 수 있는 대부분의 기능을 동일하게 수행할 수 있을 뿐만 아니라 XBAP가 실행되고 있는 컴퓨터의 그래픽 하드웨어를 활용할 수도 있습니다.
Internet Explorer에는 XBAP뿐만 아니라 순수 XAML 페이지도 직접 표시할 수 있습니다. 'XAML 사용 완화'라고 하는 이러한 페이지는 브라우저에 정적 페이지를 표시할 때 유용합니다. 그러나 이벤트를 처리하려면 코드를 사용해야 하므로 결과적으로 XBAP를 만들어야 합니다.
개발자는 XBAP를 사용하여 브라우저 응용 프로그램에서 WPF 기능의 대부분을 사용할 수 있습니다. 또한 XBAP를 사용하면 독립 실행형 응용 프로그램과 브라우저 응용 프로그램에 코드가 거의 유사한 공통적인 프로그래밍 모델을 사용할 수 있습니다. 해당 클라이언트가 최신 Windows 플랫폼에서 실행되도록 디자인된 웹 응용 프로그램의 경우에는 XBAP를 사용하는 것이 좋습니다.
XPS 문서는 WPF 환경에 사용되는 고정된 형식의 문서로, 사용자 인터페이스에서 매우 중요한 역할을 합니다. 앞에서 살펴본 것처럼 WPF는 DocumentViewer 컨트롤을 사용하여 XPS 문서를 표시합니다. 이 컨트롤을 WPF에 포함해야 하는 이유는 쉽게 이해할 수 있지만 XPS 자체를 WPF의 일부로 간주해야 하는 이유는 납득하기 어렵습니다. 실제로 XPS 사양에는 고정된 형식의 문서를 정의하기 위한 상당히 구체적인 방법이 명시되어 있으며, 이러한 문서는 다양한 방법으로 사용될 수 있습니다. 반면, WPF의 다른 모든 기능은 사용자 인터페이스를 만드는 데만 초점이 맞춰져 있습니다. 이와 같은 특징을 고려할 때 사용 범위가 더 넓은 XPS를 WPF라는 틀 안에 포함하는 것에 의문이 생길 수 있습니다.
XPS를 WPF의 일부로 보는 한 가지 중요한 이유는 XPS 문서가 XAML을 사용하여 정의된다는 점입니다. 레이아웃을 지정하는 Canvas 요소, 텍스트를 나타내는 Glyphs 요소, 2차원 그래픽을 만드는 Path 요소 등을 포함하여 XAML의 일부만 제한적으로 사용되지만 사실상 모든 XPS 문서는 XAML 문서입니다. 이 점을 고려하면 XPS를 WPF의 일부로 볼 수도 있습니다.
그러나 화면 표시 사용자 인터페이스는 XPS가 제공하는 핵심적인 기능과는 여전히 거리가 멉니다. Windows Vista부터는 XPS가 Windows의 기본 인쇄 형식으로 사용됩니다. XPS는 페이지 설명 언어로 사용되기 때문에 XPS를 지원하는 프린터에서 XPS 문서를 직접 렌더링할 수 있습니다. 따라서 화면 작업에서 프린터 작업에 이르는 모든 과정에서 XAML이라는 단일 설명 형식을 사용할 수 있습니다. 또한 XPS는 Windows에서 사용하던 기존의 GDI 기반 인쇄 방식을 향상시키므로 투명도 및 그라데이션과 같은 고급 그래픽 효과를 보다 정확하게 인쇄할 수 있습니다.
XPS 문서에는 XAML을 비롯하여 다양한 형식의 이미지(JPEG, PNG, TIFF 및 WMPhoto), 글꼴 데이터, 문서 구조에 대한 정보 등의 이진 데이터가 포함될 수 있습니다. 또한 필요한 경우 W3C XML 서명 정의 및 X.509 인증서를 사용하여 XPS 문서에 디지털 서명을 추가할 수도 있습니다. 모든 XSP 문서는 어떤 정보를 포함하는지에 관계없이 OPC(Open Packaging Convention)에서 정의하는 형식으로 저장됩니다. OPC는 XPS나 XAML 문서뿐만 아니라 XML 문서를 구성하는 여러 요소 간의 관계 및 이들 요소가 표준 ZIP 형식으로 저장되는 방법 등을 지정합니다. Microsoft Office 2007에서는 XML 형식에도 OPC를 사용하기 때문에 이 두 가지 형식의 문서 간에는 어느 정도의 공통점이 존재합니다.
앞에서 설명했듯이 WPF 응용 프로그램 사용자는 WPF DocumentViewer 컨트롤을 통해 XPS 문서를 볼 수 있습니다. Microsoft에서는 다음 그림에서 볼 수 있는 것처럼 DocumentViewer 컨트롤을 기반으로 하는 XPS 뷰어 응용 프로그램도 제공합니다. 이 응용 프로그램을 사용하면 컨트롤과 마찬가지로 문서에서 한 페이지씩 이동하고, 텍스트를 검색하는 등의 작업을 할 수 있습니다. XPS 문서는 사용 범위가 Windows로 제한되지 않기 때문에 Microsoft에서는 Apple Macintosh 같은 다른 플랫폼에서도 사용할 수 있는 XPS 뷰어를 제공할 예정입니다.
그림 8. XPS 뷰어를 사용하면 XPS 문서를 한 번에 한 페이지씩 읽을 수 있습니다.
WPF는 개발자가 XPS 문서를 만들고 로드하고 조작하는 데 사용할 수 있는 API 집합을 제공합니다. WPF 응용 프로그램에서도 OPC 수준의 문서를 사용할 수 있기 때문에 일반화된 방식으로 XPS 문서, Office 2007 문서 등에 액세스할 수 있습니다. Microsoft Windows Workflow Foundation을 사용하여 만든 응용 프로그램에서도 이러한 API를 통해 XPS 문서를 사용하는 워크플로를 만들 수 있습니다.
WPF는 응용 프로그램에서 고정된 형식의 문서를 표시하고 사용할 수 있도록 허용함으로써 최신 사용자 인터페이스의 구성 요소를 WPF가 지향하는 일관된 환경에 통합합니다. Windows Vista에서는 이와 동일한 형식으로 문서를 인쇄할 수 있기 때문에 화면에 표시되는 내용과 인쇄된 용지에 표시되는 내용 간의 일관성이 향상됩니다. XSP 문서 형식이 사용자 인터페이스에서 가장 중요한 기술은 아닐 수도 있지만 광범위하게 사용되는 XPS를 통해 WPF 기술이 얼마나 포괄적으로 사용되는지 그 범위를 단적으로 알 수 있습니다.
WPF는 개발자가 유용하게 사용할 수 있는 다양한 기능을 제공합니다. 아무리 강력한 기술도 유용한 도구가 없다면 효용성이 떨어질 수 있습니다. Microsoft에서는 WPF를 사용하는 개발자와 디자이너에 각각 맞추어 최적화된 도구를 제공합니다. 이 섹션에서는 이 두 가지 도구에 대해 간략하게 설명합니다.
Visual Studio는 소프트웨어 개발자를 위한 Microsoft의 주력 응용 프로그램입니다. WPF의 최초 릴리스에는 개발자가 WPF 응용 프로그램을 만드는 데 사용할 수 있는 Visual Studio 2005 확장 기능이 함께 제공될 예정입니다. Visual Studio의 다음 릴리스(코드 이름: "Orcas")에는 Visual Designer for WPF(코드 이름: "Cider") 등의 다른 기능도 추가될 것입니다. 개발자는 이 비주얼 도구를 사용하여 WPF 인터페이스를 그래픽 방식으로 만든 후 기본적인 XAML을 자동으로 생성시킬 수 있습니다. 공식 릴리스 날짜는 아직 발표되지 않았지만 Orcas는 2007년 중에 출시될 예정입니다.
앞에서 살펴본 것처럼 WPF의 기본적인 목표는 사용자 인터페이스를 만드는 작업에 디자이너가 적극적으로 참여할 수 있는 작업 환경을 만드는 것입니다. 이와 같은 목표는 XAML을 통해 실현할 수 있지만 새 작업 환경에서 디자이너가 작업하는 데 사용할 수 있는 도구가 필요합니다. 이를 위해 Microsoft에서는 Expression Interactive Designer를 만들었습니다.
아래의 스크린 샷에서 볼 수 있는 것처럼 Expression Interactive Designer는 사용자가 편리하게 작업할 수 있도록 가장 일반적인 디자인 도구 인터페이스를 제공합니다. 그러나 Designer는 WPF 응용 프로그램용 인터페이스를 만드는 데만 초점을 두어 설계되었습니다. 실제로 이 도구의 인터페이스도 WPF를 사용하여 만든 것입니다. 예를 들어 아래 화면에서 오른쪽 상단에 표시되는 WPF 컨트롤 목록과 맨 아래의 그래픽 시간 표시 막대가 이러한 인터페이스에 해당됩니다. 이러한 모든 특징은 이 문서의 앞부분에서 설명한 WPF 기능을 그대로 구현한 것으로, 인터페이스 디자이너가 자유롭게 사용할 수 있도록 설계되었습니다. 이 도구를 사용하면 애니메이션은 물론 변환, 효과 등의 다양한 기능을 그래픽 방식으로 만들 수 있습니다. 디자이너의 작업 결과는 자동으로 생성되는 XAML 파일 형식으로 저장되어 나중에 Visual Studio로 가져올 수 있습니다.
그림 9. 디자이너는 Expression Interactive Designer에서 WPF 인터페이스(그림 8)를 만들 수 있습니다.
Expression Interactive Designer는 Microsoft Expression 제품군의 세 가지 제품 중 하나입니다. 나머지 두 제품은 표준 기반 웹 인터페이스를 만드는 도구인 Expression Web Designer와 벡터 및/또는 비트맵 이미지를 만드는 도구인 Expression Graphic Designer입니다. 이 세 가지 응용 프로그램 중에서 Expression Interactive Designer만 WPF 응용 프로그램용 사용자 인터페이스를 만드는 데만 초점을 두어 설계되었습니다. Expression Graphic Designer에서 인터페이스의 GIF 이미지를 만드는 것처럼 나머지 두 제품을 사용하여 사용자 인터페이스의 구성 요소를 만들 수도 있지만 이 둘은 WPF 전용 도구가 아닙니다. 날짜는 아직 발표되지 않았지만 모든 Expression 도구는 WPF의 릴리스 이후에 출시될 예정입니다.
대부분의 Microsoft 신기술과 마찬가지로 WPF는 Windows 작업 환경의 다른 영역에 영향을 줍니다. 그러나 WPF 기술이 다른 영역에 미치는 영향을 살펴보기 전에 알아야 할 것은 시스템에 WPF를 설치해도 Windows Forms, MFC 등과 같이 기존의 다른 기술을 사용하는 소프트웨어와의 충돌이 발생하지 않는다는 점입니다. 즉 .NET Framework 3.0을 지원하는 시스템용으로 작성하는 새 응용 프로그램의 인터페이스는 대부분 WPF를 사용하여 만들지만 이전 기술을 사용하는 응용 프로그램도 이전과 마찬가지로 계속 실행할 수 있습니다.
.NET Framework의 최초 릴리스 이후 Windows Forms는 수많은 응용 프로그램을 만드는 데 사용되어 왔으며 WPF가 릴리스된 이후에도 일부 응용 프로그램에는 Windows Forms가 계속 사용될 것입니다. 예를 들어 이전 버전의 Windows와 같이 WPF를 사용할 수 없는 시스템에서 실행해야 하는 모든 구성 요소의 사용자 인터페이스는 Windows Forms를 사용하여 만들게 됩니다. Windows Forms가 제공하는 광범위한 컨트롤 집합을 사용하려는 경우와 같이 새 응용 프로그램에서도 필요에 따라 WPF 대신 Windows Forms를 사용할 수도 있습니다.
WPF를 사용하여 만든 응용 프로그램에서도 Windows Forms의 기능 중 일부를 활용하는 것이 유용할 수 있습니다. 예를 들어 Windows Forms는 WPF에 비해 더 많은 수의 컨트롤 집합을 제공합니다. .NET Framework 버전 2.0에 포함된 DataGridView 컨트롤의 경우 WPF에는 이 컨트롤을 대체할 수 있는 컨트롤이 없으며, 타사에서 다른 여러 가지 용도로 만든 Windows Forms 컨트롤도 상당히 많습니다. 경우에 따라서는 WPF 응용 프로그램에 이러한 기존 컨트롤을 사용해야 할 수도 있습니다. 이와는 반대로 WPF에도 3D 그래픽, 애니메이션 등 Windows Forms에서는 제공하지 않는 많은 기능이 있습니다. 이 경우에는 기존의 Windows Forms 응용 프로그램에 WPF의 기능을 통합하는 것이 좋습니다.
이러한 상호 보완은 충분히 가능합니다. 즉, WPF 응용 프로그램에서 Windows Forms 컨트롤을 호스팅하고 Windows Forms 응용 프로그램에서 WPF 컨트롤을 호스팅할 수 있습니다. 이렇게 하면 사용자는 한 응용 프로그램에서 WPF 대화 상자 및 Windows Forms 대화 상자를 별다른 불편 없이 사용할 수 있습니다.
WPF 응용 프로그램에서는 WPF의 WindowsFormsHost 컨트롤을 사용하여 Windows Forms 컨트롤을 호스팅합니다. 컨트롤의 이름에서 알 수 있듯이 이 컨트롤은 Windows Forms 컨트롤을 호스팅하여 WPF 응용 프로그램에서 Windows Forms 컨트롤을 사용할 수 있게 해 줍니다. 이 컨트롤은 ActiveX 컨트롤도 호스팅할 수 있기 때문에 이전 기술을 사용하여 만든 기존의 대규모 라이브러리를 WPF 응용 프로그램에서 액세스할 수 있습니다. 마찬가지로 Windows Forms 응용 프로그램에서는 WPF 컨트롤, 패널 및 기타 요소를 호스팅할 수 있는 Windows Forms 컨트롤인 ElementHost 컨트롤을 사용합니다. 이러한 각각의 기술을 구현하는 도구에서도 Windows Forms와 WPF용으로 작성된 소프트웨어를 모두 사용할 수 있습니다. 즉 WPF용 Visual Designer를 사용하여 Windows Forms 컨트롤을 배치하고, 마찬가지로 Windows Forms 디자이너를 사용하여 WPF 컨트롤을 배치할 수 있습니다.
그러나 WPF와 Windows Forms를 함께 사용할 경우 몇 가지 제한 사항도 있습니다. 예를 들어 WPF 컨트롤을 Windows Forms 컨트롤 위에 배치할 수는 없습니다. Windows Forms 컨트롤은 항상 최상위 계층에서 사용됩니다. WPF의 투명 효과나 변환 기능은 Windows Forms 컨트롤에 사용할 수 없습니다. 또한 WindowsFormsHost 및 ElementHost 컨트롤을 사용하려면 완전한 신뢰 수준이 필요하기 때문에 이러한 컨트롤을 사용하는 WPF 응용 프로그램을 XBAP로 실행할 수 없습니다. 그러나 대부분의 Windows 응용 프로그램의 사용자 인터페이스는 WPF와 Windows Forms를 함께 사용하여 만들 수 있습니다.
2002년 .NET Framework가 출시되기 전에는 일반적으로 Windows 개발자가 Win32 API를 직접 호출하거나, 이 API를 래핑할 수 있는 C++ 래퍼를 제공하는 MFC를 사용하여 사용자 인터페이스를 만들었습니다. 따라서 이 방식으로 만든 인터페이스의 코드는 지금까지도 많이 사용되고 있습니다. WPF 환경에서는 이 코드가 어떻게 사용될까요?
이 질문에 대한 대답은 Windows Forms의 경우와 유사합니다. 즉, 기존 Win32/MFC 코드에서 WPF 컨트롤을 호스팅하는 것이 가능하고, WPF에서 기존 Win32/MFC 컨트롤을 호스팅하는 것도 가능합니다. 실제로 WPF와 Windows Forms 간의 상호 운용에 사용되는 기능은 Win32/MFC 상호 운용성 서비스를 기반으로 만들어졌습니다. WPF에서는 WPF 환경에서 Win32/MFC 컨트롤을 사용하는 데 필요한 HwndHost 클래스와 Win32/MFC 응용 프로그램에서 WPF 컨트롤을 사용하는 데 필요한 HwndSource 클래스를 제공합니다. 각 클래스는 필요에 따라 두 기술을 매핑합니다. 예를 들어 HwndHost는 Win32/MFC 컨트롤을 참조하는 데 사용되는 hWnd를 WPF 컨트롤인 것처럼 표시하고, 반대로 HwndSource는 WPF 컨트롤을 hWnd인 것처럼 표시합니다.
그러나 Windows Forms의 경우와 마찬가지로 이 두 환경을 함께 사용하는 경우에도 몇 가지 제약이 따릅니다. 실제로 Windows Forms 상호 운용성은 HwndHost 및 HwndSource를 기반으로 하기 때문에 레이어 및 투명 효과에 대한 제약 등과 같이 앞에서 Windows Forms 컨트롤에 대해 설명한 모든 제한 사항이 이 경우에도 동일하게 적용됩니다. 또한 Windows Forms와는 달리 WPF와 Win32/MFC 코드를 함께 사용하는 응용 프로그램에서는 WPF의 관리 코드 환경과 Win32의 비관리 코드 환경 간의 상호 운용으로 인한 문제도 발생합니다. 이와 같은 모든 이유 때문에 Win32/MFC 코드를 사용하는 WPF 응용 프로그램은 XBAP로 실행할 수 없습니다. 그러나 앞에서 설명한 것과 마찬가지로 중요한 것은 Windows 응용 프로그램에서 WPF와 Win32/MFC를 함께 사용할 수 있다는 사실입니다. 즉, WPF를 사용하더라도 응용 프로그램의 기존 사용자 인터페이스 코드를 모두 버릴 필요는 없습니다.
Direct3D는 Microsoft DirectX API 제품군의 일부로, 3차원 그래픽을 만드는 Windows 개발자들이 주로 사용하는 제품으로서 WPF가 출시된 이후에도 독자적인 가치를 제공할 것입니다. 앞에서 설명한 것처럼 실제로 WPF는 Direct3D를 통해 모든 렌더링 작업을 수행합니다. WPF의 출시 이후에 달라지는 것은 이제는 WPF에서도 3D 그래픽을 만들 수 있기 때문에 3D 개발자들이 둘 중 하나를 선택해야 한다는 점입니다.
그러나 이러한 결정은 비교적 쉽게 내릴 수 있을 것입니다. 고도의 과학적 시각화가 요구되는 3D 중심의 기술적인 응용 프로그램, 게임 등과 같이 3D를 집중적으로 개발해야 하는 경우에는 Direct3D를 사용하는 것이 좋습니다. WPF는 이러한 유형의 소프트웨어를 개발하기 위해 디자인된 플랫폼이 아니므로 적어도 WPF의 초기 릴리스는 이러한 용도로 사용하기에 적합하지 않습니다.
대신 WPF를 사용하면 전문적인 지식이 없는 일반 사용자들도 비교적 쉽게 3D 그래픽을 만들 수 있습니다. 또한 WPF 환경에서는 XBAP를 통해 웹에서 3D 그래픽을 사용할 수 있을 뿐만 아니라 2차원 그래픽과 문서를 비롯하여 응용 프로그램 사용자 인터페이스의 다른 여러 요소에 3D 그래픽을 통합할 수 있습니다. 앞에서 살펴본 HwndHost 클래스를 통해 WPF 응용 프로그램에서 Direct3D 코드를 호스팅할 수도 있습니다. WPF와 Direct3D는 각자 독자적인 가치를 제공하며 앞으로도 Windows 플랫폼에서 중심적인 기능을 할 것입니다.
개발자는 AJAX를 사용하여 응답 성능이 뛰어난 브라우저 클라이언트를 만들 수 있습니다. AJAX를 사용하면 요청이 있을 때마다 페이지를 새로 고치지 않고도 응용 프로그램과 상호 작용할 수 있기 때문에 웹 브라우저와 웹 서버 사이의 데이터 교환 작업을 줄일 수 있습니다. AJAX는 브라우저에서 지원하는 XMLHttpRequest 개체를 사용하여 이 기능을 제공합니다. 이러한 개념은 1990년대 후반 Internet Explorer 5.0에 처음 소개되었으며 2000년대 중반에 이르러서는 더 많은 브라우저에서 XMLHttpRequest를 지원하게 되었고 AJAX가 보편화되었습니다.
그러나 AJAX 클라이언트를 만드는 작업은 그리 간단하지 않습니다. Microsoft는 이 작업을 위해 "Atlas"라는 코드 이름의 기술 집합을 만들었습니다. Atlas는 AJAX 응용 프로그램을 만드는 데 사용할 수 있는 라이브러리, 컨트롤 및 기타 요소의 집합으로, Internet Explorer뿐 아니라 다른 여러 브라우저에 사용할 수 있는 클라이언트 스크립트 라이브러리 및 서버 쪽에서 사용할 수 있는 ASP.NET 확장 기능으로 구성됩니다. Atlas는 AJAX 클라이언트가 포함된 웹 응용 프로그램을 보다 간단하게 만들 수 있도록 도와 줍니다.
AJAX는 다양한 브라우저에서 사용할 수 있다는 점에서 많은 개발자들이 관심을 갖는 기술입니다. AJAX를 사용하면 보다 뛰어난 대화형 인터페이스를 만들어 웹 사용자에게 제공할 수 있지만 지원되는 콘텐츠 형식이 매우 제한적이라는 단점이 있습니다. 예를 들어 AJAX만으로는 그래픽, 비디오, 애니메이션 및 기타 최신 상호 작용 방식을 지원할 수 없습니다. 따라서 해당 클라이언트에서 WPF를 지원하도록 해야 하는 응용 프로그램을 만들려면 AJAX 대신 XBAP를 선택하는 것이 좋습니다.
XBAP를 사용하면 웹 응용 프로그램에서 WPF 기능의 대부분을 구현할 수 있지만 이를 위해서는 클라이언트 컴퓨터에 WPF가 설치되어 있어야 하므로 웹 응용 프로그램의 사용 범위가 제한될 수 있습니다. 또한 WPF를 지원하지 않는 Macintosh나 기타 시스템에서도 액세스할 수 있고 최신 인터페이스를 제공하는 웹 응용 프로그램을 만들어야 하는 경우가 생길 수도 있습니다.
코드 이름 "WPF/E"는 이와 같은 문제를 해결하기 위해 곧 제공될 기술입니다. WPF/E("E"는 Everywhere를 나타냄)는 Macintosh와 같은 다양한 클라이언트 플랫폼, 소형 장치 등을 비롯하여 Internet Explorer, FireFox, Netscape 등의 다양한 웹 브라우저에 WPF의 기능 중 일부를 제공할 예정으로, 여기에는 2차원 그래픽, 이미지, 비디오, 메모 및 텍스트가 포함됩니다. 그러나 XBAP에서 지원하는 기능 중 3차원 그래픽, 문서, 하드웨어 가속 등의 일부 기능은 WPF/E에서 지원되지 않습니다.
개발자는 JavaScript를 사용하여 WPF/E 응용 프로그램을 만들 수 있습니다. C# 및 Visual Basic 언어를 사용하여 개발할 수 있도록 WPF/E에는 .NET Framework의 플랫폼 간 호환 기능 중 일부도 포함될 것입니다. WPF/E는 .NET Framework 3.0의 일부가 아니며 2007년 중에 릴리스될 것으로 예상됩니다. WPF/E가 릴리스되면 웹 응용 프로그램 작성자는 광범위한 플랫폼에서 다양한 기능을 사용하여 클라이언트 응용 프로그램을 만들 수 있는 또 다른 기술을 경험할 수 있을 것입니다.
사용자 인터페이스는 대부분의 응용 프로그램에서 매우 중요한 부분을 차지합니다. 효과적인 인터페이스를 만들면 사용자 및 사용자가 속한 조직에 상당한 이점을 제공할 수 있습니다. WPF의 기본적인 목표는 개발자가 이러한 효과적인 사용자 인터페이스를 만들 수 있도록 돕는 것이기 때문에 WPF는 Windows 응용 프로그램을 만드는 개발자나 일반 사용자 모두가 기대할 만한 기술입니다.
WPF는 최신 사용자 인터페이스를 만들기 위한 통합 플랫폼을 제공하고, 이러한 인터페이스를 만드는 작업에 디자이너가 보다 적극적으로 참여할 수 있도록 도와 주며, 독립 실행형 응용 프로그램과 브라우저 응용 프로그램을 위한 공통적인 프로그래밍 모델을 제공함으로써 Windows 사용자 환경을 크게 향상시키는 것을 목표로 합니다. WPF는 지난 20년 동안 Windows 사용자 인터페이스의 기반이 되어 온 기술을 대체하는 새로운 기술을 제시하여 향후 20년 동안 사용할 기반 기술로서의 역할을 할 것입니다