'IT'에 해당되는 글 37건

  1. 2011.05.24 Toad TeamCoding - Integration with SubVersion - part2
  2. 2011.05.24 Toad TeamCoding - Integration with SubVersion - part1
  3. 2011.05.16 실전 데이터모델링(비트컬럼으로 컬럼레벨 데이터통합) 기법 및 오라클 비트연산 가이드 (2)
  4. 2011.05.11 Toad의 강력한 기능을 eclipse 안에서 - Toad Extension for eclipse
  5. 2010.10.19 안드로이드 마켓용 QR Code 생성 방법 (3)
  6. 2010.10.18 Android Market에 유료 App 등록하기, 개발자 등록 가이드 (5)
  7. 2010.10.18 Touch Call - New Version Released! (7)
  8. 2010.10.14 Android - Shape Drawable (3)
  9. 2010.10.13 Android - Custom Dialog, AlertDialog (4)
  10. 2010.10.13 Android - Dialog, AlertDialog, ProgressDialog, DatePickerDialog, TimePickerDialog (5)
  11. 2010.09.20 안드로이드 사용 Tip - 폴더 기능 (3)
  12. 2010.09.17 [강좌A15] 안드로이드 실전 개발 - Release (apk 생성) (1)
  13. 2010.09.16 [강좌A14] 안드로이드 실전 개발 - Notification, 알림메세지 구현
  14. 2010.09.03 [강좌A13] 안드로이드 실전 개발 - Hangul2English 최종소스 공개 (한글 자모 분리, Manifest) (6)
  15. 2010.09.01 [강좌A12] 안드로이드 실전 개발 - Main UI 소스, ListAdapter (1)
2011.05.24 16:23

Toad TeamCoding - Integration with SubVersion - part2




Toad TeamCoding part 1에 이어서...

이전 글을 못보신 분은 아래링크 클릭!!
2011/05/24 - [데이터베이스/Toad] - Toad TeamCoding - Integration with SubVersion - part1

4. Toad Team Configuration

자… 이제 필요한 SW는 모두 설치했으므로 Toad Team Coding을 위한 설정 작업만 하면 된다.

4-1. Toad Team configuration

Server-side Object Install 과정에서 생성한 TOAD 계정으로 로그인 한다. TOAD 계정은 Team Coding에 필요한 Role을 모두 가지고 있기 때문이다.

상단 툴바에서 우측 마우스 클릭 > Team Coding 을 선택해 관련 툴바를 활성화 시킨다. 물론 Menu > Utils > Team Coding > 하위 메뉴를 이용해도 동일하다.



툴바 중 체크 아이콘인 “View Team Coding Status with this Session” 버튼을 클릭한 후 나타난 화면에서 Setting 버튼을 누른다.



위 화면처럼 체크하여 Team Coding 과 버전컨트롤 기능을 활성화 한다. 버전 컨트롤 Provider로 Subversion을 선택한 후 하단의 Provider Options 버튼을 클릭한다



Subversion 설정화면에서 SVN Executable 항목에는 이전 화면에서 설치한 SlikSvn 설치폴더의 svn.exe를 선택한다.



Server 설정탭에서는 SVN 서버에 대한 정보(서버명만)를 입력한다. 별도의 서버가 있다면 그 서버에 대한 정보를 입력하고, 나는 위에서 로컬에 설치한 Visual SVN 서버를 사용할 것이므로 나의 로컬 컴퓨터 명을 서버항목에 입력하였다.



옵션탭에서는 기본설정을 그대로 사용해도 무방할 듯 하다. OK>

4.2. local repository setting & init sync

실제 소스코드가 저장될 로컬 디렉토리를 생성한다. 그리고 해당 디렉토리와 svn 레파지토리와 한번은 싱크를 맞춰야 하므로 check out 한다.

먼저, 로컬에 프로젝트 소스를 저장할 디렉토리를 생성한다. 예제: C:\project_ws

해당 디렉토리를 선택한 후 우측 마우스 클릭 > SVN CheckOut… 클릭

(* 필자의 경우에는 위에서 Tortoisesvn을 설치했기 때문에 탐색기와 SVN이 통합되어 있다. 다른 툴을 사용한다면 여하튼 svn repository와 최초 한번 Checkout을 해야 한다는 것만 잊지말자)


4.3 Subversion Login

이제 마지막 작업만 남았다. Toad에서 Team Coding 툴바 중 “Log on To VCS Provider” 아이콘은 선택하자.



 

로그인 창에서 Repositorys는 그림처럼 입력한다. VCS Provider 설정화면에서 서버명까지 입력했기에, Repository의 나머지 이름을 입력한다. 프로젝트명은 빼고 레파지토리명까지만 입력한다. Working Directory 항목에는 좀전에 생성하고 Checkout한 로컬 폴더 경로를 입력한다.

유저명과 패스워드는 SVN Server에 생성한 계정정보를 입력한 후 OK 버튼을 누르면 Toad에서 SVN 서버로 로그인한다.


 


로그인이 정상적인지 확인하려면 Team Coding Toolbar 중 “View Team Coding Status for this session” 툴바를 클릭해서 위 그림처럼 모든게 녹색 체크이면 정상인 것이다.

4.4. Configure Code Control Groups

이제 정말 마지막 작업들만 남았다. Toad Team Coding Toolbar에서 “View/Configure Code Control Groups” 버튼을 클릭한다.

Code Control Group에서는 버전관리나 Team Coding할 Oracle Object와 Subversion Project와 매핑을 해준다.

Code Control Group 팝업창에서 첫번째 아이콘인 “Add Group” 아이콘을 클릭한다.


하단의 VCS Project를 우리가 생성한 SVN 프로젝트를 선택하고 Group name 항목은 프로젝트명과 동일하게 입력한다. (사실 아무거나 입력해도 상관없다.)


추가한 Code Control Group에서 관리할 Oracle Object를 지정하는 화면이 실행된다. 관리할 스키마 등의 정보를 추가 하자.


끝으로 이미 Code Control Group 화면에서 관리하고자 지정했던 스키마가 가진 PL/SQL Object를 VCS Provider 및 Team Coding 환경에 Export 하면 Toad, File, SVN 모두 싱크가 맞아진다.

Code Control Groups 화면에서 Group 명을 선택한 후 “Export to VCS” 버튼을 클릭한다.



클릭하면 미리 매핑한 스키마가 소유한 package, function, trigger, procedure, type 등의 object 목록이 나타나며 OK 버튼을 누르면 아래 그림처럼 SVN에 Commit할 때 버전을 어떻게 처리할 것인지 묻는 대화상자가 나온 후 동기화 작업이 진행된다.



두번째 옵션을 선택하자.



해당 Oracle Object가 정상적으로 Subversion에 추가되었다는 메시지가 출력된다.

모든 설정 작업은 끝났다.

5. Test & Others View

5.1. Version Control Browser

Version Control Browser는 Subversion에 저장된 파일(Oracle Object 스크립트) 목록을 조회하고, 버전 히스토리를 볼 수 있으며 해당 파일을 체크아웃/체크인 할 수 있는 일종의 VCS Provider Client이다.


아래 버전별 소스 비교 화면은 위의 “Version Control Browser” 화면의 우측 하단의 History 목록에서 우측마우스 클릭> “View Differences” 메뉴 선택 후 나타난 비교화면이다. Compare 기능도 다양하고 아주 쓸만하다.


5.2. Team Coding Options

Menu > View > Toad Options 항목 중 Source Control 부분은 Team Coding에 대한 몇 가지 설정을 할 수 있다.


5.3. Team Coding Viewer

Team Coding Viewer는 버전관리하는 oracle object에 대한 status를 조회하는 화면이다. Team Coding Viewer는 위 화면의 Version Control Browser 보다 훨씬 로딩 속도가 빠르다.

 

5.4. Source 편집.

5.4.1. 체크아웃

스키마 브라우저나 에디터에 열리는 스크립트를 편집하려고 시도해 보라. Code Control Group에 지정된 Object는 편집이 안된다. 편집을 하기 위해서는 Team Coding Toolbar에서 Check Out 버튼을 클릭해야한다. 체크아웃하면 토드는 SVN에서 소스를 가져와 파일에 싱크를 맞춘후 에디터에 로드한다.

만일 신규 스크립트의 경우에는 스크립트를 작성 후 컴파일하여 Oracle에 반영한 후 체크아웃 하면 토드는 신규이므로 SVN에 먼저 Commit하여 저장한 후 로컬레파지토리 경로로 파일을 생성한다.

체크아웃을 하면 SVN과는 달리 Toad는 Oracle 객체를 Lock을 한다. 다른 사람은 Lock한 사람이 체크인 하지 않는 한 소스를 편집할 수 없다.

5.4.2. 체크인

소스 개발 및 수정이 끝났으면 컴파일하여 Oracle에 반영하고 체크인 버튼을 눌러 SVN에 Commit 작업이 이루어 지도록 한다. 체크인시에도 토드는 로컬 레파지토리의 파일을 먼저 싱크하고, svn에 commit 하게 된다. 체크인 된 소스는 다른 사람이 편집할 수가 있다.

 


Conclusion

Toad Team Coding을 이용 하기 위해서 이렇게 많은 작업을 해야 한다니 힘이 들긴 한다.

구성이 복잡한 것 외에 또 다른 단점을 꼽으라면 체크아웃/체크인시에 생각보다 Load가 많이 발생한다. 파일과 SVN, Toad Repository까지 모두 싱크 작업을 하려니 시간이 조금 걸릴 듯 싶기는 하지만.. 그래도 빠르게 팍팍 소스가 로드되던 팀 환경 없는 방식과 비교하면 느린 것은 틀림이 없다.

하지만, PL/SQL 소스 관리는 정말 편리하다. SVN의 모든 잇점을 모두 다 누릴 수는 없지만 핵심적인 기능은 전부 이용가능하며, Toad Team Coding이 주는 혜택까지.. 반드시 사용해 볼만한 기능임에는 틀림없다.




* subversion이 아닌 cvs와 연동시 command-line tool 지정시 cvs.exe는 나만 그런지는 모르겠지만 cvs 서버(유닉스 환경)에 접속이 제대로 되지 않았다. cvsnt를 설치하여 cvsnt 폴더에 있는 cvs.exe를 지정하니 cvs 서버 접속이 정상적으로 잘 되었다. 이것을 몰라 한참 고생했던거라  cvs와 연동하실 분은 먼저 cvs.exe로 접속해보고, 잘 안되면 바로 cvsnt로 갈아타시길..

Trackback 0 Comment 0
2011.05.24 15:44

Toad TeamCoding - Integration with SubVersion - part1




현대 프로그램 개발환경에서 CVS, SVN 같은 버전관리 툴은 필수 툴이 되어 버렸다. 그러나 DB 개발언어인 Oracle의 PL/SQL 언어로 함수나 패키지, 프로시저 등을 개발할 때 버전관리 툴을 사용할까? 대부분 잘 사용하지 않는다.

사용하지 않는 원인을 보면 거의 모든 버전관리시스템이 파일기준으로 동작을 하는데 비해 PL/SQL 개발은 따로 파일에 저장을 하지 않고 DB에 바로 저장을 하기 때문일 것이다. 물론 부지런한 개발자는 항상 PL/SQL 소스를 로컬 파일에 저장해 두고, 로컬 파일 정보를 별도의 버전관리 시스템의 클라이언트를 이용해서 버전관리를 할 수는 있을 것이다. 그렇지만, 그게 얼마나 귀찮은  일인가? 개발할 때 마다 수많은 PL/SQL 코드를 매번 파일로 저장하고, 별도로 COMMIT을 해야 한다면? 혹시라도 단 하나의 파일이라도 COMMIT해야 하는걸 잊어버린다면?
이런 귀찮은 작업을 DB 클라이언트 툴이 다 해준다면 쉽게 PL/SQL 같은 프로그램도 버전관리를 할 수 있을 텐데.. 그런 툴이 없다 보니 대부분의 DBA나 DB 개발자들은 버전관리를 잘하지 않는것이다.

그런데, 그런 기능을 지원해주는 툴이 예전부터 있었다.
“Toad for Oracle” 대부분의 Oracle 개발자들이 알고 있는 두꺼비. 그러나 이런 기능이 있는지 알고 있는 사람은 정말 몇 안되며 이 Team Coding 기능을 필드에 적용하는 곳은 더더욱 적을 것으로 생각된다.

Toad에서 Team Coding 기능을 활성화하고, 서드파티 버전관리 시스템(CVS, SVN 등)과 연동한 후 Toad의 스키마 브라우저에서 Oracle에 저장된 프로그램을 편집하려고 에디터로 오픈하면 (정확히는 수정을 위해 소스를 Toad에서 체크아웃하면) Toad는 버전관리시스템에서 최신의 소스를 가져와 로컬 파일 정보도 수정 반영한 후 에디터에 로드한다. 그리고 소스 내용을 Toad에서 수정한 후 컴파일하여 Oracle에 반영한 다음 소스를 체크인하면 Toad는 자동으로 로컬파일을 수정하고 수정된 내용을 버전관리시스템에 COMMIT 한다. 버전관리시스템과 연동이 얼마나 편리한가? 별도로 파일저장 버튼 한번 클릭하지 않고 Toad에서 체크인과 체크아웃만 하면 된다. (Toad는 두 명 이상이 동시에 PL/SQL 소스를 수정하지 못하게 하도록 Team Coding 기능 lock을 사용하여 막는다. 체크아웃 한 사람만 수정이 가능하다)

Toad Team Coding Overview

Toad의 초기 제품은 Microsoft의 Microsoft SCC API를 지원하는 툴만 버전관리를 지원 했었다.
그 후 Microsoft SCC (Source Code Control)API로 CVS와 SVN을 지원하는 Component가 나와  CVS나 SVN과 연동은 가능하도록 변경되었다.

Toad v10.5에 이르러 CVS와 SVN의 경우 별도의 SCC 지원 컴포넌트 없이 command-line client만 설치하면 쉽게 연동되도록 코드가 개선되었으며, 이전보다는 쉽게 형상관리 툴과 연동을 할 수 있게 되었다.

Toad v10.6 현재 Direct Supported되는 버전관리 툴은 다음과 같다.

 Version Control Provider  Version
 Concurrent Versions System (CVS)*  CVS 1.11.9 and later
CVSNT 2.08 and later
 Microsoft Team Foundation Server  2005, 2008, and 2010
 Microsoft Visual SourceSafe  5.0, 6.0, and 2005 (8.0)
 Perforce*  2009.2 (command-line client)
 PVCS  Note: PVCS 6.6.1 and 6.8.0 are specifically not supported with Team Coding.
 Subversion*  1.6.5 and later

* 표기가 있는 버전 컨트롤러는 command-line client를 이용하여 접속함.

이 목록에 없는 제품이라 하더라도 Microsoft SCC API를 지원한다면 Toad를 통해 버전관리를 할 수 있다.

Toad에서는 Team Coding 전략을 아래 표에 나와있는 세가지 방식 중 하나를 선택해서 사용할 수 있다. Toad의 팀 코딩 기능과 서드 파티 버전관리 시스템 연동의 조합에 따라 달라지며, 그에 따라 Setup 자체가 다르므로 개발팀에게 가장 잘 맞는 방식으로 설정하는 것이 좋을 것이다

 소스 컨트롤 방식 장점   단점
 Toad 팀 코딩 (X)

서드파티 소스컨트롤 (O)

버전 히스토리 관리 가능

추가적인 database object 설치가 필요 없음.
Editor와 Project Manager에서만 사용이 가능

Database Object를 lock 할 수 없음.
 Toad 팀 코딩 (O)

서드파티 소스컨트롤 (X)

Check out을 통해 database object를 lock 할 수 있음.

Code Control Group을 통해 lock 관리해야 될 객체를 지정할 수 있음.

Toad내의 database object가 열리는 어떤 화면에서도 사용가능.
Team Coding을 위해 추가적인 Object를 설치해야 함.

버전 히스토리 관리가 불가능.
 
Toad 팀 코딩 (O)

서드파티 소스컨트롤 (O)
 위 두 방식의 장점을 모두 가짐.  설치 및 설정이 가장 복잡함.

*Toad Team Coding : 팀 코딩은 Oracle Object를 lock/unlock 하여 의도하지 않는 변경을 예방할 수 있는 기능. 이 기능을 위해 Toad Server-side Object(테이블, 패키지 등)을 설치해야 한다.

*서드파티 소스컨트롤 : svn, cvs, Team Foundation Server 같은 서드파티 버전 컨트롤 시스템을 이용하여 일반적인 소스의 버전 관리 기능을 이용할 수 있다.


Toad Team Coding & Version Control Integration Setting

Toad에서 Team Coding과 서드파티 소스컨트롤을 모두 이용하기 위해서는 아래와 같이 크게 4가지 단계에 따라 설치 및 설정을 해야 한다.

1. Toad Server Side Object Install
2. SVN Server Install & Setting
3. SVN Command Client Install
4. Toad Team Configuration

Team Coding을 위해 좀 많은 설치 및 설정을 해야 하는 게 귀찮기는 하다. 하지만, 투입되는 비용과 노력에 비해 얻는 효과가 크다면 한 번 해 볼만 하지 않을까?

1. Toad Server Side Object Install

Toad Team Coding을 이용하기 위해서는 Oracle에 Toad 계정과 몇 개의 테이블 및 관련된 Object  등이 생성되어야 한다. 이 것을 해주는 것이 Toad Server-side Objects Wizard다. Oracle 서버에 테이블 및 Object를 설치함으로써 Team Coding 뿐만 아니라 보안관리, 튜닝 등 Toad의 고급 기능을 활용할 수 있으므로 Team Coding과 상관없이 구성하는 것도 좋다.

Server Side Objects Wizard 실행 시 일반적으로 System 계정으로 로그인하여 실행해야 한다. System 계정이 꼭 필요한 것은 아니며 꼭 필요한 몇 개의 권한만 가지고 있다면 어떤 계정으로도 실행해도 상관없다.

메뉴 > Database > Administrator > Server Side Objects Wizard 클릭

첫번째 항목을 선택. Next >

기존에 이미 TOAD 스키마가 있는 경우 업그레이드하며, 신규 설치도 할 수 있는 첫번째 옵션을 선택. NEXT>

TOAD 계정이 없다고 생성을 한다는 메시지가 나온다. OK>



TOAD 스키마에 생성할 기능들을 선택하는 화면이다. Team Coding을 위해서는 Team Coding 항목만 선택하면 되지만, 추후 Toad의 다양한 기능을 경험하기 위해서 전부 다 선택하도록 하겠다. Next >



TOAD Space Manager 기능을 사용하기 위해서 권한이 필요하다는 메시지가 출력된다. 그림처럼 선택 후 Next >

 

Toad Profiler 기능을 사용하기 위해서 몇몇 권한이 필요하다고 메시지가 출력됨. 그림처럼 선택 후 Next >



이 화면에서는 TOAD 계정의 패스워드와 테이블스페이스를 지정해야 한다 Next >

테이블 스페이스가 생성되지 않았거나 Toad 전용으로 테이블 스페이스를 하나 생성해야 한다면, 현 화면을 그대로 둔 채 SQL Editor를 열어 아래 그램처럼 간단한 테이블 스페이스를 생성하면 된다.
* 데이터파일 경로 및 테이블 스페이스 명은 각자의 시스템에 맞게 수정해야 한다.


 


Toad Team Coding을 위한 Role 생성 화면. 신규 생성이니 전부 Create New를 선택한 후 Next >



Toad Server Side Objects 나머지 기능 설치를 위한 테이블 스페이스 지정화면. 전부 이전 화면에서 생성한 테이블스페이스 “TOAD_DS”를 선택. Next >



앞에서 설정한 모든 것들이 스크립트로 만들어 졌다.. Run Script 버튼을 누르면 바로 실행이 된다.
Run Script >

에러 없이 스크립트가 실행되었다. 확인>



스크립트가 실행될 때 생성된 메시지가 출력된다. 성공적으로 설치되었으니 그냥 Next >


Toad Security Administrator를 지정하는 화면. 일단 이 화면은 Next >
추후 필요할 때 설정하도록 하겠다.


Setup 이 끝났다.



현 상태에서 TOAD 계정으로 로그인하여 Menu > Utilities > Team Coding >Team Coding Status 선택 시 나타난 화면이다. Team Coding에 대한 필요사항이 Install 되었다고 나오며, VCS(Version Control System)에 대한 정보가 설치되지 않았다고 보여주고 있다.

화면이 많아서 복잡한 듯 보이지만 실제는 TOAD 계정 생성하고 몇몇 테이블 및 패키지 설치하고, TOAD 계정에 몇몇 권한만 할당해주는 단순 작업이다.

2. SVN Server Install & Setting

이제 Team Coding 을 위한 서브버전 서버를 설치한다. 로컬이든 네트워크 환경이든 상관없다. 필자는 로컬 환경에 Subversion Server를 설치 할 것이다.

윈도우용 Subversion Server로는 가장 사용이 편리한 VisualSVN Server를 설치할 것이다. 서브버전 서버로 다른 어떤 툴을 사용해도 무방하다.

http://www.visualsvn.com/server/download/ 이곳에서 VisualSVN Server를 다운받아서 설치한다.

2-1 SVN Server Install


VisualSVN Server와 관리콘솔 모두 설치를 선택한 후 Next>


설치위치, SVN Server과 관리할 Repository 위치, 접근 포트를 설정한다. 인증은 서브버전 인증을 사용하도록 설정한다. Next 하시면 설치 완료..

2-2. SVN Server Setting

먼저 SVN에 접속할 유저 계정을 하나 생성한다.
좌측 트리에서 Users를 우측마우스 클릭 > Create Users.. 선택. 계정명과 패스워드를 입력한 후 OK 버튼 클릭.


다음은 그룹 생성 단계.

유저 생성과 동일한 방식으로 Groups를 우측 마우스 클릭한 후 Create Group.. 선택. 그룹명을 입력한 후 멤버를 Add 버튼을 클릭하여 생성한 계정을 멤버로 등록한다.


이제 VisualSVN 내에서 Repository를 생성해야 한다.
VisualSVN 관리콘솔에서 좌측메뉴의 Repositories 우측 마우스 클릭 > Create New Repository.. 선택.


Repository 명을 입력합니다.
Create default Structure를 선택하시면 Oracle 폴더 하위에 trunk, branches, tags같은 서브폴더가 생성된다.

일반적으로 SVN의 레파지토리 구성은 레파지토리 > 프로젝트 > trunk/branches/tags 같은 방식으로 구성을 하지만 Toad는 프로젝트 하위에 디렉토리를(예: trunk 등) 만들어서 해당 디렉토리 레벨을 Check Out하면 제대로 인식하지 못하는 문제가 있다. 
그러므로 레파지토리나 하위 프로젝트 생성시 “Create default structure” 체크를 반드시 끄고 만든다.


프로젝트 생성:
생성된 레파지토리명(Oracle) 우측 클릭 > 새로 만들기 > Project Structure를 클릭.


프로젝트명을 입력하고 OK버튼을 클릭.

이렇게 프로젝트를 생성하면 프로젝트 폴더 하위에 trunk, branches, tags 세 폴더가 생성된다.
위에서 언급했듯이 이렇게 폴더가 있더라도 Toad에서 제대로 처리하지 못하므로 VisualSVN Sever의 좌측 트리에서 각각의 trunk, branches, tags 폴더를 삭제한다.
물론, 삭제하지 않아도 사용상의 문제는 전혀 없다.

3. SVN Command Client Install 

3-1. SVN Command Client Install (sliksvn)

Toad는 command-line subversion client tool이 반드시 필요하다. SVN command tool이 여러가지 있지만 필자는 그 중 silksvn를 설치하여 팀개발 환경을 구성할 것이다.

http://www.sliksvn.com/en/download 에서 자신의 컴퓨터 환경(x32, x64)에 맞는 sw를 다운받아서 설치하면 된다. 설치는 간단하다.

Choose Setup Type에서 Custom을 선택한다.


 


 

Subversion Client를 제외하고는 다 설치하지 않도록 설정을 바꾼 다음 Next를 눌러 설치를 진행한다. Team Coding 환경 구성 시 Toad svn.exe만 필요로 한다.

그리고 sliksvn command-line tool이므로 조작이 불편해 추가적으로 GUI Client tool tortoisesvn을 설치할 것이므로 다른 설치 옵션은 모두 빼고 설치한다. 물론 별도의 .GUI 클라이언트를 설치하지 않고 sliksvn만으로 사용한다면 좀 더 많은 옵션으로 설치하는 게 좋을 것이다.

 

끝으로 GUI 환경의 subversion client도 하나 설치한다. 여기서는 가장 많이 사용하는 tortoisesvn 을 설치한다. 별도의 svn client 는 최초 checkout 할 때만 사용하므로 tortoisesvn 설치여부는 각자 판단해서 설치하면 된다.

 

http://tortoisesvn.net/downloads.html 이곳에서 버전에 맞는 설치파일을 다운받아서 설치하시면 된다. 워낙 단순한 install이라서 별도의 설명은 생략하도록 하겠다.


한참을 달렸지만, 아직 중요한 Toad에서의 Team 환경 및 SVN 연동을 위한 설정 부분이 남았다. 이 부분은 part2 에서 살펴보도록 하겠다.

 

Trackback 0 Comment 0
2011.05.16 17:10

실전 데이터모델링(비트컬럼으로 컬럼레벨 데이터통합) 기법 및 오라클 비트연산 가이드




비트컬럼으로 속성레벨의 데이터 통합

이번 포스트에서는 비트값으로 구성된 컬럼으로 컬럼 수준의 데이터 통합에 대한 가이드 및 비트 컬럼을 SQL로 조작하는 방안에 대해 살펴볼까 합니다.

먼저, 일반적인 샘플 ERD를 하나 보겠습니다.


좌측은 Logical 엔터티이며 우측은 Physical 테이블 설계안 입니다.

지극히 간단한 테이블입니다. 웹 화면을 구성하는 메뉴가 있고, 메뉴의 속성으로는 ID, 명, 그리고 화면을 제어하는 4개의 여부 컬럼이 있습니다. 간단하지만 전혀 문제가 없습니다. 요구사항도 명확히 반영했다고 한다면 정말 문제가 없는 모델링 입니다.

프로젝트팀이 프로젝트를 완료하고 난 후 운영팀에서 운영중에 새로운 고객 요구사항들이 나오기시작합니다. 메뉴ID를 화면에 무조건 보여주지 말고 사용자가 보여줄지 말지를 선택하게 해달라는 군요. 그러면 컬럼을 추가하면 됩니다 “메뉴ID디스플레이여부” 란 컬럼이 추가됩니다.

또 다른 고객이 이와 유사한 요구를 해 옵니다.
얼마 지나지 않아 “~여부” 컬럼만 6개나 추가되었군요. 그래도 컬럼만 추가하고 프로그램 코드를 수정하면 요구사항을 반영하는 데에는 큰 문제는 없습니다.

하지만 일 하기 싫은 DBA가 고민합니다.(바로 저 입니다.)
“이런 식의 고객 요구사항이 들어와도 컬럼 수정, 테이블 수정 없이 처리할 순 없을까?”

방법은 아래 그림과 같이 모든 여부 컬럼을 비트값의 합으로 표현하여 하나의 컬럼으로 통합해버리면 됩니다.


4개의 여부 컬럼을 “디스플레이비트값” NUMBER 하나의 컬럼으로 통합했습니다. 기존 모델의 각각의 컬럼은 2진수로 표현했을 때 하나의 비트값에 대응됩니다.

예를 들어
“배너디스플레이여부” 컬럼은 2^0 = 1로 설정하면 되며,
“메뉴이미지사용여부” 컬럼은 2^1 = 2,
“컨텐츠표시여부” 2^2 = 4,
“Hybrid메뉴여부” 2^3 = 8 이 됩니다.

그리고 실제 디스플레이비트값은 각각의 값을 BITWISE OR 연산을 하여 저장을 하게 됩니다.

예를 들어
“배너디스플레이여부” = “Y” ,
“메뉴이미지사용여부”= “N”,
“컨텐츠표시여부” = “Y”,
“Hybrid메뉴여부” = “Y” 값을 갖는 다면 실제 디스플레이비트값 컬럼에 저장되는 값은
1(2^0) + 4(2^2) + 8(2^3) = 13이 됩니다.

각각의 통합전 컬럼에 해당 되는 비트값을 부여할 때 2의 승수로만 부여하기 때문에 실제 bitwise or 연산의 결과나 단순 더하기 결과나 결과는 동일해 집니다.

0001(1) + 0010(2) + 1000(8) = 1011(13)

이 모델의 장점은 고객의 새로운 요구사항이 있을 때 컬럼의 추가 없이 프로그램만 수정하면 됩니다. 좀 있어보이는 말로는 “시스템의 유연성을 높이는 설계 방법” 이라고 하죠. 쉽게 말해 DBA가 편해지는 군요. 제가 바라는 바입니다.

제가 운영하는 시스템의 사례를 얘기할까 합니다.

제가 운영하는 시스템은 마케팅 사이트로 Global 시스템이지만 국내 한 서버에서 전세계 사이트를 모두 서비스 합니다. 시스템 사양에 비해서 Request가 많기 때문에 서버는 항상 바쁩니다. 글로벌 사이트라서 고객은 일년의 단 10분도 시스템이 정지되는걸 원하지 않습니다.

그럼에도 불구하고 위의 사례처럼 자잘 하지만 “~여부” 같은 컬럼을 추가해야만 처리될 수 있는 요구를 합니다. 컬럼 추가하고 개발하면 되겠죠. 하지만 문제가 있습니다. 대부분의 테이블은 문제가 없지만 시스템의 Main Entity인 2~3개의 테이블은 거의 모든 SQL이 집중되고, 수많은 PL/SQL이 참조하고 있어서 컬럼을 추가하는 순간 library cache pin 및 lock 등의 waiting이 걸리면서 오라클이 처리를 빨리 하지 못하게 되며, WAS에서는 DB 요청에 대한 리턴을 빠르게 받지 못하게 되니 계속계속 새로운 요청마다 새로운 Oracle Connection을 맺다가 Connection Pool Max size를 넘어서게 될 때 시스템이 거의 죽어버린답니다.

이런 상황에서는 컬럼 추가 작업 자체가 online에서 쉽게 alter table 명령어로 처리할 수 없으며, 컬럼을 추가하기 위해서는 수많은 결재와 보고를 해야 하고 휴일에 특정 시간대에 시스템을 내리고 컬럼 추가하고 다시 시스템을 기동하는 식으로 처리를 합니다.

어휴~ 컬럼 추가 작업이 장난이 아닙니다. 그러니, 제가 컬럼 추가 안하고 설계할 수 있는 방안을 고민하지 않을 수 없습니다.

하지만 이 모델의 단점도 만만치 않습니다.
단점은 첫째, 가독성이 떨어집니다. DA#으로 모델링을 하면 통합전 컬럼의 비트값을 서브타입으로 표현할 수 있어서 그나마 낫지만 Erwin 같은 툴을 쓰면 속성설명 정도에만 표현할 수 밖에 없어 모델의 가독성이 떨어져 문제가 됩니다.
두번째 단점은 SQL이 복잡해 집니다. 초급 개발자들은 설명을 해줘도 잘 이해를 못하더군요.

간단하지만 위 두 모델에 대해 무엇이 좋고 나쁘다라고는 말할 수 없을 것 같습니다. 다만, 비트값으로 속성을 표현하여 통합할 수 있는 방법도 있다라는 것을 알고, 시스템의 상황에 맞게 가장 적합한 방법을 사용하시면 됩니다. 저 같은 경우는 위의 컬럼 추가가 문제가 되는 테이블, 그리고 자주 업무 요건이 변경되는 테이블은 비트값으로 컬럼을 통합하는 방식을 많이 사용하며, 그렇지 않는 테이블은 사용하지 않습니다.

비트연산 기초

비트값 형식으로 속성을 모델링 했을 때 화면 UI와 이 UI를 처리하는 DML에 대해 살펴보도록 하겠습니다. 그전에 기초로 돌아가서 비트 연산에 대해 알아보겠습니다. 요즘 개발자 분들이 처음부터 웹 개발을 하신 분들은 비트 연산을 할 일이 없어서인지 비트연산을 헷갈려 하더군요.

비트 연산(Bitwise operation)은 한 개 혹은 두 개의 이진수에 대해 비트 단위로 적용되는 연산입니다.

NOT : NOT 연산은 각 자릿수의 값을 반대로 바꾸는 연산입니다.

NOT 0111
= 1000

OR : OR 연산은 두 값의 각 자릿수를 비교해, 둘 중 하나라도 1이 있다면 1을, 아니면 0으로 계산됩니다.

0101
OR 0011
= 0111

XOR : XOR 연산은 두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1으로 계산 합니다.

0101
XOR 0011
= 0110

AND : AND 연산은 두 값의 각 자릿수를 비교해, 두 값 모두에 1이 있을 때에만 1을, 나머지 경우에는 0으로 계산합니다

0101
AND 0011
= 0001


예전에 공부했던 게 기억이 나십니까?

비트속성 컬럼값 DML 가이드

화면 UI에서 비트컬럼값을 처리하는 DML 가이드입니다.



다음과 같은 화면이 있다고 가정합니다. 위 메뉴 테이블을 관리하는 화면입니다.

각 비트값에 해당되는 항목마다 라디오 버튼을 가진 하나의 항목으로 UI는 구성될 것입니다. 물론 다른 방식으로도 가능하겠지만요. 일단 이렇게 된다고 가정합니다.

이 화면의 특징은 한 화면에서 비트컬럼값이 사용하는 모든 비트값을 다 관리를 한다는게 특징입니다.

이런 케이스에 대해서는 처리 로직은 간단합니다.

먼저, 화면을 구성하기 위한 SELECT 문을 보겠습니다.

SELECT MENU_ID
            ,MENU_NM
            ,DECODE(BITAND(DISPLAY_BV,1),1,'Y','N') AS "배너디스플레이여부"
            ,DECODE(BITAND(DISPLAY_BV,2),2,'Y','N') AS "메뉴이미지사용여부"
            ,DECODE(BITAND(DISPLAY_BV,4),4,'Y','N') AS "컨텐츠표시여부"
            ,DECODE(BITAND(DISPLAY_BV,8),8,'Y','N') AS "Hybrid메뉴여부"
FROM MENU2;

오라클에서 제공하는 BITAND 함수를 가지고 해당비트값이 1인지를 판단할 수 있습니다.

이 경우 값을 INSERT, UPDATE 할 때는 각각의 항목에 대한 값(Y/N)을 판단해서 아래와 같이 처리할 수 있습니다.

UPDATE MENU2
      SET DISPLAY_BV = DECODE('배너디스플레이여부','Y',1,0)
                                + DECODE('메뉴이미지사용여부','Y',2,0)
                                + DECODE('컨텐츠표시여부','Y',4,0)
                                + DECODE('Hybrid메뉴여부','Y',8,0)
 WHERE MENU_ID = :MENU_ID;

하지만, 위의 경우처럼 Y/N으로 INPUT TYPE=”RADIO”의 값을 구성하는 것 보다는 VALUE로 각각의 컬럼이 갖는 비트값으로 바로 지정하는게 효율적일 수 있습니다. 웹으로 구성한다면 value >0 면 checked를 지정하면 될 것 같군요.


SELECT MENU_ID
            ,MENU_NM
            ,DECODE(BITAND(DISPLAY_BV,1),1,1,0) AS "배너디스플레이여부"
            ,DECODE(BITAND(DISPLAY_BV,2),2,2,0) AS "메뉴이미지사용여부"
            ,DECODE(BITAND(DISPLAY_BV,4),4,4,0) AS "컨텐츠표시여부"
            ,DECODE(BITAND(DISPLAY_BV,8),8,8,0) AS "Hybrid메뉴여부"
  FROM MENU2;

INSERT, UPDATE문은 각각의 항목값을 모두 더해서 처리하면 됩니다.


UPDATE MENU2
      SET DISPLAY_BV = 1 + 4 + 0 + 8
 WHERE MENU_ID = :MENU_ID;

이번에는 좀 더 난이도가 높은 UI를 보겠습니다.

원칙적으로는 비트속성값 모두를 한 화면에서 핸들링 해야 하지만, 업무요건상 그렇게 될 수 없다고 가정합니다. 아래 그림을 보시면 메뉴관리 화면에서는 Menu Image Use 항목과 Is Hybrid menu 항목은 메뉴관리 화면에서 관리하고, 나머지 2개의 컬럼은 다른 화면에서 관리한다고 가정합니다.



이 경우에는 2^1(2) 과 2^3(8)만 화면에서 관리하므로 다른 비트값 2^0(1), 2^2(4) 값은 이 화면에서 핸들링하면 안됩니다.

SELECT문과 INSERT문은 이렇게 화면을 구성하더라도 기존과 동일하게 처리하면 된다. 하지만 UPDATE문은 다른쪽 화면에서 나머지 값에 대한 핸들링을 하므로 영향을 받지 않도록 다르게 처리해야 합니다.

현재 DIPLAY_BV 값을 7 (2^0(1) + 2^1(2) + 2^2(4))이라고 가정하겠습니다.

즉, 배너디스플레이여부=Y, 메뉴이미지사용여부=Y, 컨텐츠표시여부=Y, Hybrid메뉴여부=N인 상태입니다.
위의 메뉴화면에서 사용자가 Save 버튼 클릭시 값이 메뉴이미지사용여부=N, Hybrid메뉴여부=Y로 상태값이 변경되었다고 하면


현재값:                                      0111 (7)
화면에서 관리안하는 비트값의 합 : 0101 (5)
----------------------------------------------
두값의 BITAND 값                     : 0101 (5)

현재 설정값(비관리항목은0으로)  : 1000 (8)
----------------------------------------------
두값의 BITOR                           : 1101 (13)

먼저 현재값과 화면에서 관리하지 않는 항목에 해당되는 비트값의 BITWISE OR 값 (단순 더하기)을 구해서 두 값을 BITAND 합니다.
즉, 현재값인 7인 값과 화면에서 관리하지 않는 항목의 값 (배너디스플레이여부 2^0(1) + 컨텐츠표시여부 2^2(4))을 더한 값과 서로 BITAND 연산을 합니다. 결과가 0101 (5)가 나오는군요.
이 값과 현재화면에서 설정값(단, 관리안하는 UI항목에 대해서는 전부 0으로 비트값처리, 메뉴이미지사용여부=N이므로 0, Hybrid메뉴여부=Y이므로 8)의 합을 서로 BITOR 연산합니다. 5 BITWISE OR 8 = 13가 나오는군요.
13값을 저장하면 됩니다.

다음과 같은 UPDATE문이 나올 수 있습니다.


UPDATE MENU2
   SET DISPLAY_BV = PKG_UTILS.BITOR(BITAND(DISPLAY_BV,5),DECODE('메뉴이미지사용여부','Y',2,0) + DECODE('Hybrid메뉴여부','Y',8,0))
 WHERE MENU_ID = 1;

* DECODE 부분은 UI 값을 대응되는 비트값으로 구성했다면 DECODE없이 바로 더하면 됩니다.

 BITOR 함수는 오라클에서는 DEFAULT로 제공하지 않습니다. 그래서 아래와 같은 PL/SQL 패키지에서 BIT관련 함수를 구현했습니다.

그리고 패키지 주석을 보시면 이해하시겠지만, 특정 비트자리수의 값을 0으로 혹은 1로 설정할 수 있는 BIT_SET 함수도 만들어 두었습니다.
이 함수를 위 사례에 적용해서 만일 현재값과 상관없이 Hybrid메뉴여부(2^3=8) 항목을 CHECKED로 만들려고 하면 BIT_SET(컬럼값, 8, 1)로 함수를 호출하면 됩니다. 마지막 인자값은 두번째 인자값에 해당되는 비트자리수의 값을 1 혹은 0으로 설정하도록 하는 값(1/0)이 인자로 넘어갑니다.

좀 전에 사용했던 UPDATE문을 동일하게 BIT_SET 함수로 처리할 수 있습니다.

UPDATE MENU2
      SET DISPLAY_BV = PKG_UTILS.BIT_SET(PKG_UTILS.BIT_SET(DISPLAY_BV,2,0), 8,1)
 WHERE MENU_ID = 1;


BITWISE 관련 오라클 유저 함수 (패키지)

다음의 패키지(스크립트)는 오라클에서 BITAND 함수만을 제공하기 때문에 원할한 BIT연산을 위해서 관련 함수를 구현한 코드입니다. 참고하시기 바랍니다.


CREATE OR REPLACE PACKAGE PKG_UTILS AS
/******************************************************************************
   NAME        : PKG_UTILS
   PURPOSE     : 시스템 공통으로 사용되는 함수, 프로시저 등을 제공하는 패키지
   DESCRIPTION : UTILITY OPERATION PACKAGE
   REVISIONS   : 1.0
   Ver        Date        Author           Description
   ---------  ----------  ---------------  ------------------------------------
   1.0        2011-04-01  Jinook,lee       1. Created this package.
******************************************************************************/
 
    -------------------------------------------------------
    -- DESC : X, Y 값을 서로 BIT OR 연산한다.
    -- OR 연산은 두 값의 각 자릿수를 비교해, 둘 중 하나라도 1이 있다면 1을, 아니면 0을 계산한다
    -- x, y : 비트 연산할 숫자
    -- return   : bit or 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITOR(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC;
   
    -------------------------------------------------------
    -- DESC : X, Y 값을 서로 BIT XOR 연산한다.
    -- XOR 연산은 두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1을 계산한다
    -- x, y : 비트 연산할 숫자
    -- return   : BIT XOR 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITXOR(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC;
   
    -------------------------------------------------------
    -- DESC : X 값을 BIT NOT 연산한다.
    -- NOT 연산은 각 자릿수의 값을 반대로 바꾸는 연산이다.
    -- x : 비트 연산할 숫자
    -- return   : BIT NOT 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITNOT(x NUMBER)
    RETURN NUMBER DETERMINISTIC;
   
    -------------------------------------------------------
    -- DESC : 닷컴의 비트 컬럼값의 특정비트를 0으로 만드는 함수이다.
    -- 내부적으로 X & ^Y 로 연산한다.
    -- x, y : 비트 연산할 숫자
    -- return   : BIT 마스크 연산한 결과값
    --------------------------------------------------------
    FUNCTION BIT_MASK(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC;
   
    -------------------------------------------------------
    -- DESC : 닷컴의 비트 컬럼값의 특정비트를 주어진 값으로 만드는 함수이다.
    -- x,y : 비트 연산할 숫자
    -- z : x값의 y자리수를 값을 설정할 값 (0 or 1)
    -- return   : BIT SET 연산한 결과값
    --------------------------------------------------------
    FUNCTION BIT_SET(x NUMBER, y NUMBER, Z NUMBER)
    RETURN NUMBER DETERMINISTIC;
   
END PKG_UTILS;
/


CREATE OR REPLACE PACKAGE BODY PKG_UTILS AS
/******************************************************************************
   NAME        : PKG_UTILS
   PURPOSE     : 시스템 공통으로 사용되는 함수, 프로시저 등을 제공하는 패키지
   DESCRIPTION : UTILITY OPERATION PACKAGE
   REVISIONS   : 1.0
   Ver        Date        Author           Description
   ---------  ----------  ---------------  ------------------------------------
   1.0        2011-04-01  Jinook,lee       1. Created this package.
******************************************************************************/
 
    -------------------------------------------------------
    -- DESC : X, Y 값을 서로 BIT OR 연산한다.
    -- OR 연산은 두 값의 각 자릿수를 비교해, 둘 중 하나라도 1이 있다면 1을, 아니면 0을 계산한다
    -- x, y : 비트 연산할 숫자
    -- return   : bit or 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITOR(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
        RETURN x + y - BITAND(x, y);
    END;
   
    -------------------------------------------------------
    -- DESC : X, Y 값을 서로 BIT XOR 연산한다.
    -- XOR 연산은 두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1을 계산한다
    -- x, y : 비트 연산할 숫자
    -- return   : BIT XOR 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITXOR(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
        RETURN BITOR(x,y) - BITAND(x,y);
    END;
   
    -------------------------------------------------------
    -- DESC : X 값을 BIT NOT 연산한다.
    -- NOT 연산은 각 자릿수의 값을 반대로 바꾸는 연산이다.
    -- x : 비트 연산할 숫자
    -- return   : BIT NOT 연산한 결과값
    --------------------------------------------------------
    FUNCTION BITNOT(x NUMBER)
    RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
        RETURN (0 - x) - 1;
    END;
   
    -------------------------------------------------------
    -- DESC : 닷컴의 비트 컬럼값의 특정비트를 0으로 만드는 함수이다.
    -- 내부적으로 X & ^Y 로 연산한다.
    -- x, y : 비트 연산할 숫자
    -- return   : BIT 마스크 연산한 결과값
    --------------------------------------------------------
    FUNCTION BIT_MASK(x NUMBER, y NUMBER)
    RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
        RETURN BITAND(x, BITNOT(y));
    END;
   
   
    -------------------------------------------------------
    -- DESC : 닷컴의 비트 컬럼값의 특정비트를 주어진 값으로 만드는 함수이다.
    -- x,y : 비트 연산할 숫자
    -- z : x값의 y자리수를 값을 설정할 값 (0 or 1)
    -- return   : BIT SET 연산한 결과값
    --------------------------------------------------------
    FUNCTION BIT_SET(x NUMBER, y NUMBER, z NUMBER)
    RETURN NUMBER DETERMINISTIC
    IS
    BEGIN
        IF z = 1 THEN
            RETURN BITOR(x, y);
        ELSIF z = 0 THEN
            RETURN BITAND(x, BITNOT(y));
        ELSE
            RAISE PKG_GLOBAL.ERR_WRONG_PARAMETER;
        END IF;
    END;
END PKG_UTILS;
/

* BIT_SET 함수에서 RAISE PKG_GLOBAL.ERR_WRONG_PARAMETER; 부분은 제가 자주 사용하는 ERROR를 별도의 GLOBAL 패키지에 선언해두고 참조해서 사용할 수 있도록 구성한 부분입니다.  해당 부분 삭제하시고 패키지 컴파일 하시거나 일반적인 PL/SQL ERROR RAISE 하는 구문으로 대체하시면 됩니다.


Trackback 0 Comment 2
  1. 신.. 2011.09.09 01:09 신고 address edit & del reply

    참기발한 방법이군요.
    여부컬럼하면 11100111 혹은 YYYNNYYY 만 알고 있는데 이런 방법도 있군요.
    잘 봅니ㄴ다..

  2. 땅콩맨 2012.05.14 15:36 신고 address edit & del reply

    안녕하세요
    MSSQL에서 SQLite로 쿼리를 바꾸려고 하는데
    sql = "declare @num245(4), @num345 char(4) select @num245=autogubun from staff.dbo.ONR_TV_Station_tbl where num=245 select @num345=autogubun from staff.dbo.ONR_TV_Station_tbl where num=345 거든요. 원래 쿼리가...

    바꾸려고 하니깐 declare도 그렇고 select 문 2개 이어서 쓰는방식도 그렇고
    바꾸는 방법을 못찾겠는데, 혹시 이것에 대해서 답변좀 부탁드릴께요~

    이럴줄 알았으면 SQLite 공부좀 해둘껄 그랬어요... 홀홀홀...

2011.05.11 16:01

Toad의 강력한 기능을 eclipse 안에서 - Toad Extension for eclipse




Toad의 강력한 기능을 Toad를 실행하지 않고 eclipse내에서 이용할 수 있다면 개발자들에게는 유용할 것이다. Quest사의 Toad Extension(freeware)를 이용하면 eclipse 뿐만 아니라, Visual Studio에서도 툴 환경 내에서 Toad의 강력함을 이용할 수 있다.

Toad Extension for eclipse를 설치해보자.

1. Eclipse 실행 – Help > Install New Software 메뉴 선택

2. Install 대화상자의 Work with 부분에 다음의 주소를 입력

http://toaddownload.quest.com/toadextensions/eclipse/freeware/
다음, 다음 눌러서 설치하시고 re-start 하면 설치가 끝납니다.

Toad Extension은 Toad 없이도 구동 가능합니다. 다만, 기존에 Toad가 설치되어 있다면 toad User Files 디렉토리를 읽어서 현재 설정된 Connection정보를 Connection window에 자동으로 설정해 줍니다. 다만, 저장된 패스워드 정보를 읽어오지 못해서 패스워드는 다시 설정해 줘야 합니다.

Toad Extension 실행해 보자.

Eclipse 실행 후 window > Open Perspective > Other 선택 후 Toad Extension을 선택하면 됩니다.


Toad Extension Overview

Connections View

Toad Connections View에서는 신규 접속을 만들거나 기존 Connection 정보를 이용하여 Oracle에 접속할 수 있습니다. Oracle 접속은 Direct TCP/IP를 이용해서 접속할 수도 있으며 Oracle Tnsname 정보를 이용하여 접속할 수도 있습니다.

Schema Browser & Detail Browser

좌측의 Schema Browser에서 Database Object 항목들을 선택하면 우측에 상세정보가 나타납니다.
기본적인 Object만 나오는군요. Freeware라 그런지 dba들이 사용하기에는 좀 기능이 약해 보입니다. 그렇지만 왠만한 프로젝트에서는 이 툴 하나만 있어도 무방할 듯 보입니다.

좌측 Schema browser에서는 기본적으로 트리구조로 보이지만 Tree/Multi Tab/Category 방식의 UI를 지원합니다. (스키마 브라우저의 트리 아이콘을 클릭해 보시면 레이아웃이 바뀐답니다.)

스키마 브라우저에서 각각의 Object를 클릭하면 Detail View에 선택한 Object에 대한 상세한 내용이 나옵니다. 나름 필요한 정보는 모두 잘 나오는 것 같습니다.

SQL Editor


SQL Editor에서는 SQL을 실행하고 결과 및 실행계획 등을 볼 수 있습니다. 단축키가 eclipse 기준으로 설정되어 있어서 토드 사용자에게는 약간 헷갈릴 수 있습니다. 익숙한 키를 사용하려면 eclipse 설정에서 단축키 매핑 정보를 변경하시면 됩니다.

SQL Editor에서는 무엇보다 Code Intelligence 기능이 토드보다 빠릅니다. .(dot)을 입력하면 바로 바로 Object 목록이 나타나는 군요.  무척 맘에 듭니다.

PL/SQL Editor


 PL/SQL 뷰어에서 Edit를 누르면 수정화면으로 넘어갑니다.
제가 사용하는 쪽의 DB는 Character Set이 UTF-8 환경인데.. PL/SQL 뷰어에서는 주석문이 안깨지고 잘 보이는데.. 수정화면에서는 스크립트의 주석 부분이 깨지는 군요. Eclipse의 character set을 UTF-8로 변경해도 여전히 깨지는 군요. 이 부분은 약간 이슈가 있는 듯 합니다.
한글 환경의 DB를 사용하는 곳은 별 문제가 없을것보입없을 것, 테스트를 못해봐서.. 장담은 못하겠습니다.

SQL Monitor


하단의 SQL Monitor View에서 Enable Output 아이콘을 클릭하신 후 detail view나 Schema Browser를 클릭하면 Toad Extension이 해당 데이터를 화면에 뿌리기 위해서 오라클에 보내는 SQL을 모니터 할 수 있습니다. Toad의 spool 기능과 동일한 기능이라고 보면 될 것 같습니다.

Session Browser


개발용 Toad Extension임에도 불구하고 Session Browser가 있다니 놀랍습니다. 해당 세션을 선택하고 세션을 Kill 시킬 수도 있군요.

Oracle Parameter View


오라클 파라미터를 조회할 수 있는 뷰도 제공 됩니다.


Toad Extension은 자바 개발시에 Toad와 Eclipse를 번갈아 가면서 개발하지 않고 eclipse 환경 내에서 왠만한 개발을 할 수 있다는 점은 매우 매력적인 요소입니다. 다만, Toad와 같이 많은 기능을 담고 있지 않아서 DBA가 사용하기에는 조금 무리가 따릅니다.
Eclipse 환경이라서 토드에 익숙하지 않은 개발자들에게 매우 유용할 듯 보이며, 특히 프리웨어라는 점은 ㅋㅋ 무지 좋습니다.
프로젝트에서 DBA만 정품 Toad를 구매하고, 나머지 개발자들은 Toad Extension을 사용한다면 비용이 많이 절감되겠네요.

데이터를 조회하고 핸들링 할 수 있는 다른 eclipse plug-in 들과 비교해 보더라도 개인적으로는Toad Extension이 가장 맘에 드는 툴입니다. 물론 아직은 버그도 있고 기능도 부족하지만 점점 더 좋아지리라 막연한 기대를 해봅니다.

제 글을 읽고 맘에 드신다면… 한번 사용해 보세요.^^

Trackback 2 Comment 0
2010.10.19 09:32

안드로이드 마켓용 QR Code 생성 방법




안드로이드 마켓에 올라와 있는 App을 소개하는 페이지를 보면 의래 QR Code가 있습니다. 저도 블로그에 QR Code  붙일려고 찾아봤는데, 여러 Open Source Generator 도 있지만, 웹에서 간단히 QR Code를 만드는 방법이 있어서 소개합니다.

http://www.rittr.com/tools/qr_code_generator.php 에 접속합니다.



Size는 4로 설정합니다. 1로 하니 너무 작게 나오는 군요.

QR Link 부분에 market://details?id=패키지명 을 기술한 후 Generate 버튼을 누릅니다.
그러면 위 그림과 같이 QR 코드가 만들어집니다. 이미지 저장한 후 App 소개 페이지에 붙이면 끝났니다.

QR Reader로 테스트 해보니 바로 마켓의 제 앱에 들어가 지는 군요.

Android 마켓용 QR Code는 이 사이트가 가장 편합니다.

좀 더 다양한 QR Code를 웹에서 만들고 싶으시면 http://zxing.appspot.com/generator/ 를 들러보세요.
zxing은 유명한 모바일용 QR 관련 open soruce library 이죠.

Trackback 1 Comment 3
  1. uggs on sale 2011.11.25 14:11 신고 address edit & del reply

    사랑은 포도주처럼해야합니다.더 이상 당신은 그것이 맛이납니다 강하고, 그것을 유지.

  2. jordan retro 12 2012.03.05 17:58 신고 address edit & del reply

    총괄 은 미국 의 인기 는 패션 여성복 브랜드 나인 의 창시자 토 리 버 치 (총괄) 여사.

  3. Jordan Retro 12 2012.03.27 12:16 신고 address edit & del reply

    나이키 신발은 정말 처음 신발에 색상을 도입하여 운동 신발 전용 라인의 디자인과 스타일링에서 온 오토바이를 갔다.
    http://www.retrojordan12.com/ Jordan Retro 12
    http://www.retrojordan12.com/ Jordan 12

2010.10.18 11:25

Android Market에 유료 App 등록하기, 개발자 등록 가이드




어제 Android Market에 유료 App을 처음으로 등록했습니다. 예전에는 무료만 가능해서 등록을 안하고 있었는데, 이제 유료로 변경되었으니 일단 개발자에게 많은 기회가 온 것 같아 개발자의 한 사람으로서 다행으로 여겨집니다.

얼마전만 하더라도 모든 등록 관련 페이지가 영문이었던걸로 기억하는데, 이제는 한글화가 상당히 진행되었더군요. 상당히 쉽게 등록할 수 있습니다.

* 유료로 앱을 등록할려고 하면 반드시 Google 계정과 Adsense 계정이 있어야 합니다.

[개발자 등록하기]

먼저 http://market.android.com/publish/signup 으로 이동합니다.

사용하고자 하는 Google 계정으로 로그인 합니다. (이 계정은 이후의 등록과정에서 변경할 수 없습니다. 단, 연락용 이메일 주소는 이후에 변경하는 것이 가능합니다.)



개발자로 등록하라는 메세지가 나오는 군요. 돈은 25$ (비용 지불을 위해 VISA, Master, American Express 마크가 찍힌 신용카드가 있어야 겠지요.)


개발자 명, 이메일주소, 웹사이트 URL, 전화번호 (국제전화번호 양식: +82-10-123-1234 :: 우리나라 국가 코드는 82입니다. +82로 시작하고 0으로 시작되는 번호는 빼고 기입하시면 됩니다.) 를 입력하 Continue를 누릅니다.


등록비 지불을 위한 신용카드 및 주소 정보 입력 화면입니다. 이 화면부터는 한글로 제공되더군요. 그래서 저도 주소 정보를 한글로 입력해버렸습니다. 카드번호 입력하면 알아서 해당 되는 카드만 활성화 됩니다. 유효기간 및 카드뒷면 끝에 숫자 세자리  (CVC코드) 입력하시고, 주소, 우편번호, 전화번호 입력후 "동의하고 계속하기" 버튼 클릭하시면 됩니다.


신용카드 결제를 위해서 한번더 로그인을 하라는 군요. 로그인합니다.


최종 결제를 위해 정보를 보여줍니다. 주문 버튼 클릭하면 결제가 되고, 신용카드로 결제되었다는 메세지가 오는군요.


개발자 등록이 완료되었습니다. Android Market Developer Site를 클릭하여 이동합니다.


앱 배포 정책에 동의합니다. Continue 클릭.

[유료 판매자 전환]


이제 무료 앱은 등록이 가능합니다.
유료 앱을 등록하기 위해서는 추가적으로 Merchant Account 계정으로 전환해야 합니다.

Edit Profile을 클릭해서 현재 정보를 확인해 보도록 하겠습니다.


Account Type 이 Publisher : Free Applications 이라고 나옵니다. 무료 앱만 배포가 가능한 계정입니다.
setup a Merchant Account at Google Checkout 을 클릭하여 유료 판매자 계정을 위한 정보 기입 화면으로 이동 합니다. 앞의 개발자 콘솔 하단 부분의 링크를 클릭하셔도 동일합니다.


연락처 정보, 재무정보 등을 입력 후 가입하기 버튼을 클릭하시면 구글 애드센스 Pub ID 입력창이 나타납니다.
구글 AdSense Pub ID는 AdSense에 로그인 하시면 우측 상단에 나타나 있습니다.




AdSense Publisher ID를 입력하시고 "Pay me via this account" 버튼을 클릭하시면 판매자 계정으로 전환이 완료됩니다.


유료 판매자 계정으로 전환되었다는 메세지 화면입니다. 이제 유료 앱을 등록해 보겠습니다.
Upload Application 링크를 클릭합니다.

[유료 앱 등록]



먼저, 배포할 apk 파일을 업로드 합니다. apk 파일만 업로드 하면 알아서 아이콘이랑 permission이랑 버전 정보를 표시해 줍니다. tstore 보다 훨씬 편리하군요.

이미지도 등록합니다. 2개까지 등록 가능합니다. 이미지는 사이즈가 정확해야 합니다. 사이즈가 틀리면 업로드가 실패합니다. 정확히 320 * 480 이거나 480 * 854로 제작하셔서 업로드 하셔야 합니다.

App의 타이틀과 설명을 추가합니다. 각 언어별로 추가하셔서 어플 설명을 추가할 수 있습니다.
가격을 입력합니다. 최소가격은 무조건 0.99$ 입니다. 더 싸게 하고 싶어도 불가능합니다.

어플리케이션 카테고리 및 기타 정보를 입력 하시고 Publish 누르면 바로 앱에 등록이 됩니다. 간단합니다.



Trackback 84 Comment 5
  1. 남시언 2010.10.18 11:45 신고 address edit & del reply

    좋은 글 감사합니다 ^^
    항상 RSS 로 매번 구독 하며 좋은글만 슬쩍 훔쳐보고 나가고 있네요 ㅎ;;;
    이해해 주실꺼죠?ㅎㅎ

    한가지 궁금한점은 유료앱 개발자로 등록할 시에 해당 계정으로 무료,유료 모두 업로드가 가능한 것인지요...?

    • 보고픈 2010.10.18 12:28 신고 address edit & del

      네. 유료/무료 업로드 가능합니다. Merchant 계정으로 전환하지 않으면 무료만 업로드 가능하구요.

  2. 남시언 2010.10.18 12:35 신고 address edit & del reply

    아하^^ 네 . 감사합니다 ~ 자주 들러 글만 보지말고 댓글도 남기고 할께요 ;;;;
    좋은 하루 보내시기 바랍니다~

  3. 홍홍홍 2012.01.20 11:42 신고 address edit & del reply

    근데, 개발자 통장계좌는 입력안하나요??
    수익금은 개발자한데 어떻게 들어오죠???

  4. 하늘바다 2012.10.21 23:53 신고 address edit & del reply

    인앱 결제테스트시 동의및구매를 누르면 => [결제방법] => 카드선택등...이 나타나지 않습니다. 어떻게 하면 되는지 여쭈어 봅니다.

2010.10.18 02:24

Touch Call - New Version Released!



 
터치콜 v2.0이 런칭되었습니다.
구글 마켓에 먼저 올라갔습니다. 티스토어에는 심사중이라 금주 내에 새 버전이 나올것으로 기대합니다.

[v2.0 개선사항 / What’s new in v2.0]

- Android 2.2 (Froyo) 지원
- 단축번호 직접 등록/수정 기능 추가
- SD카드에서 사진 불러오기 기능 추가
- 단축번호 변경 기능 추가
- 상태바에 나타난 Touch Call 아이콘을 클릭하여 쉽게 어플 실행 기능 추가

- Support Android 2.2 (Froyo)
- Immensely improve edit features
- Modify a contact
- Directly add/modify a contact
- Load pictures from a SD card
- Add a status bar to provide the instant use of Touch Call at any time conveniently.

[App 개요 / Overview]

터치콜은 한번의 터치로 전화를 바로 걸수 있는 편리한 App 입니다. 그리고 긴 터치는 바로 SMS 화면을 띄워 주기 때문에 상당히 편리합니다. 자주 사용하는 번호만을 등록해 두고 사용하시면 되며, 등록되지 않는 번호로 전화를 걸때는 터치콜의 전화번호부 검색 기능을 이용하시면 쉽게 초성검색, 이름, 전화번호 검색으로 검색하셔서 전화를 걸거나 문자를 보낼수 있습니다.
특히 스마트폰 사용이 어려운 초보자 분들에게 더더욱 편리한 앱입니다.

Touch call is the one click dialer that enables you to make a call or SMS conveniently. By simply touch the screen once, you can call anyone without looking up contacts or browsing the menu several times. You can send a SMS by long-touch on your phone. It also provides search options for contacts and initial consonant for Korean so that you can find the infrequently used number with ease. It is specially recommended to those users who have difficulties in using smart phone.




[주요기능 / Features]
1. 비주얼 원터치 콜 및 롱터치 SMS 기능 제공
2. 초성검색, 전화번호, 이름 등 강력한 전화번호부 검색 기능 제공
3. 터치 및 롱터치 기능 제어 옵션 제공
4. 다른 앱 사용중에도 상태바의 아이콘을 클릭하여 쉽게 앱 실행 기능 제공
5. 쉽고 편리한 수정 기능 제공
   - 단축번호 편집( 번호 변경 기능 제공)
   - 전화번호부 검색을 통한 입력/수정 및 직접 입력/수정 기능 제공
   - SD카드나 주소록에 등록된 사진 추가/편집 기능 제공

1. Visual one touch call + long touch SMS supported.
2. Various search options including initial consonant for Korean, number and name – more powerful than the basic address book your phone provides
3. Option for changing the use of one touch call or long touch SMS
4. Status notification to switch the apps easily when using other apps.
5. Easy to use edit functions
   A. Contact number modification
   B. Direct contact registration/modification
   C. Contact registration/modification from your address book
   D. Loading pictures from your address book or SD card


[사용가이드]

1. 신규 단축번호 등록
   * [방법1]
   - 프로그램 실행 후 우측 상단의 편집 버튼을 누릅니다.
   - 등록하고자 하는 번호 항목의 우측 + (플러스) 버튼을 누릅니다.
   - 전화번호부 검색에서 이름/전화번호/초성 등을 입력하여 나온 결과화면에서 + 버튼을 눌러 추가합니다.

   * [방법2]
   - 프로그램 실행 후 우측 상단의 편집 버튼을 누릅니다.
   - 등록하고자 하는 번호의 리스트(목록)을 선택합니다.
   - 등록창에서 전화번호/이름/전화번호 타입을 입력 후 추가버튼을 누르면 추가됩니다.


2. 단축번호 수정
   - 수정은 등록 과정과 동일합니다. 기존에 등록된 데이터가 있으면 수정이 되며, 기존에 등록된 데이터가
      없으면 등록이 됩니다.

3. 단축번호 삭제
   - 편집 화면에서 삭제하고자 하는 목록의 X 버튼을 클릭하면 삭제됩니다.

4. 사진 추가 및 편집
  - 주소록(전화번호부)에서 불러와 단축번호를 등록하는 경우 , 전화번호부에 사진이 등록되어 있으면 자동으로
    사진이 등록됩니다.
  - SD카드에서 사진을 불러와 등록하고자 한다면, 편집화면의 목록에서 사진추가 아이콘을 클릭하시면 SD카드의
    사진중에서 원하는 사진을 선택하고, 크기를 조정 후 확인하시면 사진이 추가됩니다.
  - 이미 사진이 추가된 경우 편집화면의 사진을 클릭하시면 삭제하거나 다른 사진으로 대체가 가능합니다.

5. 단축번호 변경
  - 편집화면에서 수정하고자 하는 단축번호의 번호부분을 선택하시면 번호를 변경할 수 있는 팝업창이
    실행됩니다. 원하는 번호를 입력 후 변경 버튼을 클릭하시면 번호가 변경됩니다.
    * 변경되는 번호에 이미 다른 정보가 입력되어 있는 경우에는 서로간의 번호가 바뀌게 됩니다.(swap)



6. 전화걸기
   - 메인화면에서 등록된 번호를 터치하세요. 전화가 걸립니다.
   - 길게 누르시면 SMS 메세지 입력화면으로 바로 이동됩니다.
   - 메뉴 버튼 클릭후 환경설정에서 터치 및 롱터치 기능을 변경하실 수 있습니다.
     (바로 전화걸기가 싫은 경우 전화 걸기 직전 화면으로 이동할 수도 있음)

7. 전화번호부 검색
   - 앱 사용중에 전화번호부 앱을 별도로 실행시키지 않고, 앱 내에서 전화번호를 검색 및 전화걸기, SMS
     보내기가 가능합니다. 메인화면의 상단 좌측 버튼 - 전화번호부 검색 을 클릭하신 후 초성, 전화번호,
     이름 등을 입력하여 검색하신 후 결과 목록의 통화나 SMS 보내기 버튼을 클릭하시면 됩니다.


[How to use]

1. Adding a new contact
   A. Option 1
      i. Run the program, click the Edit button. (on the right top of the title)
      ii. Click the + button on the item number you want to use.
      iii. Click the + button after looking up the contact.
   B. Option 2
      i. Run the program, click the Edit button. (on the right top of the title)
      ii. Click the list of the item number you want to use.
      iii. Input name/phone number/number type on the dialogue and click the Add button.

2. Modifying a contact
  A. Similar to the way of registration. – Just modify an existing contact.

3. Deleting a contact
  A. Simply click the X button on the edit page.

4. Adding a new pictures
   A. When adding a new contact from your address book, the picture is automatically added if the picture
        is available.
   B. Click the image button after inputting the contact information on the edit page. Then, select a picture
        to use from your SD card.

5. Modifying a contact number
    A. You can modify only existing numbers.
    B. Click the contact you want to edit and update the number. Then, click the OK button.

6. Making a call / Sending SMS
    A. Run program and simply click the button to call. Then, you can make a call.
    B. Long-click allows you to send SMS.
    C. In the Setting menu, you can change the use of one-touch and long touch.
    D. According to the screen size, 9~12 buttons are displayed in the one page.
        By selecting the screen and moving left and right, you can change the screen.

7. Searching contacts
    A. If you want to call someone not registered on Touch Call, click the Search button on the main page.
        Then, input the phone number/name/initial consonant (for Korean) to find the contact information. 
        In the search result, you can make a call by clicking the Call button or send SMS by clicking
        SMS button.



[구매방법]

Touch Call은 TStore 및 구글 마켓에 등록되어 있습니다. Tstore는 10/17일 현재 v1.x 버전이며, 심사 완료되는되로 v2.0 으로 업그레이드 될 것입니다.
tstore에는 2010년 10월까지 저렴한 가격으로 판매될 것이며, 11월 이후 금액이 인상될 것이니, 필요하신 분은 저렴할 때 구입하시기 바랍니다.

구글마켓 및 tstore 모두 "touch call" 혹은 "터치콜"로 검색하시면 앱을 찾으실 수 있습니다.

[Android Market - QR Code]



[TStore URL]

http://www.tstore.co.kr/userpoc/game/viewProduct.omp?insDpCatNo=DP04003&insProdId=0000025016&prodGrdCd=PD004401&t_top=DP000504 

[기타 사항]

앱 사용 중 불편한 점이나 개선사항이 있으시면 코멘트로 남겨 주시기 바랍니다.

'앱스' 카테고리의 다른 글

Touch Call - New Version Released!  (7) 2010.10.18
두번째 App - "Hangul To English" 출시  (3) 2010.08.14
첫번째 Andorid App - Touch Call (터치콜)  (3) 2010.07.31
Trackback 0 Comment 7
  1. playwith.n 2010.10.19 10:35 신고 address edit & del reply

    좋은글이 참 많네요........... (:광고도 눌러주는 센스 작렬하고 갑니다.

  2. Lawyer Marketing 2011.08.18 09:07 신고 address edit & del reply

    일반적으로 안녕! 그것은 좋아요, 문제 lttle 약간을해야겠습니까? 나는 그럼에도 불구하고 그것이 효과적으로 존재하지 않는 애플에서 내 ipad에 블로그 게시물 기사를보고하기 위해 노력하고있어, 거기는 어떤 제안에 대해서입니까? 잘했어. 우리의 안부는 물론, 애플의 소프트웨어는 거리의 혜택을 유지. 사실 마이크로 소프트 준 관련된 여러 상당히 우울 숫자 반대로 소프트웨어를 포함한 모든 유형의 다양한이야. '마이크로 소프트'기능이 전략은 특히 온라인 게임의 전체 세계에, 그럼에도 불구하고 내가 네 요구 사항에 매우 중요합니다 다가오는 상세 패싯에 대한 도박을 할 수 있습니다 모르겠어요.

  3. mahasiswa terbaik 2011.10.23 05:13 신고 address edit & del reply

    당신의 또 다른 유용한 인터넷 사이트를 주셔서 감사합니다. 어디든지 이외에 진짜 좋은 의미의 코드 정보를 일종의 것으로 구입할 수 있습니다? 나는 우리가 구현하는 순간, 나는 디자인을 빼내 정보의 이런 종류에 관한 얘기했습니다 걸 벤처 소유하고있다.

  4. uggs on sale 2011.11.25 14:25 신고 address edit & del reply

    사랑은 포도주처럼해야합니다.더 이상 당신은 그것이 맛이납니다 강하고, 그것을 유지.

  5. Uggs Outlet 2011.11.25 14:31 신고 address edit & del reply

    좋은 책은 친한 오늘 영원히 동일합니다.

  6. 손한석 2012.03.28 14:42 신고 address edit & del reply

    마켓에서 안 보이네요 ~~~

2010.10.14 23:57

Android - Shape Drawable



안드로이드(Android)에는 Shape Drawable이라는 것이 있습니다. XML로 쉽게 Drawable 객체를 생성하는 것인데, 배경이미지를 만들 때 사용하면 편리합니다. 실제 비트맵을 사용하지 않아도 되므로 apk의 용량도 줄여주고 쉽게 모양을 바꿀수 있어서 개발자가 사용하기 딱 입니다.

파일 저장 위치: res/drawable/filename.xml
참조 방법      : Java : R.drawable.filename
                    XML : @[package:]drawable/filename



[Syntax]

<?xml version="1.0" encoding="utf-8"?>
<shape
   
xmlns:android="http://schemas.android.com/apk/res/android"
   
android:shape=["rectangle" | "oval" | "line" | "ring"] >
   
<corners
       
android:radius="integer"
       
android:topLeftRadius="integer"
       
android:topRightRadius="integer"
       
android:bottomLeftRadius="integer"
       
android:bottomRightRadius="integer" />
   
<gradient
       
android:angle="integer"
       
android:centerX="integer"
       
android:centerY="integer"
       
android:centerColor="integer"
       
android:endColor="color"
       
android:gradientRadius="integer"
       
android:startColor="color"
       
android:type=["linear" | "radial" | "sweep"]
       
android:usesLevel=["true" | "false"] />
   
<padding
       
android:left="integer"
       
android:top="integer"
       
android:right="integer"
       
android:bottom="integer" />
   
<size
       
android:width="integer"
       
android:color="color"
       
android:dashWidth="integer"
       
android:dashGap="integer" />
   
<solid
       
android:color="color" />
   
<stroke
       
android:width="integer"
       
android:color="color"

         android:dashWidth="integer"
       
android:dashGap="integer" />
</shape>


  • <shape> elements - shape xml의 루트 요소임.
    • xmlns:android="http://schemas.android.com/apk/res/android"
      shap xml의 namespace 부분. 위와 동일하게 값을 지정하면 됩니다.
    • android:shape
      shape의 도형을 저정하는 속성입니다. 지정가능한 값은 . “rectangle” (사각형), “oval” (타원), “line” (선), “ring” (링) 4개의 값을 지정할 수 있습니다.

      만일, android:shape="ring" 인 경우에는 추가적으로 다음의 속성을 지정할 수 있습니다.
    • android:innerRadius
      Dimension 값. 링의 안쪽원의 반지름의 값입니다.
    • android:innerRadiusRatio
      float 값. 링의 가로사이즈에 대한 안쪽원의 비율값. 디폴트값은 9. 만일 android:innerRadiusRatio="5" 로 지정하면 전체 ring의 width 값을 5로 나눈값이 아쪽 원의 반지름으로 설정됩니다. android:innerRadius 값이 설정되면 android:innerRadiusRatio 값을 덮어씁니다.
    • android:thickness
      Dimension 값. 링의 두께. 바깥쪽 원에서 안쪽 원을 제외한 부분이 두께가 됩니다.
    • android:thicknessRatio
      float 값. 전체 링의 가로 사이즈에 대한 비율값으로 표시되는 링의 두께값. 디폴트는 3. 만일 android:thicknessRatio="2" 라면 링의 두께는 링의 가로 사이즈를 2로 나눈 값이 됩니다. 이 값은 android:innerRadius 값이 설정되면 override 됩니다.
    • android:useLevel
      Boolean 값. 만일 이 값이 사용되고 "true"이면 Shape는 LevelListDrawable 객체로 생성됩니다. 일반적으로는 "false"로 설정하면 됩니다. "true"로 설정하면 Shape가 보이지 않게 됩니다.
  • <coners>
    가장자리를 둥글게  처리를 해 줍니다. 이 element는 shape가 "rectangle"일때만 유효합니다.
    • android:radius
      Dimension 값. 모든 coners를 위한 반지름 값. 아래에 나타나는 각 코너의 값을 지정하면 이 값을 덮어쓰게 됩니다.
    • android:topLeftRadius
      Dimension 값. top-left 코너의 반지름.
    • android:topRightRadius
      Dimension 값. top-right 코너의 반지름.
    • android:bottomLeftRadius
      Dimension 값. bottom-left 코너의 반지름.
    • android:bottomRightRadius
      Dimension 값. bottom-right 코너의 반지름.
  • <gradient>
    도형에 gradient 색상을 지정할 수 있습니다. 
    • android:angle
      Integer 값. gradient 각도. 값이 0이면 좌측에서 우측으로 gradient가 지정되며, 90이면, bottom에서 top 방향으로 gradient가 지정됩니다. 이 값은 45의 배수이어야 하며, 디폴트 값은 0입니다.
    • android:centerX
      Float 값. gradient 중심의 X 위치의 상대값. (0 ~ 1.0) 만일 android:type="linear" 이면 적용이 안됨.
    • android: centerY
      Float 값. gradient 중심의 Y 위치의 상대값. (0 ~ 1.0) 만일 android:type="linear" 이면 적용이 안됨.
    • android:centerColor
      Color 값. 시작 color 값과 끝 color 값 사이에 나타나는 Color 값. Optional 값.
    • android:startColor
      Color 값. gradient 시작 부분의 색상값.
    • android:endColor
      Color 값. gradient 끝 부분의 색상값.
    • android:gradientRadius
      Float 값. gradient의 반지름 값. android:type="radial" 인 경우에만 적용 가능함.
    • android:type
      Keyword 값. gradient 패턴의 종류를 지정함.
      "linear" - 선형 gradient (디폴트)
      "radial" - 방사형 gradient, 시작 Color가 Center Color와 동일함.
      "sweep" - sweeping line gradient
    • android:useLevel
      Boolean 값. 값이 "true"이면 LevelListDrawable 객체로 생성됨.
  • <padding>
    패딩값을 지정한다. 이 패딩값은 shape에 지정되는 게 아니고, shape를 포함하고 있는 View에 설정되는 값이다.
    • android:left
      Dimension 값. Left 패딩값.
    • android:right
      Dimension 값. Right 패딩값.
    • android:top
      Dimension 값. Top 패딩값.
    • android:bottom
      Dimension 값. Bottom 패딩값.
  • <size>
    Shape의 크기를 설정. 크기값은 shape를 감싸고 있는 container view의 스케일 설정에 따라 함께 scale  될 수 있습니다.
    • android:height
      Dimension 값. shape의 높이값.
    • android:width
      Dimension 값. shape의 너비값

  • <solid>
    shape를 단색을 채웁니다.
    • android:color
      Color 값. Shape를 칠할 색상값.
  • <stroke>
    테두리를 그릴 때. 선을 그릴때도 사용합니다.
    • android:width
      Dimension 값. 모든 coners를 위한 반지름 값. 아래에 나타나는 각 코너의 값을 지정하면 이 값을 덮어쓰게 됩니다.
    • android:color
      Color 값. 선 색
    • android:dashGap
      Dimension 값. 점선인 경우 점선의 간격
    • android:dashWidth
      Dimension 값. 점선의 크기값.
[Shape Sample]

몇가지 샘플을 만들어 보겠습니다. 위 설명을 보셨으니, XML만 보더라도 대충 이해가 가실겁니다.



1. 점선 외곽선을 가진 사각형. (첫번째 도형)


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <stroke android:color="#ffffffff"

            android:dashGap="2dp"

            android:dashWidth="2dp"

            android:width="2dp" />

    <solid android:color="#3FB7FF"/>

    <coners android:radius="3dip"/>                     

</shape>


2. Oval


<shape android:shape="oval"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <stroke android:color="#ffffffff"

            android:width="4dp" /> 

    <padding android:left="10dip"

             android:right="10dip"

             android:top="10dip"

             android:bottom="10dip"/>           

</shape>

 


3. Ring


<shape xmlns:android="http://schemas.android.com/apk/res/android"

       android:shape="ring"

       android:innerRadiusRatio="3"

       android:useLevel="false">

    <solid android:color="#3FB7FF"/>           

</shape>


4. Ring - 링을 gradient로 채움.


<shape xmlns:android="http://schemas.android.com/apk/res/android"

       android:shape="ring"

       android:innerRadiusRatio="3"

       android:useLevel="false" >

     <gradient android:type="sweep"

         android:startColor="#3FB7FF"

         android:centerColor="#000000"

        android:endColor="#3FB7FF"

        android:centerX="1"

        android:centerY="1"/>       

</shape>


5. line

<shape android:shape="line"

       xmlns:android="http://schemas.android.com/apk/res/android">

   <stroke android:color="#ffffff"/>

</shape>


6. Rectangle - 사이즈를 줄여 선처럼 보이도록 처리.

<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

   <gradient android:type="linear"

        android:startColor="#F8F8F8"

        android:centerColor="#000000"

        android:endColor="#F8F8F8" />

    <size android:height="2dip"/>       

</shape>



[Gradient Sample]

Gradient는 문서만 봐서는 제가 잘 이해가 가지 않아 속성값에 따른 샘플을 만들어 보았습니다. XML과 대응하는 이미지를 함께 보시면 쉽게 이해가 가실 겁니다.

[Linear Gradient]




<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="linear"

        android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994"/>

</shape>


위 그림중 좌측 첫번째 그림입니다.
android:angle의 값을 설정하지 않았으므로 0값인 좌측에서 우측으로 그라디언트 효과가 주어졌습니다. centerColor 값을 흰색으로 설정하였습니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="linear"

        android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994"

        android:angle="270"/>

</shape>


angle을 270으로 설정하여 위에서 아래쪽으로 그라디언트 효과를 주었음.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="linear"

        android:startColor="#3FB7FF"

        android:endColor="#000994"

        android:angle="270"/>

</shape>


android:centerColor 값을 설정하지 않았음.

[radial Gradient]



<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="radial"

         android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994"

        android:gradientRadius="100"/>

</shape>




<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="radial"

         android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994"

        android:gradientRadius="100"

        android:angle="270"/>

</shape>


radial gradient에서는 angle 값은 크게 의미가 없는것 같습니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="radial"

         android:startColor="#3FB7FF"

        android:endColor="#000994"

        android:gradientRadius="100"

        android:angle="270"

        android:centerX="0.3"

        android:centerY="0.3"/>

</shape>


centerX 및 centerY를 설정하여 중심축을 옮기니 좀 있어보입니다.

[sweep gradient]




<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994" />

</shape>


sweep 그라디언트는 원뿔 모양이 나오는 군요.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

        android:centerColor="#ffffff"

        android:endColor="#000994"

        android:angle="270"/>

</shape>


android:angle 값을 변경해도 처음 이미지와 동일하게 결과가 나오는 걸로 보아 angle은 sweep에서는 별 의미가 없는 것 같습니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

        android:endColor="#000994"

        android:angle="270"/>

</shape>


android:centerColor 값을 빼버리는 원뿔의 안쪽 모양 같은 그라디언트 효과가 나타납니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

         android:centerColor="#ffffff"

        android:endColor="#3FB7FF"

        android:centerX="0.3"

        android:centerY="0.3"

        android:angle="270"/>

</shape>


중심축을 이동한 모습입니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

         android:centerColor="#000994"

        android:endColor="#ffffff"

        android:centerX="1.0"

        android:centerY="1.0"/>

</shape>


중심축을 하단 끝점으로 옮겨서 그라디언트 효과를 준 모습입니다.


<shape android:shape="rectangle"

       xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:type="sweep"

         android:startColor="#3FB7FF"

         android:centerColor="#000000"

        android:endColor="#3FB7FF"

        android:centerX="1"

        android:centerY="1"/>

</shape>



[참고자료]
http://developer.android.com/guide/topics/resources/drawable-resource.html#Shape 본 포스트의 앞쪽 Shape 설명은 위 링크의 구글 자료를 번역한 내용입니다.
Trackback 2 Comment 3
  1. wow account 2010.11.26 15:33 신고 address edit & del reply

    好好生活

  2. uggs on sale 2011.11.25 16:13 신고 address edit & del reply

    사랑은 포도주처럼해야합니다.더 이상 당신은 그것이 맛이납니다 강하고, 그것을 유지.

  3. huewu 2012.03.23 14:27 신고 address edit & del reply

    많은 도움이 되었습니다. 감사합니다~

2010.10.13 16:41

Android - Custom Dialog, AlertDialog




이번에는 Android 환경에서 Dialog로 좀 더 다양한 활용을 위한 Custom Dialog 제작에 관해 알아보겠습니다.

이전 포스트(http://overoid.tistory.com/28)에서 Android에서 제공하는 기본 Dialog에 대해 설명하였으니, 관심 있으시면 그 부분도 살펴보시기 바랍니다.

Custom Dialog를 만드는 가장 쉬운 방법은 별도의 레이아웃을 xml로 작성하시고, AlertDialog.builder에서 addView로 생성한 레이아웃을 등록하시는게 가장 쉬운 방법입니다.

Custom Dialog Sample1



TextView 2개와 입력 받을수 있는 EditText 2개로 구성되어 있는 Dialog입니다.

먼저 XML Layout을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="wrap_content"

  android:id="@+id/layout_root"

  android:layout_height="wrap_content"

  android:orientation="vertical">

  <LinearLayout android:orientation="horizontal"

                  android:layout_width="fill_parent"

                  android:layout_height="fill_parent"

                  android:layout_marginTop="5dip">

                 

        <TextView android:layout_width="70dip"

                      android:layout_height="wrap_content"

                      android:layout_gravity="center_vertical"

                      android:layout_marginLeft="10dip"

                      android:textColor="#FFFFFF"

                      android:textSize="14sp"

                      android:text="Name"/>

                     

        <EditText  android:id="@+id/dlgDisplayName"

                   android:layout_width="wrap_content"

                   android:layout_height="38dip"

                   android:textSize="12sp"

                   android:layout_weight="1"

                   android:layout_marginLeft="10dip"

                   android:layout_marginRight="10dip"

                   android:hint="Name"

                   />

    </LinearLayout>              

   

 

    <LinearLayout android:orientation="horizontal"

             android:layout_width="fill_parent"

             android:layout_height="fill_parent">        

         <TextView android:layout_width="70dip"

                      android:layout_height="wrap_content"

                      android:layout_gravity="center_vertical"

                      android:layout_marginLeft="10dip"

                      android:textColor="#FFFFFF"

                      android:textSize="14sp"

                      android:text="Phone Number"/>     

                     

         <EditText  android:id="@+id/dlgPhoneNumber"

                   android:layout_height="38dip"

                   android:layout_weight="1"

                   android:layout_width="fill_parent"

                   android:layout_marginLeft="10dip"

                   android:layout_marginRight="10dip"

                   android:inputType="phone"

                   android:hint="Phone Number"

                   android:textSize="12sp"/>

     </LinearLayout>      

                            

 

</LinearLayout>


 


Dialog
Content View로 위에 정의해 둔 Layout을 설정하고 각 UI 항목을 정의하면 됩니다.


AlertDialog.Builder builder;

AlertDialog alertDialog;

 

Context mContext = MainActivity.this;

LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);

View layout = inflater.inflate(R.layout.custom_dialog1,(ViewGroup) findViewById(R.id.layout_root));

 

final EditText name = (EditText)layout.findViewById(R.id.dlgDisplayName);

final EditText number = (EditText)layout.findViewById(R.id.dlgPhoneNumber);

 

builder = new AlertDialog.Builder(mContext);

builder.setView(layout);

builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

   

    @Override

    public void onClick(DialogInterface dialog, int which) {

        if(TextUtils.isEmpty(name.getText())) {

            Toast.makeText(getApplicationContext(), "name is empty", Toast.LENGTH_SHORT).show();

        } else {

            dialog.dismiss();

        }

    }

});

alertDialog = builder.create();

XML Layout을 Inflating시키기 위해서는 LayoutInflater를 얻어와 inflate(int, ViewGroup)을 호출해야 합니다. 여기서 첫번째 파라미터는 Layout Resource ID이고 두번째 파라미터는 Root View의 ID입니다. Inflate 호출후에는 EditText에 대한 내용을 정의할 수 있습니다.

그 다음 AlertDialog.Builder를 인스턴스화하고 setView(View)를 통해 해당 Dialog를 위한 Inflated Layout을 설정한다.

쉽습니다. 하지만, 위 Dialog에서 사용자의 입력값을 받고 확인 버튼 클릭시 값의 설정 유무를 체크한 후 값이 있는 경우에만 창을 닫아주고 싶은데.. 기본적인 AlertDialog Builder에서 제공하는 버튼을 사용하는 경우에는 무조건 창이 닫깁니다. 즉, 원하는 바를 할 수 없습니다.

이 문제를 해결하기 위해서 첫번째는 트릭으로, 두번째는 정식 방법으로 해결을 해 보도록 하겠습니다.

Custom Dialog Sample2

위 이슈에 대한 첫번째 해결 방법은 AlertDialog에서 제공하는 set..Button 기능을 사용하지 않고 Layout xml.에서 직접 .button을 넣은 후 그걸로 처리하는 방법입니다.

먼저, 버튼 부분이 추가된 layout xml 입니다.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="wrap_content"

  android:id="@+id/layout_root"

  android:orientation="vertical"

  android:layout_height="wrap_content"

  >

  <LinearLayout android:orientation="horizontal"

                  android:layout_width="fill_parent"

                  android:layout_height="fill_parent"

                  android:layout_marginTop="5dip">

                 

        <TextView android:layout_width="80dip"

                      android:layout_height="wrap_content"

                      android:layout_gravity="center_vertical"

                      android:layout_marginLeft="10dip"

                      android:textColor="#FFFFFF"

                      android:textSize="14sp"

                      android:text="Name"/>

                     

        <EditText  android:id="@+id/dlgDisplayName"

                   android:layout_width="wrap_content"

                   android:layout_height="38dip"

                   android:textSize="12sp"

                   android:layout_weight="1"

                   android:layout_marginLeft="10dip"

                   android:layout_marginRight="10dip"

                   android:hint="Name"

                   />

    </LinearLayout>              

   

 

    <LinearLayout android:orientation="horizontal"

             android:layout_width="fill_parent"

             android:layout_height="fill_parent">        

         <TextView android:layout_width="80dip"

                      android:layout_height="wrap_content"

                      android:layout_gravity="center_vertical"

                      android:layout_marginLeft="10dip"

                      android:textColor="#FFFFFF"

                      android:textSize="14sp"

                      android:text="Phone Number"/>     

                     

         <EditText  android:id="@+id/dlgPhoneNumber"

                   android:layout_height="38dip"

                   android:layout_weight="1"

                   android:layout_width="fill_parent"

                   android:layout_marginLeft="10dip"

                   android:layout_marginRight="10dip"

                   android:inputType="phone"

                   android:hint="Phone Number"

                   android:textSize="12sp"/>

     </LinearLayout>      

                            

    

 

    <LinearLayout android:id="@+id/buttonPanel"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:minHeight="54dip"

        android:orientation="vertical"

        android:background="#aaa" >    

        <LinearLayout

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:orientation="horizontal"

            android:paddingTop="4dip"

            android:paddingLeft="2dip"

            android:paddingRight="2dip" >

            <LinearLayout android:id="@+id/leftSpacer"

                android:layout_weight="0.25"

                android:layout_width="0dip"

                android:layout_height="wrap_content"

                android:orientation="horizontal"

                android:visibility="gone" />

            <Button android:id="@+id/button1"

                android:layout_width="0dip"

                android:layout_gravity="left"

                android:layout_weight="1"

                android:maxLines="2"

                android:text="OK"

                android:layout_height="wrap_content" />

            <Button android:id="@+id/button2"

                android:layout_width="0dip"

                android:layout_gravity="right"

                android:layout_weight="1"

                android:maxLines="2"

                android:text="Cancel"

                android:layout_height="wrap_content" />

            <LinearLayout android:id="@+id/rightSpacer"

                android:layout_width="0dip"

                android:layout_weight="0.25"

                android:layout_height="wrap_content"

                android:orientation="horizontal"

                android:visibility="gone" />

        </LinearLayout>

     </LinearLayout>                  

          

 

 

</LinearLayout>



기존 Sample1에 리소스 xml에 버튼 부분을 더 추가했습니다.


AlertDialog.Builder builder;

AlertDialog alertDialog;

 

Context mContext = MainActivity.this;

LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);

View layout = inflater.inflate(R.layout.custom_dialog2,(ViewGroup) findViewById(R.id.layout_root));

 

final EditText name = (EditText)layout.findViewById(R.id.dlgDisplayName);

final EditText number = (EditText)layout.findViewById(R.id.dlgPhoneNumber);

final Button okButton = (Button)layout.findViewById(R.id.button1);

okButton.setOnClickListener(new OnClickListener() {

   

    @Override

    public void onClick(View v) {

        if(TextUtils.isEmpty(name.getText())) {

            Toast.makeText(getApplicationContext(), "name is empty", Toast.LENGTH_SHORT).show();

        } else {

            Toast.makeText(getApplicationContext(), "name is ok", Toast.LENGTH_SHORT).show();

            customDialogInstance.dismiss();

        }

    }

});

 

final Button cancelButton = (Button)layout.findViewById(R.id.button2);

cancelButton.setOnClickListener(new OnClickListener() {

   

    @Override

    public void onClick(View v) {

        customDialogInstance.dismiss();               

    }

});

 

builder = new AlertDialog.Builder(mContext);

builder.setView(layout);

alertDialog = builder.create();

기존 코드와 비슷하지만, 처음 Sample1은 setPositiveButton 메소드를 사용해서 버튼을 추가했지만, 이번 샘플은 직접 버튼을 XML에서 불러들여 okButton에 직접 클릭이벤트 처리를 추가했습니다. 편의상 EditText 값 체크는 하나만 처리했습니다.



결과 화면입니다.

Name 부분에 값을 설정하지 않고 OK 버튼을 누르면 창이 닫히질 않은 상태로 Toast 메시지가 나타납니다. 원하는 바는 얻었으나, 왠일인지 버튼 아래로 약간의 공간이 생성됩니다. 버튼이 Dialog 아래쪽에 완전히 붙지를 않는UI적인 문제가 있군요.

Custom Dialog Sample3

끝으로 위 이슈를 해결하기 위해서 AlertDialog.builder를 사용하지 않고, Dialog Class에서 직접 상속을 받아서 Custom Dialog를 만들어 보도록 하겠습니다.

XML layout 코드는 Sample2와 동일한 xml을 사용하도록 하겠습니다.

먼저 Dialog 화면의 대한 클래스 코드입니다.

class CustomDialog3 extends Dialog implements OnClickListener {

    EditText name;

    EditText number;

    Button okButton;

    Button cancelButton;

    Context mContext;

   

    public CustomDialog3(Context context) {

        super(context);

        mContext = context;

        /** 'Window.FEATURE_NO_TITLE' - Used to hide the title */

        requestWindowFeature(Window.FEATURE_NO_TITLE); 

        setContentView(R.layout.custom_dialog2);

       

        name = (EditText)findViewById(R.id.dlgDisplayName);

        number = (EditText)findViewById(R.id.dlgPhoneNumber);

        okButton = (Button)findViewById(R.id.button1);

        cancelButton = (Button)findViewById(R.id.button2);

       

        okButton.setOnClickListener(this);

        cancelButton.setOnClickListener(this);

    }

 

    @Override

    public void onClick(View v) {

       if(v == okButton) {

           if(TextUtils.isEmpty(name.getText())) {

               Toast.makeText(mContext, "name is empty", Toast.LENGTH_SHORT).show();

           } else {

               Toast.makeText(mContext, "name is ok", Toast.LENGTH_SHORT).show();

                   dismiss();

               }

              

           } else if(v == cancelButton) {

               dismiss();

           }

        }

 

    }



Dialog 클래스에서 직접 상속을 받았으며, 생성자에서 requestWindowFeature(Window.FEATURE_NO_TITLE); 메소드를 사용하여 Dialog에 타이틀이 나오지 않도록 처리했습니다.

코드를 보시면 아시겠지만, 일반 Activity 코드 작성과 별반 다를게 없습니다. 생성자에서 setContentView()를 호출해 xml을 로드한 후 각 UI에 대해서 Event Handler를 작성하시면 됩니다.



버튼 부분의 디자인도 창의 하단에 딱 붙는게 UI적인 문제도 없고, Text값 Validation 체크를 통해 창을 제어할 수 있어서 원하는 바를 다 할 수 있습니다.

다만, AlertDialog.builder를 통해 Dialog를 생성하시면 화면에 보이는 창의 가로크기는 모두 동일합니다. Layout xml에 작게 설정되어 있더라도 가로 부분을 늘려서 동일하게 맞춥니다. 그러나, Dialog Class를 상속해서 만든 경우 사용자가 작성한 xml에 따라 Dialog의 가로창 사이즈가 변하게 됩니다. 그렇지만, 가로 최대값은 메인 화면의 90%를 넘지는 않는 것 같습니다.

아래는 Custom Dialog Sample을 테스트 할 수 있는 전체 코드입니다.

public class MainActivity extends Activity {

   

    static final int DIALOG_CUSTOM1 = 0;

    static final int DIALOG_CUSTOM2 = 1;

    static final int DIALOG_CUSTOM3 = 2;

   

    Button customDialog1;

    Button customDialog2;

    Dialog customDialogInstance;

   

    Button customDialog3;

   

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

       

        customDialog1 = (Button)findViewById(R.id.customDialog1);

        customDialog1.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_CUSTOM1);

            }

        });

       

        customDialog2 = (Button)findViewById(R.id.customDialog2);

        customDialog2.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_CUSTOM2);

            }

        });

       

        customDialog3 = (Button)findViewById(R.id.customDialog3);

        customDialog3.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                CustomDialog3 customDialog3 = new CustomDialog3(MainActivity.this);

                customDialog3.show();

            }

        });

    }

 

    @Override

    protected Dialog onCreateDialog(int id) {

        Dialog dialog;

        switch(id) {

            case DIALOG_CUSTOM1:

                dialog = getCustomDialog1();

                break;

            case DIALOG_CUSTOM2:

                dialog = getCustomDialog2();

                customDialogInstance = dialog;

                break;

            default:

                dialog = null;

        }

        return dialog;

    }

   

    private Dialog getCustomDialog1() {

        AlertDialog.Builder builder;

        AlertDialog alertDialog;

       

        Context mContext = MainActivity.this;

        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);

        View layout = inflater.inflate(R.layout.custom_dialog1,(ViewGroup) findViewById(R.id.layout_root));

       

        final EditText name = (EditText)layout.findViewById(R.id.dlgDisplayName);

        final EditText number = (EditText)layout.findViewById(R.id.dlgPhoneNumber);

       

        builder = new AlertDialog.Builder(mContext);

        builder.setView(layout);

        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

           

            @Override

            public void onClick(DialogInterface dialog, int which) {

                if(TextUtils.isEmpty(name.getText())) {

                    Toast.makeText(getApplicationContext(), "name is empty", Toast.LENGTH_SHORT).show();

                } else {

                    dialog.dismiss();

                }

            }

        });

        alertDialog = builder.create();

       

        return alertDialog;

    }

   

    private Dialog getCustomDialog2() {

        AlertDialog.Builder builder;

        AlertDialog alertDialog;

       

        Context mContext = MainActivity.this;

        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(LAYOUT_INFLATER_SERVICE);

        View layout = inflater.inflate(R.layout.custom_dialog2,(ViewGroup) findViewById(R.id.layout_root));

       

        final EditText name = (EditText)layout.findViewById(R.id.dlgDisplayName);

        final EditText number = (EditText)layout.findViewById(R.id.dlgPhoneNumber);

        final Button okButton = (Button)layout.findViewById(R.id.button1);

        okButton.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                if(TextUtils.isEmpty(name.getText())) {

                    Toast.makeText(getApplicationContext(), "name is empty", Toast.LENGTH_SHORT).show();

                } else {

                    Toast.makeText(getApplicationContext(), "name is ok", Toast.LENGTH_SHORT).show();

                    customDialogInstance.dismiss();

                }

            }

        });

       

        final Button cancelButton = (Button)layout.findViewById(R.id.button2);

        cancelButton.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                customDialogInstance.dismiss();               

            }

        });

       

        builder = new AlertDialog.Builder(mContext);

        builder.setView(layout);

        alertDialog = builder.create();

                

        return alertDialog;

    }

   

   

    class CustomDialog3 extends Dialog implements OnClickListener {

        EditText name;

        EditText number;

        Button okButton;

        Button cancelButton;

        Context mContext;

       

        public CustomDialog3(Context context) {

            super(context);

            mContext = context;

            /** 'Window.FEATURE_NO_TITLE' - Used to hide the title */

            requestWindowFeature(Window.FEATURE_NO_TITLE); 

            setContentView(R.layout.custom_dialog2);

           

            name = (EditText)findViewById(R.id.dlgDisplayName);

            number = (EditText)findViewById(R.id.dlgPhoneNumber);

            okButton = (Button)findViewById(R.id.button1);

            cancelButton = (Button)findViewById(R.id.button2);

           

            okButton.setOnClickListener(this);

            cancelButton.setOnClickListener(this);

        }

   

        @Override

        public void onClick(View v) {

           if(v == okButton) {

               if(TextUtils.isEmpty(name.getText())) {

                   Toast.makeText(mContext, "name is empty", Toast.LENGTH_SHORT).show();

               } else {

                   Toast.makeText(mContext, "name is ok", Toast.LENGTH_SHORT).show();

                       dismiss();

                   }

                  

               } else if(v == cancelButton) {

                   dismiss();

               }

            }

    

        }

}



리소스 및 소스가 전부 포함된 소스코드 첨부합니다.


 


 

Trackback 0 Comment 4
  1. ^^ 2011.01.21 18:16 신고 address edit & del reply

    우와. 훌륭한 포스트 고맙습니다!^^ 덕분에 많은 도움이 되었어요~

  2. 늘근초보 2011.02.13 22:38 신고 address edit & del reply

    찾고있던 자료입니다. 고맙습니다.

  3. 소영 2011.09.21 09:13 신고 address edit & del reply

    정말 많은 도움이 되었습니다 ^^ 감사합니다

  4. 도용훈 2011.12.12 13:18 신고 address edit & del reply

    감사합니다^^

2010.10.13 12:46

Android - Dialog, AlertDialog, ProgressDialog, DatePickerDialog, TimePickerDialog




Android에서 사용하는 Dialog에 대해서 알아보도록 하겠습니다.

[참고자료]

메인글 - 앞부분 설명은 아래 원문을 번역한 글입니다.
http://developer.android.com/guide/topics/ui/dialogs.html

DatePickerDialog Source 참조.
http://developer.android.com/resources/tutorials/views/hello-datepicker.html

TimePickerDialog Source 참조.
http://developer.android.com/resources/tutorials/views/hello-timepicker.html


Dialog는 보통 현 Activity 앞에 보여지는 작은 윈도우입니다. 많은 경우 새로운 Activity를 띄워 서로 데이터를 주고 받기 보다는 Dialog를 사용한다면 좀 더 편리하게 Android 개발을 할 수 있습니다.

먼저 Dialog 및 AlertDialog의 기본 기능을 살펴 본 후 다음 Post에서 Custom Dialog를 만들어 보도록 하겠습니다.

Dialog 하위 클래스에는 다음과 같은 특수한 Dialog가 존재합니다

AlertDialog : 대부분의 Dialog UI를 제공함.
ProgressDialog : Progress Wheel 이나 progress Bar 기능 지원
DatePickerDialog : 날짜 선택
TimePickereDialog : 시간 선택

Showing a Dialog


Dialog는 항상 Activity의 일부로 생성되어 보여집니다. 보통은 Activity 내부의 onCreateDialog(int) Callback Method 안에서 Dialog를 생성하는데, 이 Callback 메소드를 사용해서 Dialog를 생성하면 Android는 각각의 Dialog의 상태를 자동으로 관리하고, 호출된 Dialog의 소유자로 호출한 Activity가 자동으로 지정됩니다.

Dialog의 소유자 Activity가 지정되면, Dialog는 Activity로부터 Properties를 상속 받을 수 있습니다. 예를 들어 Dialog가 Open 되었을때 Menu Key는 Activity에 정의되어 있는 Option menu를 보여주며, Volumn Key는 Activity에 의해 사용되는 Audio Stream을 조절 할 수 있습니다.
(* 이 부분은 저로서도 명확하게 이해하기가 어렵군요. Menu 부분은 테스트를 해 봤는데, 별로 차이점을 찾지 못했구요. 좀 더 다른 테스트를 더 해봐야 할 듯 합니다.)

Note: 만일 onCreateDialog(int) 메소드 외부에서 Dialog를 생성하면 Activity에 붙어지지 않게 됩니다만, setOwnerActivity(Activity)를 통해 Activity에 Dialog를 붙일 수도 있습니다.

Dialog를 보여주고 싶다면 showDialog(int)를 호출하고 보여주고 싶은 Dialog를 구분해주는 번호를 넘겨주면 됩니다.

Dialog가 처음 요청 받게 되면, Android는 onCreateDialog(int)를 호출하는데 그곳에서 Dialog를 인스턴스를 생성해야 합니다. onCreateDialog(int) Callback Method는 showDialog(int)로 받은 것과 동일한 ID를 넘겨받아 Dialog를 생성한 후 Method의 맨 끝에서 해당 Object를 리턴합니다.

Dialog가 보여지기 이전에, Android는 선택적 Callback Method인 onPrepareDialog(int, Dialog)를 호출합니다. 만일 Dialog가 열릴 때 Dialog의 Property를 바꾸고 싶다면 onPrepareDialog Method를 재정의 해야 합니다. onPrepareDialog Method는 Dialog가 열릴 때마다 호출되는 되지만, onCreateDialog(int)는 Dialog가 처음 열릴 때 한번 만 호출됩니다.
만일 onPrepareDialog()를 정의하지 않았다면 Dialog는 이전 상태를 그대로 유지합니다. onPrepareDialog Method는 onCreateDialog()를 통해 만들어진 Dialog Object와 함께 Dialog의 ID를 인자로 넘겨 받습니다.

onCreateDialog(int)와 onPrepareDialog(int, Dialog) Callback Methods구현은 일반적으로 인자로 넘겨받는 ID값을 체크하여 처리하는 swith 구문을 사용하는 것이 일반적입니다.

Dialog 관련 코딩 작성 스타일을 보겠습니다.

먼저, 각각의 Dialog에 대한 ID를 정의한다.

static final int DIALOG_PAUSED_ID = 0;

static final int DIALOG_GAMEOVER_ID = 1;



그 이후, onCreateDialog(int) Callback을 정의합니다.


protected Dialog onCreateDialog(int id) {

        Dialog dialog;

        switch(id) {

        case DIALOG_PAUSED_ID:

            // do the work to define the pause Dialog

            break;

        case DIALOG_GAMEOVER_ID:

            // do the work to define the game over Dialog

            break;

        default:

            dialog = null;

        }

        return dialog;

    }


Dismissing a Dialog

Dialog를 닫고 싶을 때 Dialog Object에서 dismiss()를 호출하여 종료시킬 수 있으며 필요시 dismissDialog(int)를 호출할 수 있습니다.

Dialog의 State 관리를 위해 onCreateDialog(int)를 사용한다면 Dialog가 닫힐때마다 Dialog Object의 State은 Activity에 의해 유지됩니다. 만일 생성한 Object가 더이상 필요없거나 State가 Clear되는 것이 중요하다면 removeDialog(int)를 호출하면 됩니다. removeDialog() 메소드를 호출하면 Object에 연결된 모든 내부 참조를 삭제하며 만일 Dialog가 현재 보여지고 있으면 닫아주게 됩니다.


Using Dismiss Listeners

만약 Dialog가 닫히는 그 시점에 Appliction이 특정 기능을 수행하도록 처리하려면 Dialog에 on-dismiss listener를 구현해야 합니다.

먼저 DialogInterface.onDismissListener Interface를 정의합니다. 이 Interface는 onDismiss(DialogInterface) Method 하나를 가지고 있으며 Dialog가 닫힐 때 호출됩니다. 그 다음 setOnDismissListener()에 구현된 OnDismissListener를 넘겨주면 됩니다..

Dialog가 닫히는 동작이 dismiss 메소드가 호출될 때 말고도 Cancel에 의해서도 일어날 수 있는 점을 알아야 합니다. 사용자가 "back" Key를 눌렀을 때나 Dialog내에 있는 Cancel 버튼으로 cancel()이 호출되었을 때 발생하게 합니다. Dialog가 취소되었을 때 OnDismissListener은 알림을 기다리게 되지만 Dialog가 명시적으로 취소 되었음을 알려주고 싶다면 setOnCancelListen()를 DialogInterface.OnCancelListener로 등록시켜야 합니다.

Creating an AlertDialog

AlertDialog는 Dialog Class의 하위 클래스로 가장 많이 사용하는 Dialog 타입입니다.
기본적으로 Title, Text Message, One, Two, or Three Buttons, List of Selectable Itmes (check box or Raido Button) 을 가진 Dialog 이면 AlertDialog를 사용하시면 됩니다.

AlertDialog를 생성하려면 AlertDialog.Builder subclass를 사용합니다. AlertDialog.Builder(Context)를 통해 Builder를 얻어와 AlertDialog Properties를 설정한 후  create()을 통해 AlertDialog Object를 얻어온다.

샘플 코드를 보겠습니다.

AlertDialog Sample1



protected Dialog onCreateDialog(int id) {

    Dialog dialog;

    AlertDialog.Builder builder;

    switch(id) {

       

        case DIALOG_SIMPLE1:

            builder = new AlertDialog.Builder(this);

            builder.setMessage("Are you sure you want to exit?")      

                   .setCancelable(false)      

                   .setPositiveButton("Yes", new DialogInterface.OnClickListener() {

                       public void onClick(DialogInterface dialog, int id) {

                           MainActivity.this.finish();          

                       }      

                    })      

                   .setNegativeButton("No", new DialogInterface.OnClickListener() {          

                       public void onClick(DialogInterface dialog, int id) {               

                           dialog.cancel();          

                        }      

                     });

            dialog = builder.create();

            break;

                default :

            dialog = null;

    }

    return dialog;

}



위 그림처럼 두개의 버튼을 가지는 Dialog를 만들기 위해서 AlertDialog.Builder 인스턴스에 set..Button 메소드를 사용해서 버튼을 지정하고, 버튼 클릭시 처리 내용을 기술하면 됩니다.

setCancelable(false)로 지정하면 Dialog가 실행된 상태에서 취소 버튼을 클릭해도 화면이 취소(사라지지) 않습니다.

AlertDialog Sample3 – List Style




//array xml에서 불러옴.

final String[] colors = getResources().getStringArray(R.array.colors);

builder = new AlertDialog.Builder(this);

builder.setTitle("Pick a color");

builder.setItems(colors, new DialogInterface.OnClickListener() {

   

    @Override

    public void onClick(DialogInterface dialog, int item) {

        Toast.makeText(getApplicationContext(), colors[item], Toast.LENGTH_SHORT).show();

    }

});

dialog = builder.create();


Array 데이터는 res\values\array.xml로 다음과 같이 정의해서 사용했습니다

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string-array name="colors">

        <item>Red</item>

        <item>Green</item>

        <item>Yellow</item>

        <item>White</item>

    </string-array>

</resources>


List형의 AlertDialog는 setItmes 메소드로 배열정보와 항목 클릭시 코드를 기술하면 됩니다. 리스트 항목 선택시 별도로 dialog를 dismiss() 하지 않아도 자동으로 Dialog가 닫기는 군요.

AlertDialog Sample4 – CheckBox




final CharSequence[] items = {"Red", "Green","Blue"};

final boolean[] states = {false, false, true};

builder = new AlertDialog.Builder(this);

builder.setTitle("Pick a color");

//setMultiChoiceItems(int itemsId, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)

builder.setMultiChoiceItems(items, states, new DialogInterface.OnMultiChoiceClickListener() {

   

    @Override

    public void onClick(DialogInterface dialog, int item, boolean state) {

        Toast.makeText(getApplicationContext(), items[item] + " set to " + state, Toast.LENGTH_SHORT).show();

        //자동으로 닫기지는 않는다.

    }

   

});

builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

   

    @Override

    public void onClick(DialogInterface dialog, int which) {

        String msg = "";

        for(boolean state : states) {

            msg += String.valueOf(state) + " ";        

        }

        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();

    }

});

dialog = builder.create();


CheckBox 리스트를 가진 Dialog를 만들기 위해서는 리스트에 뿌릴 배열(items) 및 각 항목의 체크여부를 가진 Boolean[] 배열 변수가 필요합니다.
Builder.setMultiChoiceItmes 메소드에 두 배열 변수 및 DialogInterface.OnMultiChoiceClickListener 인터페이스를 정의하면 됩니다.
각 항목이 선택되거나 해제될 때 마다 states  boolean 배열 변수의 값이 자동으로 설정되므로, Dialog 창이 닫길 때 해당 정보를 읽어서 다른 처리하고자 한다면 states 배열을 읽어서 처리하면 됩니다.

AlertDialog Sample5 – Radio Button




final CharSequence[] items1 = {"Red", "Green","Blue"};

builder = new AlertDialog.Builder(this);

builder.setTitle("Pick a color");

//setSingleChoiceItems(CharSequence[] items, int checkedItem, DialogInterface.OnClickListener listener)

builder.setSingleChoiceItems(items1, 0, new DialogInterface.OnClickListener() {

   

    @Override

    public void onClick(DialogInterface dialog, int item) {

        Toast.makeText(getApplicationContext(), items1[item] , Toast.LENGTH_SHORT).show();

        //자동으로 닫기지 않는다.

        dialog.dismiss();

    }

});

dialog = builder.create();


Radio Button은 CheckBox과 코드 스타일이 비슷합니다. setMultiChoiceItems() 메소드 대신 setSingleChoiceItems 메소드로 항목을 지정하면 됩니다.
setSingleChoiceItems 메소드의 두번째 파라미터는 checkedItem 항목을 지정하는 변수로 지정된 인덱스를 가진 Radio Button이 선택되어 표시됩니다.
Radio Button 선택시 자동으로 Dialog 창이 닫기지 않아 dismiss() 코드를 onClick 이벤트에 추가했습니다.

ProgressDialog – Circle



ProgressDialog는 위 그림처럼 Spinning Wheel의 형태로 애니메이션 프로그레스를 보여줄 수 도 있으며, 아래 그림처럼 정해진 진행과정을 가지는 Task를 위해 Progress Bar의 형태로 보여줄 수도 있습니다. Progress Dialog를 여는 것은 간단하게 ProgressDialog.show()만 호출하면 됩니다.

저는 onCreateDialog Callback 내에서 dialog를 생성했으며,


case DIALOG_PROGRESS1:

    dialog = ProgressDialog.show(MainActivity.this, "", "Loading, Please Wait..", true, true);


버튼 클릭시 ProgressDialog롤 보여주고, 5초가 지나면 자동으로 닫기도록 Sample 코드를 작성했습니다.


progressDialog1 = (Button)findViewById(R.id.progressDialog1);

progressDialog1.setOnClickListener(new OnClickListener() {

   

    @Override

    public void onClick(View v) {

        showDialog(DIALOG_PROGRESS1);

       

        TimerTask myTask = new TimerTask(){

            public void run(){

                dismissDialog(DIALOG_PROGRESS1);

                                    

            }

        };

       

        Timer timer = new Timer();

        timer.schedule(myTask, 5000);

    }

});


ProgressDialog – Bar



애니메이션 형태의 Progress Bar를 보여주기 위해서는 :

1. ProgressDialog(Context) 생성자를 통해 ProgressDialog를 초기화 한다.
2. setProgressStyle(int)를 통해 "STYLE_HORIZONTAL"로 Style을 설정하고 그런 메시지를 이용하여 다른 Properties도 설정한다.
3. Dialog를 보여줄 준비기 되었다면, show()를 호출하거나 onCreateDialog(int) Callback을 통해 ProgressDialog를 반환시켜 준다.
4. 전체 진행 과정이 완료되기 까지 setProgress(int)를 호출하여 Bar에 보여지는 상태 진행정도를 증가 시킬수 있으며 incrementProgressBy(int)를 통해서도 가능하다.


dialog = new ProgressDialog(MainActivity.this);

((ProgressDialog)dialog).setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

((ProgressDialog)dialog).setMessage("Loading..");

((ProgressDialog)dialog).setCancelable(true);

progressDialog = (ProgressDialog)dialog;


일반적으로 Bar 형태의 Progress는 별도 Thread를 생성해 다른 처리를 하면서 진행상태를 표시하기 위해 사용합니다. onCreateDialog() 메소드 내에서 dialog 지역변수 값을 progressDialog 멤버변수에 할당하여 Thread내에서 progressDialog 값을 증가시키도록 테스트 코드를 작성했습니다.

//ProgressBar 값을 증가시키는 Handler
Handler
handler = new Handler() {

 

        @Override

        public void handleMessage(Message msg) {

            int total = msg.getData().getInt("total");           

            progressDialog.setProgress(total);

            if(total >= 100) {

                progressDialog.dismiss();

            }

        }

       

    };

 


//OnCreate() 메소드 내에 버튼 클릭시 ProgressBar를 보여주고, 값을 증가시키는 Thread 생성 코드

progressDialog2 = (Button)findViewById(R.id.progressDialog2);

progressDialog2.setOnClickListener(new OnClickListener() {

   

    @Override

    public void onClick(View v) {

        showDialog(DIALOG_PROGRESS2);

       

        new Thread(new Runnable() {

           

            @Override

            public void run() {

                

                try {

                    for(int i = 0; i <= 100; i++) {

                        Thread.sleep(100);

                        Message msg = handler.obtainMessage();

                        Bundle b = new Bundle();  

                        b.putInt("total", i);               

                        msg.setData(b);               

                        handler.sendMessage(msg);

                    }

                } catch(Throwable t) {

                   

                }

               

            }

        }).start();

    }

});


DatePickerDialog



DatePickerDialog와 TimePickerDialog는 onCreateDialog() 메소드내에서 DatePickerDialog 혹은 TimePickerDialog 인스턴스를 생성해 리턴하면 됩니다.

생성자에서 사용자가 날짜 설정시 처리할 Callback Method인 DatePickerDialog.OnDateSetListener 를 먼저 구현한 다음 파라미터로 함게 넘겨주면 쉽게 호출 할 수 있습니다.

private DatePickerDialog.OnDateSetListener mDateSetListener = new DatePickerDialog.OnDateSetListener() {

       

        @Override

        public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {

           mYear = year;

           mMonth = monthOfYear;

           mDay = dayOfMonth;

           updateDateDisplay();

        }

    };



//OnCreateDialog() 메소드 내에서
case
DIALOG_DATEPICKER:

      dialog = new DatePickerDialog(MainActivity.this, mDateSetListener , mYear, mMonth, mDay);


TimePickerDialog



TimePickerDialog는 DatePickerDialog과 거의 코드가 유사합니다. 생성자로 인스턴스 생성시에 24시간제인지? 12시간제인지를 지정할 수 있습니다. 소스는 아래 전체 코드를 보시기 바랍니다.

public class MainActivity extends Activity {

   

    //Dialog 호출을 위한 상수 정의

    static final int DIALOG_SIMPLE1 = 0;

    static final int DIALOG_SIMPLE2 = 1;

    static final int DIALOG_LIST = 3;

    static final int DIALOG_CHECKBOX = 4;

    static final int DIALOG_RADIO = 5;

    static final int DIALOG_PROGRESS1 = 6;

    static final int DIALOG_PROGRESS2 = 7;

    static final int DIALOG_DATEPICKER = 8;

    static final int DIALOG_TIMEPICKER = 9;

   

   

    public static final int MENU_INFO = Menu.FIRST;

   

    Button simpleAlertDialog1;

    Button simpleAlertDialog2;

    Button listAlertDialog;

    Button checkboxAlertDialog;

    Button radioAlertDialog;

    Button progressDialog1;

    Button progressDialog2;

 

    ProgressDialog progressDialog;

    //DatePickerDialog

    private TextView mDateDisplay;   

    private Button mPickDate;   

    private int mYear;   

    private int mMonth;   

    private int mDay;

   

    //TimePickerDialog

    private TextView mTimeDisplay;   

    private Button mPickTime;   

    private int mHour;   

    private int mMinute;

   

   

    Handler handler = new Handler() {

 

        @Override

        public void handleMessage(Message msg) {

            int total = msg.getData().getInt("total");           

            progressDialog.setProgress(total);

            if(total >= 100) {

                progressDialog.dismiss();

            }

        }

       

    };

   

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

       

        simpleAlertDialog1 = (Button)findViewById(R.id.simpleAlertDialog1);

        simpleAlertDialog1.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_SIMPLE1);

            }

        });

       

        simpleAlertDialog2 = (Button)findViewById(R.id.simpleAlertDialog2);

        simpleAlertDialog2.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showSimpleDialog();

            }

        });

       

        listAlertDialog = (Button)findViewById(R.id.listAlertDialog);

        listAlertDialog.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_LIST);

            }

        });

       

        checkboxAlertDialog = (Button)findViewById(R.id.checkboxAlertDialog);

        checkboxAlertDialog.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_CHECKBOX);

            }

        });

       

        radioAlertDialog = (Button)findViewById(R.id.radioAlertDialog);

        radioAlertDialog.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_RADIO);

            }

        });

       

        progressDialog1 = (Button)findViewById(R.id.progressDialog1);

        progressDialog1.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_PROGRESS1);

               

                TimerTask myTask = new TimerTask(){

                    public void run(){

                        dismissDialog(DIALOG_PROGRESS1);

                                            

                    }

                };

               

                Timer timer = new Timer();

                timer.schedule(myTask, 5000);

            }

        });

       

       

       

        progressDialog2 = (Button)findViewById(R.id.progressDialog2);

        progressDialog2.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_PROGRESS2);

               

                new Thread(new Runnable() {

                   

                    @Override

                    public void run() {

                        

                        try {

                            for(int i = 0; i <= 100; i++) {

                                Thread.sleep(100);

                                Message msg = handler.obtainMessage();

                                Bundle b = new Bundle();  

                                b.putInt("total", i);               

                                msg.setData(b);               

                                handler.sendMessage(msg);

                            }

                        } catch(Throwable t) {

                           

                        }

                       

                    }

                }).start();

            }

        });

       

        //DatePicker Dialog Test

        mDateDisplay = (TextView) findViewById(R.id.dateDisplay);       

        mPickDate = (Button) findViewById(R.id.pickDate);

       

        mPickDate.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_DATEPICKER);

            }

        });

       

       // 현재 날짜를 가져온다.     

        final Calendar c = Calendar.getInstance();       

        mYear = c.get(Calendar.YEAR);       

        mMonth = c.get(Calendar.MONTH);       

        mDay = c.get(Calendar.DAY_OF_MONTH);       

       

        // display the current date (this method is below)       

        updateDateDisplay();

       

       

        //TimePicker Dialog Test

        mTimeDisplay = (TextView) findViewById(R.id.timeDisplay);       

        mPickTime = (Button) findViewById(R.id.pickTime);

       

        mPickTime.setOnClickListener(new OnClickListener() {

           

            @Override

            public void onClick(View v) {

                showDialog(DIALOG_TIMEPICKER);

            }

        });

       

        final Calendar c1 = Calendar.getInstance();       

        mHour = c1.get(Calendar.HOUR_OF_DAY);       

        mMinute = c1.get(Calendar.MINUTE);

       

        updateTimeDisplay();

    }

 

    @Override

    protected Dialog onCreateDialog(int id) {

        Dialog dialog;

        AlertDialog.Builder builder;

        switch(id) {

           

            case DIALOG_SIMPLE1:

                builder = new AlertDialog.Builder(this);

                builder.setMessage("Are you sure you want to exit?")      

                       .setCancelable(false)      

                       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {

                           public void onClick(DialogInterface dialog, int id) {

                               MainActivity.this.finish();          

                           }      

                        })      

                       .setNegativeButton("No", new DialogInterface.OnClickListener() {          

                           public void onClick(DialogInterface dialog, int id) {               

                               dialog.cancel();          

                            }      

                         });

                dialog = builder.create();

                break;

            case DIALOG_LIST:

                //array xml에서 불러옴.

                final String[] colors = getResources().getStringArray(R.array.colors);

                builder = new AlertDialog.Builder(this);

                builder.setTitle("Pick a color");

                builder.setItems(colors, new DialogInterface.OnClickListener() {

                    

                    @Override

                    public void onClick(DialogInterface dialog, int item) {

                        Toast.makeText(getApplicationContext(), colors[item], Toast.LENGTH_SHORT).show();

                    }

                });

                dialog = builder.create();

                break;

            case DIALOG_CHECKBOX:

                final CharSequence[] items = {"Red", "Green","Blue"};

                final boolean[] states = {false, false, true};

                builder = new AlertDialog.Builder(this);

                builder.setTitle("Pick a color");

                //setMultiChoiceItems(int itemsId, boolean[] checkedItems, DialogInterface.OnMultiChoiceClickListener listener)

                builder.setMultiChoiceItems(items, states, new DialogInterface.OnMultiChoiceClickListener() {