06
20

1) DB 생성, 테이블 생성

USE BookRentalShopDB
GO

CREATE TABLE userTbl (
	id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
	userID varchar(12) NOT NULL,
	password varchar(max) NOT NULL,
	lastLoginDt datetime NULL,
	loginIpAddr varchar(30) NULL
)

-- 구분 테이블
CREATE TABLE divtbl (
  Division CHAR(4) NOT NULL PRIMARY KEY,
  Names NVARCHAR(45)
)

-- 책 테이블
CREATE TABLE bookstbl (
  Idx INT NOT NULL IDENTITY PRIMARY KEY,
  Author VARCHAR(45),
  Division CHAR(4) NOT NULL
	FOREIGN KEY REFERENCES divtbl(Division),
  Names VARCHAR(100),
  ReleaseDate DATE,
  ISBN VARCHAR(200),
  Price DECIMAL(10,0))

-- 회원테이블
CREATE TABLE membertbl (
  Idx INT NOT NULL IDENTITY PRIMARY KEY,
  Names VARCHAR(45) NOT NULL,
  Levels CHAR(1),
  Addr VARCHAR(100),
  Mobile VARCHAR(13),
  Email VARCHAR(50))

-- 대여테이블
CREATE TABLE rentaltbl (
  Idx INT NOT NULL IDENTITY PRIMARY KEY,
  memberIdx INT
   FOREIGN KEY REFERENCES membertbl(Idx),
  bookIdx INT
	FOREIGN KEY REFERENCES bookstbl(Idx),
  rentalDate DATE,
  returnDate DATE)
  
  -- 회원테이블
INSERT INTO membertbl VALUES 
('이동욱', 'A', '부산시 사하구', '010-2967-1016', 'ldw@naver.com'), 
('방용혁', 'B', '부산시 남구', '010-9291-4419', 'byh@daum.com'),
('황동주', 'B', '부산시 북구', '010-8956-7423', 'hdj@gmail.com'),
('김효용', 'D', '부산시 영도구', '010-8736-2919', 'khy@hotmail.com'),
('박현수', 'A', '부산시 강서구', '010-9295-6600', 'phs@yahoo.co.kr'),
('서동우', 'C', '부산시 동래구', '010-5341-0128', 'sdw@naver.com'),
('강다은', 'A', '부산시 중구', '010-2244-0675', 'kde@empal.com'),
('김문성', 'D', '부산시 수영구', '010-6318-2590', 'kms@hotmail.com'),
('김영환', 'A', '부산시 강서구', '010-5615-1344', 'kyh@nate.com'),
('최원영', 'C', '김해시 삼안동', '010-9291-0882', 'cwy@dreamwiz.com'),
('전대한', 'D', '부산시 남구', '010-8956-6008', 'jdh@korea.com'),
('이우영', 'A', '부산시 금정구', '010-2923-2919', 'lwy@hotmail.com'),
('이준호', 'B', '부산시 부산진구', '010-9295-5718', 'ljh@gmail.com'),
('이창수', 'D', '부산시 동구', '010-9341-0128', 'lcs@naver.com'),
('이하응', 'A', '부산시 사상구', '010-5436-0675', 'lhe@hotmail.com'),
('장국빈', 'C', '부산시 남구', '010-6318-4654', 'jgb@freechal.com'),
('김종훈', 'A', '부산시 남구', '010-5615-7437', 'kjh@nate.com'),
('김한진', 'B', '부산시 수영구', '010-6566-4419', 'khj@daum.com'),
('박지윤', 'C', '부산시 사상구', '010-8956-1508', 'pjy@gmail.com'),
('김효정', 'B', '부산시 연제구', '010-5667-2919', 'kimhj@hotmail.com'),
('박효민', 'A', '부산시 해운대구', '010-9295-0341', 'phm@yahoo.com'),
('정재민', 'A', '부산시 사하구', '010-5341-4736', 'jjm@naver.com'),
('정성권', 'A', '부산시 금정구', '010-2244-5121', 'jsg@empal.com'),
('유혜진', 'B', '부산시 수영구', '010-6318-3734', 'yhj@hotmail.com');

INSERT INTO membertbl VALUES 
('성명건', 'D', '부산시 해운대구', '010-7625-0677', 'smg@naver.com');

-- 책 구분
INSERT INTO divtbl VALUES ('B001', '공포/스릴러'), ('B002', '로맨스'), ('B003', '무협'), ('B004', '전쟁/역사'),
('B005', '추리'), ('B006', 'SF/판타지');

-- 책정보
INSERT INTO bookstbl VALUES
('넬레 노이하우스', 'B001', '잔혹한 어머니의 날 1', '2019-10-07', '9791158791179', 11520),
('넬레 노이하우스', 'B001', '잔혹한 어머니의 날 2', '2019-10-07', '9791158791186', 11520),
('매뉴 라인하트', 'B006', '월드 오브 워크래프트 팝업북', '2019-10-21', '9788959527779', 52200),
('묵향동후', 'B003', '마도조사 2', '2019-09-03', '9791127852122', 12600),
('오코제키 다이', 'B005', '루팡의 딸', '2019-09-25', '9788998274412', 13500),
('조엘 디케르', 'B001', '스테파니 메일러 실종사건', '2019-08-12', '9788984373761', 16200),
('이지환', 'B002', '닥터 퀸 1-2세트', '2019-09-20', '9791164664122', 27000),
('김수지', 'B002', '희란국 연가', '2019-11-01', '9791131594100', 14000),
('알파타르트', 'B002', '재혼 황후 1', '2019-10-18', '9791164790289', 14000),
('안나 토드', 'B002', '애프터 7', '2019-08-30', '9791188253166', 14000),
('안타 토드', 'B002', '애프터 8', '2019-08-30', '9791188253173', 14000),
('남혜인', 'B002', '아도니스 11', '2019-08-26', '9791163022237', 11800),
('안드레아스 빙겔만', 'B001', '쉐어하우스', '2019-09-27', '9791186809792', 13320),
('비프케 로렌츠', 'B001', '너도 곧 쉬게 될 거야', '2019-09-18', '9791162834930', 12600),
('전건우', 'B001', '어위크', '2019-09-02', '9791188660353', 12600),
('토머스 해리스', 'B005', '카리 모라', '2019-09-11', '9791158511470', 15000),
('토머스 해리스', 'B005', '한니발', '2019-09-11', '9791158511500', 15000),
('정준', 'B003', '화산전생 17', '2019-08-23', '9791128394683', 8000),
('묵향동후', 'B003', '마도조사 1', '2019-07-30', '9791127851446', 14000),
('용대운', 'B003', '군림천사 35', '2019-07-26', '9788926706763', 9000),
('정준', 'B003', '화산전생 15', '2019-04-30', '9791128394669', 8000),
('김석진', 'B003', '삼류무사 2부16', '2019-04-02', '9791135413698', 8000),
('히가시노 게이고', 'B006', '기도의 막이 내릴 때', '2019-08-06', '9788990982780', 16800),
('히가시노 게이고', 'B006', '악의', '2019-07-25', '9788972750031', 14000),
('서철원', 'B004', '최후의 만찬', '2019-09-25', '9791130625843', 15000),
('마이 셰발, 페르 발뢰', 'B004', '어느 끔찍한 남자', '2019-09-20', '9788954657648', 12800),
('마이 셰발, 페르 발뢰', 'B004', '폴리스, 폴리스, 포타티스모스!', '2019-09-20', '9788954656535', 13800),
('김진명', 'B004', '살수 1', '2019-09-16', '9788925567716', 14800),
('김진명', 'B004', '살수 2', '2019-09-16', '9788925567723', 14800),
('손정미', 'B004', '도공 서란', '2019-09-16', '9788965708575', 14000),
('요안나', 'B002', '순수하지 않은 감각', '2019-10-02', '9791135445705', 12500),
('노승아', 'B002', '오늘부터 천생연분 1', '2019-09-18', '9791130039480', 12800),
('노승아', 'B002', '오늘부터 선생연분 2', '2019-09-18', '9791130039497', 12800),
('김이랑', 'B002', '조선혼담공작소 꽃파당', '2019-09-06', '9791159099724', 138000),
('전민석', 'B004', '감치', '2019-08-15', '9788947545075', 15000),
('나관중', 'B004', '삼국지 세크', '2019-07-25', '9788936479497', 60000),
('에리크 뷔야르', 'B004', '그날의 비밀', '2019-07-20', '9788932919751', 12800),
('요 네스뵈', 'B004', '폴리스', '2019-07-08', '9788934996699', 16000),
('T. M. 로건', 'B005', '29초', '2019-09-18', '9788950983208', 15000),
('토머스 해리스', 'B005', '양들의 침묵', '2019-09-11', '9791158511494', 15000),
('송시우', 'B005', '대나무가 우는 섬', '2019-09-06', '9788952739087', 14000),
('A. J. 핀', 'B005', '우먼 인 윈도', '2019-09-03', '9788934998952', 15800),
('이정명', 'B005', '밤의 양들 1', '2019-08-30', '9791189982461', 11500),
('이정명', 'B005', '밤의 양들 2', '2019-08-30', '9791189982478', 11500),
('정해연', 'B005', '내가 죽였다', '2019-08-21', '9791160748604', 14000),
('정준', 'B003', '화산전생 16', '2019-07-19', '9791128394676', 8000),
('무공진', 'B003', '화중매 상하 세트', '2019-07-15', '9791162764428', 32000),
('촌부', 'B003', '천애협로 10', '2019-06-03', '9791104920066', 8000),
('손선영', 'B003', '소암, 바람의 노래', '2019-05-17', '9791187440475', 13800),
('전민희', 'B006', '룬의 아이들 블러디드 2', '2019-09-25', '9788954657556', 14500),
('요나스 요나손', 'B006', '핵을 들고 도망친 101세 노인', '2019-09-25', '9788932919874', 14800),
('닐 셔스터먼, 재러드 셔스터먼', 'B006', '드라이', '2019-09-20', '9788936477783', 15800),
('스테파니 버지스', 'B006', '초콜릿 하트 드래곤', '2019-09-04', '9791135443947', 14800),
('브누아 필리퐁', 'B001', '루거 총을 든 할머니', '2019-07-30', '9791190182591', 13500),
('캐서린 스테드먼', 'B001', '썸씽 인 더 워터', '2019-07-24', '9788950982164', 13500),
('에이미 몰로이', 'B001', '퍼펙트 마더', '2019-07-22', '9791130623177', 14220),
('지건, 콕콕', 'B001', '잔혹동화', '2019-07-20', '9791161950938', 12420),
('류츠신', 'B006', '삼체 3', '2019-09-25', '9788954439923', 17500),
('히가시노 게이고', 'B006', '방과 후', '2019-07-10', '9791163898078', 14800)
;

-- 대여 목록
INSERT INTO rentaltbl VALUES
(22, 30, '2020-01-03', '2020-01-15'),
(10, 10, '2020-02-01', NULL),
(1, 12, '2020-02-01', '2020-02-03'),
(25, 34, '2020-02-02', NULL),
(23, 22, '2020-02-11', '2020-02-17'),
(7, 30, '2020-02-14', NULL),
(9, 31, '2020-02-14', '2020-02-17'),
(21, 15, '2020-02-18', '2020-02-18'),
(22, 17, '2020-02-20', '2020-02-25'),
(14, 7, '2020-02-26', '2020-02-28'),
(15, 9, '2020-03-01', '2020-03-05'),
(19, 44, '2020-03-02', NULL),
(20, 59, '2020-03-03', '2020-03-08'),
(24, 24, '2020-03-08', '2020-03-09')
;

 

생성된 DB, TABLE

 

userTbl에 admin 아이디 저장

 

2) MainForm 생성

새 프로젝트 생성

 

Form 이름 변경

 

디자인을 위해 NuGet 패키지 다운

 

MainForm소스에서 MetroForm 적용(오른쪽 화면 : using을 사용해 짧게 작성된 코드)

 

MainForm 속성 변경

 

3) LoginForm 추가

LoginForm 생성

 

LoginForm 속성 변경

 

LoginForm소스에서 MetroForm 적용

 

Label, Button, TextBox 각각 2개 추가

 

 

LoginForm Label 속성 변경

 

첫번째 TextBox 속성 변경

 

두번째 TextBox 속성 변경

 

왼쪽 Button 속성 변경

 

오른쪽 Button 속성 변경

 

4) LoginForm Button_Cancel 이벤트 추가

Btn_Cancel 클릭 이벤트 추가

 

LoginForm에 추가된 클릭 이벤트

/// <summary>
/// 로그인 창에서 취소버튼을 클릭 시 프로그램 종료
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Btn_Cancel_Click(object sender, EventArgs e)
{
	// 아래 두 소스는 같은 동작을 실행함
	//Application.Exit();// 정확하게 메모리 해제가 안되는 경우가 있음
	Environment.Exit(0); // 0(FLASE): 에러가 없이 정상종료 // 1(TRUE) : 에러가 있어 종료가 안됨
}

 

5) MainForm에서 LoginForm 호출

Btn_Cancel Load 이벤트 추가

 

MainForm에 추가된 클릭 이벤트

 

/// <summary>
/// LoginForm 호출
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_Load(object sender, EventArgs e)
{
	//LoginForm 객체 생성
	LoginForm form = new LoginForm();
	form.ShowDialog(); // 로그인이 안될 시 MainForm을 사용불가 상태로 만들기 위해 모달(Modal)로 호출한다.
}

 

실행 화면

 

6) LoginForm 로그인 기능 구현

Btn_OK 클릭 이벤트 추가

 

LoginForm에 추가된 클릭 이벤트

/// <summary>
/// 로그인 창에서 OK버튼을 클릭 시 로그인프로세스 메서드 호출
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnOK_Click(object sender, EventArgs e)
{
	LoginProcess(); // 로그인 구현 메서드
}

private void LoginProcess()
{
	throw new NotImplementedException();
}

 

로그인을 위한 DB 연결 경로 찾기

연결 문자열을 복사해둔다.

 

복사해둔 연결 문자열을 LoginForm에 string형 멤버 변수에 저장한다. 

string strConnString = "Data Source=127.0.0.1;Initial Catalog=BookRentalShopDB;User ID=sa;Password=p@ssw0rd!";

 

LoginProcess 메서드에 ID 또는 Password에 입력되지 않았을 경우 오류창 호출 구현

/// <summary>
/// 로그인 구현 메서드
/// </summary>
private void LoginProcess()
{
	//만약 아이디TextBox나 패스워드TextBox에 입력을 하지 않을 경우 경고창을 띄워준다.
	if(string.IsNullOrEmpty(TxtUserID.Text) || string.IsNullOrEmpty(TxtPassword.Text))
	{
		MetroMessageBox.Show(this, "아이디/패스워드를 입력하세요", "오류",MessageBoxButtons.OK,MessageBoxIcon.Error);
		return;//경고창 확인을 누르면 다시 로그인창으로 복귀
	}
}

 

입력하지 않을 경우 오류 메시지를 띄우며 Ok버튼을 누르면 Login창으로 돌아간다.

 

LoginProcess 메서드에서 SQL Server를 연결해 Login 기능 구현

SSMS를 이용해 Query문 작성

 

/// <summary>
/// 로그인 구현 메서드
/// </summary>
private void LoginProcess()
{
	//만약 아이디TextBox나 패스워드TextBox에 입력을 하지 않을 경우 경고창을 띄워준다.
	if(string.IsNullOrEmpty(TxtUserID.Text) || string.IsNullOrEmpty(TxtPassword.Text))
	{
		MetroMessageBox.Show(this, "아이디/패스워드를 입력하세요", "오류",MessageBoxButtons.OK,MessageBoxIcon.Error);
		return;//경고창 확인을 누르면 다시 로그인창으로 복귀
	}

	//DB연결 -> 로그인
	//using문 : 해당 클래스들을 다 사용한 후에 적절한 때에 해당 리소스(자원)을 자동으로 해제(Dispose)하여 프로그래머가 자원관리를 쉽게하도록 도와준다.
	using (SqlConnection conn = new SqlConnection(strConnString)) // SqlConnection 클래스 : SQL Server를 접속하기 위한 클래스로 접속을 위해서는 서버명, 인증방법, DB명 등을 지정해야한다.
	{
		conn.Open(); // 데이터베이스 연결을 연다
		SqlCommand cmd = new SqlCommand(); // SqlCommand 클래스 : SQL Server에 어떤 명령을 내리기 위한 클래스로 SELECT, INSERT, UPDATE, DELETE, 저장프로시저 사용을 하기 위해 이 클래스를 사용한다.
		cmd.Connection = conn; // SqlCommand 객체에서 사용되는 SqlConnection을 가져오거나 설정한다.
   		cmd.CommandText = "SELECT userID "
						 + " FROM userTbl "
						+ " WHERE userID = @userID "
						  + " AND password = @password"; // DB의 userTbl에서 아이디와 패스워드를 비교해 userID를 찾는 Query문 작성

		SqlParameter parmUserId = new SqlParameter("@userID", SqlDbType.VarChar, 12); // SqlParameter 클래스 : sqlCommand 객체에 파라미터(인자)가 필요한 경우 사용되는 클래스로 파라미터명, 타입, 사이즈를 넣어줘야한다.
		parmUserId.Value = TxtUserID.Text; // ID TextBox에 적힌 데이터를 파라미터에 저장
		cmd.Parameters.Add(parmUserId);// sqlCommand 객체에 파리미터를 추가

		SqlParameter parmPassword = new SqlParameter("@password", SqlDbType.VarChar, 20);
		parmPassword.Value = TxtPassword.Text;
		cmd.Parameters.Add(parmPassword);

		SqlDataReader reader = cmd.ExecuteReader();
		//SqlDataReader 클래스 : SQL Server와 연결을 유지한 상태에서 한번에 한 레코드(One Row)씩 SqlCommand.ExecuteReader()로부터 리턴되는 데이터를 가져오는데 사용된다.
		//SqlCommand클래스의 ExecuteReader메서드 : 명령을 수행하고 SELECT문처럼 명령 수행 결과가 집합일때 사용하는 메서드이다.
		reader.Read(); // 다음 레코드(One Row)를 읽는다.
		string strUserID = reader["userID"].ToString(); // userID 열에 들어있는 데이터를 string형으로 변환하여 변수에 저장
		MetroMessageBox.Show(this, "접속성공", "로그인"); // 로그인이 성공했다는 메시지박스를 화면에 띄어줌
	}
}

Query문에서 조건에 바로 입력값을 대입해도 정상적으로 구현되지만 SQL Inject의 위험이 있기 때문에 보안상의 이유로 SqlParameter를 사용한다.

 

 

알맞은 ID와 Password 입력 시 로그인 접속이 성공한다.

 

사용자 편의 : ID, Password TextBox에서 입력 후 Enter를 치면 선택된 오브젝트가 내려가도록 구현

TxtUserID KeyPress 이벤트 추가

 

LoginForm에 추가된 KeyPress 이벤트

 

/// <summary>
/// 아이디 텍스트박스에서 엔터 입력시 패스워드 텍스트박스가 선택되며 커서이동
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TxtUserID_KeyPress(object sender, KeyPressEventArgs e)
{
	if(e.KeyChar == (char)13) // 눌러진 키가 엔터일 경우
	{
		TxtPassword.Focus(); // Focus를 패스워드 텍스트 박스로 옮긴다.
	}
}

 

TxtUserID KeyPress 이벤트 추가

 

LoginForm에 추가된 KeyPress 이벤트

 

/// <summary>
/// 패스워드 텍스트박스에서 엔터 입력시 OK 버튼으로 포커스
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TxtPassword_KeyPress(object sender, KeyPressEventArgs e)
{
	if (e.KeyChar == (char)13) // 눌러진 키가 엔터일 경우
	{
		BtnOK.Focus(); // Focus를 패스워드 텍스트 박스로 옮긴다.
	}
}

 

7) LoginProcess 메서드 로그인 시 접속실패, 에러 제어

/// <summary>
/// 로그인 구현 메서드
/// </summary>
private void LoginProcess()
{
	if(string.IsNullOrEmpty(TxtUserID.Text) || string.IsNullOrEmpty(TxtPassword.Text))
	{
		MetroMessageBox.Show(this, "아이디/패스워드를 입력하세요", "오류",MessageBoxButtons.OK,MessageBoxIcon.Error);
		return;
	}

	//에러 제어
	try
	{
		using (SqlConnection conn = new SqlConnection(strConnString))
		{
			conn.Open();
			SqlCommand cmd = new SqlCommand();
			cmd.Connection = conn;
			cmd.CommandText = "SELECT userID "
							 + " FROM userTbl "
							+ " WHERE userID = @userID "
							  + " AND password = @password";

			SqlParameter parmUserId = new SqlParameter("@userID", SqlDbType.VarChar, 12);
			parmUserId.Value = TxtUserID.Text;
			cmd.Parameters.Add(parmUserId);

			SqlParameter parmPassword = new SqlParameter("@password", SqlDbType.VarChar, 20);
			parmPassword.Value = TxtPassword.Text;
			cmd.Parameters.Add(parmPassword);

			SqlDataReader reader = cmd.ExecuteReader();
			reader.Read();
			string strUserID = reader["userID"]!= null ? reader["userID"].ToString() : ""; // userID행에서 읽어온 데이터가 있을시 string형으로 저장하며 없으면 ""을 저장

			if(strUserID != "") // 로그인 성공할 경우
			{
				MetroMessageBox.Show(this, "접속성공", "로그인성공"); // 로그인이 성공했다는 메시지박스를 화면에 띄어줌
				this.Close(); // LoginForm을 종료하며 MainForm을 사용할 수 있게됨
			}
			else // 로그인 실패할 경우
			{
				MetroMessageBox.Show(this, "접속실패", "로그인실패",MessageBoxButtons.OK, MessageBoxIcon.Warning); // 접속실패 했다는 경고창을 화면에 띄어주며 OK를 누를시 로그인창으로 돌아감
			}
                    
		}
	}
	catch (Exception ex) // 에러가 날 경우
	{
		MetroMessageBox.Show(this, $"Error : {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); // 에러가 무엇인지 화면에 표시해주는 메시지박스 생성
		return;//OK버튼을 누르면 로그인창으로 돌아감
	}
}

 

오류창에서 Ok를 누르면 로그인창으로 돌아간다.

 

8) 메뉴 - 구분코드 관리 구현

MenuStrip을 추가
구분코드관리 속성 변경

 

DivForm 생성

 

DivForm소스에서 MetroForm 적용

 

SplitContainer, MetroGrid, Label 2개, TextBox 2개, Button 4개 추가

 

MetroGird 속성 변경

 

Lable 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

DivForm Load 이벤트 추가
DivForm에 추가된 Load 이벤트

 

/// <summary>
/// DivFoam이 로드 될 때 데이터 불러오기
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DivForm_Load(object sender, EventArgs e)
{
	// UpdateData 메서드로 데이터그리드에 DB데이터 연결
	UpdateData();
}

Private void UpdateData()
{
	throw new NotImplementedException();
}

 

DB를 사용하기 위해 LoginForm에서 사용한 경로 복사 후 DivForm 멤버 변수에 붙여 넣기

string strConnString = "Data Source=127.0.0.1;Initial Catalog=BookRentalShopDB;User ID=sa;Password=p@ssw0rd!";

 

/// <summary>
/// 구분코드 데이터 로딩
/// </summary>
private void UpdateData()
{
	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		string strQuery = "SELECT Division, Names FROM divtbl"; // LoginForm에서 Query문 작성때와 동일하게 SSMS에서 작성 후 복사 붙여넣기
		SqlDataAdapter dataAdapter = new SqlDataAdapter(strQuery, conn); // SqlDataAdapter 클래스 : SQL Server에서 데이터를 클라이언트로 가져온 후 연결을 끊고 데이터를 사용할 수 있게 하는 클래스이다.

		// DataSet 클래스 : 클라이언트 메모리 상에 존재하는 테이블들을 가지며 서버와의 연결을 유지하지 않는다
		// 일반적으로 SqlDataAdapter을 이용하여 데이터를 서버로부터 가져와 메모리상의 DataSet에 할당 후 사용한다. DataSet 객체는 DataGridView 같은 그리드에 데이터를 바인딩하여 사용할 수 있다.
		DataSet ds = new DataSet();
		dataAdapter.Fill(ds, "divtbl"); // DataAdapter.Fill 메서드 : DataSet 객체에 각 Resultset이 순서대로 DataTables 컬렉션에 들어간다. 일부만 받아올 수도 있다.

		GrdDivTbl.DataSource = ds; // DataGirdView에 데이터가 표시되는 데이터 소스를 가져오거나 설정한다.
		GrdDivTbl.DataMember = "divtbl"; // DataGirdView에 데이터가 표시되는 데이터 소스의 목록 또는 테이블에 대한 이름을 가져오거나 설정한다.
	}
}

 

MainForm에서 DivForm 호출

MnuItemDivMng(구분코드관리) Click 이벤트 추가

 

MainForm에 추가된 Click 이벤트

/// <summary>
/// 구분코드관리 메뉴 선택 시
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MnuItemDivMng_Click(object sender, EventArgs e)
{
	// DivForm 객체 생성
	DivForm form = new DivForm();
	InitChildForm(); // 자식Form을 호출하는 메서드 구현
}

private void InitChildForm()
{
	throw new NotImplementedException();
}
/// <summary>
/// 구분코드관리 메뉴 선택 시
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MnuItemDivMng_Click(object sender, EventArgs e)
{
	// DivForm 객체 생성
	DivForm form = new DivForm();
	InitChildForm(form, "구분코드 관리"); // 자식Form을 호출하는 메서드 구현
}

/// <summary>
/// 자식Form을 호출하는 메서드
/// </summary>
/// <param 호출할 자식Form="form"></param>
/// <param 자식Form의 Title="strFormTitle"></param>
private void InitChildForm(Form form, string strFormTitle)
{
	form.Text = strFormTitle; // 자식 Form의 Text 속성 변경한다.
	form.Dock = DockStyle.Fill; // 부모 Form에 자식 Form 전체가 도킹한다.
	form.MdiParent = this; // 자식 Form의 부모를 MainForm으로 설정한다.
	form.Show(); // 자식 form을 모달리스(Modeless)로 호출한다.
	form.WindowState = FormWindowState.Maximized; // 자식 form 최대화한다.
}

 

로그인 성공 후 메뉴 활성화

 

구분코드 관리 메뉴를 클릭하면 데이터가 로딩되어 볼 수 있다.

 

자식Form이 생성된 후 바로 부모Form을 종료하면 에러가 난다.

 

사용자 편의 - 종료 의사 묻기, 에러 제어

MainForm FormClosing 이벤트 추가

 

MainForm에 추가된 FormClosing 이벤트

 

/// <summary>
/// 정말 종료할 것인지 물어본다.(사용자 편의)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
	//사용자가 메시지박스에서 종료한다는 의미로 예를 선택한 경우
	if(MetroMessageBox.Show(this,"정말 종료하시겠습니까?", "종료",MessageBoxButtons.YesNo,MessageBoxIcon.Question) == DialogResult.Yes)
	{
		foreach (Form item in this.MdiChildren) // MainForm의 자식 Form 전체 종료
		{
			item.Close();
		}
		e.Cancel = false; // 자신 종료
	}
	else
	{
		e.Cancel = true; // 종료하지 않음
	}
}

 

MainForm 닫기 버튼을 눌렀을 경우 해당 메시지가 나오며 에러가 나오지 않는다.

 

DivForm DataGridView 클릭 시 TextBox에 데이터 표시

GrdDivTbl(DataGridView) CellClick 이벤트 추가
DivForm에 추가된 CellClick 이벤트

 

/// <summary>
/// 데이터를 클릭했을시 텍스트박스에 데이터가 들어간다.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1) // INDEX가 0이상이면
	{
		DataGridViewRow data = GrdDivTbl.Rows[e.RowIndex]; // DataGridView RowIndex번째 행의 데이터를 data에 넣는다.
		TxtDivision.Text = data.Cells[0].Value.ToString(); // 행의 0번째 데이터를 구분코드 TextBox에 넣는다.
		TxtNames.Text = data.Cells[1].Value.ToString(); // 행의 1번째 데이터를 이름 TextBox에 넣는다.
		TxtDivision.ReadOnly = true; // 구분코드 TextBox의 데이터를 수정하지 못하도록 한다.
		TxtDivision.BackColor = Color.Beige; // 구분코드 TextBox의 데이터가 수정불가라는 것을 가시적으로 표시하기 위해 배경색을 베이지로 바꾼다.
	}
}

 

선택된 행의 데이터가 TextBox에 표시된다.

 

DivForm 저장 버튼 구현

BtnSave(저장버튼) Click 이벤트 추가
DivForm에 추가된 Click 이벤트

/// <summary>
/// 데이터 수정
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSave_Click(object sender, EventArgs e)
{
	//수정할 구분코드 또는 이름이 공백일 경우 수정 불가
	if(string.IsNullOrEmpty(TxtDivision.Text) || string.IsNullOrEmpty(TxtNames.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return; // 경고창에서 OK버튼을 누를시 DivForm창으로 복귀
	}

	SaveProcess(); // SaveProces 메서드를 이용해 데이터 수정 구현
}

private void SaveProcess()
{
	throw new NotImplementedException();
}


선택한 데이터의 이름(TxtNames.Text)의 값이 DB와 연동되어 수정되어야 하므로 앞서 구현한 DataGridView CellClick이벤트와 멤버 변수를 소스를 추가한다.

//멤버변수 추가
string mode = ""; // 현재 수행하는 모드를 나타냄(INSERT, UPDATE)

GrdDivTbl CellClick 이벤트 수정

/// <summary>
/// 데이터를 클릭했을시 텍스트박스에 데이터가 들어간다.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1) // INDEX가 0이상이면
	{
		DataGridViewRow data = GrdDivTbl.Rows[e.RowIndex];
		TxtDivision.Text = data.Cells[0].Value.ToString();
		TxtNames.Text = data.Cells[1].Value.ToString();
		TxtDivision.ReadOnly = true;
		TxtDivision.BackColor = Color.Beige;

		mode = "UPDATE"; // 이름을 수정할 수 있으니 수정모드로 변환
	}
}

 

/// <summary>
/// 데이터를 수정하는 메서드
/// </summary>
private void SaveProcess()
{
	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;

		string strQuery = "";

		if(mode == "UPDATE")
		{
			strQuery = "UPDATE divtbl "
					   + " SET Names = @Names "
					 + " WHERE Division = @Division"; // 수정을 위한 UPDATE 쿼리문 작성
		}

		cmd.CommandText = strQuery;

		SqlParameter paramDivision = new SqlParameter("@Division", SqlDbType.Char, 4);
		paramDivision.Value = TxtDivision.Text;
		cmd.Parameters.Add(paramDivision);

		SqlParameter parmNames = new SqlParameter("@Names", SqlDbType.NVarChar, 45);
		parmNames.Value = TxtNames.Text;
		cmd.Parameters.Add(parmNames);

		cmd.ExecuteNonQuery(); // SqlCommand.ExecuteNonQuery 메서드 : INSERT, UPDATE, DELEFT 등에 사용되며 영향을 받은 행의 수가 리턴된다.
	}
}

 

BtnSave(저장버튼) Click 메서드 수정

/// <summary>
/// 데이터 수정
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSave_Click(object sender, EventArgs e)
{
	if(string.IsNullOrEmpty(TxtDivision.Text) || string.IsNullOrEmpty(TxtNames.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData(); // 수정된 데이터를 다시 불러와야한다.
}

 

저장버튼을 이용한 데이터 수정

 

사용자 편의 - 데이터가 수정되고 난 뒤 구분코드, 이름 TextBox가 초기화되어야 한다.

BtnSave(저장버튼) Click 이벤트 수정

/// <summary>
/// 데이터 수정
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSave_Click(object sender, EventArgs e)
{
	if(string.IsNullOrEmpty(TxtDivision.Text) || string.IsNullOrEmpty(TxtNames.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData();
	ClearTextControls();// ClearTextControls 메서드로 수정후 TxtDivision과 TxtNames 초기화
}

private void ClearTextControls()
{
	throw new NotImplementedException();
}

 

/// <summary>
/// 구현코드, 이름 텍스트박스 초기화
/// </summary>
private void ClearTextControls()
{
	TxtDivision.Text = ""; // 입력창 초기화
	TxtNames.Text = ""; // 입력창 초기화
	TxtDivision.ReadOnly = false; // 구분코드 입력허용
	TxtDivision.BackColor = Color.White; // 구분코드 입력허용을 가시화하기 위해 배경색을 하얀색으로 바꿈
	TxtDivision.Focus(); // 구분코드 입력창에 포커스
}

 

수정과정이 끝난 후 초기화된 입력창

 

 

DB에 새로운 데이터를 저장하기 위한 신규 버튼 구현

BtnNew(신규버튼) Click 이벤트 추가
DivForm에 추가된 Click 이벤트

 

private void BtnNew_Click(object sender, EventArgs e)
{
	ClearTextControls(); // 추가할 데이터를 입력하기 위해 입력창 초기화
	mode = "INSERT"; // mode를 INSERT로 바꾼다.
	//신규버튼을 누른 뒤 추가할 데이터를 입력하고 저장버튼을 눌러 DB에 저장하는 형식으로 구현한다.
}

 

SaveProcess 메서드 수정

/// <summary>
/// 데이터를 수정하는 메서드
/// </summary>
private void SaveProcess()
{
	// mode 변환이 되지 않은 상태로 데이터 저장을 누른 경우 경고창을 띄운다.
	if(string.IsNullOrEmpty(mode))
	{
		MetroMessageBox.Show(this, "신규버튼을 누르고 데이터를 저장하십시오", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;//경고창에서 OK버튼을 누르면 DivForm으로 복귀한다.
	}

	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;

	string strQuery = "";

	if(mode == "UPDATE")
	{
		strQuery = "UPDATE divtbl "
				   + " SET Names = @Names "
				 + " WHERE Division = @Division";
	}
	else // 신규 데이터를 추가하는 경우
	{
		strQuery = "INSERT INTO divtbl(Division, Names) "
			   + " VALUES(@Division, @Names)"; // 신규데이터 추가를 위한 Query문 작성
	}

	cmd.CommandText = strQuery;

	SqlParameter paramDivision = new SqlParameter("@Division", SqlDbType.Char, 4);
	paramDivision.Value = TxtDivision.Text;
	cmd.Parameters.Add(paramDivision);

	SqlParameter parmNames = new SqlParameter("@Names", SqlDbType.NVarChar, 45);
	parmNames.Value = TxtNames.Text;
	cmd.Parameters.Add(parmNames);

	cmd.ExecuteNonQuery();
	}
}

 

신규버튼 클릭 -> 데이터 입력 -> 저장버튼 클릭 -> 데이터가 저장됨

 

DB에 있는 데이터를 삭제하기 위한 삭제 버튼 구현

BtnDelete(삭제버튼) Click 이벤트 추가
DivForm에 추가된 Click 이벤트

 

/// <summary>
/// 삭제버튼 구현
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnDelete_Click(object sender, EventArgs e)
{
	// 구분코드와 이름이 입력되지 않으면 경고창을 띄운다.
	if(string.IsNullOrEmpty(TxtDivision.Text) || string.IsNullOrEmpty(TxtNames.Text))
	{
		MetroMessageBox.Show(this, "빈칸은 삭제할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return; // 경고창의 OK버튼을 누르면 DivForm으로 복귀한다.
	}

	DeleteProcess(); // 데이터를 삭제하는 메서드를 구현
	UpdateData(); // 데이터 삭제후 수정된 데이터를 다시 불러와야한다.
	ClearTextControls(); // 입력창 초기화
}

private void DeleteProcess()
{
	throw new NotImplementedException();
}
/// <summary>
/// 데이터 삭제 구현
/// </summary>
private void DeleteProcess()
{
	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		cmd.CommandText = "DELETE FROM divtbl "
						+ " WHERE Division = @Division"; // 데이터 삭제를 위한 Query문 작성

		SqlParameter parmDivision = new SqlParameter("@Division", SqlDbType.Char, 4);
		parmDivision.Value = TxtDivision.Text;
		cmd.Parameters.Add(parmDivision);

		cmd.ExecuteNonQuery();
	}
}

 

삭제할 행을 클릭 후 삭제버튼을 누르면 입력창이 초기화되고 데이터가 삭제된다.

 

삭제할 행을 선택안할시 경고창이 뜬다.

 

사용자 편의 - 이름 창에서 입력 후 엔터키를 누르면 저장버튼이 클릭되도록 구현

TxtNames KeyPress 이벤트 추가
DivForm에 추가된 KeyPress 이벤트

 

private void TxtNames_KeyPress(object sender, KeyPressEventArgs e)
{
	if(e.KeyChar == 13)
	{
		BtnSave_Click(sender, new EventArgs());
	}
}

 

9) 메뉴 - 사용자 관리 구현

MainForm Menu에 사용자관리 추가
사용자관리 속성 변경

 

사용자 관리는 구분코드 관리와 비슷하므로 DivForm자체를 복사하여 구현해볼 것이다.

DivForm 복사 붙여넣기를 이용해 복사본을 만들고 UserForm으로 이름을 바꾼다.

 

복사 붙여넣기, 이름변경을 하게되면 소스가 중복되고 꼬여 에러가 많이 발생하게 된다. 

 

먼저 UserForm.cs의 클래스 이름과 생성자 이름을 변경한다.

 

UserForm.Designer.cs에서도 클래스이름을 변경한다.

 

위의 두 가지 소스를 변경해주면 에러는 없어진다.

사용자 관리를 하기 위해 간단히 UserForm을 변경한다.

 

UesrForm 속성 변경

 

삭제버튼을 없애고 레이블과 TextBox하나를 추가해서 넣는다.

 

DataGridView 속성 변경

 

Lable 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

Button 속성 변경

 

UserForm UpdateData 메서드 수정

/// <summary>
/// 사용자 데이터 가져오기
/// </summary>
private void UpdateData()
{
	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		string strQuery = "SELECT id,userID,password,lastLoginDt,loginIpAddr "
						 + " FROM userTbl"; // Qurey문 수정
		SqlDataAdapter dataAdapter = new SqlDataAdapter(strQuery, conn);

		DataSet ds = new DataSet();
		dataAdapter.Fill(ds, "userTbl"); // userTbl

		GrdUserTbl.DataSource = ds;
		GrdUserTbl.DataMember = "userTbl"; // userTbl
	}

	DataGridViewColumn column = GrdUserTbl.Columns[0]; // id 열
	column.Width = 40; // id열 길이 변경(테이블 열길이)
	column.HeaderText = "순번"; // 열이름 변경
	column = GrdUserTbl.Columns[1]; // userID 열
	column.Width = 80;
	column.HeaderText = "아이디";
	column = GrdUserTbl.Columns[2]; // Password 열
	column.Width = 100;
	column.HeaderText = "패스워드";
	column = GrdUserTbl.Columns[3]; // 최종 접속 시간
	column.Width = 120;
	column.HeaderText = "최종접속시간";
	column = GrdUserTbl.Columns[4]; // 접속 아이피 주소
	column.Width = 150;
	column.HeaderText = "접속아이피주소";
}

 

UserForm ClearTextControls 메서드 수정

/// <summary>
/// 입력창 초기화
/// </summary>
private void ClearTextControls()
{
	TxtId.Text = ""; // 입력창 초기화
	TxtUserID.Text = ""; // 입력창 초기화
	TxtPassword.Text = ""; // 입력창 초기화
	TxtUserID.Focus(); // 아이디 입력창에 포커스
}

 

UserForm BtnSave_Click 이벤트 수정

/// <summary>
/// 데이터 수정
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSave_Click(object sender, EventArgs e)
{
	// 아이디 패스워드가 빈칸이면 에러 창
	if (string.IsNullOrEmpty(TxtUserID.Text) || string.IsNullOrEmpty(TxtPassword.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData();
	ClearTextControls();
}

 

UserForm SaveProcess 메서드 수정

/// <summary>
/// 데이터를 수정하는 메서드
/// </summary>
private void SaveProcess()
{
	if(string.IsNullOrEmpty(mode))
	{
		MetroMessageBox.Show(this, "신규버튼을 누르고 데이터를 저장하십시오", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	using(SqlConnection conn = new SqlConnection(strConnString))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;

		string strQuery = "";

		if(mode == "UPDATE")
		{
			strQuery = "UPDATE userTbl "
					   + " SET userID = @userID,password = @password "
					 + " WHERE id = @id "; // Query문 수정
		}
		else if (mode == "INSERT")
		{
			strQuery = "INSERT INTO userTbl(userID, password) "
					+ " VALUES(@userID, @password) "; // Query문 수정
		}

		cmd.CommandText = strQuery;

		SqlParameter parmUserID = new SqlParameter("@userID", SqlDbType.VarChar, 12); // 파라미터 수정
		parmUserID.Value = TxtUserID.Text;
		cmd.Parameters.Add(parmUserID);

		SqlParameter parmPassword = new SqlParameter("@password", SqlDbType.VarChar, 20); // 파라미터 수정
		parmPassword.Value = TxtPassword.Text;
		cmd.Parameters.Add(parmPassword);

		if(mode == "UPDATE") // UPDATE문의 경우 인자가 하나 더 필요하다(id/순번)
		{
			SqlParameter parmid = new SqlParameter("@id", SqlDbType.Int); // 파라미터 수정
			parmid.Value = TxtId.Text;
			cmd.Parameters.Add(parmid);
		}

		cmd.ExecuteNonQuery();
	}
}

 

UserForm GrdDivTbl_CellClick 이벤트 수정

/// <summary>
/// 데이터를 클릭했을시 텍스트박스에 데이터가 들어간다.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1) // INDEX가 0이상이면
	{
		DataGridViewRow data = GrdUserTbl.Rows[e.RowIndex];
		TxtId.Text = data.Cells[0].Value.ToString();
		TxtUserID.Text = data.Cells[1].Value.ToString();
		TxtPassword.Text = data.Cells[2].Value.ToString();

		mode = "UPDATE";
	}
}

 

UserForm TxtNames_KeyPress 이벤트 삭제

 

MainForm 메뉴 Click 이벤트 추가

MnuUserMng(사용자관리) Click 이벤트 추가
MainForm에 추가된 Click 이벤트

 

private void MnuUserMng_Click(object sender, EventArgs e)
{
	UserForm form = new UserForm();
	InitChildForm(form, "사용자관리");
}

 

로그인을 하면 새로운 메뉴인 사용자관리가 보인다

 

사용자관리에 들어가면 현재 DB에 저장된 사용자정보가 보인다.

 

사용자 행을 클릭하면 정보가 TextBox에 들어간다.

 

사용자를 추가하기 위해선 신규버튼을 누른 뒤 아이디와 패스워드를 입력한다.

 

그 뒤 저장버튼을 누르면 저장된 사용자를 확인할 수 있다.

 

사용자 정보를 수정하려면 수정할 행을 선택한 뒤 수정할 데이터를 입력한다.

 

그 뒤 저장하면 수정된 사용자 데이터를 확인 할 수 있다.

 

10) 회원관리 구현

MainForm에서 메뉴 추가

 

DivForm을 복사해 MemberForm 만들기

DivForm의 복사본을 만들고 MemberForm로 이름을 변경한다.

 

MemberForm.cs과 MemberForm.Designer.cs의 소스를 바꿔준다.

 

메뉴에서 MemberForm 호출 구현

회원관리메뉴 Click 이벤트 추가
MainForm에 추가도니 Click 이벤트

 

private void 회원관리MToolStripMenuItem_Click(object sender, EventArgs e)
{
	MemberForm form = new MemberForm();
	InitChildForm(form, "회원관리");
}

 

MemberForm 디자인 변경

Label 4개, TextBox 3개, ComboBox 1개 추가

 

MemberForm 속성 변경

 

DataGridView 속성 변경

 

Label 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

ComboBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

삭제버튼 비활성화

 

Button 속성 변경

 

개발자 편의 - Class 추가를 통한 공용 연결 문자열 생성

해당 프로젝트에서 추가 -> 새항목을 클릭한다.
클래스를 선택후 적절한 이름을 입력하고 추가를 누른다.

 

새로 만든 클래스에 많은 Form 소스에서 공통으로 사용하는 문자열을 public으로 저장해둔다.

namespace BookRentalShop20
{
    public static class Commons
    {
        //공용 연결 문자열
        public static string CONNSTRING =
            "Data Source=192.168.0.10;Initial Catalog=BookRentalshopDB;Persist Security Info=True;User ID=sa;Password=p@ssw0rd!";

        public static string LOGINUSERID = "";
    }
}

 

LoginForm, DIVForm, UserForm, MemberForm에서 사용하던 멤버 변수 strConnString의 선언을 각각의 Form 소스에서 삭제한 후 strConnString이 사용되던 자리에 Commons.CONNSTRING을 대신 입력한다.

 

ex)

using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))

 

많은 소스에서 이와 동일한 방법으로 공통된 부분을 같이 사용할 수 있다.

 

MemberForm에서 데이터 보여주기, 수정, 저장

MemberForm UpdateDate 메서드 수정

private void UpdateData()
{
	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open(); // DB 열기
		string strQuery = "SELECT Idx, Names, Levels, Addr, Mobile, Email "
						 + " FROM membertbl";
		SqlDataAdapter dataAdapter = new SqlDataAdapter(strQuery, conn);
		DataSet ds = new DataSet();
		dataAdapter.Fill(ds, "membertbl");

		GrdMemberTbl.DataSource = ds;
		GrdMemberTbl.DataMember = "membertbl";
	}
}

 

MemberForm GrdDivTbl(GrdMemberTbl이지만 이벤트 이름변경을 하지 않음)의 CellClick 이벤트 수정

private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1)
	{
		DataGridViewRow data = GrdMemberTbl.Rows[e.RowIndex];
		TxtIdx.Text = data.Cells[0].Value.ToString();
		TxtNames.Text = data.Cells[1].Value.ToString();
		CboLevels.SelectedIndex = CboLevels.FindString(data.Cells[2].Value.ToString());
		TxtAddr.Text = data.Cells[3].Value.ToString();
		TxtMobile.Text = data.Cells[4].Value.ToString();
		TxtEmail.Text = data.Cells[5].Value.ToString();

		mode = "UPDATE"; // 수정은 UPDATE
	}
}

 

MemberForm BtnSave(저장버튼)의 Click 이벤트 수정

/// <summary>
/// 데이터 수정
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSave_Click(object sender, EventArgs e)
{
	if(string.IsNullOrEmpty(TxtAddr.Text) || string.IsNullOrEmpty(TxtMobile.Text) || string.IsNullOrEmpty(TxtNames.Text)
	   || string.IsNullOrEmpty(TxtEmail.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData();
	ClearTextControls();
}

 

MemberForm ClearTextControls 메서드 수정

/// <summary>
/// 입력창 초기화
/// </summary>
private void ClearTextControls()
{
	TxtIdx.Text = "";
	TxtNames.Text = "";
	TxtAddr.Text = "";
	TxtEmail.Text = "";
	TxtMobile.Text = "";
	CboLevels.SelectedIndex = -1;// 아무것도 선택안함
	TxtNames.Focus();
}

 

MemberForm SaveProcess 메서드 수정

/// <summary>
/// DB 저장 프로세스
/// </summary>
private void SaveProcess()
{
	if(string.IsNullOrEmpty(mode))
	{
		MetroMessageBox.Show(this, "신규버튼을 누르고 데이터를 저장하십시오.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		string strQuery = "";

		if (mode == "UPDATE")
		{
			strQuery = "UPDATE membertbl "
					   + " SET Names = @Names, Levels = @Levels, Addr = @Addr, Mobile = @Mobile, Email = @Email "
					 + " WHERE Idx = @Idx";
		}
		else if (mode == "INSERT")
		{
			strQuery = "INSERT INTO membertbl(Names, Levels, Addr, Mobile, Email) "
					+ " VALUES(@Names, @Levels, @Addr, @Mobile, @Email)";
		}
		cmd.CommandText = strQuery;

		SqlParameter parmNames = new SqlParameter("@Names", SqlDbType.NVarChar, 45);
		parmNames.Value = TxtNames.Text;
		cmd.Parameters.Add(parmNames);

		SqlParameter parmLevels = new SqlParameter("@Levels", SqlDbType.Char, 1);
		parmLevels.Value = CboLevels.SelectedItem;
		cmd.Parameters.Add(parmLevels);

		SqlParameter parmAddr = new SqlParameter("@Addr", SqlDbType.VarChar, 100);
		parmAddr.Value = TxtAddr.Text;
		cmd.Parameters.Add(parmAddr);

		SqlParameter parmMobile = new SqlParameter("@Mobile", SqlDbType.VarChar, 13);
		parmMobile.Value = TxtMobile.Text;
		cmd.Parameters.Add(parmMobile);

		SqlParameter parmEmail = new SqlParameter("@Email", SqlDbType.VarChar, 50);
		parmEmail.Value = TxtEmail.Text;
		cmd.Parameters.Add(parmEmail);

		if(mode == "UPDATE")
		{
			SqlParameter parmIdx = new SqlParameter("@Idx", SqlDbType.Int);
			parmIdx.Value = TxtIdx.Text;
			cmd.Parameters.Add(parmIdx);
		}    

		cmd.ExecuteNonQuery();
	}
}

 

MemberTbl에 들어잇는 회원들의 정보가 표시된다.

 

한 행을 선택 시 오른쪽에 데이터가 표시된다.

 

신규버튼을 누른뒤 새로 저장하고 싶은 회원 정보를 입력한다.

 

정보 입력후 저장버튼을 누르면 회원관리 테이블에 데이터가 저장되어있음을 확인 할 수 있다.

 

11) 사용자편의 - 현재 로그인된 아이디 표시

앞서 만들어 놓은 Commons 클래스에 현재 로그인되있는 아이디를 저장하기 위한 변수를 선언한다.

public static string LOGINUSERID = "";

 

MainForm에 현재 로그인 아이디를 표시하기 위해 Label을 2개 추가한다.

 

Label 속성 변경

 

Label 속성 변경

 

현재 접속한 아이디를 저장하기 위해 LoginForm의 LoginProcess에 코드를 추가한다.

private void LoginProcess()
{
	if(string.IsNullOrEmpty(TxtUserID.Text) || string.IsNullOrEmpty(TxtPassWord.Text) )
	{
		MetroMessageBox.Show(this, "아이디/패스워드를 입력하세요!", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
		return;
	}

	string strUserId = string.Empty;

	try
	{
		using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
		{
			conn.Open();
			SqlCommand cmd = new SqlCommand();
			cmd.Connection = conn;
			cmd.CommandText = "SELECT userID FROM userTbl"
							+ " WHERE userID = @userID"
							  + " AND password = @password";

			SqlParameter parmUserID = new SqlParameter("@userID", SqlDbType.VarChar, 12);
			parmUserID.Value = TxtUserID.Text;
			cmd.Parameters.Add(parmUserID);

			SqlParameter parmPassword = new SqlParameter("@password", SqlDbType.VarChar, 20);
			parmPassword.Value = TxtPassWord.Text;
			cmd.Parameters.Add(parmPassword);

			SqlDataReader reader = cmd.ExecuteReader();
			reader.Read();
			strUserId = reader["userID"] != null ? reader["userID"].ToString() : "";

			if (strUserId != "")
			{
            	//접속에 성공했을 시 아이디를 저장
				Commons.LOGINUSERID = strUserId;
				MetroMessageBox.Show(this, "접속성공", "로그인성공");
				this.Close();
			}
			else
			{
				MetroMessageBox.Show(this, "접속실패", "로그인실패", MessageBoxButtons.OK, MessageBoxIcon.Warning);
			}
		}
	}
	catch (Exception ex)
	{
		MetroMessageBox.Show(this, $"Error : {ex.StackTrace}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
	return;
	} 
}

 

MainForm이 활성화 될 때 아이디가 표시되어야 하므로 Activated 이벤트를 추가한다.

MainForm Activated 이벤트 추가

private void MainForm_Activated(object sender, EventArgs e)
{
	LblUserID.Text = Commons.LOGINUSERID;
}

 

로그인을 하게되면 현재 로그인중인 아이디가 MainForm 상단에 표시된다.

 

12) BooksForm(책관리 메뉴) 구현

MainForm 메뉴에서 책관리를 추가한다.

 

MemberForm을 이용해 복사본을 만든 뒤 소스를 고친다

 

BooksForm에 Label 1개 추가 , DateTimePicker 1개 추가

 

BooksForm 속성 변경

 

DataGridView 속성 변경

 

Label 속성 변경

 

DataText 속성 변경

 

DataText 속성 변경

 

ComboBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

TextBox 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

Button 속성 변경

 

MainForm 책관리 메뉴 Click 이벤트 추가

private void 책관리BToolStripMenuItem_Click(object sender, EventArgs e)
{
	BooksForm form = new BooksForm();
	InitChildForm(form, "책 관리");
}

 

BooksForm UpdateDate 메서드 수정

private void UpdateData()
{
	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open(); // DB 열기
		//string strQuery = "SELECT Idx, Author, Division, Names, ReleaseDate, ISBN, Price "
		//                 + " FROM bookstbl ";
		string strQuery = "SELECT b.Idx, b.Author, b.Division, d.Names AS 'DivNames', b.Names, b.ReleaseDate, b.ISBN "
							+ " , REPLACE(CONVERT(VARCHAR, CONVERT(MONEY, b.Price), 1), '.00', '') AS 'price' "
						 + " FROM bookstbl AS b "
						+ " INNER JOIN divtbl AS d ON d.Division = b.Division";
		SqlDataAdapter dataAdapter = new SqlDataAdapter(strQuery, conn);
		DataSet ds = new DataSet();
		dataAdapter.Fill(ds, "bookstbl");

		GrdBooksTbl.DataSource = ds;
		GrdBooksTbl.DataMember = "bookstbl";
	}
	DataGridViewColumn column = GrdBooksTbl.Columns[2];
	column.Visible = false;// Division 비활성화를 통한 화면에 안보이게 하기.
}

 

BooksForm SaveProcess 메서드 수정

private void SaveProcess()
{
	if(string.IsNullOrEmpty(mode))
	{
		MetroMessageBox.Show(this, "신규버튼을 누르고 데이터를 저장하십시오.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		string strQuery = "";

		if (mode == "UPDATE")
		{
			strQuery = "UPDATE bookstbl SET Author = @Author, Division = @Division, Names = @Names, ReleaseDate = @ReleaseDate, "
										+ " ISBN = @ISBN, Price = @Price "
					 + " WHERE Idx = @Idx";
		}
		else if (mode == "INSERT")
		{
			strQuery = "INSERT INTO bookstbl(Author, Division, Names, ReleaseDate, ISBN, Price) "
					+ " VALUES(@Author, @Division, @Names, @ReleaseDate, @ISBN, @Price)";
		}
		cmd.CommandText = strQuery;

		SqlParameter parmAuthor = new SqlParameter("@Author", SqlDbType.NVarChar, 45);
		parmAuthor.Value = TxtAuthor.Text;
		cmd.Parameters.Add(parmAuthor);

		SqlParameter parmDivision = new SqlParameter("@Division", SqlDbType.Char, 4);
		parmDivision.Value = CboDivision.SelectedValue;
		cmd.Parameters.Add(parmDivision);

		SqlParameter parmNames = new SqlParameter("@Names", SqlDbType.VarChar, 100);
		parmNames.Value = TxtNames.Text;
		cmd.Parameters.Add(parmNames);

		SqlParameter parmReleaseDate = new SqlParameter("@ReleaseDate", SqlDbType.Date);
		parmReleaseDate.Value = DtpReleaseDate.Value;
		cmd.Parameters.Add(parmReleaseDate);

		SqlParameter parmISBN = new SqlParameter("@ISBN", SqlDbType.VarChar, 200);
		parmISBN.Value = TxtISBN.Text;
		cmd.Parameters.Add(parmISBN);

		SqlParameter parmPrice = new SqlParameter("@Price", SqlDbType.Decimal, 10);
		parmPrice.Value = TxtPrice.Text;
		cmd.Parameters.Add(parmPrice);

		if (mode == "UPDATE")
		{
			SqlParameter parmIdx = new SqlParameter("@Idx", SqlDbType.Int);
			parmIdx.Value = TxtIdx.Text;
			cmd.Parameters.Add(parmIdx);
		}    

		cmd.ExecuteNonQuery();
	}
}

 

BooksForm BtnSave(저장버튼) Click 이벤트 수정

private void BtnSave_Click(object sender, EventArgs e)
{
	if(string.IsNullOrEmpty(TxtNames.Text) || string.IsNullOrEmpty(TxtAuthor.Text)
		|| string.IsNullOrEmpty(TxtISBN.Text) || string.IsNullOrEmpty(TxtNames.Text) ||string.IsNullOrEmpty(TxtPrice.Text))
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData();
	ClearTextControls();
}

 

BooksForm의 Load 이벤트에서 ComboBox의 데이터를 업데이트 하기 위한 메서드 구현

private void UpdateCboDivision()
{
	using(SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		cmd.CommandText = "SELECT Division, Names FROM divtbl ";
		SqlDataReader reader = cmd.ExecuteReader();

		Dictionary<string, string> temps = new Dictionary<string, string>();
		while(reader.Read())
		{
			temps.Add(reader[0].ToString(), reader[1].ToString());
		}

		CboDivision.DataSource = new BindingSource(temps, null); // 콤보박스에 데이터테이블을 넣어두고
		CboDivision.DisplayMember = "Value"; // 콤보박스에 표시할 테스트
		CboDivision.ValueMember = "Key"; // 콤보박스에 숨길 값
		CboDivision.SelectedIndex = -1;
	}
}

 

DateTimePicker의 초기화를 위해 여러 메서드에 코드를 추가해주어야 한다.

BooksForm ClearTextControls 메서드 수정

private void ClearTextControls()
{
	TxtIdx.Text = "";
	TxtAuthor.Text = "";
	TxtNames.Text = "";
	TxtISBN.Text = "";
	TxtPrice.Text = "";
	CboDivision.SelectedIndex = -1;// 아무것도 선택안함
	DtpReleaseDate.CustomFormat = " ";//  공백이 한칸 들어가야함
	DtpReleaseDate.Format = DateTimePickerFormat.Custom;
	TxtAuthor.Focus();
}

 

BooksForm GrdDivTbl CellClick 이벤트 수정 (GrdBooksTbl이지만 Form복사후 이벤트, 메서드 이름을 변경하지 않음)

private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1)
	{
		DataGridViewRow data = GrdBooksTbl.Rows[e.RowIndex];
		TxtIdx.Text = data.Cells[0].Value.ToString();
		TxtIdx.ReadOnly = true;
		TxtAuthor.Text = data.Cells[1].Value.ToString();
                
		//아래의 두 소스는 같은 역할을 한다.
		//CboDivision.SelectedIndex = CboDivision.FindString(data.Cells[3].Value.ToString());
		CboDivision.SelectedValue = data.Cells[2].Value;
                
		TxtNames.Text = data.Cells[4].Value.ToString();

		DtpReleaseDate.CustomFormat = "yyyy-MM-dd";
		DtpReleaseDate.Format = DateTimePickerFormat.Custom;
		DtpReleaseDate.Value = DateTime.Parse(data.Cells[5].Value.ToString());
		TxtISBN.Text = data.Cells[6].Value.ToString();
		TxtPrice.Text = data.Cells[7].Value.ToString();

		mode = "UPDATE"; // 수정은 UPDATE
	}
}

 

BooksForm MemberForm Load 이벤트 수정 (BooksForm이지만 Form복사후 이벤트, 메서드 이름을 변경하지 않음)

private void MemberForm_Load(object sender, EventArgs e)
{
	DtpReleaseDate.CustomFormat = "yyyy-MM-dd";//
	DtpReleaseDate.Format = DateTimePickerFormat.Custom;
	UpdateData(); // 데이터그리드 DB 데이터 로딩하기
	UpdateCboDivision();
}

 

DateTimePicker의 값이 바뀔 시 사용 되는 ValueChanged 이벤트 추가

private void DtpReleaseDate_ValueChanged(object sender, EventArgs e)
{
	DtpReleaseDate.CustomFormat = "yyyy-MM-dd";
	DtpReleaseDate.Format = DateTimePickerFormat.Custom;
}

 

메뉴에서 책 관리르 클릭할 경우 현재 DB에 저장되어 있는 책 목록이 표시된다.

 

행을 선택할 경우 오른쪽에 행의 데이터가 표시된다.

 

행의 정보를 수정하고 저장버튼을 누르면 수정된 데이터가 저장되며 왼쪽에 표시된다.

 

새로운 데이터를 저장하고 싶은 경우 신규 버튼을 누른 뒤 데이터를 입력하고 저장버튼을 누른다.

 

왼쪽에 새롭게 저장된 데이터가 표시된다.

 

13) Test ( RentalTbl / 대여책관리 구현 )

BooksForm을 복사하여 Form 이름을 수정한다.

 

MainForm의 메뉴에 대여책관리를 추가한다.

 

대여책관리 Click 이벤트 추가

private void 대여책관리RToolStripMenuItem_Click(object sender, EventArgs e)
{
	RentalForm form = new RentalForm();
	InitChildForm(form, "대여책 관리");
}

 

RentalForm에 Label 5개, ComboBox 2개, DateTimePicker 2개를 추가한다.

 

RentalForm 속성 변경

 

DataGridView 속성 변경

 

Label 속성 변경

 

TextBox 속성 변경

 

ComboBox 속성 변경

 

ComboBox 속성 변경

 

DateTimePicker 속성 변경

 

DateTimePicker 속성 변경

 

Button 속성 변경

 

RentalForm UpdateDate 메서드 수정

DataGridView에 데이터를 보여주기 위해 코드 수정

private void UpdateData()
{
	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open(); // DB 열기
		string strQuery = "SELECT r.idx AS '대여번호', m.Names AS '대여회원', "
							  + " t.Names AS '장르', "
							  + " b.Names AS '대여책제목'  ,b.ISBN, "
							  + " r.rentalDate AS '대여일' , r.returnDate AS '반납일'"
						 + " FROM rentaltbl AS r "
						+ " INNER JOIN membertbl AS m "
						   + " ON r.memberIdx = m.Idx "
						+ " INNER JOIN bookstbl AS b "
						   + " ON r.bookIdx = b.Idx "
						+ " INNER JOIN divtbl AS t "
						   + " ON b.division = t.division";
		SqlDataAdapter dataAdapter = new SqlDataAdapter(strQuery, conn);
		DataSet ds = new DataSet();
		dataAdapter.Fill(ds, "rentaltbl");

		GrdRentalTbl.DataSource = ds;
		GrdRentalTbl.DataMember = "rentaltbl";
	}
}

 

RentalForm GrdDivTbl(GrdRentalTbl이지만 이벤트 이름을 바꾸지 않아서) CellClick 이벤트 수정

private void GrdDivTbl_CellClick(object sender, DataGridViewCellEventArgs e)
{
	if(e.RowIndex > -1)
	{
		DataGridViewRow data = GrdRentalTbl.Rows[e.RowIndex];
		TxtIdx.Text = data.Cells[0].Value.ToString();
		TxtIdx.ReadOnly = true;
		CboMemberIdx.SelectedIndex = CboMemberIdx.FindString(data.Cells[1].Value.ToString());
		CboBookIdx.SelectedIndex = CboBookIdx.FindString(data.Cells[3].Value.ToString());

		DtpRentalDate.CustomFormat = "yyyy-MM-dd";
		DtpRentalDate.Format = DateTimePickerFormat.Custom;
		DtpRentalDate.Value = DateTime.Parse(data.Cells[5].Value.ToString());

		//반납일이 NULL값인지 확인하는 절차가 필요하다. 만약 NULL이라면 빈값을 넣어준다.
		if(string.IsNullOrEmpty(data.Cells[6].Value.ToString()))
		{
			DtpReturnDate.CustomFormat = " ";//  공백이 한칸 들어가야함
			DtpReturnDate.Format = DateTimePickerFormat.Custom;
		}
		else
		{
			DtpReturnDate.CustomFormat = "yyyy-MM-dd";
			DtpReturnDate.Format = DateTimePickerFormat.Custom;
			DtpReturnDate.Value = DateTime.Parse(data.Cells[6].Value.ToString());
		}
		mode = "UPDATE"; // 수정은 UPDATE
	}
}

 

RentalForm BtnSave(저장 버튼) Click 이벤트 수정

private void BtnSave_Click(object sender, EventArgs e)
{
	// ComboBox의 값을 선택하지 않았을 경우 에러가 나옴
	if(CboMemberIdx.SelectedIndex == -1 || CboBookIdx.SelectedIndex == -1)
	{
		MetroMessageBox.Show(this, "빈값은 저장할 수 없습니다.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	SaveProcess();
	UpdateData();
	ClearTextControls();
}

 

RentalForm ClearTextControls 메서드 수정

private void ClearTextControls()
{
	TxtIdx.Text = "";
	CboMemberIdx.SelectedIndex = -1;// 아무것도 선택안함
	CboBookIdx.SelectedIndex = -1;// 아무것도 선택안함
	DtpRentalDate.CustomFormat = " ";//  공백이 한칸 들어가야함
	DtpRentalDate.Format = DateTimePickerFormat.Custom;
	DtpReturnDate.CustomFormat = " ";//  공백이 한칸 들어가야함
	DtpReturnDate.Format = DateTimePickerFormat.Custom;
	CboMemberIdx.Focus();
}

 

RentalForm SaveProcess 메서드 수정

private void SaveProcess()
{
	if(string.IsNullOrEmpty(mode))
	{
		MetroMessageBox.Show(this, "신규버튼을 누르고 데이터를 저장하십시오.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
		return;
	}

	using (SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		string strQuery = "";

		if (mode == "UPDATE")
		{
			strQuery = "UPDATE rentaltbl "
					   + " SET memberIdx = @memberIdx, bookIdx = @bookIdx, rentalDate = @rentalDate, returnDate = @returnDate"
					 + " WHERE Idx = @Idx";
		}
		else if (mode == "INSERT")
		{
			strQuery = "INSERT INTO rentaltbl(memberIdx, bookIdx, rentalDate, returnDate) "
					+ " VALUES(@memberIdx, @bookIdx, @rentalDate, @returnDate)";
		}
		cmd.CommandText = strQuery;

		SqlParameter parmMemberIdx = new SqlParameter("@memberIdx", SqlDbType.Int);
		parmMemberIdx.Value = CboMemberIdx.SelectedValue;
		cmd.Parameters.Add(parmMemberIdx);

		SqlParameter parmBookIdx = new SqlParameter("@bookIdx", SqlDbType.Int);
		parmBookIdx.Value = CboBookIdx.SelectedValue;
		cmd.Parameters.Add(parmBookIdx);

		SqlParameter parmRentalDate = new SqlParameter("@rentalDate", SqlDbType.Date);
		parmRentalDate.Value = DtpRentalDate.Value;
		cmd.Parameters.Add(parmRentalDate);

		SqlParameter parmReturnDate = new SqlParameter("@returnDate", SqlDbType.Date);
		parmReturnDate.Value = DtpReturnDate.Value;
		cmd.Parameters.Add(parmReturnDate);

		if (mode == "UPDATE")
		{
			SqlParameter parmIdx = new SqlParameter("@Idx", SqlDbType.Int);
			parmIdx.Value = TxtIdx.Text;
			cmd.Parameters.Add(parmIdx);
		}    

		cmd.ExecuteNonQuery();
	}
}

 

RentalForm MemberForm(RentalForm이지만 이름을 바꾸지 않음) Load 이벤트 수정

private void MemberForm_Load(object sender, EventArgs e)
{
	DtpRentalDate.CustomFormat = " ";//  공백이 한칸 들어가야함
	DtpRentalDate.Format = DateTimePickerFormat.Custom;
	DtpReturnDate.CustomFormat = " ";//  공백이 한칸 들어가야함
	DtpReturnDate.Format = DateTimePickerFormat.Custom;
	UpdateData(); // 데이터그리드 DB 데이터 로딩하기
	UpdateCboDivision();
}

 

RentalForm UpdateCboDivision 메서드 수정

private void UpdateCboDivision()
{
	using(SqlConnection conn = new SqlConnection(Commons.CONNSTRING))
	{
		conn.Open();
		SqlCommand cmd = new SqlCommand();
		cmd.Connection = conn;
		cmd.CommandText = "SELECT Idx, Names, Levels, Addr, Mobile, Email "
						 + " FROM membertbl";  

		SqlDataReader reader = cmd.ExecuteReader();
		Dictionary<string, string> temps = new Dictionary<string, string>();
		Dictionary<string, string> temps2 = new Dictionary<string, string>();
		while(reader.Read())
		{
			temps.Add(reader[0].ToString(), reader[1].ToString());
		}

		cmd.CommandText = "SELECT Idx, Author, Division, Names, ReleaseDate, ISBN, Price "
						 + " FROM dbo.bookstbl";
		reader.Close();
		reader = cmd.ExecuteReader();
		while(reader.Read())
		{
			temps2.Add(reader[0].ToString(), reader[3].ToString());
		}

		CboMemberIdx.DataSource = new BindingSource(temps, null);
		CboMemberIdx.DisplayMember = "Value";
		CboMemberIdx.ValueMember = "Key";
		CboMemberIdx.SelectedIndex = -1;

		CboBookIdx.DataSource = new BindingSource(temps2, null);
		CboBookIdx.DisplayMember = "Value";
		CboBookIdx.ValueMember = "Key";
		CboBookIdx.SelectedIndex = -1;

	}
}

 

DtpRentalDate에 추가된 ValueChange 이벤트

RentalForm DtpReleaseDate(DtpRentalDate이지만 이름을 바꾸지 않음) ValuleChanged 이벤트 수정

private void DtpReleaseDate_ValueChanged(object sender, EventArgs e)
{
	DtpRentalDate.CustomFormat = "yyyy-MM-dd";
	DtpRentalDate.Format = DateTimePickerFormat.Custom;
}

 

DtpReturnDate에 추가된 ValueChange 이벤트

RentalForm DtpReturnDate ValuleChanged 이벤트 수정

private void DtpReturnDate_ValueChanged(object sender, EventArgs e)
{
	DtpReturnDate.CustomFormat = "yyyy-MM-dd";
	DtpReturnDate.Format = DateTimePickerFormat.Custom;
}

 

BtnCancel에 추가된 Click 이벤트

RentalForm BtnCancel(취소버튼) Click 이벤트 추가

private void BtnCancel_Click(object sender, EventArgs e)
{
	ClearTextControls();
}

 

최종 실행 화면

 

COMMENT