When I do a character search in the DataGridView, I want only the searched specific characters to be selected (I give it a red box), without selecting all characters in cell. How do I do it?
I wrote code, and the result looks like this:
Output expected:
I want only the searched specific character to be selected (I give it a red box), without selecting all characters in cell
private void AddCustomer_DataGridView_CellFormatting(object? sender, DataGridViewCellFormattingEventArgs e)
{
try
{
if (e.RowIndex >= 0 && e.ColumnIndex >= 0)
{
if (!String.IsNullOrEmpty(AddCustomer_SearchTextBox.Text) && e.Value != null)
{
string strValue = (String)e.Value;
if (strValue.Contains(AddCustomer_SearchTextBox.Text))
{
DataGridViewCellStyle? cellStyle = e.CellStyle;
if (cellStyle != null)
{
// Problem ini here, how to select only specific characters in a cell (not all characters)
cellStyle.BackColor = ColorTranslator.FromHtml("#0078D7");
cellStyle.ForeColor = ColorTranslator.FromHtml("#FFFFFF");
}
}
}
}
}
catch (Exception ex)
{
ExLogger.LogException(ex, "");
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// It works normally
public List<Customer>? SearchCustomers(int count, int minOffset, int maxOffset, string keyword, string sortExpression = "ASC")
{
if (count <= 0)
{
return new List<Customer>();
}
int limit = 1 + (maxOffset - minOffset);
if (limit < 0)
{
limit = 0;
}
int offset = minOffset - 1; // start
if (offset < 0)
{
offset = 0;
}
if (offset >= count)
{
offset = count - 1;
}
string sql = @"SELECT customer.ID, customer.created, customer.name, customer.place_of_birth, customer.date_of_birth, customer.gender_id, customer.address, customer.neighbourhood_hamlet, customer.urban_village, customer.subdistrict, customer.religion_id, customer.marital_status_id, customer.profession, customer.citizenship_id, customer.email, customer.phone_number, customer.send_me
FROM customer
INNER JOIN gender ON gender_id = gender.ID
INNER JOIN religion ON religion_id = religion.ID
INNER JOIN marital_status ON marital_status_id = marital_status.ID
INNER JOIN citizenship ON citizenship_id = citizenship.ID
WHERE customer.ID LIKE @ID OR
customer.created LIKE @created OR
customer.name LIKE @name OR
customer.place_of_birth LIKE @place_of_birth OR
customer.date_of_birth LIKE @date_of_birth OR
gender.gender_name LIKE @gender_id OR
customer.address LIKE @address OR
customer.neighbourhood_hamlet LIKE @neighbourhood_hamlet OR
customer.urban_village LIKE @urban_village OR
customer.subdistrict LIKE @subdistrict OR
religion.religion_name LIKE @religion_id OR
marital_status.marital_name LIKE @marital_status_id OR
customer.profession LIKE @profession OR
citizenship.citizenship_name LIKE @citizenship_id OR
customer.email LIKE @email OR
customer.phone_number LIKE @phone_number OR
customer.send_me LIKE @send_me
ORDER BY STR_TO_DATE(customer.created, '%d/%m/%Y %H:%i:%s') " + sortExpression + " LIMIT " + limit + " OFFSET " + offset;
object[] parms = { "@ID", '%'+ keyword + '%',
"@created", '%'+ keyword + '%',
"@name", '%'+ keyword + '%',
"@place_of_birth", '%'+ keyword + '%',
"@date_of_birth", '%'+ keyword + '%',
"@gender_id", '%'+ keyword + '%',
"@address", '%'+ keyword + '%',
"@neighbourhood_hamlet", '%'+ keyword + '%',
"@urban_village", '%'+ keyword + '%',
"@subdistrict", '%'+ keyword + '%',
"@religion_id", '%'+ keyword + '%',
"@marital_status_id", '%'+ keyword + '%',
"@profession", '%'+ keyword + '%',
"@citizenship_id", '%'+ keyword + '%',
"@email", '%'+ keyword + '%',
"@phone_number", '%'+ keyword + '%',
"@send_me", '%'+ keyword + '%'
};
return db.Read(sql, Make, parms).ToList();
}
You can handle the CellPainting event to highlight sections of text inside the Cells of a DataGridView.
The StringFormat.SetMeasurableCharacterRanges() method can be used to create CharacterRange elements that are then fed to Graphics.MeasureCharacterRanges(), to generate Regions that describe the bounds of sections of text in those ranges.
Some things to keep in mind:
e.PaintBackground()
and e.PaintContent()
to reset the rendering of the Cells when these need to be refreshed, otherwise you end up painting the same graphic multiple times over the same graphic surfacee.Handled = true
when you draw your custom graphic content, otherwise it's not rendered[StringFormat].LineAlignment = StringAlignment.Center;
RectangleF
with [Region].GetBounds([Graphics])
. Use Rectangle.Round()
to generate a Rectangle
e.CellBounds
value to define the bounds of the text you're submitting to the methods used to both measure the text and render the graphics; if you find yourself adjusting these bounds with magic numbers, there's something wrong in the procedure. If you need to move a Region, use [Region].Translate()
; to move a Rectangle, use [Rectangle].Offset()
or [Rectangle].Inflate()
Brushes
, Pens
and SystemBrushes
classes. Explicitly disposing of these object is very important, they hold unmanaged resources. The GC cannot help youprivate void someDataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
e.PaintBackground(e.ClipBounds, true);
e.PaintContent(e.ClipBounds);
if (string.IsNullOrEmpty(searchString)) return;
var cellValue = e.FormattedValue?.ToString();
if (string.IsNullOrEmpty(cellValue)) return;
var positions = Regex.Matches(cellValue, searchString);
if (positions.Count == 0) return;
using (var format = new StringFormat(StringFormatFlags.FitBlackBox)) {
// The Cell's vertical alignment is usually centered. Adjust as required
format.LineAlignment = StringAlignment.Center;
if (e.CellStyle.WrapMode == DataGridViewTriState.False ||
e.CellStyle.WrapMode == DataGridViewTriState.NotSet) {
format.FormatFlags |= StringFormatFlags.NoWrap;
}
// Generates ranges for all matching strings found
format.SetMeasurableCharacterRanges(positions.OfType<Match>()
.Select(m => new CharacterRange(m.Index, m.Length)).ToArray());
// Generate Regions that contain the search text
var regions = e.Graphics.MeasureCharacterRanges(cellValue, e.CellStyle.Font, e.CellBounds, format);
using (var brush = new SolidBrush(Color.FromArgb(80, Color.Fuchsia))) {
foreach (var region in regions) {
e.Graphics.FillRegion(brush, region);
// And / or draw a rectangle around the claculated box
e.Graphics.DrawRectangle(Pens.Red, Rectangle.Round(region.GetBounds(e.Graphics)));
}
}
}
e.Handled = true;
}
You can subscribe to the KeyDown
event of a TextBox to handle the Enter
key and invalidate your DataGridView when the search string changes:
private string searchString = "";
private void searchTextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter) {
e.SuppressKeyPress = true;
searchString = (sender as Control).Text;
someDataGridView.Invalidate();
}
}
This is how it works: