I want to refactor this code that is currently in a partial Form Class and deals with edits made on certain cells in a datagridview (in this case called dgvReplenish).
I would like to refactor so that I can create unit tests to be able to test the logic that effects changing the back color of cells, and updating other cells.
The problem I am having is how to refactor logic to an external class, that has references to the winform controls.
Form

All following code is part of public partial class ReplenForm : Form
DataGridView Event Handlers
private string lastEditedCellValue;
private void dgvReplenish_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
lastEditedCellValue = dgvReplenish.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
}
private void dgvReplenish_CellValidating(Object sender, DataGridViewCellValidatingEventArgs e)
{
if (e.ColumnIndex == (int)ProductColumnIndex.ReplenishAmount)
{
ValidateEditOfReplenishAmount(e);
}
}
private void dgvReplenish_CellClicked(object sender, DataGridViewCellEventArgs e)
{
PopulateSelectedLineDetails(e.RowIndex);
}
private void dgvReplenish_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
ChangeColorOfWarehouseShopName(e);
}
private void dgvReplenish_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == (int)ProductColumnIndex.ReplenishAmount){
OnReplenishAmountEdit(e.RowIndex, e.ColumnIndex);
AddEditedRowToEditedRowsDictionary(e.RowIndex, e.ColumnIndex);
}
ChangeAvailableStockOnEditOfReplenishAmount(e.RowIndex, e.ColumnIndex);
}
Cell Edit functions
/// <summary>
/// When Replenish Amount is edited change back colour of cell and update database
/// </summary>
/// <param name="e">DataGridView Cell Arguments object</param>
private void OnReplenishAmountEdit(int rowIndex, int columnIndex)
{
var editedReplenishAmount = GetColumnValueForRow(
rowIndex,
ProductColumnIndex.ReplenishAmount.ToString()
);
var originalReplenishAmount = GetColumnValueForRow(
rowIndex,
ProductColumnIndex.OriginalReplenishAmount.ToString()
);
if (editedReplenishAmount != originalReplenishAmount)
{
GridViewHelper.ChangeBackColorOfCellOnEdit(rowIndex, columnIndex, dgvReplenish);
// UpdateDatabase();
}
else
{
GridViewHelper.RevertBackColorOfCellOnEdit(rowIndex, columnIndex, dgvReplenish);
}
}
/// <summary>
/// Checks if the amount entered for Replenish Amount is a number,
/// is less/equal to the free stock for that part. If so clears error messages,
/// if not adds error message to status bar
/// </summary>
/// <param name="e">DataGridView Cell Arguments object</param>
private void ValidateEditOfReplenishAmount(DataGridViewCellValidatingEventArgs e)
{
if (!string.IsNullOrEmpty(lastEditedCellValue))
{
Int32 result;
bool valueIsAnInteger = Int32.TryParse(e.FormattedValue.ToString(), out result);
if (valueIsAnInteger)
{
ValidateCellEdit(e.RowIndex, int.Parse(e.FormattedValue.ToString()));
}
else
{
//Value Entered Is A Non Number
ResetCellAndAddErrorMessage("A non number has been entered as the Replenish Amount");
}
}
}
/// <summary>
/// Check if new cell value added to existing replenish amount total
/// for that row's part is less or equal to that parts available stock.
/// if not undo edit and return error
/// </summary>
/// <param name="rowIndex">Row Number</param>
/// <param name="newValue">Edited Cells new value</param>
private void ValidateCellEdit(int rowIndex, int newValue)
{
bool replenishTotalIsEqualToOrLessThanAvailableStock =
IsReplenishTotalEqualOrLessToAvailableStock(rowIndex);
if (replenishTotalIsEqualToOrLessThanAvailableStock)
{
lbStatusBar.ForeColor = Color.Black;
lbStatusBar.Text = string.Empty;
}
else
{
//Value Entered Is More Than Free Stock
ResetCellAndAddErrorMessage("Replenish Amount entered is more than the available free stock");
}
}
/// <summary>
/// Works out if the running total of Replenish amounts
/// is less or equal to the available stock for that part.
/// </summary>
/// <param name="rowIndex">Row Number</param>
/// <param name="newValue">Replenish amount value entered</param>
/// <returns>True or false</returns>
private bool IsReplenishTotalEqualOrLessToAvailableStock(int editedRowIndex)
{
var rows = GetRowsIndexAndValues(ProductColumnIndex.ReplenishAmount.ToString());
int replenishAmountTotal = ReplenishEditHelper.CalculateReplenishAmountTotal(editedRowIndex, rows);
var currentAvailableStock = GetColumnValueForRow(
editedRowIndex,
ProductColumnIndex.OriginalAvailableStock.ToString()
);
if (replenishAmountTotal <= currentAvailableStock)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Changes colour of cell if cell is in the ShopName column and
/// row has a StoreGrade type MailOrder to WareHouse
/// </summary>
/// <param name="e">DataGridView Cell Arguments object</param>
private void ChangeColorOfWarehouseShopName(DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == (int)ProductColumnIndex.ShopName)
{
var currentRowStoreGradeValue = GridViewHelper
.GetCellValueAsInteger(
dgvReplenish,
e.RowIndex,
ProductColumnIndex.StoreGradeEnum.ToString()
);
var storeIsWarehouse = currentRowStoreGradeValue == (int)StoreGradeType.Warehouse;
var storeIsMailOrder = currentRowStoreGradeValue == (int)StoreGradeType.MailOrder;
if (storeIsWarehouse || storeIsMailOrder)
{
e.CellStyle.BackColor = Color.LightBlue;
}
}
}
/// <summary>
/// Updates the Available Stock column for all rows that have the same Part
/// </summary>
/// <param name="editedRowIndex">Row Number</param>
/// <param name="columnIndex">Column Number</param>
private void ChangeAvailableStockOnEditOfReplenishAmount(int editedRowIndex, int columnIndex)
{
if (columnIndex == (int)ProductColumnIndex.ReplenishAmount)
{
var amountChangedBy = GetAmountTotalReplenishmentChangedBy(editedRowIndex);
foreach (DataGridViewRow row in dgvReplenish.Rows)
{
var currentRowPartValue = GetRowPartValue(row.Index);
var editedRowPartValue = GetRowPartValue(editedRowIndex);
if (currentRowPartValue == editedRowPartValue)
{
var currentAvailableStock = GetColumnValueForRow(
editedRowIndex,
ProductColumnIndex.OriginalAvailableStock.ToString()
);
row.Cells[(int)ProductColumnIndex.AvailableStock]
.Value = currentAvailableStock - amountChangedBy;
}
}
}
}
private int GetAmountTotalReplenishmentChangedBy(int rowIndex)
{
var rows = GetRowsIndexAndValues(ProductColumnIndex.ReplenishAmount.ToString());
var newTotalReplenishAmount = ReplenishEditHelper.CalculateReplenishAmountTotal(rowIndex, rows);
rows = GetRowsIndexAndValues(ProductColumnIndex.OriginalReplenishAmount.ToString());
var currentTotalReplenishAmount = ReplenishEditHelper.CalculateReplenishAmountTotal(rowIndex, rows);
var amountChangedBy = newTotalReplenishAmount - currentTotalReplenishAmount;
return amountChangedBy;
}
private Dictionary<int,Tuple<string, int>> GetRowsIndexAndValues(string columnName)
{
int numOfRows = dgvReplenish.Rows.Count;
Dictionary<int, Tuple<string, int>> rows = new Dictionary<int, Tuple<string, int>>();
for (int i = 0; i < numOfRows; i++)
{
int rowIndex = dgvReplenish.Rows[i].Index;
Tuple<string,int> rowValues = new Tuple<string,int>(
GetRowPartValue(rowIndex),
GridViewHelper
.GetCellEditedFormattedValueAsInteger(
dgvReplenish,
rowIndex,
columnName
)
);
rows[dgvReplenish.Rows[i].Index] = rowValues;
}
return rows;
}
/// <summary>
/// Sets all visible Replenish Amount Cells to the value of tbUpdateAll
/// </summary>
private void UpdateAllReplenishAmountCells()
{
var updateValue = tbUpdateAll.Text;
if (!string.IsNullOrEmpty(updateValue))
{
var columnIndex = (int)ProductColumnIndex.ReplenishAmount;
bool canUpdateAllRows = CanAllRowsBeUpdated(updateValue, columnIndex);
if (canUpdateAllRows)
{
foreach (DataGridViewRow row in dgvReplenish.Rows)
{
row.Cells[columnIndex].Value = updateValue;
}
}
}
}
/// <summary>
/// Loops through rows visible in grid and checks to see if there is available
/// stock for each part to change each row's replenish amount to the passed value
/// </summary>
/// <param name="updateValue">Value for each cell to be updated to</param>
/// <param name="columnIndex">Column Number</param>
/// <returns>True or false</returns>
private bool CanAllRowsBeUpdated(string updateValue, int columnIndex)
{
foreach (DataGridViewRow row in dgvReplenish.Rows)
{
bool replenishTotalIsEqualToOrLessThanAvailableStock =
IsReplenishTotalEqualOrLessToAvailableStock(row.Index);
if (replenishTotalIsEqualToOrLessThanAvailableStock)
{
row.Cells[columnIndex].Value = updateValue;
}
else
{
StatusBarHelper.SetErrorMessage(
lbStatusBar,
"Cannot update all rows with amount as not enough available stock"
);
return false;
}
}
return true;
}
Helpers
/// <summary>
/// Resets Cell Value and adds an error message to status bar
/// </summary>
private void ResetCellAndAddErrorMessage(string message)
{
ResetEditedCell();
StatusBarHelper.SetErrorMessage(
lbStatusBar,
message
);
}
/// <summary>
/// Reset edited cell to the original value before edit
/// </summary>
private void ResetEditedCell()
{
if(dgvReplenish.IsCurrentCellInEditMode){
dgvReplenish.EditingControl.Text = lastEditedCellValue;
}
}
/// <summary>
/// Get the Part cell value for a particular row
/// </summary>
/// <param name="rowIndex">Row Number</param>
/// <returns>Part cell value</returns>
private string GetRowPartValue(int rowIndex)
{
return GridViewHelper
.GetCellValueAsString(
dgvReplenish,
rowIndex,
ProductColumnIndex.Part.ToString()
);
}
/// <summary>
/// Gets the column value as an integer for a given row and column
/// </summary>
/// <param name="rowIndex">Row Number</param>
/// <param name="columnName">Column Number</param>
/// <returns>Column Value</returns>
private int GetColumnValueForRow(int rowIndex, string columnName)
{
return GridViewHelper
.GetCellValueAsInteger(
dgvReplenish,
rowIndex,
columnName
);
}
/// <summary>
/// Gets the row ID for the row
/// </summary>
/// <param name="rowIndex">Row Number</param>
/// <returns>Row ID</returns>
private int GetRowID(int rowIndex)
{
var row = this.dgvReplenish.Rows[rowIndex];
return (row.Cells[(int)ProductColumnIndex.ID].Value == null) ? 0 :
(int)row.Cells[(int)ProductColumnIndex.ID].Value;
}
Databinding
private DataTable dataTable;
private ProductDataSet dataSet;
/// <summary>
/// Add datasource and set Column Display settings
/// </summary>
private void InitialiseDataGridView()
{
RefreshData();
if (dataSet.HasDataSetCreationFailed() == false)
{
dgvReplenish.AutoGenerateColumns = false;
//Make sure all cells autosize for width
dgvReplenish.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
// numeric columns need to be right aligned, and boolean ones centred
GridViewHelper.SetColumnContentAlignment(dgvReplenish);
// Set Display and edit settings for each column
SetColumnDisplaySettings();
dgvReplenish.Update();
isDataGridIntialised = true;
Add_dgReplenish_EventHandlers();
}
else
{
DataSetCreationHasFailed();
}
}
/// <summary>
/// Refreshes data for Gridview from source dataset
/// </summary>
private void RefreshData()
{
if (dataSet != null)
{
dataSet = null;
}
CreateDataSet();
if (dataSet.HasDataSetCreationFailed() == false)
{
SetGridDataSource();
}
else
{
DataSetCreationHasFailed();
}
}
/// <summary>
/// Assisgns refreshed DataTable to the DataGridView's datasource
/// </summary>
private void SetGridDataSource()
{
this.dataTable = dataSet.GetDataTable();
dgvReplenish.DataSource = this.dataTable;
}
/// <summary>
/// Sets a boolean that the dataset has failed to initialise
/// and adds error to status bar
/// </summary>
private void DataSetCreationHasFailed()
{
isDataGridIntialised = false;
StatusBarHelper.SetErrorMessage(lbStatusBar, dataSet.ErrorInDataSetCreation);
}
/// <summary>
/// Refreshes the dataset from its source data
/// </summary>
private void CreateDataSet()
{
// dataSet = new ProductDataSet(new DBRplenEditImport());
dataSet = new ProductDataSet(new CSVDataImport());
dataSet.SetDataList();
}
/// <summary>
/// For each column set read only, if to display on grid,
/// sortmode to not sortable and column header to use the
/// datacolumn caption rather than name
/// </summary>
private void SetColumnDisplaySettings()
{
for (var i = 0; i < dgvReplenish.Columns.Count; i++)
{
GridViewHelper.SetColumnToReadOnly(i, dgvReplenish);
GridViewHelper.SetColumnNoDisplayOnGrid(i, dgvReplenish);
dgvReplenish.Columns[i].SortMode = DataGridViewColumnSortMode.NotSortable;
// Use caption for header text rather than column name
// as Name uses the product property name which has no formatting
dgvReplenish.Columns[i].HeaderText = dataTable.Columns[dgvReplenish.Columns[i].HeaderText].Caption;
}
}
/// <summary>
/// Set up all the event handlers that will be used in the DataGridView
/// </summary>
private void Add_dgReplenish_EventHandlers()
{
dgvReplenish.CellBeginEdit -= new DataGridViewCellCancelEventHandler(dgvReplenish_CellBeginEdit);
dgvReplenish.CellBeginEdit += new DataGridViewCellCancelEventHandler(dgvReplenish_CellBeginEdit);
dgvReplenish.CellValidating -= new DataGridViewCellValidatingEventHandler(dgvReplenish_CellValidating);
dgvReplenish.CellValidating += new DataGridViewCellValidatingEventHandler(dgvReplenish_CellValidating);
dgvReplenish.CellValueChanged -= new DataGridViewCellEventHandler(dgvReplenish_CellValueChanged);
dgvReplenish.CellValueChanged += new DataGridViewCellEventHandler(dgvReplenish_CellValueChanged);
dgvReplenish.CellFormatting -= new DataGridViewCellFormattingEventHandler(dgvReplenish_CellFormatting);
dgvReplenish.CellFormatting += new DataGridViewCellFormattingEventHandler(dgvReplenish_CellFormatting);
dgvReplenish.CellClick -= new DataGridViewCellEventHandler(dgvReplenish_CellClicked);
dgvReplenish.CellClick += new DataGridViewCellEventHandler(dgvReplenish_CellClicked);
dgvReplenish.DataBindingComplete -= new DataGridViewBindingCompleteEventHandler(dgvReplenish_DataBindingComplete);
dgvReplenish.DataBindingComplete += new DataGridViewBindingCompleteEventHandler(dgvReplenish_DataBindingComplete);
}
