3

*Sorry if the title is not great. I am not sure how to summarize this question into a few words.


I have a DataGridView and a search box. When the user types a query, any matching result in the DataGridView cells, is highlighted. To achieve this I use the CellPainting event of DataGridView and draw a rectangle behind the results.

Some of the cells are oriented Right-To-Left:

enter image description here

And some are oriented Left-To-Right:

enter image description here

When the orientation is RTL, I use the following formula to calculate the highlight rectangle X-coordinate:

e.CellBounds.Right - queryWidth - stringBeforeQueryWidth;

and stringBeforeQueryWidth refers to this:

enter image description here

When the orientation is LTR, I use the following formula:

e.CellBounds.Left + stringBeforeQueryWidth;

and stringBeforeQueryWidth refers to this:

enter image description here

The way I calculate stringBeforeQueryWidth is as follows:

var stringBeforeQuery = cellValue.Substring(0, cellValue.IndexOf(query));
var stringBeforeQueryWidth =
    e.Graphics.MeasureString(stringBeforeQuery, font, e.CellBounds.Width, format).Width;

So when the orientation is RTL, I use the fact that all the characters that come before the query itself will be drawn to the right of it, and when the orientation is LTR, I use the fact that all the characters that come before the query itself will be drawn to the left of it.

The problem starts when the cell contains a string that combines LTR and RTL texts. For example: enter image description here

Let's say the query is 13. To calculate stringBeforeQueryWidth I need the width of רחוב ישראל ישראלי and the width of /5. I cannot use cellValue.Substring(0, cellValue.IndexOf(query)) to retrieve them, like I did when there was only one orientation, because רחוב ישראל ישראלי comes before the query, and /5 comes after the query.

enter image description here

So how can I get the width of the part of the string that is located to the right of the query?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Michael Haddad
  • 4,085
  • 7
  • 42
  • 82
  • @DanielReyhanian - Please write in English for people who do not understand Hebrew. Anyway, what part confuses you? Please tell me so I could improve the question. – Michael Haddad Feb 07 '19 at 13:29
  • what does queryWidth refers to? – Daniel Reyhanian Feb 07 '19 at 13:37
  • And could you explain please what do u mean with query? – Daniel Reyhanian Feb 07 '19 at 13:38
  • 1
    It seems like a mathematical problem. I'd suggest to split the sentence into the desired parts (RTL and LTR) and to the calculation on each on of them, just the way you used to do. Then combine the results. – Daniel Reyhanian Feb 07 '19 at 13:39
  • @DanielReyhanian - the query is the thing the user searched (highlighted in yellow). `queryWidth` is a previously calculated width of the query itself, using `e.Graphics.MeasureString()`. – Michael Haddad Feb 07 '19 at 13:53
  • @DanielReyhanian - I have thought about this idea. The problem is the fact the something is LTR or RTL doesn't predict if it will be drawn to the right or to the left of the query. I need a way to identify which of the characters is drawn in which side of the query. Also, please review my edit for further information. – Michael Haddad Feb 07 '19 at 13:54
  • check [that one](https://stackoverflow.com/questions/38848130/combine-two-language-text-rtl-ltr) out. Maybe try to set the textbox in such a way that it will always be RTL or LTR? Try to format it as the guy in the question did (also in hebrew :)) – Daniel Reyhanian Feb 07 '19 at 13:58
  • @DanielReyhanian Thanks. The linked question is not about C#... Also, this is not a `TextBox`, but a cell of a `DataGridView`. If you do have a code solution, please share it in an answer. Thank you for your time. – Michael Haddad Feb 07 '19 at 14:02
  • `.MeasureCharacterRanges` might be of help, assuming that's bidi aware. – Jeroen Mostert Feb 07 '19 at 14:07
  • @JeroenMostert - Could you please elaborate in an answer? – Michael Haddad Feb 07 '19 at 14:08
  • That would require me to actually set up this situation, write code and test it, all things I lack time for (as opposed to a drive by comment alerting you to the existence of the method). – Jeroen Mostert Feb 07 '19 at 14:09
  • @JeroenMostert - Ok, thanks. I am not sure how it will help me. I mean, it's not like `MeasureString` doesn't work for me, it's just that I cannot figure out what string to pass into it... How do I isolate all the characters that are visually to the right of the query? – Michael Haddad Feb 07 '19 at 14:13
  • I was hoping that `.MeasureCharacterRanges` would allow you to simply grab the region where the text to highlight is (using custom `CharacterRanges`), since that's where you're going to draw a rectangle. `.MeasureString` cannot know about the bidi mixing going on because the rest of the string is cut out, but `.MeasureCharacterRanges` can (although, as I said, I don't know if it actually *does*). – Jeroen Mostert Feb 07 '19 at 14:17
  • Tried it with `MeasuerCharacterRanges`. Same issue. – Michael Haddad Feb 08 '19 at 14:27

1 Answers1

1

Note: This is not a direct answer to the question. It's an alternative.

As an option, you can show the search result in a HTML table and display it in WebBrowser control and highlight the search text using javascript.

To show search result as HTML, I'll use use T4 Run-time Text Templates. This way I can pass data to the html template and render the report easily by assigning output string of the template to a DocumentText property of the WebBrowser control. I've used this idea for creating simple and fast print document, for example take a look at this post.

TO highlight the text, you can use some javascript code or plugins. For example you can take a look at this post.

Here is result of the example which I will share in this post:

enter image description here

Example

  1. Create a Form and drop a WebBrowser control and a ToolStrip control on it, like what you see in above image.

  2. Add the following .cs file to the project and paste the following code in the file:

    namespace Sample
    {
        public class ReportModel
        {
            public string RTL { get; set; }
            public string LTR { get; set; }
        }
    }
    
  3. Add a new RunTime Text Template item to the project and name it ReportTemplate.tt. Open the file and paste the following content. Here I've used this plugin to highlight the text. And passed a model to the t4 template to easily generate HTML:

    <#@ template language="C#"#>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ parameter name="Model" type="System.Collections.Generic.List<Sample.ReportModel>"#>
    <!DOCTYPE html>
    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=11" />
        <title></title>
        <style type="text/css">
            body { font-family: Calibri;}
            table { text-align:center; border-collapse: collapse;}
            table, th, td { border: 1px solid black; }
            th {background-color: #EEEEEE;}
            th , td {padding: 2px;}
            .container { width:100%; height:100%; }
            .highlight { background: yellow; }
            .rtl {direction: rtl; text-align: right;}
            .ltr {direction: ltr; text-align: left;}
        </style>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script src="https://johannburkard.de/resources/Johann/jquery.highlight-5.js"></script>
        <script>
        function highlight(text) {
            $('#container').highlight(text);
        }
        </script>
    </head>
    <body>
    <div id ="container" class="container">
    <table style="width:100%">
        <tr>
            <th style="width:50%">LTR</th>
            <th style="width:50%">RTL</th>
        </tr>
        <#
        foreach(var item in Model) 
        {
        #>
        <tr>
            <td class="ltr"><#=item.LTR#></td>
            <td class="rtl"><#=item.RTL#></td>
        </tr>
        <#
        }
        #>
    </table>
    <div>
    </body>
    </html>
    
  4. Handle Load event of the form and turn off script errors and initialize sample data:

    List<ReportModel> list;
    private void Form1_Load(object sender, EventArgs e)
    {
        webBrowser1.ScriptErrorsSuppressed = true;
        list = new List<ReportModel>()
        {
            new ReportModel(){ LTR = "Text 123 text", RTL = "متن 123 متن" }  ,
            new ReportModel(){ LTR = "Text 123 text", RTL = "متن 123 متن" }  ,
            new ReportModel(){ LTR = "Text 456 text", RTL = "متن 456 متن" }  ,
            new ReportModel(){ LTR = "Text 456 text", RTL = "متن 456 متن" }  ,
            new ReportModel(){ LTR = "Text 456 text", RTL = "متن 456 متن" }  ,
            new ReportModel(){ LTR = "Text 456 text", RTL = "متن 456 متن" }  ,
        };
    }
    
  5. Handle Click event of the search button and seach, and pass the search result to template and run the template and show the result in WebBrowser control:

    private void searchButton_Click(object sender, EventArgs e)
    {
        var txt = searchTextBox.Text;
        var rpt = new ReportTemplate();
        rpt.Session = new Dictionary<string, object>();
        rpt.Session["Model"] = list.Where(x => x.LTR.Contains(txt) ||
            x.RTL.Contains(txt)).ToList();
        rpt.Initialize();
        webBrowser1.DocumentText = rpt.TransformText();
    }
    
  6. Handle DocumentCompleted event of the WebBrowser and call InvokeScript method of Document object and call highlight javascript function which we have already created in the html:

    private void webBrowser1_DocumentCompleted(object sender,
        WebBrowserDocumentCompletedEventArgs e)
    {
        var txt = searchTextBox.Text;
        if (!string.IsNullOrEmpty(txt))
            webBrowser1.Document.InvokeScript("highlight",
                new object[] { txt });
    }
    
  7. Run the application and type 123 in the text box and press search button to see the result.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398