在DataGridView中突出显示特定字符

huangapple go评论79阅读模式
英文:

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();
}

在DataGridView中突出显示特定字符

英文:

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 &gt;= 0 &amp;&amp; e.ColumnIndex &gt;= 0)
{
if (!String.IsNullOrEmpty(AddCustomer_SearchTextBox.Text) &amp;&amp; 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(&quot;#0078D7&quot;);
cellStyle.ForeColor = ColorTranslator.FromHtml(&quot;#FFFFFF&quot;);
}
}
}
}
}
catch (Exception ex)
{
ExLogger.LogException(ex, &quot;&quot;);
MessageBox.Show(ex.ToString(), &quot;Error&quot;, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

// It works normally

public List&lt;Customer&gt;? SearchCustomers(int count, int minOffset, int maxOffset, string keyword, string sortExpression = &quot;ASC&quot;)
{
if (count &lt;= 0)
{
return new List&lt;Customer&gt;();
}
int limit = 1 + (maxOffset - minOffset);
if (limit &lt; 0)
{
limit = 0;
}
int offset = minOffset - 1; // start
if (offset &lt; 0)
{
offset = 0;
}
if (offset &gt;= count)
{
offset = count - 1;
}
string sql = @&quot;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, &#39;%d/%m/%Y %H:%i:%s&#39;) &quot; + sortExpression + &quot; LIMIT &quot; + limit + &quot; OFFSET &quot; + offset;
object[] parms = { &quot;@ID&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@created&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@name&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@place_of_birth&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@date_of_birth&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@gender_id&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@address&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@neighbourhood_hamlet&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@urban_village&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@subdistrict&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@religion_id&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@marital_status_id&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@profession&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@citizenship_id&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@email&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@phone_number&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;,
&quot;@send_me&quot;,  &#39;%&#39;+ keyword + &#39;%&#39;
};
return db.Read(sql, Make, parms).ToList();
}

在DataGridView中突出显示特定字符

答案1

得分: 4

你可以处理CellPainting事件来突出显示DataGridView单元格中文本的部分。使用StringFormat.SetMeasurableCharacterRanges()方法创建CharacterRange元素,然后将其传递给Graphics.MeasureCharacterRanges(),以生成描述这些范围中文本部分边界的区域。

要记住的一些事情:

  1. 当需要刷新这些单元格时,需要调用e.PaintBackground()e.PaintContent()来重置单元格的渲染,否则会多次在同一图形表面上绘制相同的图形。
  2. 在绘制自定义图形内容时,需要设置e.Handled = true,否则不会渲染。
  3. 需要考虑单元格中文本的对齐方式。通常是垂直居中,但也可以是水平居中或其他方式(例如,左对齐/顶部对齐)。由于我们可以控制它,因此可以相应地调整StringFormat的垂直和水平对齐。在示例中,我假设默认布局并指定[StringFormat].LineAlignment = StringAlignment.Center;
  4. 一个或多个单元格的文本可能包含搜索字符串多次,因此最好使用简单的正则表达式来查找文本中所有匹配的部分,以便我们可以突出显示所有匹配项。
  5. 您可以填充区域的内容,但如果需要矩形,因为某些绘图方法不接受区域作为绘图区域,您可以使用[Region].GetBounds([Graphics])将区域转换为RectangleF。使用Rectangle.Round()生成Rectangle
  6. 始终使用e.CellBounds值来定义您提交给用于测量文本和渲染图形的方法的文本边界;如果发现自己使用“神奇数字”来调整这些边界,那么程序中可能存在问题。如果需要移动区域,请使用[Region].Translate();要移动矩形,请使用[Rectangle].Offset()[Rectangle].Inflate()
  7. 您需要处理所有可处置对象。这包括(在这里)您创建的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();
    }
}

这是它的工作原理:

在DataGridView中突出显示特定字符

英文:

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:

  1. You need to call 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 surface
  2. You need to set e.Handled = true when you draw your custom graphic content, otherwise it's not rendered
  3. 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;
  4. 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
  5. 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]). Use Rectangle.Round() to generate a Rectangle
  6. 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()
  7. 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 and SystemBrushes 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 &lt; 0 || e.RowIndex &lt; 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&#39;s vertical alignment is usually centered. Adjust as required
format.LineAlignment = StringAlignment.Center;
// Generates ranges for all matching strings found 
format.SetMeasurableCharacterRanges(positions.OfType&lt;Match&gt;()
.Select(m =&gt; 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 = &quot;&quot;;
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:

在DataGridView中突出显示特定字符

huangapple
  • 本文由 发表于 2023年6月6日 13:52:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76411755.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定