I have a question about the cell truncation (replaced with \"...\"):
How to display the replacement \"...\" on the left side of a cell when the column is right-align
It's definitely an unusual thing to do - but (like anything else) it can be done. It's a question of measuring the size of the string and comparing it with the size of the cell. (Note that I assume that the data is entered by a user. If you're databinding you basically have to consume other events.)
This works but might need some fine tuning:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
dataGridView1.Columns.Add("col1", "col1");
dataGridView1.Columns[0].CellTemplate.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
dataGridView1.Columns.Add("col2", "col2");
dataGridView1.Columns.Add("col3", "col3");
dataGridView1.Rows.Add();
dataGridView1.CellEndEdit += new DataGridViewCellEventHandler(dataGridView1_CellEndEdit);
dataGridView1.ColumnWidthChanged += new DataGridViewColumnEventHandler(dataGridView1_ColumnWidthChanged);
}
void dataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
if (e.Column.Index == 0)
{
// We need to truncate everything again when the width changes
foreach (DataGridViewRow row in dataGridView1.Rows)
{
RightTruncateText(row.Cells[0]);
}
}
}
void RightTruncateText(DataGridViewCell cell)
{
// check if the content is too long:
using (Graphics g = Graphics.FromHwnd(this.Handle))
{
SizeF size = g.MeasureString((string)cell.Tag, dataGridView1.Font); // NOTE: using the tag
if (size.Width > cell.Size.Width)
{
StringBuilder truncated = new StringBuilder((string)cell.Tag);
truncated.Insert(0, "...");
// Truncate the string until small enough (NOTE: not optimized in any way!)
while (size.Width > cell.Size.Width)
{
truncated.Remove(3, 1);
size = g.MeasureString(truncated.ToString(), dataGridView1.Font);
}
cell.Value = truncated.ToString();
}
else
{
cell.Value = cell.Tag;
}
}
}
void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0)
{
// Save the value in the tag but show the truncated value
DataGridViewCell cell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
cell.Tag = cell.Value; // Saving the actual state
RightTruncateText(cell);
}
}
}
I have made an workaround, it's working except the "..." (truncation works well)
protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
{
base.OnCellPainting(e);
if (e.RowIndex >= 0 && e.ColumnIndex >= 0 &&
CustomRightToLeftColumnNames.Contains(this.Columns[e.ColumnIndex].Name))
{
// Method 2:
e.PaintBackground(e.CellBounds, true);
if (e.FormattedValue != null)
{
TextFormatFlags flags = TextFormatFlags.RightToLeft |
TextFormatFlags.VerticalCenter |
TextFormatFlags.Right |
TextFormatFlags.LeftAndRightPadding;// |
//TextFormatFlags.EndEllipsis;
TextRenderer.DrawText
(
e.Graphics,
e.FormattedValue.ToString(),
e.CellStyle.Font,
e.CellBounds,
e.CellStyle.ForeColor,
flags
);
}
e.Handled = true;
}
}
The only problem with this solution is I don't know how to Set the TextFormatFlags to get the right behavior I want, exactly same as when DataGridView.RightToLeft = Yes
.
If I turn on TextFormatFlags.EndEllipsis
, the three dots "..." will appear on the left side, but it truncates from the right end of the string.
I'm not sure which flag of TextFormatFlags
enumeration to turn on.
I've ended up implementing this by creating my own DataGridViewLeftCropTextBoxCell
. Unfortunately DataGridViewTextBoxCell::Paint is bit a complicated method Reference Source .NET Framework 4.5.2 which uses many .NETs internal methods.
But at first I let base class draw background and borders (and if there's no sensible foreground, just leave it).
Then measure text and shrink it until it fits value bounds.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace Project
{
public class DataGridViewLeftCropTextBoxCell : DataGridViewTextBoxCell
{
/// <summary>
/// Paints contents
/// </summary>
/// <param name="graphics"></param>
/// <param name="clipBounds"></param>
/// <param name="cellBounds"></param>
/// <param name="rowIndex"></param>
/// <param name="cellState"></param>
/// <param name="value"></param>
/// <param name="formattedValue"></param>
/// <param name="errorText"></param>
/// <param name="cellStyle"></param>
/// <param name="advancedBorderStyle"></param>
/// <param name="paintParts"></param>
protected override void Paint( Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts )
{
string formattedString = formattedValue as string;
// Nothing to draw
if (String.IsNullOrEmpty( formattedString )) {
base.Paint( graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts );
return;
}
// Draw parently without foreground
base.Paint( graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentForeground );
// No foreground?
if ((paintParts & DataGridViewPaintParts.ContentForeground) == DataGridViewPaintParts.None) {
return;
}
// Calculate value bounds
Rectangle borderWidths = BorderWidths( advancedBorderStyle );
Rectangle valBounds = cellBounds;
valBounds.Offset( borderWidths.X, borderWidths.Y );
valBounds.Width -= borderWidths.Right;
valBounds.Height -= borderWidths.Bottom;
bool cellSelected = (cellState & DataGridViewElementStates.Selected) != 0;
// Prepare text flags
TextFormatFlags flags = ComputeTextFormatFlagsForCellStyleAlignment( this.DataGridView.RightToLeft == RightToLeft.Yes, cellStyle.Alignment, cellStyle.WrapMode );
if ((flags & TextFormatFlags.SingleLine) != 0) {
flags |= TextFormatFlags.EndEllipsis;
}
// Prepare size of text
Size s = TextRenderer.MeasureText( graphics,
formattedString,
cellStyle.Font
);
// Text fits into bounds, just append
if (s.Width < valBounds.Width) {
TextRenderer.DrawText( graphics,
formattedString,
cellStyle.Font,
valBounds,
cellSelected ? cellStyle.SelectionForeColor : cellStyle.ForeColor,
flags );
return;
}
// Prepare
StringBuilder truncated = new StringBuilder( formattedString );
truncated.Insert( 0, "..." );
// Truncate the string until it's small enough
while ((s.Width > valBounds.Width) && (truncated.Length > 5)) {
truncated.Remove( 3, 1 );
formattedString = truncated.ToString();
s = TextRenderer.MeasureText( graphics,
formattedString,
cellStyle.Font
);
}
TextRenderer.DrawText( graphics,
formattedString,
cellStyle.Font,
valBounds,
cellSelected ? cellStyle.SelectionForeColor : cellStyle.ForeColor,
flags
);
}
}
}
And you can also create your own column type:
class DataGridViewLeftCropTextBoxColumn : DataGridViewTextBoxColumn
{
public override DataGridViewCell CellTemplate
{
get { return new DataGridViewLeftCropTextBoxCell(); }
set { base.CellTemplate = value; }
}
}
I've borrowed text shrinking from steinar's answer and TextFormatFlags ComputeTextFormatFlagsForCellStyleAlignment
from .NET Framework Reference Source.