WinForms DataGridView 기본 설정
WinForms 애플리케이션에서 DataGridView 컨트롤을 효율적으로 사용하기 위해서는 초기 설정이 중요합니다. 다음은 일반적인 권장 설정입니다.
- 편집 활성화: 사용자가 셀 내용을 편집할 수 있도록 `EditMode`를 적절히 설정합니다. 기본적으로는 `EditOnEnter` 또는 `EditOnKeystrokeOrF2` 등이 사용됩니다.
- 열 정의: 각 열의 이름 (`Name`), 표시 텍스트 (`HeaderText`), 컨트롤 유형 (예: `DataGridViewTextBoxColumn`, `DataGridViewCheckBoxColumn`, `DataGridViewButtonColumn`), 그리고 가시성 (`Visible`)을 설정합니다. 데이터 표시 전용 열은 `ReadOnly` 속성을 `true`로 설정하는 것이 좋습니다. 체크박스나 버튼 열은 일반적으로 읽기 전용으로 설정하지 않습니다.
- 행 헤더 숨기기: 기본 행 헤더 (가장 왼쪽의 빈 열)가 필요 없는 경우, `RowHeadersVisible` 속성을 `false`로 설정하여 공간을 절약할 수 있습니다.
- 자동 행 추가/삭제 방지: 사용자가 직접 행을 추가하거나 삭제하는 것을 막으려면 `AllowUserToAddRows`와 `AllowUserToDeleteRows` 속성을 `false`로 설정합니다.
데이터 바인딩 및 초기화
DataGridView에 데이터를 동적으로 채우는 방법은 다양합니다. 여기서는 수동으로 행을 추가하고 데이터를 바인딩하는 예시를 보여줍니다. 데이터 소스는 실제 데이터베이스 조회 결과와 유사하게 C# 리스트를 사용합니다.
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Category { get; set; } // 예: "Electronics", "Food", "Drink"
}
// 폼 로드 시 DataGridView 초기화 및 데이터 채우기
private void MainForm_Load(object sender, EventArgs e)
{
var products = new List<Product>
{
new Product { ProductId = 101, Name = "Laptop", Description = "High-performance laptop.", Category = "Electronics" },
new Product { ProductId = 102, Name = "Coffee Mug", Description = "Insulated travel mug.", Category = "Drink" },
new Product { ProductId = 103, Name = "Gaming Mouse", Description = "Ergonomic gaming mouse.", Category = "Electronics" }
};
// 기존 데이터 초기화
productDataGridView.Rows.Clear();
// 데이터 추가
foreach (var prod in products)
{
int rowIndex = productDataGridView.Rows.Add();
productDataGridView.Rows[rowIndex].Cells["colProductId"].Value = prod.ProductId;
productDataGridView.Rows[rowIndex].Cells["colProductName"].Value = prod.Name;
productDataGridView.Rows[rowIndex].Cells["colViewDetails"].Value = "상세 보기"; // 버튼 텍스트
productDataGridView.Rows[rowIndex].Tag = prod; // 전체 엔티티 객체를 Tag에 바인딩
// 특정 조건에 따라 버튼 비활성화 (예: 'Drink' 카테고리 제품)
if (prod.Category == "Drink")
{
productDataGridView.Rows[rowIndex].Cells["colViewDetails"] = new DataGridViewTextBoxCell(); // 텍스트 셀로 변경
productDataGridView.Rows[rowIndex].Cells["colViewDetails"].Value = "정보 없음";
productDataGridView.Rows[rowIndex].Cells["colViewDetails"].ReadOnly = true;
}
}
}
주의: `productDataGridView`는 디자이너에서 설정된 DataGridView 컨트롤의 이름입니다. 열 이름(`colProductId`, `colProductName`, `colViewDetails`)도 디자이너에서 미리 정의되어 있어야 합니다.
셀 이벤트 처리: 버튼 클릭 및 내용 가져오기
DataGridView 내의 버튼을 클릭하거나 특정 셀을 클릭했을 때 이벤트를 처리하는 방법입니다.
셀 내용 클릭 이벤트 (`CellContentClick`)
버튼, 체크박스 등 특정 콘텐츠를 포함하는 셀을 클릭했을 때 발생합니다. 예를 들어 '상세 보기' 버튼을 눌러 제품 설명을 팝업으로 보여주는 경우에 사용됩니다.
private void productDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
// 'colViewDetails' 열의 버튼이 클릭되었는지 확인 (버튼 열의 인덱스 확인)
if (e.ColumnIndex == productDataGridView.Columns["colViewDetails"].Index && e.RowIndex >= 0)
{
// Tag에 바인딩된 Product 엔티티 가져오기
var clickedProduct = productDataGridView.Rows[e.RowIndex].Tag as Product;
if (clickedProduct != null)
{
MessageBox.Show(clickedProduct.Description, clickedProduct.Name + " 상세 정보");
}
}
}
일반 셀 클릭 이벤트 (`CellClick`)
어떤 셀이든 클릭될 때 발생하며, `CellContentClick`보다 더 광범위하게 사용될 수 있습니다. 클릭된 셀의 텍스트를 가져오는 예시입니다.
private void productDataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0 && e.ColumnIndex >= 0)
{
var cellValue = productDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
if (cellValue != null)
{
Console.WriteLine($"클릭된 셀 내용: {cellValue}");
}
}
}
체크박스 기반 다중 선택 처리
DataGridView에 체크박스 열을 추가하여 여러 행을 선택하고 선택된 항목들을 처리하는 방법을 설명합니다.
'모두 선택' 체크박스 기능
별도의 체크박스 컨트롤이 체크되면 DataGridView의 모든 체크박스 열이 선택되도록 합니다.
// '모두 선택' 체크박스 컨트롤 (예: 'selectAllCheckBox')의 CheckedChanged 이벤트
private void selectAllCheckBox_CheckedChanged(object sender, EventArgs e)
{
if (selectAllCheckBox.Checked)
{
foreach (DataGridViewRow row in productDataGridView.Rows)
{
// 'colIsSelected'는 DataGridView에 정의된 체크박스 열의 이름
DataGridViewCheckBoxCell checkBoxCell = row.Cells["colIsSelected"] as DataGridViewCheckBoxCell;
if (checkBoxCell != null)
{
checkBoxCell.Value = true;
}
}
}
else
{
// 체크박스 해제 시 모든 체크박스 해제 (필요하다면 구현)
foreach (DataGridViewRow row in productDataGridView.Rows)
{
DataGridViewCheckBoxCell checkBoxCell = row.Cells["colIsSelected"] as DataGridViewCheckBoxCell;
if (checkBoxCell != null)
{
checkBoxCell.Value = false;
}
}
}
}
선택된 항목 목록 가져오기
버튼 클릭 시 `colIsSelected` 열이 체크된 모든 `Product` 엔티티를 리스트로 반환합니다.
// '선택 항목 보기' 버튼 (예: 'showSelectedButton') 클릭 이벤트
private void showSelectedButton_Click(object sender, EventArgs e)
{
var selectedProducts = new List<Product>();
foreach (DataGridViewRow row in productDataGridView.Rows)
{
DataGridViewCheckBoxCell checkBoxCell = row.Cells["colIsSelected"] as DataGridViewCheckBoxCell;
if (checkBoxCell != null && checkBoxCell.Value != null && (bool)checkBoxCell.Value)
{
// Tag에 바인딩된 Product 엔티티 가져오기
var productEntity = row.Tag as Product;
if (productEntity != null)
{
selectedProducts.Add(productEntity);
}
}
}
// 선택된 제품들을 활용하는 로직 (예: 메시지 박스 표시)
if (selectedProducts.Any())
{
string productNames = string.Join(", ", selectedProducts.Select(p => p.Name));
MessageBox.Show($"선택된 제품: {productNames}", "선택 항목");
}
else
{
MessageBox.Show("선택된 제품이 없습니다.", "알림");
}
}
선택된 행/셀의 엔티티 가져오기
사용자가 DataGridView의 특정 부분을 클릭하거나 선택했을 때 해당 행에 바인딩된 엔티티를 가져오는 방법입니다.
단일 행 선택 시 엔티티 가져오기
`SelectionMode`가 `FullRowSelect`로 설정되어 있고, 사용자가 한 행을 선택했을 때 해당 엔티티를 가져옵니다.
// 예: '선택된 행 정보' 버튼 (예: 'getSelectedRowButton') 클릭 이벤트
private void getSelectedRowButton_Click(object sender, EventArgs e)
{
if (productDataGridView.SelectedRows.Count == 1)
{
var selectedProduct = productDataGridView.SelectedRows[0].Tag as Product;
if (selectedProduct != null)
{
MessageBox.Show($"선택된 제품 ID: {selectedProduct.ProductId}, 이름: {selectedProduct.Name}", "선택된 행");
}
}
else if (productDataGridView.SelectedRows.Count > 1)
{
MessageBox.Show("여러 행이 선택되었습니다. 단일 행만 선택해주세요.", "경고");
}
else
{
MessageBox.Show("선택된 행이 없습니다.", "알림");
}
}
행 헤더 클릭 시 엔티티 가져오기
`RowHeadersVisible`이 `true`일 때, 사용자가 행 헤더를 클릭하면 해당 행의 엔티티를 가져옵니다.
private void productDataGridView_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex >= 0)
{
var clickedProduct = productDataGridView.Rows[e.RowIndex].Tag as Product;
if (clickedProduct != null)
{
MessageBox.Show($"행 헤더 클릭 - 제품 ID: {clickedProduct.ProductId}, 이름: {clickedProduct.Name}", "행 헤더");
}
}
}
DataGridView의 모든 데이터 엔티티 가져오기
DataGridView에 표시된 모든 데이터를 특정 엔티티 타입의 리스트로 추출하는 일반화된 메서드입니다.
/// <summary>
/// 지정된 DataGridView의 모든 행에 바인딩된 엔티티를 List<T>로 변환하여 반환합니다.
/// </summary>
/// <typeparam name="T">엔티티 타입</typeparam>
/// <param name="dataGridView">대상 DataGridView 컨트롤</param>
/// <returns>엔티티 리스트</returns>
private List<T> GetAllEntitiesFromGrid<T>(DataGridView dataGridView) where T : class
{
List<T> entityList = new List<T>();
foreach (DataGridViewRow row in dataGridView.Rows)
{
var item = row.Tag as T;
if (item != null)
{
entityList.Add(item);
}
}
return entityList;
}
사용 예시:
// DataGridView의 모든 Product 엔티티를 가져옵니다.
List<Product> allProducts = GetAllEntitiesFromGrid<Product>(productDataGridView);
// now you can iterate or process allProducts
DataGridView 외형 및 스타일 사용자 지정
DataGridView의 행과 열 헤더의 스타일을 변경하여 가독성을 높일 수 있습니다.
특정 행의 스타일 변경
조건에 따라 특정 행의 글꼴 색상이나 배경색을 변경할 수 있습니다.
// MainForm_Load 이벤트나 데이터를 채우는 시점에 적용
foreach (var prod in products)
{
int rowIndex = productDataGridView.Rows.Add();
// ... 셀 값 설정 ...
productDataGridView.Rows[rowIndex].Tag = prod;
if (prod.Category == "Electronics")
{
productDataGridView.Rows[rowIndex].DefaultCellStyle.ForeColor = Color.Blue; // 글꼴 색상
productDataGridView.Rows[rowIndex].DefaultCellStyle.BackColor = Color.LightCyan; // 배경 색상
}
}
열 헤더 정렬 및 정렬 기능 비활성화
열 헤더의 텍스트 정렬을 설정하고, DataGridView의 기본 정렬 기능을 비활성화할 수 있습니다. 정렬 버튼이 헤더에 나타나는 것을 방지합니다.
// 열 헤더 중앙 정렬
productDataGridView.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
// 특정 열의 정렬 기능 비활성화 (모든 열에 적용 가능)
// (예시: ProductId 열)
if (productDataGridView.Columns.Contains("colProductId"))
{
productDataGridView.Columns["colProductId"].SortMode = DataGridViewColumnSortMode.NotSortable;
}
// 모든 열에 대해 정렬 기능을 비활성화하려면:
foreach (DataGridViewColumn col in productDataGridView.Columns)
{
col.SortMode = DataGridViewColumnSortMode.NotSortable;
}
고급 기능: 제네릭 데이터 채우기 및 페이지네이션
제네릭 데이터 채우기 메서드 (리플렉션 활용)
어떤 타입의 엔티티 리스트라도 받아와 DataGridView에 동적으로 열을 생성하고 데이터를 채울 수 있는 일반화된 메서드입니다. 미리 DataGridView의 열을 정의할 필요가 없어 유연합니다.
/// <summary>
/// 제네릭 리스트 데이터를 DataGridView에 동적으로 채웁니다.
/// </summary>
/// <typeparam name="T">데이터 엔티티 타입</typeparam>
/// <param name="dataGridView">대상 DataGridView 컨트롤</param>
/// <param name="dataList">채울 데이터 리스트</param>
/// <param name="columnHeaders">표시할 열 헤더 텍스트 리스트 (순서 중요)</param>
/// <exception cref="Exception">열 헤더 개수 불일치 시 발생</exception>
private void FillGenericDataGridView<T>(DataGridView dataGridView, List<T> dataList, List<string> columnHeaders) where T : class
{
// DataGridView 기본 설정
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.ReadOnly = true;
dataGridView.RowHeadersVisible = false;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; // 열 너비 자동 조정
// 열 헤더 스타일 설정
dataGridView.ColumnHeadersDefaultCellStyle = new DataGridViewCellStyle
{
Alignment = DataGridViewContentAlignment.MiddleCenter,
BackColor = Color.LightSteelBlue,
ForeColor = Color.Black,
Font = new Font("Malgun Gothic", 10F, FontStyle.Bold),
};
// T 타입의 속성(필드) 정보 가져오기
var properties = typeof(T).GetProperties();
// 열 헤더와 속성 개수 일치 확인
if (columnHeaders.Count != properties.Length)
{
throw new Exception("열 헤더의 개수가 엔티티 속성의 개수와 일치해야 합니다!");
}
// DataGridView 열 생성
dataGridView.Columns.Clear();
for (int i = 0; i < columnHeaders.Count; i++)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn
{
HeaderText = columnHeaders[i], // 표시될 헤더 텍스트
Name = properties[i].Name, // 내부적으로 사용될 속성 이름
DefaultCellStyle = new DataGridViewCellStyle { Alignment = DataGridViewContentAlignment.MiddleCenter },
ReadOnly = true,
SortMode = DataGridViewColumnSortMode.NotSortable
});
}
// 데이터 행 채우기
dataGridView.Rows.Clear();
foreach (var item in dataList)
{
int rowIndex = dataGridView.Rows.Add();
for (int i = 0; i < properties.Length; i++)
{
var propName = properties[i].Name;
var propValue = properties[i].GetValue(item);
dataGridView.Rows[rowIndex].Cells[propName].Value = propValue;
}
dataGridView.Rows[rowIndex].Tag = item; // 엔티티 전체를 Tag에 바인딩
}
}
사용 예시:
// Product 엔티티를 사용하여 데이터 로드
private void LoadProductsButton_Click(object sender, EventArgs e)
{
var products = new List<Product>
{
new Product { ProductId = 201, Name = "Keyboard", Description = "Mechanical keyboard.", Category = "Electronics", Price = 80.00m },
new Product { ProductId = 202, Name = "Monitor", Description = "27-inch 4K display.", Category = "Electronics", Price = 350.50m },
new Product { ProductId = 203, Name = "Book", Description = "Science fiction novel.", Category = "Books", Price = 15.75m }
};
var headers = new List<string> { "제품 ID", "이름", "설명", "카테고리", "가격" }; // Product 클래스 속성 순서와 일치해야 함
FillGenericDataGridView(genericDataGridView, products, headers);
}
이 예시에서는 `Product` 클래스에 `Price` 속성이 추가되었다고 가정했습니다.
DataGridView 페이지네이션 (페이징)
많은 양의 데이터를 효율적으로 보여주기 위해 DataGridView에 페이지네이션 기능을 추가합니다.
private int currentPageIndex = 1; // 현재 페이지 번호
private int totalPages = 0; // 총 페이지 수
private const int pageSize = 10; // 한 페이지에 표시할 항목 수
// 전체 데이터 원본 (실제로는 DB에서 조회)
private List<Product> allProductData = new List<Product>();
// 페이지네이션 컨트롤 업데이트 및 현재 페이지 데이터 로드
private void LoadPagedData(DataGridView targetGrid, Label totalCountLabel, Label pageInfoLabel, Button prevButton, Button nextButton)
{
// (예시) 실제 데이터는 DB에서 가져오거나 미리 로드된 allProductData 사용
if (!allProductData.Any())
{
// 데이터가 없는 경우를 위한 더미 데이터 또는 실제 데이터 로드 로직
for (int i = 1; i <= 55; i++)
{
allProductData.Add(new Product { ProductId = i, Name = $"Product {i}", Description = $"Desc {i}", Category = (i % 2 == 0) ? "Even" : "Odd" });
}
}
int totalItems = allProductData.Count;
totalPages = (int)Math.Ceiling((double)totalItems / pageSize);
// 현재 페이지 범위의 데이터 추출
var pagedList = allProductData
.Skip((currentPageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
// DataGridView에 데이터 채우기 (FillGenericDataGridView 활용)
var headers = new List<string> { "제품 ID", "이름", "설명", "카테고리" };
FillGenericDataGridView(targetGrid, pagedList, headers);
// 페이지 정보 및 버튼 상태 업데이트
totalCountLabel.Text = $"총 {totalItems}개 항목";
pageInfoLabel.Text = $"현재 {currentPageIndex} / {totalPages} 페이지";
prevButton.Enabled = (currentPageIndex > 1);
nextButton.Enabled = (currentPageIndex < totalPages);
}
// '이전 페이지' 버튼 클릭 이벤트
private void prevPageButton_Click(object sender, EventArgs e)
{
if (currentPageIndex > 1)
{
currentPageIndex--;
LoadPagedData(pagedDataGridView, totalCountLabel, pageInfoLabel, prevPageButton, nextPageButton);
}
}
// '다음 페이지' 버튼 클릭 이벤트
private void nextPageButton_Click(object sender, EventArgs e)
{
if (currentPageIndex < totalPages)
{
currentPageIndex++;
LoadPagedData(pagedDataGridView, totalCountLabel, pageInfoLabel, prevPageButton, nextPageButton);
}
}
// 초기 로드 시 페이지 데이터 호출 (예시)
private void PaginationForm_Load(object sender, EventArgs e)
{
LoadPagedData(pagedDataGridView, totalCountLabel, pageInfoLabel, prevPageButton, nextPageButton);
}
위 코드에서 `pagedDataGridView`, `totalCountLabel`, `pageInfoLabel`, `prevPageButton`, `nextPageButton`은 각각 DataGridView 컨트롤 및 페이지네이션을 위한 UI 컨트롤의 이름입니다.
단일 엔티티 편집 양식 DataGridView
단일 엔티티의 속성을 DataGridView를 사용하여 '필드'와 '값' 두 개의 열로 구성된 형태로 표시하고 편집할 수 있도록 합니다. 이는 속성 수가 많거나 동적으로 필드를 표시해야 할 때 유용합니다.
/// <summary>
/// 단일 엔티티의 속성을 DataGridView에 '필드 이름'과 '값' 형태로 표시합니다.
/// </summary>
/// <typeparam name="T">엔티티 타입</typeparam>
/// <param name="entity">표시할 엔티티 객체</param>
/// <param name="dataGridView">대상 DataGridView 컨트롤</param>
/// <param name="fieldDisplayMap">엔티티 속성 이름과 표시될 한글 이름의 매핑 딕셔너리</param>
private void DisplayEntityForEditing<T>(T entity, DataGridView dataGridView, Dictionary<string, string> fieldDisplayMap) where T : class
{
dataGridView.Rows.Clear();
dataGridView.Columns.Clear();
// '필드'와 '내용' 열 추가
dataGridView.Columns.Add("FieldColumn", "필드");
dataGridView.Columns.Add("ContentColumn", "내용");
// DataGridView 속성 설정
dataGridView.EditMode = DataGridViewEditMode.EditOnEnter;
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
Type entityType = typeof(T);
foreach (var entry in fieldDisplayMap)
{
string propertyName = entry.Key;
string displayFieldName = entry.Value;
var property = entityType.GetProperty(propertyName);
if (property != null)
{
object propertyValue = property.GetValue(entity);
int rowIndex = dataGridView.Rows.Add(displayFieldName, propertyValue?.ToString());
dataGridView.Rows[rowIndex].Cells["FieldColumn"].ReadOnly = true; // 필드 이름은 수정 불가
}
}
dataGridView.Tag = entity; // 원본 엔티티를 Tag에 저장
}
/// <summary>
/// '필드'와 '내용' 형태로 표시된 DataGridView에서 편집된 내용을 가져와 새 엔티티 객체를 생성합니다.
/// </summary>
/// <typeparam name="T">엔티티 타입</typeparam>
/// <param name="dataGridView">대상 DataGridView 컨트롤</param>
/// <param name="fieldDisplayMap">엔티티 속성 이름과 표시될 한글 이름의 매핑 딕셔너리</param>
/// <returns>편집된 내용으로 채워진 새 엔티티 객체</returns>
private T RetrieveEntityFromEditingGrid<T>(DataGridView dataGridView, Dictionary<string, string> fieldDisplayMap) where T : class
{
T newEntity = Activator.CreateInstance<T>();
Type entityType = typeof(T);
foreach (DataGridViewRow row in dataGridView.Rows)
{
string displayFieldName = row.Cells["FieldColumn"].Value?.ToString();
string editedValue = row.Cells["ContentColumn"].Value?.ToString();
// 한글 필드명에 해당하는 원본 속성명 찾기
string propertyName = fieldDisplayMap.FirstOrDefault(x => x.Value == displayFieldName).Key;
if (!string.IsNullOrEmpty(propertyName))
{
var property = entityType.GetProperty(propertyName);
if (property != null)
{
try
{
// 데이터 타입에 맞게 값 변환 후 할당
object convertedValue = Convert.ChangeType(editedValue, property.PropertyType);
property.SetValue(newEntity, convertedValue);
}
catch (FormatException)
{
MessageBox.Show($"'{displayFieldName}' 필드의 값이 올바른 형식이 아닙니다.", "데이터 형식 오류");
return null; // 또는 예외 처리
}
catch (InvalidCastException)
{
MessageBox.Show($"'{displayFieldName}' 필드의 값을 {property.PropertyType.Name} 타입으로 변환할 수 없습니다.", "타입 변환 오류");
return null;
}
}
}
}
return newEntity;
}
사용 예시:
// 편집할 Product 객체 (예시)
Product productToEdit = new Product
{
ProductId = 101,
Name = "Original Laptop",
Description = "Powerful computing machine.",
Category = "Electronics",
Price = 1200.00m
};
// 속성명과 표시될 필드 이름 매핑 딕셔너리
Dictionary<string, string> productFieldMap = new Dictionary<string, string>
{
{ "ProductId", "제품 ID" },
{ "Name", "제품명" },
{ "Description", "설명" },
{ "Category", "카테고리" },
{ "Price", "가격" }
};
// DataGridView에 엔티티 정보 표시
private void ShowEditForm_Click(object sender, EventArgs e)
{
DisplayEntityForEditing(productToEdit, editProductDataGridView, productFieldMap);
}
// DataGridView에서 편집된 내용 가져오기
private void SaveEditedProduct_Click(object sender, EventArgs e)
{
Product updatedProduct = RetrieveEntityFromEditingGrid<Product>(editProductDataGridView, productFieldMap);
if (updatedProduct != null)
{
MessageBox.Show($"편집된 제품: ID={updatedProduct.ProductId}, 이름={updatedProduct.Name}, 가격={updatedProduct.Price}", "저장 완료");
// updatedProduct 객체를 사용하여 DB 업데이트 등의 로직 수행
}
}
이 방법을 사용하면 고정된 폼 대신 DataGridView를 통해 유연한 엔티티 편집 인터페이스를 제공할 수 있습니다.