본문 바로가기

C#/WinForm

[C# WinForm] 실시간 편의점 물품 가격변동에 따른 그래프 그리기 (Client)_(2) - 짱우의 코딩일기 - 티스토리

반응형

  우선 이 글은 제목 그대로 타이머를 이용해 실시간으로 DB에 있는 데이터를 가져와 그래프를 그리는 것에 대한 글이다. 하지만 간단한 예제를 이용한게 아니라 개인 프로젝트를 진행했던 코드를 바탕으로 사용했기 때문에 밑의 링크에서 어떤 프로젝트인지 확인하고 글을 읽는 것을 추천한다.


2020/01/09 - [개인 프로젝트] C# WinForm을 이용한 실시간 편의점 물품 가격변동에 따른 그래프 그리기 - 짱우의 코딩일기 - 티스토리

 

[개인 프로젝트] C# WinForm을 이용한 실시간 편의점 물품 가격변동에 따른 그래프 그리기 - 짱우의 코딩일기 - 티스토리

물론 지금도 인턴이지만 회사에서 인턴기간동안 만든 과제중 하나에 대한 글을 써보려 한다. 우선 제목대로 프로젝트에서 사용한 데이터는 실제 편의점 물품의 가격 변동 데이터를 사용하지는 않았다. 서버에서..

zzangwoo.tistory.com

2020/01/11 - [C# WinForm] 실시간 편의점 물품 가격변동에 따른 그래프 그리기 (Client)_(1) - 짱우의 코딩일기 - 티스토리

 

[C# WinForm] 실시간 편의점 물품 가격변동에 따른 그래프 그리기 (Client)_(1) - 짱우의 코딩일기 - 티스토리

우선 이 글은 제목 그대로 타이머를 이용해 실시간으로 DB에 있는 데이터를 가져와 그래프를 그리는 것에 대한 글이다. 하지만 간단한 예제를 이용한게 아니라 개인 프로젝트를 진행했던 코드를 바탕으로 사용했기..

zzangwoo.tistory.com


  Client Form에 대해서 글을 쓰려는데 생각보다 양이 많아져서 글을 두 개로 나눠서 쓰려고 한다.


Client 기능

< Client Form >

1. 자동 새로고침 버튼

  위에서 '자동 새로고침' 버튼을 누르면 5초에 한 번씩 자동으로 DB에서 가격 변동 데이터들을 가져온다.

a. Main Form

/// <summary>
/// 자동 새로고침 버튼 메서드
/// 누르면 Timer가 작동하면서 자동으로 DB에서 값을 받을 수 있게 해준다
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void autoButton_Click(object sender, EventArgs e)
{
  if (autoButton.Text == "자동 새로고침")
  {
    autoButton.Text = "중지";

    timer.Interval = 5000;
    timer.Tick += new EventHandler(autoUpdate);
    timer.Start();
  }
  else if (autoButton.Text == "중지")
  {
    autoButton.Text = "자동 새로고침";

    timer.Stop();
  }
}

  '자동 새로고침' 버튼을 누르면 버튼 텍스트는 '중지'로 바뀌게 되고 5초에 한 번씩 autoUpdate 메서드가 실행되게 만들어 놓았다. 그리고 '중지' 상태인 버튼을 누르게 되면 다시 원래대로 버튼 텍스트는 '자동 새로고침'으로 바뀌게 되고 타이머는 멈추게 된다.

/// <summary>
/// Timer에서 사용할 메서드
/// 핸들러 연결해주고 DB에서 데이터를 받아올 수 있게 DAO를 호출한다
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void autoUpdate(object sender, EventArgs e)
{
  DAO dao = new DAO();
  dao.UpdateProdPrice += new DAO.UpdateProdPriceHandler(UpdateProdPrice);
  dao.RowChartClear += new DAO.RowClearHandler(RowChartClear);
  dao.UpdateChart += new DAO.UpdateChartHandler(UpdateChart);

  RowChartClear();
  dao.GetProdPrice(isType);	

  string[] dateArr = dao.GetDates();
  firstDate = dateArr[0];
  lastDate = dateArr[1];

  UpdateDate(firstDate, lastDate);
  
  ... 생략 ...
}

  자동 새로고침할 때 호출되는 autoUpdate 메서드에서 다른 기능을 위한 코드가 있어서 지금은 지워둔 상태다. 나머지 코드는 Client Form이 로드될 때 실행되는 코드와 동일하기 때문에 DAO 클래스에 대해서는 설명을 생략하도록 한다. 그리고 'dateArr' 배열같은 경우에는 위의 그림 5번 영역에 검색가능한 DB 갱신 시작날짜와 마지막 날짜를 넣기 위해서 날짜를 저장해주는 역할을 해준다.

 

2. DataGridView

  가격 변동 최신날짜 기준으로 한 달간의 데이터를 가져온다. 그리고 새로고침이 되면 DataGridView의 밑에 부분을 보고있는데도 스크롤이 맨 위로 올라가는 현상이 있었다. 내가 사용자였으면 불편할 것 같다는 생각에 새로고침이 되도 DataGridView의 스크롤이 맨 위로 올라가는 것이 아니고 사용자가 현재 보고있는 데이터 값이 있는 위치에 머물도록 만들었다.

a. DataGridView 초기화

/// <summary>
/// Chart, DataGridView 초기화해주는 메서드
/// </summary>
private void RowChartClear()
{
  // DataGridView가 비어있지 않으면 Clear
  if (prodPriceDataGridView.Rows.Count != 0)
  {
    prodPriceDataGridView.Columns.Clear();
    prodPriceDataGridView.Rows.Clear();
    prodPriceDataGridView.Refresh();
  }
  
  ... 생략 ...
}

  DataGridView에 있는 Column에 대한 데이터와 Row에 대한 데이터를 초기화 시켜주는 메서드다. 물론 Chart를 초기화시켜주는 코드도 있었지만 지금은 DataGridView에 대한 설명 부분이기 때문에 제외했다. 

b. DataGridView 값 넣기

/// <summary>
/// DataGridView 레코드 값 업데이트 해주는 메서드
/// DataGridView에 접근하기 위해서 DAO와 Delegate로 묶어주었다
/// </summary>
private void UpdateProdPrice(DateTime updatedDate, long[] prodArr)
{
  // 컬럼명과 물품 가격을 동적으로 업데이트해주기위한 for문
  // prodArr (물품 가격)에 0값이 있으면 List에 저장해주지 않는다
  List<string> prodPriceList = new List<string>();
  for (int i = 0; i < prodArr.Length; i++)
  {
    if (prodArr[i] != 0)
  	  prodPriceList.Add(prodArr[i].ToString());
  }

  // DataGridView의 Row에 값 집어넣기 위한 string 배열 생성
  string[] rowData = new string[prodPriceList.Count + 1];
  rowData[0] = string.Format("{0:yyyy-MM-dd}", updatedDate);

  for (int i = 0; i < prodPriceList.Count; i++)
  {
 	 rowData[i + 1] = prodPriceList[i];
  }

  SetupDataGridView(); // 컬럼 값 업데이트
  prodPriceDataGridView.Rows.Add(rowData);
}

  위의 'UpdateProdPrice' 메서드를 이용해서 DataGridView에 데이터를 집어넣는다. 자세한 코드 설명은 글 맨위에 링크 걸어놓은 '2020/01/11 - [C# WinForm] 실시간 편의점 물품 가격변동에 따른 그래프 그리기 (Client)_(1) - 짱우의 코딩일기 - 티스토리' 글에 설명해두었다.

c. 새로고침 되도 사용자가 현재 보고있는 DataGridView 위치 고정

// 현재 보고있는 Row Index 담아주는 변수
int currentRowIndex;

// 현재 보고있는 Row의 UpdateDate 담아주는 변수
string currentRowUpdateDate;

  현재 사용자가 보고있는 DataGridView의 맨 위에 있는 데이터에 대한 Row Index와 그 Row Index에 있는 갱신날짜에 대한 값을 담아줄 변수들을 전역변수로 선언해두었다.

private void Form1_Load(object sender, EventArgs e)
{
  ... 생략 ...

  // 초기 DataGridView 위치 저장
  currentRowIndex = 0;
  currentRowUpdateDate = prodPriceDataGridView[0, currentRowIndex].Value.ToString();

  // DataGridView 스크롤 핸들러
  prodPriceDataGridView.Scroll += new ScrollEventHandler(GridScroll);
}

  처음 Client Form이 로드될 때에는 DataGridView에서 맨 위에 보여지는 Row Index가 0이기 때문에 0을 저장시켰고 Index가 0일 때 갱신 날짜를 'currentRowUpdateDate' 변수에 저장시켰다.

  그리고 DataGridView에 스크롤 핸들러를 추가시켜 DataGridView의 스크롤이 움직일 때마다 'GirdScroll' 메서드가 실행되게 만들었다.

/// <summary>
/// 스크롤바가 움직이면 발생하는 EVENT
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GridScroll(object sender, ScrollEventArgs e)
{
  // 다음 변수에 현재 보고있는 데이터들 중 맨위에 있는 데이터의 Index와 해당 Index의 갱신날짜 값을 입력
  currentRowIndex = prodPriceDataGridView.FirstDisplayedCell.RowIndex;
  currentRowUpdateDate = prodPriceDataGridView[0, currentRowIndex].Value.ToString();
}

  위의 메서드는 스크롤이 움직일 때마다 그 때 DataGridView의 맨 위에 보여지는 Row의 Index와 갱신날짜를 'currentRowIndex', 'currentRowUpdateDate' 변수에 저장시켜주는 역할을 한다.

/// <summary>
/// Timer에서 사용할 메서드
/// 핸들러 연결해주고 DB에서 데이터를 받아올 수 있게 DAO를 호출한다
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void autoUpdate(object sender, EventArgs e)
{

	... 생략 ...

  // 해당 Cell 위치로 이동시키기 위한 변수
  int goIndex = 0;

  // 현재 보고 있는 데이터들의 제일 위의 값을 DataGridView 안의 데이터들과 비교
  for (int i = 0; i < prodPriceDataGridView.RowCount; i++)
  {
  	if (prodPriceDataGridView[0, i].Value.Equals(currentRowUpdateDate))
 	 {
  		goIndex = i;
 		 break;
  	 }
  }

  // 스크롤을 내리지 않은 상태라면 최신 데이터가 맨위로
  if (currentRowIndex == 0)
 	 prodPriceDataGridView.FirstDisplayedCell = prodPriceDataGridView.Rows[0].Cells[0];
  // 스크롤을 내린 상태라면 지금 보고 있는 데이터중 제일 위의 데이터가 보이게
  else
 	 prodPriceDataGridView.FirstDisplayedCell = prodPriceDataGridView.Rows[goIndex].Cells[0];
}

  위에서 스크롤이 움직일 때마다 전역변수에 저장시켜둔 값들을 이용해서 새로고침이 되도 DataGridView의 스크롤이 사용자가 현재 보고 있는 위치에 머물도록 'autoUpdate' 메서드에서 구현해놓았다.

  우선 스크롤을 마지막으로 움직였을 때 DataGridView의 맨 위의 데이터들이 있던 Index 값과 새로 DB에서 값이 들어와 새로고침이 되었을 때 아까 맨 위에 있던 데이터들이 있던 Index 값은 다를 것이다. 새로고침이 되면서 데이터가 한 줄 더 들어와 Index 값이 하나씩 커지기 때문이다. 그래서 새로고침된 후 사용자가 보고 있었던 Row Index를 찾기 위해 for문을 이용해서 찾고 Index값을 goIndex 변수에 저장시킨다. Index를 찾는 기준은 'currentRowUpdateDate' 변수다. DataGridView에서 갱신 날짜는 중복되지 않는 값이기 때문에 Index를 찾는 기준으로 충분하다 생각했다. 

  새로고침이 된 후에 새로고침이 되기 전 보고있었던 데이터의 Index값을 찾았으면 그 위치로 Cell을 이동시켜야 한다. 이동시키기 위해서는 '[DataGridView 변수명].FirstDisplayedCell = [DataGridView 변수명].Rows[해당위치Index].Cells[0]' 코드를 이용하면 된다. 그리고 사용자가 스크롤을 내리지 않은 상태에서 위의 코드가 작동이 되면 스크롤은 자꾸 밑으로 내려가게 된다. 그렇게 되면 사용자는 새로운 데이터를 확인하려면 스크롤을 다시 위로 올려야 하는 불편함이 생긴다. 

  그렇기 때문에 사용자가 스크롤을 내리지 않은 상태라면 최신 데이터가 맨 위로 올 수있게 위와 같이 Index를 0을 집어 넣었고 그렇지 않은 경우에는 for문에서 구한 goIndex로 스크롤을 위치하도록 기능을 구현했다.

< 새로고침이 되도 스크롤 변화가 없는 화면 >

 

3. Chart

  가격 변동 최신날짜 기준으로 한 달간의 데이터를 꺾은선 그래프로 나타내준다.

a. Chart 초기화

/// <summary>
/// Chart, DataGridView 초기화해주는 메서드
/// </summary>
private void RowChartClear()
{

	... 생략 ...

  for (int i = 0; i < 9; i++)
  {
 	 priceChart.Series[i].Points.Clear();
  }
}

  위에서 생략된 부분은 DataGridView를 초기화 해주는 코드가 생략되지 않은 부분이 Chart를 초기화 해주는 부분이다. 초기화를 해주지 않으면 전에 그려진 그래프 위에 새로 그래프가 덮어서 그려지기 때문에 원하는 모양의 그래프를 얻을 수 없다. 

b. Chart 그려주기

 private void UpdateChart(DateTime updatedDate, long A1, long A2, long A3, long B1, long B2, long B3,
 long C1, long C2, long C3)
 {
   long[] priceArr = new long[9] { A1, A2, A3, B1, B2, B3, C1, C2, C3 };

  for (int i = 0; i < 9; i++)
  {
    priceChart.Series[i].Points.AddXY(updatedDate, priceArr[i]);
  }
}

  DAO 클래스에서 갱신날짜와 가격값들을 받아와서 x축 값은 updateDate(갱신날짜), y축 값은 각각 물품들의 가격변동 값들을 기준으로 Chart에 값을 넣어준다.

 

4. CheckBox

  체크된 물품 종류의 데이터들만 확인할 수 있다.

< 맥주, 라면의 데이터만 확인한 화면 >

/// <summary>
/// 과자 CheckBox가 눌렸을 때 EVENT
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void snackCheckBox_CheckedChanged(object sender, EventArgs e)
{
  isType["과자"] = snackCheckBox.Checked;


  if (snackCheckBox.Checked)
  {
    priceChart.Series[3].Enabled = true;
    priceChart.Series[4].Enabled = true;
    priceChart.Series[5].Enabled = true;

    boolColumnArr[4] = true;
    boolColumnArr[5] = true;
    boolColumnArr[6] = true;
  }
  else
  {
    priceChart.Series[3].Enabled = false;
    priceChart.Series[4].Enabled = false;
    priceChart.Series[5].Enabled = false;

    boolColumnArr[4] = false;
    boolColumnArr[5] = false;
    boolColumnArr[6] = false;
}

}

  위의 화면처럼 과자 CheckBox의 체크를 풀면 위의 메서드가 실행이 된다. CheckBox의 상태가 달라지면 isType 변수 (Dictionary로 선언)에 현재 체크 여부가 달라진 CheckBox의 Text에 해당하는 Value 값을 바꿔준다. 바꿔줌으로써 DB에서 원하는 데이터 값들만 가져오기 위해서다.

  그리고 CheckBox의 체크 여부를 판별해 체크된 상태면 차트를 보여주게 만들었고 (Chart의 Series 부분의 Enabled를 true로 바꿔줌) boolColumnArr 배열 변수에 true 값을 집어넣음으로써 DataGridView에서도 데이터 값이 보여질 수 있게 기능을 구현해놓았다.

  물론 CheckBox의 체크가 해제된 상태면 코드는 else문으로 넘어가게 되고 모든것이 false로 바뀌면서 데이터값들을 확인할 수 없는 상태가 된다.

 

5. 조회버튼

/// <summary>
/// 조회버튼 눌렀을 때의 EVENT
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void findButton_Click(object sender, EventArgs e)
{
  if (string.IsNullOrEmpty(insertFirstDateTextBox.Text))
  {
    MessageBox.Show("시작날짜를 입력해주세요");
  }
  else if (string.IsNullOrEmpty(insertLastDateTextBox.Text))
  {
    MessageBox.Show("마지막날짜를 입력해주세요");
  }
  // yyyy-MM-dd에 대한 날짜 정규식
  else if (!Regex.IsMatch(insertFirstDateTextBox.Text, @"^(20)\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$"))
  {
    MessageBox.Show("시작날짜 형식을 맞춰주세요");
  }
  else if (!Regex.IsMatch(insertLastDateTextBox.Text, @"^(20)\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$"))
  {
    MessageBox.Show("마지막날짜 형식을 맞춰주세요");
  }
  else
  {
    firstDate_Form = insertFirstDateTextBox.Text;
    lastDate_Form = insertLastDateTextBox.Text;

    FindForm findForm = new FindForm();
    findForm.Text = firstDate_Form + " ~ " + lastDate_Form + " 까지의 가격 변동 데이터 조회";
    findForm.Show(this);
  }
}

  위의 메서드는 조회버튼을 눌렀을 때의 Event를 구현해놓은 메서드다. 처음날짜와 마지막날짜를 입력하는 TextBox에 대한 예외처리를 해놓았고 모두 만족할 경우 조회 Form을 활성화시켜서 특정날짜에 대한 데이터를 조회할 수 있게 기능을 구현해놓았다. 물론 새로 켜지는 Form은 Form.Show() 메서드를 이용해서 새로운 Form이 켜지더라도 메인 Form에서는 새로고침이 일어난 것을 확인할 수 있게 만들어놓았다.

< 특정날짜 입력 후 조회버튼을 눌렀을 때의 화면 >

  특정날짜를 입력한 후에 조회 버튼을 누르게 되면 위와 같이 새로운 Form이 켜지면서 특정날짜의 데이터값들을 확인할 수 있다. 코드같은 경우에는 Main Form에서 실시간으로 DB에서 값을 받아오는 코드를 제외하고 나머지가 모두 동일하기 때문에 생략하도록 한다.

반응형