WPFのListBoxでTextWrappingが効かないときの完全対処ガイド|GridSplitterや2列レイアウトで折り返し・行高さが伸びない原因と解決策

WPF の ListBox に長文テキストを表示すると TextWrapping="Wrap" を付けているのに 1 行で切れてしまい、行の高さが伸びない――。特に 2 列 Grid に並べて GridSplitter で幅を変える画面で起きやすい現象です。本稿では原因を測定(Measure)/配置(Arrange)の仕組みから説明し、最短で直る設定と、レイアウトの作法・落とし穴・実務でのチェックリストまでを網羅的に解説します。

目次

問題の概要と再現コード

以下のように、ListBox のアイテム テンプレートで TextBlock に TextWrapping="Wrap" を指定しても、テキストが折り返されず 1 行で途切れることがあります。Grid に 2 列配置し、間に GridSplitter を置くと発生率が上がります。

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto"/>   <!-- ← ここが Auto だと起きやすい -->
    <ColumnDefinition Width="5"/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>

  <ListBox Grid.Column="0" ItemsSource="{Binding LongTexts}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding}" TextWrapping="Wrap"/>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>

  <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch"/>

  <ListBox Grid.Column="2" ItemsSource="{Binding OtherTexts}"/>
</Grid>

ズバリの結論(最短で直す)

最も手軽で効果が高いのは、ListBox に ScrollViewer.HorizontalScrollBarVisibility="Disabled" を付けることです。これにより ListBox 内部の ScrollViewer が横スクロールを想定しなくなり、アイテムの測定幅が列幅に固定されます。結果としてテキストが正しく折り返され、行の高さも内容に応じて自動で拡張されます。

<ListBox
  ItemsSource="{Binding LongTexts}"
  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
  HorizontalContentAlignment="Stretch">  <!-- 併用推奨 -->
  <ListBox.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}" TextWrapping="Wrap"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

なぜ折り返されないのか ― WPF の Measure/Arrange を正しく理解する

原因はレイアウト段階の「幅の認識」にあります。WPF の Measure は親要素が子要素に「使ってよい幅・高さ」を渡し、子はその制約内で望ましいサイズ(DesiredSize)を返します。ListBox のテンプレートには ScrollViewer が内包されていますが、横方向スクロールが許可されている場合(Auto/Visible)、ScrollViewer は子に対して横幅を 無限大(∞) として測定する振る舞いをしがちです。このとき、

  • TextBlock TextWrapping="Wrap" は「与えられた幅で折り返す」という仕様なので、幅が無限だと折り返す必要が無いと判断します。
  • 結果、アイテムは 1 行表示のままになり、行の高さも伸びません。

さらに、Grid の列幅が Auto だと「内容に合わせて広がる」ため、広がる → 無限幅とみなす → 折り返さないという悪循環が生まれます。GridSplitter でリサイズしても、ListBox が「実幅」を安定的に認識できないと同じ問題が続きます。

解決策・対処方法(比較表)

施策効果・ポイント補足
① ListBox に ScrollViewer.HorizontalScrollBarVisibility="Disabled" を付与横スクロールを前提にしなくなるため、アイテムの測定幅が列幅に固定。
テキストが正しく折り返され、行の高さも内容に応じて拡張。
最も簡単で確実。既存テンプレートに手を入れず適用可。
② ListBox に HorizontalContentAlignment="Stretch" を設定各 ListBoxItem が列幅いっぱいに広がり、内部要素が「無限幅」と誤認しにくくなる。①の併用を推奨。横スクロールを無効化できない場合の代替にも。
③ Grid の列幅を *(スター)指定にするGridSplitter でリサイズしても各列が実幅を持ち続け、ListBox に正しい幅が伝わる。左右 2 列なら * / *、中央に Splitter を置く構成が安定。
④ 表示専用なら TextBlock を使うTextBox より軽量で描画が安定。編集が不要ならこちらが推奨。編集が必要な場合のみ TextBox TextWrapping="Wrap" を選択。
⑤ 最終手段:MaxWidth を強制アイテム内テキストの最大幅を ListBox の幅に束縛して強制的に折り返す。局所対応。根本原因(横スクロール前提・Auto 幅)も合わせて見直し推奨。

推奨構成(サンプル)

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="5"/>   <!-- GridSplitter -->
    <ColumnDefinition Width="*"/>
  </Grid.ColumnDefinitions>



```
&lt;ListBox.ItemTemplate&gt;
  &lt;DataTemplate&gt;
    &lt;TextBlock Text="{Binding}" TextWrapping="Wrap"/&gt;
  &lt;/DataTemplate&gt;
&lt;/ListBox.ItemTemplate&gt;

&lt;!-- あるいは全アイテムに Stretch を強制 --&gt;
&lt;ListBox.ItemContainerStyle&gt;
  &lt;Style TargetType="ListBoxItem"&gt;
    &lt;Setter Property="HorizontalContentAlignment" Value="Stretch"/&gt;
  &lt;/Style&gt;
&lt;/ListBox.ItemContainerStyle&gt;
```






 

「幅が無限だと折り返さない」― よくある落とし穴

  1. ScrollViewer の横スクロールが許可されている
    内部 ScrollViewer が横方向にスクロールできると、子の測定に無限幅を渡すことがあり、折り返しが無効化されます。まずは HorizontalScrollBarVisibility="Disabled"。
  2. Grid の列幅が Auto
    内容に応じて広がるため、ListBox が「自分の幅」を測定できなくなります。レイアウトが確定する * 指定に見直しましょう。
  3. ItemsPanel を StackPanel(水平)にしている
    独自の ItemsPanelTemplate で横向き StackPanel を使うと、横幅があふれてもスクロール前提になりがちです。既定の VirtualizingStackPanel(垂直)を基本とし、必要時のみ変更します。
  4. ListBoxItem が Stretch していない
    既定ではコンテンツ幅で収まり、列幅いっぱいに広がりません。HorizontalContentAlignment="Stretch" を忘れずに。
  5. 長大な「空白なし単語」
    非常に長い ID などは単語境界で改行されません。必要に応じてゼロ幅スペース &#8203; を差し込むか、TextTrimming の併用を検討します。

TextBlock と TextBox の選び分け

要素折り返しの指定性能・用途注意点
TextBlockTextWrapping="Wrap"軽量・描画が速い。表示専用なら最有力。選択や編集が不要な箇所に最適。
TextBoxTextWrapping="Wrap"(必要に応じ AcceptsReturn="True")編集可能。行高さは内容に追従。大量アイテムでは描画コストが上がるため、仮想化の維持に留意。

「どうしても横スクロールを残したい」ケースの対処

横スクロールを完全には消せない要件でも、以下の幅固定テクニックで折り返しを成立させられます。

&lt;TextBlock
  Text="{Binding}"
  TextWrapping="Wrap"
  MaxWidth="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=ActualWidth}"/&gt;

これはテキストの最大幅を ListBox の実幅に束縛することで、ScrollViewer が無限幅を渡していても実質的に折り返しを発生させる手法です。根本原因は残るため、レイアウト全体の見直しと併用してください。

GridSplitter と列幅のベストプラクティス

  • 左右 2 列なら <ColumnDefinition Width="*"/> <ColumnDefinition Width="5"/> <ColumnDefinition Width="*"/> を基本形に。
  • 固定幅が必要な列は Auto ではなく 100 のような「実数値」を使うと安定。Auto は「内容依存」のため、測定順序に引きずられやすい。
  • Splitter は列間(専用 5px 列)に置き、隣接列に MinWidth を設定して折り返し時の極端な再配置を回避。
&lt;Grid.ColumnDefinitions&gt;
  &lt;ColumnDefinition Width="*" MinWidth="200"/&gt;
  &lt;ColumnDefinition Width="5"/&gt;
  &lt;ColumnDefinition Width="2*" MinWidth="300"/&gt;
&lt;/Grid.ColumnDefinitions&gt;

仮想化(Virtualization)との関係

既定の ListBox は VirtualizingStackPanel を使ってアイテムの仮想化を行います。HorizontalScrollBarVisibility="Disabled" や HorizontalContentAlignment="Stretch" は、仮想化を無効化しません。ただし、テキスト折り返しで行高が可変になると再測定が増えるため、数万件規模では描画コストが上がる可能性があります。以下の設定で滑らかさと可読性のバランスを取りましょう。

&lt;ListBox
  VirtualizingPanel.IsVirtualizing="True"
  VirtualizingPanel.VirtualizationMode="Recycling"
  VirtualizingPanel.ScrollUnit="Pixel"
  ScrollViewer.CanContentScroll="True"
  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
  HorizontalContentAlignment="Stretch"/&gt;
  • Recycling はアイテムコンテナの再利用で GC 負荷を低減。
  • ScrollUnit=”Pixel” は可変高さアイテムと相性が良い(細かいスクロール単位)。

デバッグのコツ(原因切り分け)

  1. 幅の可視化:テンプレート内のルートに背景色を付け、Stretch できているか目で確認。
  2. 実幅の確認:一時的に TextBlock.Text に {Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=ActualWidth, StringFormat=Width:\{0\}} を足し、Measure が列幅を見ているか確認。
  3. 列幅変更の追従:GridSplitter で幅変更後も折り返しが追従しない場合は、列が Auto になっていないか再点検。

落ちやすい罠のパターン集

  • DataTemplate 内でさらに ScrollViewer をネスト:余計な ScrollViewer は無限幅の温床。基本は 1 つに。
  • ItemsPanel を WrapPanel に変更:カード風には便利だが、横方向が自由になりやすく折り返し要件と衝突しがち。
  • アイテムに固定幅を持たせる:アイテム側で Width を固定すると列リサイズに追従しない。MaxWidth 束縛が安全。

運用チェックリスト(そのまま使える)

チェックOK 条件メモ
横スクロール無効ScrollViewer.HorizontalScrollBarVisibility="Disabled"最優先。迷ったらまずこれ。
Stretch 指定HorizontalContentAlignment="Stretch"ItemContainerStyle で一括適用が便利。
列幅の安定Grid 列は * 指定Split 可能な 2 列は */* が基本。
可変高さ対応VirtualizationMode="Recycling" + ScrollUnit="Pixel"大量データでの滑らかさを確保。
長大単語対策ゼロ幅スペースや TextTrimming要件に応じて表示優先/可読性優先を選択。

実例:スタイルで共通化する

画面全体の ListBox に同じ規約を適用したいなら、リソース化して再利用すると安全です。

<Window.Resources>
  <Style x:Key="WrappingListBoxStyle" TargetType="ListBox">
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
    <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"/>
    <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
  </Style>








 

TextBox を使う場合の注意点(編集シナリオ)

  • TextBox TextWrapping="Wrap" に加えて、手入力で改行を許すなら AcceptsReturn="True"。
  • 既定の境界描画で視認性が強くなるため、読み取り専用表示では IsReadOnly="True" と BorderThickness="0" で見た目を落ち着かせる。
  • 大量表示時は描画コストが大きいので、仮想化設定とスクロール単位の見直しを忘れずに。
&lt;DataTemplate&gt;
  &lt;TextBox Text="{Binding Detail}"
           TextWrapping="Wrap"
           AcceptsReturn="True"
           IsReadOnly="{Binding IsReadOnly}"
           BorderThickness="0"/&gt;
&lt;/DataTemplate&gt;

原因別・最短回答(クイックリファレンス)

症状原因即効策
折り返されない / 行が伸びない横スクロール前提で無限幅測定ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Splitter で幅を変えても折り返し不安定列幅が Auto列を * 指定に
一部アイテムだけ 1 行になるListBoxItem が Stretch していないHorizontalContentAlignment="Stretch"
長い英数字がはみ出す単語境界なし&#8203; を挿入 / トリミング併用

すべてを統合した完成サンプル

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" MinWidth="240"/>
    <ColumnDefinition Width="5"/>
    <ColumnDefinition Width="*" MinWidth="240"/>
  </Grid.ColumnDefinitions>



```
&lt;ListBox.ItemTemplate&gt;
  &lt;DataTemplate&gt;
    &lt;Border Padding="6" Margin="2" Background="#08000000"&gt;
      &lt;TextBlock Text="{Binding}" TextWrapping="Wrap"/&gt;
    &lt;/Border&gt;
  &lt;/DataTemplate&gt;
&lt;/ListBox.ItemTemplate&gt;
```






 

まとめ ― 原因と対処の指針

  • 横スクロールを無効化して「幅は列幅まで」という制約をはっきりさせる。
  • Item を Stretch させて列幅いっぱいを使う。
  • Grid の列は * 指定で安定した実幅を ListBox に伝える。
  • 表示専用は TextBlock、編集は TextBox と役割を分ける。
  • 長大単語や超大量データなど例外条件では、MaxWidth 束縛や仮想化設定で微調整。

以上を守れば、「TextWrapping を付けているのに折り返されない」「行の高さが伸びない」といった不思議な挙動は再現しなくなります。根本は「レイアウトエンジンに正しい幅の制約を渡すこと」。そのカギが、ScrollViewer の横スクロール無効化とStretch、そして列の * 指定です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次