![]() |
Languages »
C# »
Windows Forms
Intermediate
License: The Code Project Open License (CPOL)
Auto EllipsisBy Thomas PolaertAdd "Auto Ellipsis" feature to any Windows Form control |
C# (C# 2.0), Windows, .NET (.NET 2.0), WinForms, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Why yet another ellipsis control, when the .NET Framework already provides several built-in options to achieve this task? System.Windows.Forms.Label control comes with an AutoEllipsis property. System.Drawing.Graphics.DrawString or System.Windows.Forms.TextRenderer.DrawText offer a reliable way to make text fit into predefined boundaries. Just have a look at StringTrimming or TextFormatFlags enumeration! Not to mention PathCompactPath API from shlwapi.dll or Static control styles SS_ENDELLIPSIS and SS_PATHELLIPSIS.
Unfortunately, the built-in auto ellipsis controls provide no flexibility at all for ellipsis alignment. The text is always trimmed off at the end the string. This might be an issue, such as in the following example:
C:\Documents and Settings\TPOL\My Documents\Visual Studio 2005\
Projects\MyProject1\Program.cs
C:\Documents and Settings\TPOL\My Documents\Visual Studio 2005\
Projects\MyProject2\Program.cs
The built-in auto ellipsis controls display paths as follows:
C:\Documents and Settings\TPOL\My Documents\Visual...\Program.cs
C:\Documents and Settings\TPOL\My Documents\Visual...\Program.cs
It would be helpful to keep the last part of the path as it is more significant in this case.
C:\...Documents\Visual Studio 2005\Projects\MyProject1\Program.cs
C:\...Documents\Visual Studio 2005\Projects\MyProject2\Program.cs
By the way, Visual Studio 2005 behaves like this in the "File/Recent Files" menu.
This is why I came up with the Ellipsis class. It is a static class with a single method:
public static string Compact(string text, Control ctrl, EllipsisFormat options)
The Compact function trims off argument text to make it fit into ctrl boundaries. EllipsisFormat enumeration is defined as follows:
[Flags]
public enum EllipsisFormat
{
// Text is not modified.
None = 0,
// Text is trimmed at the end of the string. An ellipsis (...)
// is drawn in place of remaining text.
End = 1,
// Text is trimmed at the beginning of the string.
// An ellipsis (...) is drawn in place of remaining text.
Start = 2,
// Text is trimmed in the middle of the string.
// An ellipsis (...) is drawn in place of remaining text.
Middle = 3,
// Preserve as much as possible of the drive and filename information.
// Must be combined with alignment information.
Path = 4,
// Text is trimmed at a word boundary.
// Must be combined with alignment information.
Word = 8
}
The Ellipsis class can be used to implement flexible auto ellipsis on various Windows Form controls. I provided two examples in the demo project, one for Label control, one for TextBox control.
The TextBoxEllipsis switches to "full text" mode when it gains focus so its content can be edited as usual. It switches back to "ellipsis" mode when it loses focus.
A working ellipse algorithm should find the longest substring that can fit into the control boundaries. The brute force approach would test all substrings by removing characters one by one. The proposed solution uses the bisection method to minimize the number of iterations to get the closest match.
Bisection example:
The algorithm uses the TextRenderer.MeasureText method to get the size, in pixels, of the specified text drawn on the specified control (using the control's font). The bisection method is implemented as follows (some code has been removed for clarity):
public static readonly string EllipsisChars = "...";
public static string Compact(string text, Control ctrl, EllipsisFormat options)
{
using (Graphics dc = ctrl.CreateGraphics())
{
Size s = TextRenderer.MeasureText(dc, text, ctrl.Font);
// control is large enough to display the whole text
if (s.Width <= ctrl.Width)
return text;
int len = 0;
int seg = text.Length;
string fit = "";
// find the longest string that fits into
// the control boundaries using bisection method
while (seg > 1)
{
seg -= seg / 2;
int left = len + seg;
int right = text.Length;
if (left > right)
continue;
if ((EllipsisFormat.Middle & options) ==
EllipsisFormat.Middle)
{
right -= left / 2;
left -= left / 2;
}
else if ((EllipsisFormat.Start & options) != 0)
{
right -= left;
left = 0;
}
// build and measure a candidate string with ellipsis
string tst = text.Substring(0, left) +
EllipsisChars + text.Substring(right);
s = TextRenderer.MeasureText(dc, tst, ctrl.Font);
// candidate string fits into control boundaries,
// try a longer string
// stop when seg <= 1
if (s.Width <= ctrl.Width)
{
len += seg;
fit = tst;
}
}
if (len == 0) // string can't fit into control
{
return EllipsisChars;
}
return fit;
}
}
The Compact method in action:
The .NET Framework allows to trim text at a word boundary. We implement it by adjusting the substring bounds with regular expressions:
"\w*\W*" matches a word followed by whitespaces"\W*\w*$" matches whitespaces followed by a word at the end of the stringThese matches are subtracted from the substring (according to ellipsis alignment) in order to round up text at a word boundary.
private static Regex prevWord = new Regex(@"\W*\w*$");
private static Regex nextWord = new Regex(@"\w*\W*");
public static string Compact(string text, Control ctrl, EllipsisFormat options)
{
using (Graphics dc = ctrl.CreateGraphics())
{
[..]
int len = 0;
int seg = text.Length;
string fit = "";
// find the longest string that fits into
// the control boundaries using bisection method
while (seg > 1)
{
seg -= seg / 2;
int left = len + seg;
int right = text.Length;
[..]
// trim at a word boundary using regular expressions
if ((EllipsisFormat.Word & options) != 0)
{
if ((EllipsisFormat.End & options) != 0)
{
left -= prevWord.Match(text,
0, left).Length;
}
if ((EllipsisFormat.Start & options) != 0)
{
right += nextWord.Match(text,
right).Length;
}
}
// build and measure a candidate string with ellipsis
string tst = text.Substring(0, left) +
EllipsisChars + text.Substring(right);
s = TextRenderer.MeasureText(dc, tst, ctrl.Font);
// candidate string fits into control boundaries,
// try a longer string
// stop when seg <= 1
if (s.Width <= ctrl.Width)
{
len += seg;
fit = tst;
}
}
if (len == 0) // string can't fit into control
{
return EllipsisChars;
}
return fit;
}
}
Example of text trimmed at a word boundary:
The "path" mode is a feature where the specified text is handled as a file path. The algorithm preserves as much as possible of the drive and filename information:
public static string Compact(string text, Control ctrl, EllipsisFormat options)
{
using (Graphics dc = ctrl.CreateGraphics())
{
[..]
string pre = "";
string mid = text;
string post = "";
bool isPath = (EllipsisFormat.Path & options) != 0;
// split path string into <drive><directory><filename>
if (isPath)
{
pre = Path.GetPathRoot(text);
mid = Path.GetDirectoryName(text).Substring(pre.Length);
post = Path.GetFileName(text);
}
int len = 0;
int seg = mid.Length;
string fit = "";
// find the longest string that fits into
// the control boundaries using bisection method
while (seg > 1)
{
seg -= seg / 2;
int left = len + seg;
int right = mid.Length;
[..]
// build and measure a candidate string with ellipsis
string tst = mid.Substring(0, left) +
EllipsisChars + mid.Substring(right);
// restore path with <drive> and <filename>
if (isPath)
{
tst = Path.Combine(Path.Combine(pre, tst), post);
}
s = TextRenderer.MeasureText(dc, tst, ctrl.Font);
// candidate string fits into control boundaries,
// try a longer string
// stop when seg <= 1
if (s.Width <= ctrl.Width)
{
len += seg;
fit = tst;
}
}
if (len == 0) // string can't fit into control
{
// "path" mode is off, just return ellipsis characters
if (!isPath)
return EllipsisChars;
// <drive> and <directory> are empty, return <filename>
if (pre.Length == 0 && mid.Length == 0)
return post;
// measure "C:\...\filename.ext"
fit = Path.Combine(Path.Combine(pre, EllipsisChars), post);
s = TextRenderer.MeasureText(dc, fit, ctrl.Font);
// if still not fit then return "...\filename.ext"
if (s.Width > ctrl.Width)
fit = Path.Combine(EllipsisChars, post);
}
return fit;
}
}
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 20 Jun 2009 Editor: Deeksha Shenoy |
Copyright 2009 by Thomas Polaert Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |