3

I'm making a scheduling application which looks similar to Google Calendar, iCalendar, Outlook's calendar, and similar... As you may have seen in such apps, scheduled events are represented in rectangles, which can be very small especially if you're viewing a whole week.

My challenge is to give the user a "meaningful summary" of the event before they click it, using text in the rectangle. The text should be very compact. To that end, I want it to wrap lines in a way that isn't very common, but I suspect .NET is capable of handling it natively.

If a single word is wider than the rectangle, only the beginning of that word should show. But wrapping should still occur upon encountering spaces, if the word which follows would fall partly outside the rectangle. Not every space will cause wrapping to the next line, because if two or more words happen to fit on a line that's fine.

I suspect that the answer lies in the capabilities of StringFormat, StringTrimming, and FormatFlags, but I haven't found the right combination to accomplish the goals.

For example if we have...

Chuck Norris
Dentist Appointment

Due to limited space it might become...

Chuc
Norr
Denti
Appo

I do NOT want it to become:

Chuc
k
Norr
is
Dent
ist
Appo
intme
nt

As you see in this example, if the user already knows who Chuck Norris is, the presence of letter "k" and the letters "is" don't help the user recognize the name. In fact, the presence of those letters may force "Dentist Appointment" to be hidden from view because vertical space is also limited.

I already know how to draw text in a rectangle that wraps in the normal way, and I also know how to get it to avoid drawing any text that falls outside the rectangle. What I DON'T know is how to get it to wrap upon encountering a space (or carriage return) while preventing it from wrapping mid-word if a single word is wider than the rectangle. Any help there?

PaulOTron2000
  • 1,115
  • 1
  • 13
  • 20
  • I'm surprised no answers yet. Actually I do have a way to get the results I need, but I'm sure it's more code-intensive than necessary and it eats CPU cycles. That is, I can loop through the entire string, repeatedly checking the width of portions of the text, replace some of the spaces with CRLF's, and then render the text without wrapping. If nobody answers that's what I'll do, but it seems a waste when I suspect the capability to do what I need already exists in .net. – PaulOTron2000 Feb 15 '12 at 01:49

2 Answers2

3

Try using the TextRenderer.DrawText method with the TextFormatFlags.WordBreak flag:

Example using a panel's paint event:

Private Sub Panel1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Panel1.Paint
  Dim sb As New StringBuilder
  sb.AppendLine("Chuck Norris")
  sb.AppendLine("Dentist Appointment")

  e.Graphics.Clear(Color.White)
  TextRenderer.DrawText(e.Graphics, sb.ToString, Panel1.Font, _
                        Panel1.ClientRectangle, Color.Black, Color.Empty, _
                        TextFormatFlags.WordBreak)
End Sub

You can also add TextFormatFlags.WordEllipsis to add the "..." to the end of the words that are getting cut off at the edge of the rectangle:

TextRenderer.DrawText(e.Graphics, sb.ToString, Panel1.Font, _
                      Panel1.ClientRectangle, Color.Black, Color.Empty, _
                      TextFormatFlags.WordBreak Or TextFormatFlags.WordEllipsis)
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • @PaulOTron2000 DrawText fixed a lot of issues with DrawString. See this answer for [Drawing text in .NET](http://stackoverflow.com/a/7269191/719186) – LarsTech Feb 16 '12 at 18:22
  • I've tried this and although it does work, there are two issues. The first is that when a CRLF is encountered, it may add an extra blank line. The second is that I prefer using DrawString rather than DrawText, because it allows ClearType text. (If you don't know, it's a great technology that enhances readability using subtle red and blue hues. Chances are you're using it now, although it's invisible unless you take a screen shot and zoom in.) – PaulOTron2000 Feb 16 '12 at 18:23
  • Sorry I deleted the comment you replied to and rewrote it because I pressed ENTER before it was done. Anyway, is ClearType available with DrawText? – PaulOTron2000 Feb 16 '12 at 18:25
  • Scratch that... It does indeed! In fact I've been using it elsewhere in my code. (TextRenderingHint, SmoothingMode, and CompositingQuality help.) I still hope to eliminate the extra blank lines which may arise from this approach. – PaulOTron2000 Feb 16 '12 at 18:37
  • @PaulOTron2000 You might want to play with the `e.Graphics.TextRenderingHint` property. There is a `ClearTypeGridFit` option among others. I don't know if TextRenderer respects that or not. Personally, TextRenderer has always been cleaner that DrawString for me. Printing to paper is a different issue — there you pretty much have to use DrawString. – LarsTech Feb 16 '12 at 18:40
  • @PaulOTron2000 I did notice the extra line when making it extra, extra crunchy. Don't have an immediate work around for that. – LarsTech Feb 16 '12 at 18:41
  • I just upvoted and gave this answer the green check because I think it's the only possible answer to my question as-asked, but due the possibility of extra linebreaks I'm going to be doing this manually. Thank you however, since I'm also going to be migrating to use DrawText exclusively. Also I upvoted Basic's comment as thanks for his "sportsmanship." Yes, I will be optimizing the code that does it manually so I only check width upon encountering whitespace, but I won't make a custom control. – PaulOTron2000 Feb 16 '12 at 19:13
1

I don't see any way to do this other than manually.

To my knowledge, there aren't any standard controls which behave the way you want - the next step from there is to implement it yourself or find someone else who already has.

That said, you can make small improvements over the method you described in comments. Hopefully you can move it from "a bit kludgy" to "minimalist"

  • Create a custom control which inherits from label
  • Override the Paint event so that you can control what gets drawn (Here's another clear example and a more advanced one)
  • Only calculate string width where whitespace occurs - You don't care if you draw extra characters off-canvas and you won't break without whitespace.
  • Cache the final string info and only recalculate if a resize/invalidate occurs or the text changes (You need to monitor the appropriate events)

By using a custom control, you don't change the underlying Text property - so if you ever wanted to read it out, it would be the same way you put it in. This has another bonus in that you don't have to remember to do the same conversion everywhere you want this to appear - just reuse your control.

If you inherit from a control which does almost what you want (most likely label unless you have other requirements), it shouldn't be too much code / too much processing.

If you haven't already. you almost certainly want a custom object to represent a schedule entry anyway - easier to read, maintain and code against, provides a standard appearance across the app, etc.

Apologies if you know all this already - as you don't have many SO Questions/Answers, it's difficulkt to gauge your level of experience.

Community
  • 1
  • 1
Basic
  • 26,321
  • 24
  • 115
  • 201