web-dev-qa-db-ja.com

テキストが1行に収まらなくなったときに、自動的に垂直方向にサイズ変更するようにTextBoxコントロールを構成するにはどうすればよいですか?

テキストが1行に収まらなくなったときに、自動的に垂直方向にサイズ変更するようにTextBoxコントロールを構成するにはどうすればよいですか?

たとえば、次のXAMLでは次のようになります。

<DockPanel LastChildFill="True" Margin="0,0,0,0">
  <Border Name="dataGridHeader" 
    DataContext="{Binding Descriptor.Filter}"
    DockPanel.Dock="Top"                         
    BorderThickness="1"
    Style="{StaticResource ChamelionBorder}">
  <Border
    Padding="5"
    BorderThickness="1,1,0,0"                            
    BorderBrush="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=dc:NavigationPane, 
    ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleBorder}}}">
    <StackPanel Orientation="Horizontal">
      <TextBlock                                
        Name="DataGridTitle"                                                                                                
        FontSize="14"
        FontWeight="Bold"                                    
        Foreground="{DynamicResource {ComponentResourceKey 
        TypeInTargetAssembly=dc:NavigationPane, 
        ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}"/>
      <StackPanel Margin="5,0"  Orientation="Horizontal" 
              Visibility="{Binding IsFilterEnabled, FallbackValue=Collapsed, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"
              IsEnabled="{Binding IsFilterEnabled, FallbackValue=false}"  >                                    
          <TextBlock  />                                                                
          <TextBox    
            Name="VerticallyExpandMe"
            Padding="0, 0, 0, 0"  
            Margin="10,2,10,-1"                                                                                                                                                                                                                     
            AcceptsReturn="True"
            VerticalAlignment="Center"                                    
            Text="{Binding QueryString}"
            Foreground="{DynamicResource {ComponentResourceKey 
            TypeInTargetAssembly=dc:NavigationPane, 
            ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}">
          </TextBox>
        </StackPanel>                               
    </StackPanel>
  </Border>              
  </Border>
</DockPanel>

「VerticallyExpandMe」という名前のTextBoxコントロールは、バインドされたテキストが1行に収まらない場合、自動的に垂直方向に展開する必要があります。 AcceptsReturnをtrueに設定すると、TextBoxは、その中でEnterキーを押すと垂直方向に展開しますが、これを自動的に実行したいと思います。

12
Klaus Nji

Andre Luusの提案は基本的に正しいですが、レイアウトによってテキストの折り返しが無効になるため、実際にはここでは機能しません。その理由を説明します。

基本的に、問題は次のとおりです。テキストの折り返しは、要素の幅が制約されている場合にのみ何も行いませんが、TextBoxは、水平方向のStackPanelの子孫であるため、制約のない幅になります。 (まあ、2つの水平スタックパネル。例を挙げたコンテキストによっては、おそらくもっと多くなります。)幅に制約がないため、TextBoxはいつ折り返しを開始するかわからないため、折り返しが発生することはありません。ラッピングを有効にした場合。 2つのことを行う必要があります。幅を制限することと、折り返しを有効にすることです。

これがより詳細な説明です。

あなたの例には、問題に関係のない多くの詳細が含まれています。これは、何が問題なのかを説明しやすくするために、いくらかトリミングしたバージョンです。

<StackPanel Orientation="Horizontal">
    <TextBlock Name="DataGridTitle" />
    <StackPanel
        Margin="5,0"
        Orientation="Horizontal"
        >
        <TextBlock />
        <TextBox
            Name="VerticallyExpandMe"
            Margin="10,2,10,-1"
            AcceptsReturn="True"
            VerticalAlignment="Center"
            Text="{Binding QueryString}"
            >
        </TextBox>
    </StackPanel>
</StackPanel>

そこで、含まれているDockPanelとその中にネストされた2つのBorder要素を削除しました。これらは問題の一部ではなく、解決策にも関連していないためです。だから私はあなたの例のネストされたStackPanel要素のペアから始めています。また、ほとんどの属性はレイアウトに関連していないため、ほとんどの属性も削除しました。

これは少し奇妙に見えます-このように2つのネストされた水平スタックパネルがあると冗長に見えますが、実行時にネストされたものを表示または非表示にする必要がある場合は、元のパネルでは実際に意味があります。ただし、問題がわかりやすくなります。

(空のTextBlockタグも奇妙ですが、それは元のタグとまったく同じです。それは何の役にも立たないようです。)

そして、ここに問題があります:あなたのTextBoxはいくつかの水平StackPanel要素の内側にあります、つまりその幅は制約されていません-あなたはテキストボックスに、実際に利用可能なスペースの量に関係なく、任意の幅に自由に拡張できると誤って伝えました。

StackPanelは常に、スタッキングの方向に制約のないレイアウトを実行します。したがって、そのTextBoxをレイアウトする場合、水平サイズdouble.PositiveInfinityTextBoxに渡します。したがって、TextBoxは常に、必要以上のスペースがあると見なします。さらに、StackPanelの子が実際に使用可能なスペースよりも多くのスペースを要求すると、StackPanelは嘘をつき、そのスペースを与えるふりをしますが、それをトリミングします。

(これは、StackPanelの極端な単純さに対して支払う代償です-実際には適合しないレイアウトを喜んで構築するため、骨の折れるほど単純です。StackPanelを使用するのは、どちらかが本当に無制限である場合のみにしてください。 ScrollViewer内にいるため、またはスペースが不足しないようにアイテムが十分に少ないと確信している場合、またはアイテムが取得されたときにパネルの端からアイテムが実行されることを気にしない場合は、スペース大きすぎて、レイアウトシステムが単にコンテンツをトリミングするよりも賢いことをしようとしないようにします。)

したがって、テキストの折り返しをオンにしても、ここでは役に立ちません。StackPanelは常に、テキストに十分なスペースがあるふりをするからです。

別のレイアウト構造が必要です。スタックパネルは、テキストの折り返しを開始するために必要なレイアウトの制約を課さないため、使用するのは間違っています。

これはあなたが望むことを大まかに行う簡単な例です:

<Grid VerticalAlignment="Top">
    <DockPanel>
        <TextBlock
            x:Name="DataGridTitle"
            VerticalAlignment="Top"
            DockPanel.Dock="Left"
            />
        <TextBox
            Name="VerticallyExpandMe"
            AcceptsReturn="True"
            TextWrapping="Wrap"
            Text="{Binding QueryString}"
            >
        </TextBox>
    </DockPanel>
</Grid>

まったく新しいWPFアプリケーションを作成し、それをメインウィンドウのコンテンツとして貼り付けると、必要な処理が実行されるはずです。TextBoxは、高さが1行から始まり、使用可能な幅を埋めます。テキストを入力すると、テキストを追加すると、一度に1行ずつ大きくなります。

もちろん、レイアウトの動作は常にコンテキストに敏感であるため、既存のアプリケーションの真ん中にそれを投げ込むだけでは不十分な場合があります。これは、固定サイズのスペース(ウィンドウの本体など)に貼り付けると機能しますが、幅に制約がないコンテキストに貼り付けると正しく機能しません。 (たとえば、ScrollViewerの内部、または水平のStackPanelの内部。)

したがって、これがうまくいかない場合は、レイアウトの他の場所で他の問題が発生している可能性があります。おそらく、他の場所でさらに多くのStackPanel要素があります。あなたの例の外観から、レイアウトに本当に必要なものを考えてそれを単純化するのに時間を費やす価値があるでしょう-負のマージンの存在、そしてその空のTextBlockのようなことをしていないように見える要素は通常過度に複雑なレイアウト。また、レイアウトが不必要に複雑になると、探している効果を実現するのが非常に難しくなります。

43
Ian Griffiths

または、TextBlockWidthを親のActualWidthにバインドすることで、次のように制約することもできます。

<TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}" 
           Height="Auto" />

これにより、高さのサイズも自動的に変更されます。

6
Erik Vullings

MaxWidthTextWrapping="WrapWithOverflow"を使用します。

2
Andreas

TextBlockを拡張するこのクラスを使用できます。自動縮小を行い、MaxHeight/MaxWidthを考慮に入れます。

public class TextBlockAutoShrink : TextBlock
    {
        private double _defaultMargin = 6;
        private Typeface _typeface;

        static TextBlockAutoShrink()
        {
            TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
        }

        public TextBlockAutoShrink() : base() 
        {
            _typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
            base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
        }

        private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var t = sender as TextBlockAutoShrink;
            if (t != null)
            {
                t.FitSize();
            }
        }

        void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            FitSize();
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            FitSize();

            base.OnRenderSizeChanged(sizeInfo);
        }


        private void FitSize()
        {
            FrameworkElement parent = this.Parent as FrameworkElement;
            if (parent != null)
            {
                var targetWidthSize = this.FontSize;
                var targetHeightSize = this.FontSize;

                var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
                var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;

                if (this.ActualWidth > maxWidth)
                {
                    targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
                }

                if (this.ActualHeight > maxHeight)
                {
                    var ratio = maxHeight / (this.ActualHeight);

                    // Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
                    // And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
                    ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;

                    targetHeightSize = (double)(this.FontSize *  ratio);
                }

                this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
            }
        }
    }
0
Uri Abramson

ドキュメントのレイアウトを変更しないようにする別の簡単なアプローチを使用しています。

主なアイデアは、変更を開始する前にコントロールWidthを設定しないことです。 TextBoxesの場合、SizeChangedイベントを処理します。

<TextBox TextWrapping="Wrap" SizeChanged="TextBox_SizeChanged" />

private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
    FrameworkElement box = (FrameworkElement)sender;
    if (e.PreviousSize.Width == 0 || box.Width < e.PreviousSize.Width)
        return;
    box.Width = e.PreviousSize.Width;
}
0
Denis Kucherov