Для System.Windows.Forms.TextBox с Multiline = True я хотел бы отображать полосы прокрутки только тогда, когда текст не подходит.
Это текстовое поле только для чтения, используемое только для отображения. Это TextBox, чтобы пользователи могли копировать текст. Есть ли что-нибудь встроенное для поддержки автоматического показа полос прокрутки? Если нет, следует ли использовать другой элемент управления? Или мне нужно подключить TextChanged и вручную проверить переполнение (если да, как определить, подходит ли текст?)
Не повезло с различными комбинациями настроек WordWrap и Scrollbars. Я бы хотел, чтобы изначально не было полос прокрутки и чтобы каждая из них отображалась динамически, только если текст не соответствует заданному направлению.
@nobugz, спасибо, это работает, когда WordWrap отключен. Я бы предпочел не отключать перенос слов, но это меньшее из двух зол.
@ André Neves, хорошая мысль, и я бы пошел по этому пути, если бы он был доступен для редактирования пользователем. Я согласен с тем, что согласованность - это кардинальное правило интуитивности пользовательского интерфейса.





Я также провел несколько экспериментов и обнаружил, что вертикальная полоса всегда будет отображаться, если вы ее включите, а горизонтальная полоса всегда отображается, пока она включена и WordWrap == false.
Думаю, здесь вы не получите именно того, чего хотите. Однако я считаю, что пользователям хотелось бы лучшего поведения Windows по умолчанию, чем то, которое вы пытаетесь заставить. Если бы я использовал ваше приложение, я, вероятно, был бы обеспокоен, если бы мое текстовое поле внезапно уменьшилось только потому, что ему нужно разместить неожиданную полосу прокрутки, потому что я дал ему слишком много текста!
Возможно, было бы неплохо просто позволить вашему приложению следовать внешнему виду Windows.
Добавьте новый класс в свой проект и вставьте код, показанный ниже. Скомпилировать. Перетащите новый элемент управления из верхней части панели инструментов в форму. Это не совсем идеально, но должно работать на вас.
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyTextBox : TextBox {
private bool mScrollbars;
public MyTextBox() {
this.Multiline = true;
this.ReadOnly = true;
}
private void checkForScrollbars() {
bool scroll = false;
int cnt = this.Lines.Length;
if (cnt > 1) {
int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
if (pos0 >= 32768) pos0 -= 65536;
int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
if (pos1 >= 32768) pos1 -= 65536;
int h = pos1 - pos0;
scroll = cnt * h > (this.ClientSize.Height - 6); // 6 = padding
}
if (scroll != mScrollbars) {
mScrollbars = scroll;
this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
}
}
protected override void OnTextChanged(EventArgs e) {
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnClientSizeChanged(EventArgs e) {
checkForScrollbars();
base.OnClientSizeChanged(e);
}
}
Уже упоминалось в ОП. Нажмите кнопку «Задать вопрос», чтобы получить помощь.
Я столкнулся с этим вопросом, когда хотел решить ту же проблему.
Самый простой способ сделать это - перейти на System.Windows.Forms.RichTextBox. Для свойства ScrollBars в этом случае можно оставить значение по умолчанию RichTextBoxScrollBars.Both, которое указывает «Отображать как горизонтальную, так и вертикальную полосу прокрутки при необходимости». Было бы неплохо, если бы эта функциональность была предоставлена в TextBox.
+1: просто, используйте вместо этого RichTextBox. Работал у меня. Спасибо слабое
Имейте в виду, что RichTextBox создан для текста в формате RTF и требует больших затрат на рендеринг и обработку по сравнению с многострочным TextBox. Я бы не советовал использовать его, если вы не хотите отображать форматированный текст.
Я использовал это решение RichTextBox, без побочных эффектов. Я считаю, что в большинстве случаев беспокойство Камиллы необоснованно. Спасибо user73892.
В решении nobugz есть очень тонкая ошибка, которая приводит к повреждению кучи, но только если вы используете AppendText () для обновления TextBox.
Установка свойства ScrollBars из OnTextChanged приведет к уничтожению и воссозданию окна Win32 (дескриптора). Но OnTextChanged вызывается из недр элемента управления редактированием Win32 (EditML_InsertText), который сразу после этого ожидает, что внутреннее состояние этого элемента управления редактированием Win32 останется неизменным. К сожалению, поскольку окно воссоздается, это внутреннее состояние было освобождено ОС, что привело к нарушению доступа.
Итак, мораль этой истории такова: не используйте AppendText (), если собираетесь использовать решение nobugz.
У меня проблема с нарушением прав доступа, которая, похоже, связана с отображением формы с помощью TextBox (stackoverflow.com/q/7458915/68936). Увидев ваш пост, я подумал, что это могло быть из-за вызова AppendText (). Однако я заменил вызов AppendText только на TextBox.Text =, и нарушение прав доступа все еще возникает время от времени. Должен ли я понять из вашего ответа, что TextBox.Text = должен быть в порядке, а AppendText () - нет? Или они оба страдают от одной и той же проблемы (разве они оба не вызовут OnTextChanged?)
Я добился успеха с приведенным ниже кодом.
public partial class MyTextBox : TextBox
{
private bool mShowScrollBar = false;
public MyTextBox()
{
InitializeComponent();
checkForScrollbars();
}
private void checkForScrollbars()
{
bool showScrollBar = false;
int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;
using (Graphics g = this.CreateGraphics())
{
// Calcualte the size of the text area.
SizeF textArea = g.MeasureString(this.Text,
this.Font,
this.Bounds.Width - padding);
if (this.Text.EndsWith(Environment.NewLine))
{
// Include the height of a trailing new line in the height calculation
textArea.Height += g.MeasureString("A", this.Font).Height;
}
// Show the vertical ScrollBar if the text area
// is taller than the control.
showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));
if (showScrollBar != mShowScrollBar)
{
mShowScrollBar = showScrollBar;
this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
}
}
}
protected override void OnTextChanged(EventArgs e)
{
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnResize(EventArgs e)
{
checkForScrollbars();
base.OnResize(e);
}
}
То, что описывает Эйдан, почти в точности соответствует сценарию пользовательского интерфейса, с которым я столкнулся. Поскольку текстовое поле предназначено только для чтения, мне не нужно, чтобы оно отвечало на TextChanged. И я бы предпочел, чтобы перерасчет автопрокрутки был отложен, чтобы он не запускался десятки раз в секунду, пока изменяется размер окна.
Для большинства пользовательских интерфейсов текстовые поля с вертикальными и горизонтальными полосами прокрутки - это зло, поэтому меня здесь интересуют только вертикальные полосы прокрутки.
Я также обнаружил, что MeasureString произвел высоту, которая на самом деле была больше, чем требовалось. Использование PreferredHeight текстового поля без границы, поскольку высота строки дает лучший результат.
Следующее, кажется, работает очень хорошо, с рамкой или без нее, и работает с WordWrap.
Просто вызовите AutoScrollVertical (), когда вам это нужно, и при необходимости укажите recalculateOnResize.
public class TextBoxAutoScroll : TextBox
{
public void AutoScrollVertically(bool recalculateOnResize = false)
{
SuspendLayout();
if (recalculateOnResize)
{
Resize -= OnResize;
Resize += OnResize;
}
float linesHeight = 0;
var borderStyle = BorderStyle;
BorderStyle = BorderStyle.None;
int textHeight = PreferredHeight;
try
{
using (var graphics = CreateGraphics())
{
foreach (var text in Lines)
{
var textArea = graphics.MeasureString(text, Font);
if (textArea.Width < Width)
linesHeight += textHeight;
else
{
var numLines = (float)Math.Ceiling(textArea.Width / Width);
linesHeight += textHeight * numLines;
}
}
}
if (linesHeight > Height)
ScrollBars = ScrollBars.Vertical;
else
ScrollBars = ScrollBars.None;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
finally
{
BorderStyle = borderStyle;
ResumeLayout();
}
}
private void OnResize(object sender, EventArgs e)
{
m_timerResize.Stop();
m_timerResize.Tick -= OnDelayedResize;
m_timerResize.Tick += OnDelayedResize;
m_timerResize.Interval = 475;
m_timerResize.Start();
}
Timer m_timerResize = new Timer();
private void OnDelayedResize(object sender, EventArgs e)
{
m_timerResize.Stop();
Resize -= OnResize;
AutoScrollVertically();
Resize += OnResize;
}
}
Это работает, только если вы явно добавляете в текст новые строки. Если вы хотите показать длинную строку текста с переносом слов, этот подход не работает, так как TextBox.Lines всегда будет 1. Просто подумал, что выкину это там, так как это был тот случай, который я искал, и это не совсем решило проблему.