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')
;
2) MainForm 생성
3) LoginForm 추가
4) LoginForm Button_Cancel 이벤트 추가
/// <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 호출
/// <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 로그인 기능 구현
/// <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;//경고창 확인을 누르면 다시 로그인창으로 복귀
}
}
LoginProcess 메서드에서 SQL Server를 연결해 Login 기능 구현
/// <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 TextBox에서 입력 후 Enter를 치면 선택된 오브젝트가 내려가도록 구현
/// <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를 패스워드 텍스트 박스로 옮긴다.
}
}
/// <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버튼을 누르면 로그인창으로 돌아감
}
}
8) 메뉴 - 구분코드 관리 구현
/// <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 호출
/// <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 최대화한다.
}
사용자 편의 - 종료 의사 묻기, 에러 제어
/// <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; // 종료하지 않음
}
}
DivForm DataGridView 클릭 시 TextBox에 데이터 표시
/// <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의 데이터가 수정불가라는 것을 가시적으로 표시하기 위해 배경색을 베이지로 바꾼다.
}
}
DivForm 저장 버튼 구현
/// <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에 새로운 데이터를 저장하기 위한 신규 버튼 구현
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에 있는 데이터를 삭제하기 위한 삭제 버튼 구현
/// <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();
}
}
사용자 편의 - 이름 창에서 입력 후 엔터키를 누르면 저장버튼이 클릭되도록 구현
private void TxtNames_KeyPress(object sender, KeyPressEventArgs e)
{
if(e.KeyChar == 13)
{
BtnSave_Click(sender, new EventArgs());
}
}
9) 메뉴 - 사용자 관리 구현
사용자 관리는 구분코드 관리와 비슷하므로 DivForm자체를 복사하여 구현해볼 것이다.
위의 두 가지 소스를 변경해주면 에러는 없어진다.
사용자 관리를 하기 위해 간단히 UserForm을 변경한다.
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 이벤트 추가
private void MnuUserMng_Click(object sender, EventArgs e)
{
UserForm form = new UserForm();
InitChildForm(form, "사용자관리");
}
10) 회원관리 구현
MainForm에서 메뉴 추가
DivForm을 복사해 MemberForm 만들기
메뉴에서 MemberForm 호출 구현
private void 회원관리MToolStripMenuItem_Click(object sender, EventArgs e)
{
MemberForm form = new MemberForm();
InitChildForm(form, "회원관리");
}
MemberForm 디자인 변경
개발자 편의 - 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();
}
}
11) 사용자편의 - 현재 로그인된 아이디 표시
앞서 만들어 놓은 Commons 클래스에 현재 로그인되있는 아이디를 저장하기 위한 변수를 선언한다.
public static string LOGINUSERID = "";
현재 접속한 아이디를 저장하기 위해 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 이벤트를 추가한다.
private void MainForm_Activated(object sender, EventArgs e)
{
LblUserID.Text = Commons.LOGINUSERID;
}
12) BooksForm(책관리 메뉴) 구현
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;
}
13) Test ( RentalTbl / 대여책관리 구현 )
private void 대여책관리RToolStripMenuItem_Click(object sender, EventArgs e)
{
RentalForm form = new RentalForm();
InitChildForm(form, "대여책 관리");
}
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;
}
}
RentalForm DtpReleaseDate(DtpRentalDate이지만 이름을 바꾸지 않음) ValuleChanged 이벤트 수정
private void DtpReleaseDate_ValueChanged(object sender, EventArgs e)
{
DtpRentalDate.CustomFormat = "yyyy-MM-dd";
DtpRentalDate.Format = DateTimePickerFormat.Custom;
}
RentalForm DtpReturnDate ValuleChanged 이벤트 수정
private void DtpReturnDate_ValueChanged(object sender, EventArgs e)
{
DtpReturnDate.CustomFormat = "yyyy-MM-dd";
DtpReturnDate.Format = DateTimePickerFormat.Custom;
}
RentalForm BtnCancel(취소버튼) Click 이벤트 추가
private void BtnCancel_Click(object sender, EventArgs e)
{
ClearTextControls();
}
'스마트팩토리 > C# 데스크톱 앱(윈폼),WPF' 카테고리의 다른 글
7. WPF(Windows Presentation Foundation) (0) | 2020.06.23 |
---|---|
6. 윈폼 - DB 연동(책 대출 프로그램) 완성 (0) | 2020.06.22 |
5. 윈폼 - DB연동 (0) | 2020.06.19 |
4. 차트, 윈폼 - DB 연동 (0) | 2020.06.18 |
3. 메뉴, 마우스, 입력 포커스, 키보드, 리스트 뷰, 트리 뷰, 프로그레스 바, 타이머, 그래픽 (0) | 2020.06.17 |