Стандартный ListBox поддерживает остановки табуляции (он создается в стиле LBS_USETABSTOPS ), но для настраиваемого таб-стопа должна быть включена настройка UseCustomTabOffsets
= true
, затем добавляется один или несколько значения, представляющие позиции табуляции, для CustomTabOffsets IntegerCollection .
Есть хитрость в расчете этих позиций ; как описано в Документах о сообщении LB_SETTABSTOPS :
(...) целые числа представляют количество четвертей среднего символа
ширина для шрифта, который выбран в окне списка. Например,
табуляция 4 помещается на 1,0 символьных единиц, а табуляция 6 -
размещается на 1,5 средних символьных единиц. Однако, если список
часть диалогового окна, целые числа находятся в единицах шаблона диалога.
табуляция должна быть отсортирована по возрастанию (...)
Поскольку Font.ToLogFont () не возвращает средний размер символа в lfWidth
элементе структуры LOGFONT , мы можем вычислить его (без PInvoking) из пикселей измеряется как:
([CurrentTextWidth] / [AverageCharWidth]) * [TabSize]
Где [CurrentTextWidth]
- ширина строки в пикселях, рассчитанная с использованием TextRenderer.MeasureText ,
[AverageCharWidth]
можно рассчитать, измеряя разницу между M
и i
,
и [TabSize]
представляют четверти средней ширины символа (как описано в Документах, TabStop, равный 4
, равен ширине среднего символа относительно выбранного в настоящий момент шрифта).
Пример теста для создания 3 столбцов из текстовых частей, разделенных '\t'
в ListBox:
listBox1.Items.AddRange(new[] {
"x\tyyyyyyy\teeeeee",
"xxxx\tyyyyyyy\tmmmmmmm",
"xxxxxx\tyyyyyyy\tlllllll",
"AbcdEfgHilm\tyyyyyyy\tgggggggg",
"xxxxxx\tyyyyyyy\tzzzzzzz",
"XXaaMMiixxx\tyyyyyyy\tiiiiiiiiiiiiiiii"
});
SetListBoxTabs(listBox1);
Здесь я устанавливаю float tabSize = 4.2f
вместо 4.0
, потому что между частями текста, разделенными табуляцией, должно быть некоторое пространство, поэтому я добавляю часть базового значения, чтобы создать пространство между столбцами .
Это значение затем можно использовать для пропорциональной корректировки пространства между столбцами.
public void SetListBoxTabs(ListBox listBox)
{
float tabSize = 4.2f;
float currTabStop = 0;
int tabs = listBox.GetItemText(listBox.Items[0]).Split('\t').Length - 1;
if (tabs == 0) return;
var tabStops = new List<int>(tabs);
tabStops.AddRange(Enumerable.Repeat(0, tabs).ToArray());
using (var g = Graphics.FromHwnd(listBox.Handle))
{
float average = GetFontAverageCharSize(g, listBox.Font);
foreach (var item in listBox.Items)
{
string text = listBox.GetItemText(item);
string[] parts = text.Split('\t'); // Use Substring(IndexOf()) here
for (int i = 0; i < parts.Length - 1; i++)
{
float width = TextRenderer.MeasureText(g, parts[i], listBox.Font,
Size.Empty, TextFormatFlags.LeftAndRightPadding).Width;
float tabWidth = (width / average) * tabSize;
currTabStop += tabWidth;
tabStops[i] = (int)Math.Max(currTabStop, tabStops[i]);
}
currTabStop = 0;
}
}
listBox.UseTabStops = true; // Just in case 1 ...
listBox.UseCustomTabOffsets = true;
var offsets = listBox.CustomTabOffsets;
offsets.Clear(); // Just in case 2 ...
foreach (int tab in tabStops) { offsets.Add(tab); }
}
public float GetFontAverageCharSize(Graphics g, Font font)
{
string textMax = new string('M', 100);
string textMin = new string('i', 100);
float maxWidth = TextRenderer.MeasureText(g, textMax, listBox1.Font).Width;
float minWidth = TextRenderer.MeasureText(g, textMin, listBox1.Font).Width;
return (maxWidth + minWidth) / (2.0f * textMax.Length);
}