テキストが収まらない場合にのみ System.Windows.Forms.TextBox にスクロールバーを表示するにはどうすればよいですか?
質問
Multiline=True の System.Windows.Forms.TextBox の場合、テキストが収まらない場合にのみスクロールバーを表示したいと考えています。
これは表示のみに使用される読み取り専用のテキストボックスです。これは TextBox なので、ユーザーはテキストをコピーできます。スクロールバーの自動表示をサポートする組み込みの機能はありますか?そうでない場合は、別のコントロールを使用する必要がありますか?それとも、TextChanged をフックして、オーバーフローを手動でチェックする必要がありますか (そうであれば、テキストが適合するかどうかを確認する方法はありますか?)
WordWrap とスクロールバーの設定をさまざまに組み合わせてもうまくいきません。最初はスクロールバーを持たず、テキストが指定された方向に収まらない場合にのみスクロールバーを動的に表示したいと考えています。
@ bougz さん、ありがとうございます。WordWrap が無効な場合でも機能します。wordwrap を無効にしないほうがよいのですが、これは 2 つの害のうちの小さい方です。
@André Neves、良い指摘です。ユーザーが編集可能であれば、私はそうするでしょう。一貫性が UI の直観性の鉄則であることに私は同意します。
解決
新しいクラスをプロジェクトに追加し、以下に示すコードを貼り付けます。コンパイル。新しいコントロールをツールボックスの上部からフォームにドロップします。完全ではありませんが、うまくいくはずです。
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で提供されている場合、それは素晴らしいことです。
また、いくつかの実験を行ったところ、垂直バーは有効にすると常に表示され、水平バーは有効である限り常に表示されることがわかりました。 WordWrap == false
.
ここではあなたが望むものを正確に得ることはできないと思います。ただし、ユーザーは、あなたが強制しようとしている動作よりも優れた Windows のデフォルト動作を望んでいると思います。もし私があなたのアプリを使っていたら、テキストを入れすぎて予期せぬスクロールバーに対応する必要があるという理由だけでテキストボックスの領域が突然縮小したら、おそらく困るでしょう。
おそらく、アプリケーションを Windows のルック アンド フィールに従うようにするのが良い考えかもしれません。
Nobugz のソリューションには、ヒープの破損を引き起こす非常に微妙なバグがありますが、これは AppendText() を使用して TextBox を更新している場合に限ります。
OnTextChanged から ScrollBars プロパティを設定すると、Win32 ウィンドウ (ハンドル) が破棄され、再作成されます。ただし、OnTextChanged は Win32 編集コントロール (EditML_InsertText) の内部から呼び出され、その直後にその Win32 編集コントロールの内部状態が変更されていないことが期待されます。残念ながら、ウィンドウが再作成されるため、その内部状態は OS によって解放され、アクセス違反が発生します。
したがって、この話の教訓は次のとおりです。novagz のソリューションを使用する場合は、AppendText() を使用しないでください。
以下のコードである程度成功しました。
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);
}
}
Aidan が説明していることは、私が直面している UI シナリオとほぼ同じです。テキスト ボックスは読み取り専用であるため、TextChanged に応答する必要はありません。また、ウィンドウのサイズ変更中に自動スクロールの再計算が 1 秒あたり何十回も実行されないように、自動スクロールの再計算を遅らせたいと考えています。
ほとんどの UI では、垂直スクロール バーと水平スクロール バーの両方を備えたテキスト ボックスは悪であるため、ここでは垂直スクロール バーのみに興味があります。
また、MeasureString が実際には必要な高さよりも大きな高さを生成したこともわかりました。行の高さとして境界線のないテキスト ボックスの PreferredHeight を使用すると、より良い結果が得られます。
次の例は、枠線の有無にかかわらず、かなりうまく機能するようです。WordWrap がオンの場合でも機能します。
必要なときに AutoScrollVertively() を呼び出し、オプションで 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;
}
}