英文:
Highlight specific characters in all Cells of a DataGridView
问题
在DataGridView中进行字符搜索时,我希望只选择搜索到的特定字符(我用红色框标记),而不是选择单元格中的所有字符。我该如何实现?
我编写了代码,结果如下:
期望的输出:
我只希望选择搜索到的特定字符(我用红色框标记),而不是选择单元格中的所有字符
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)
{
// 问题在于如何仅选择单元格中的特定字符(而不是所有字符)
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);
}
}
// 这段代码正常工作
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; // 起始位置
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();
}
英文:
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();
}
答案1
得分: 4
你可以处理CellPainting事件来突出显示DataGridView单元格中文本的部分。使用StringFormat.SetMeasurableCharacterRanges()方法创建CharacterRange元素,然后将其传递给Graphics.MeasureCharacterRanges(),以生成描述这些范围中文本部分边界的区域。
要记住的一些事情:
- 当需要刷新这些单元格时,需要调用
e.PaintBackground()
和e.PaintContent()
来重置单元格的渲染,否则会多次在同一图形表面上绘制相同的图形。 - 在绘制自定义图形内容时,需要设置
e.Handled = true
,否则不会渲染。 - 需要考虑单元格中文本的对齐方式。通常是垂直居中,但也可以是水平居中或其他方式(例如,左对齐/顶部对齐)。由于我们可以控制它,因此可以相应地调整StringFormat的垂直和水平对齐。在示例中,我假设默认布局并指定
[StringFormat].LineAlignment = StringAlignment.Center;
- 一个或多个单元格的文本可能包含搜索字符串多次,因此最好使用简单的正则表达式来查找文本中所有匹配的部分,以便我们可以突出显示所有匹配项。
- 您可以填充区域的内容,但如果需要矩形,因为某些绘图方法不接受区域作为绘图区域,您可以使用
[Region].GetBounds([Graphics])
将区域转换为RectangleF
。使用Rectangle.Round()
生成Rectangle
- 始终使用
e.CellBounds
值来定义您提交给用于测量文本和渲染图形的方法的文本边界;如果发现自己使用“神奇数字”来调整这些边界,那么程序中可能存在问题。如果需要移动区域,请使用[Region].Translate()
;要移动矩形,请使用[Rectangle].Offset()
或[Rectangle].Inflate()
- 您需要处理所有可处置对象。这包括(在这里)您创建的StringFormat和Brushes/Pens,除非使用从Brushes、Pens和SystemBrushes类获取的库存对象。明确处理这些对象非常重要,它们持有非托管资源。垃圾回收无法帮助您
您可以订阅TextBox的KeyDown
事件以处理Enter
键,并在搜索字符串更改时使DataGridView无效化:
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();
}
}
这是它的工作原理:
英文:
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:
- You need to call
e.PaintBackground()
ande.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 surface - You need to set
e.Handled = true
when you draw your custom graphic content, otherwise it's not rendered - The alignment of the text in the Cells needs to be considered. It's usually centered vertically, but it could be also centered horizontally, or something else (e.g., aligned Left / Top). Since it's under our control, we can adjust the StringFormat's vertical and horizontal alignment accordingly. In the example, I assume the default layout and specify
[StringFormat].LineAlignment = StringAlignment.Center;
- The text of one or more Cells may contain the search string multiple times, so better use a simple Regex to find all the matching sections of the text, so we can highlight all
- You can fill the content of a Region, but if you need a Rectangle instead, since some drawing methods do not accept a Region as the drawing area, you can convert a Region to a
RectangleF
with[Region].GetBounds([Graphics])
. UseRectangle.Round()
to generate aRectangle
- Always use the
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()
- You need to dispose all disposable objects. This includes (here) the StringFormat and Brushes / Pens you create, unless you use stock objects as those you get from the
Brushes
,Pens
andSystemBrushes
classes. Explicitly disposing of these object is very important, they hold unmanaged resources. The GC cannot help you
<br/>
private void someDataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
e.PaintBackground(e.ClipBounds, true);
e.PaintContent(e.ClipBounds);
var cellValue = e.FormattedValue?.ToString();
if (string.IsNullOrEmpty(searchString) || 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;
// 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:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论