none
WPF: динамическое изменение ширины TextBlock внутри ListBoxItem. Можно ли это сделать? RRS feed

  • Вопрос

  • Есть ListBox, который отображает коллекцию моих элементов. Я использую TextBlock для вывода поля Name - это очень длинный текст. Если задать для TextBlock фиксированную Width и установить TextWrapping="Wrap", то текст будет переноситься, но данная область всегда останется фиксированной. Я бы хотел привязаться к какой-то изменяемой величине, например к ширине окна, чтобы при изменении размеров окна менялась ширина ListBox и TextBlock внутри его ListBoxItems. Возможно ли это?

    <ListBox
      ItemContainerStyle="{DynamicResource ResourceKey=MyListBoxItem}"
      ItemsSource="{Binding Source={StaticResource ResourceKey=LayersCollection}}"
    >
    </ListBox>                                               

    Стиль описан в разделе Windows.Resources:

    <Style TargetType="{x:Type ListBoxItem}" x:Key="MyListBoxItem">
                <Setter Property="Background" Value="#FFBF40"></Setter>
                <Setter Property="BorderBrush" Value="#FFBF40"></Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">

                            <Border
                                Margin="3" BorderThickness="1" BorderBrush="Brown" Padding="3"
                                CornerRadius="5" Background="#FFD073"
                                >
                                <Grid>
                                    <TextBlock Text="{Binding Path=Name}" TextWrapping="Wrap"></TextBlock>
                                </Grid>
                            </Border>

                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

    Изначально был вариант, в котором использовался DataTemplate с DataType="{x:Type local:MyTypeName}" - но и там TextBlock растягивались под всю длину текста и отказывались реагировать на свойство TextWrapping="Wrap".

                            

Ответы

  • Про перенос я немного упустил. Доработайте мой XAML так:

        <Grid>
            <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                <ListBoxItem HorizontalContentAlignment="Stretch">
                    <TextBlock Background="LightBlue" Text="1111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111" TextWrapping="Wrap"></TextBlock>
                </ListBoxItem>
            </ListBox>
        </Grid>

    По сути я просто отключил горизонтальную полосу прокрутки у листбокса. Строка ScrollViewer.HorizontalScrollBarVisibility="Disabled"

    Заливку у TextBlock сделал для наглядности. что мы имеем полное заполнение и перенос одновременно


    VB.Net - WPF, UWP

    • Помечено в качестве ответа Denis Prokofjev 29 мая 2017 г. 13:32
    Отвечающий

Все ответы

  • Пожалуй можно вопрос упростить до минимума:

    <Grid>
            <ListBox HorizontalAlignment="Stretch"
                     Background="Azure">
                <ListBox.Items>
                    <ListBoxItem>
                        <TextBlock
                            Text="111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
                            TextWrapping="Wrap"
                            >
                        </TextBlock>
                    </ListBoxItem>
                </ListBox.Items>
            </ListBox>
        </Grid>

    Вот простой код - возможно ли сделать так, чтобы ListBoxItem всегда умещался в размеры окна? Ну и/или TextBlock внутри него. HorizontalAlignment="Stretch" не помогает, уже во всех местах ее пробовал установить - эффекта не достигаю :(

    PS

    А вот просто TextBlock с TextWrapping="Wrap" в корневом Grid замечательно работает по умолчанию. Что ему не хватает, когда он в составе ListBox?


  • Пробую через конвертер:

    public class TextBlockWidthConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return 100;
            }

            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return DependencyProperty.UnsetValue;
            }
        }

    В разметке XAML в TextBlock написал так: Width="{Binding Converter={StaticResource myTextBlockWidthConverter}}"

    И ведь получается!!! Когда окно открывается, все TextBlock имеют фиксированную ширину = 100. Если это приемлемый путь решения задачи, подскажите пожалуйста, как мне внутри Converter получить ширину ListBox или самого окна? Какие объекты нужно использовать и как?

    PS Посмотрел в отладчике, конвертер в этом случае вызывается один раз и не реагирует на изменения размеров окна или переключение табов (использую TabControl, чтобы отделать примеры друг от друга). Как то все не здорово :(
  • У ListBox нашел событие SizeChanged() (от FrameworkElement), которое вызывается при любом интерактивном действии, которое приводит к визуальному изменению размеров (например меняем размер окна). В принципе, оно подходит для моих целей.

    if(e.WidthChanged)

    { ListBox lbx = sender as ListBox;

       ItemContainerGenerator icg = lbx.ItemContainerGenerator;
       ListBoxItem firstLBI = icg.ContainerFromIndex(0) as ListBoxItem; // для примера работаю с 1-м элементом

    До этих пор все работает ожидаемо - объекты существуют. Далее, я пытаюсь найти свой TextBlock, размером которого мне нужно управлять. В шаблоне у него задано имя Name="tblckMainText".

       object elem = firstLBI.FindName("tblckMainText"); // от FrameworkElement

    И на это все - elem равен null. Почему? В msdn про метод FindName написано, что имя ищется рекурсивно по всем дочерним элементам. Насколько я понимаю, ListBoxItem должен иметь дочерние элементы - это как минимум элементы шаблона, который описан в ресурсах, в элементе Style для типа ListBoxItem.

    И не нашел, как получить все дочерние элементы для FrameworkElement, было бы интересно понять как это можно сделать. И Parent у него = null. В общем, запутался окончательно. Подскажите, что не так делаю. Мне надо при изменении размеров ListBox подгонять под него TextBlock, который отображает длинную строку с переносами текста.

  • Говорят иногда полезно поговорить самому с собой, но вас это звело в знатные дебри ))

    Все банально и просто:

        <Grid>
            <ListBox>
                <ListBoxItem HorizontalContentAlignment="Stretch">
                    <TextBlock Text="1111111111111111111111111111111111111111111111"></TextBlock>
                </ListBoxItem>
            </ListBox>
        </Grid>

    Как вы заметили я всего лишь добавил HorizontalContentAlignment="Stretch" к ListBoxItem.

    Тоже самое можно проделать на уровне стиля через Setter Property.

    Обратите внимание, что если у элемента внутри будет стоять HorizontalAlignment, например, в Left, то установка выше проигнорируется.


    VB.Net - WPF, UWP

    Отвечающий
  • Как вариант, работает такой код:

    private void GLayers_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                if (e.WidthChanged)
                {
                    IEnumerable<DependencyObject> txtBlocks = (sender as DependencyObject).Descendants<TextBlock>();

                    double _width = (sender as FrameworkElement).ActualWidth;

                    foreach (DependencyObject item in txtBlocks)
                    {
                        if ((item as TextBlock).Name == "txtDescr")
                            (item as TextBlock).Width = Math.Max(_width - 30, 0);
                    }
                }
            }

    Но я не могу его использовать как решение, потому что он работает у меня не стабильно. В простом окне с небольшим количеством элементов он работает быстро - после запуска приложения программа дважды входит в данный обработчик (создал счетчик в классе окна и увеличиваю его в этой процедуре). И дальше выравнивание происходит быстро и без ошибок.

    В более сложном окне - где элементов много, есть Splitter, который влияет на ширину колонки Grid, в которой находится данный ListBox происходит следующее: при старте программа подвисает на на несколько секунд. И потом, когда окно начинает реагировать на мои действия, оказывается, что в данный обработчик было более 1000 входов. При этом после следующих попыток изменить размер области количество входов в GLayers_SizeChanged становится более 5000 и порой программа просто зависает, такое ощущение, что она не вылезает из этих пересчетов. Хотя бывают моменты, когда устанавливается какое-то равновесие и все начинает работать как в простом примере - быстро и предсказуемо.

    В чем может быть проблема?

  • Ура! Наконец-то нашелся сочувствующий. Я пишу свои шаги, даже неудачные, чтобы показать, что стараюсь не быть халявщиком - задал вопрос и заснул :))

    Нет, ваш вариант не прошел вот текст:

    <ListBox>
                
                    <ListBoxItem HorizontalAlignment="Stretch">
                        <TextBlock
                            Text="111111111  1111111111  11111111111  111111111  111111111111 11 111111111111111 1111111111 1111111 111111"
                            TextWrapping="Wrap"
                            ></TextBlock>
                    </ListBoxItem>
                
            </ListBox>

    Моя задача - автоматизировать перенос по словам в TextBlock. Переноса не происходит - TextBlock всегда максимального размера. Ну или того размера, который в нем задать в XAML. Если я не прав - поправьте, покажите, где я вас понял не правильно :)

    Еще раз прочитал, понял что я использую HorizontalAlignment, а вы HorizontalContentAlignment. Исправил - все равно нет результата. Ваш пример целиком скопировал - тоже не дал эффекта. Переноса по словам не происходит.


  • Про перенос я немного упустил. Доработайте мой XAML так:

        <Grid>
            <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                <ListBoxItem HorizontalContentAlignment="Stretch">
                    <TextBlock Background="LightBlue" Text="1111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111" TextWrapping="Wrap"></TextBlock>
                </ListBoxItem>
            </ListBox>
        </Grid>

    По сути я просто отключил горизонтальную полосу прокрутки у листбокса. Строка ScrollViewer.HorizontalScrollBarVisibility="Disabled"

    Заливку у TextBlock сделал для наглядности. что мы имеем полное заполнение и перенос одновременно


    VB.Net - WPF, UWP

    • Помечено в качестве ответа Denis Prokofjev 29 мая 2017 г. 13:32
    Отвечающий
  • О да!!!!

    Это решение! Перенос сразу заработал. попробовал даже с более сложным контентом:

    <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                    <ListBoxItem HorizontalContentAlignment="Stretch">
                    <ListBoxItem.Content>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="3*"></ColumnDefinition>
                                <ColumnDefinition Width="*"></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <TextBlock
                            TextWrapping="Wrap"
                            Text="111111111 11111111111111111 11111111111111111111 1111111111111111111111 11111111111 111111111 1111111 11111"></TextBlock>
                            <Button Grid.Column="1" Content="123"></Button>
                        </Grid>
                    </ListBoxItem.Content>
                </ListBoxItem>
            </ListBox>

    Все работает! Огромное спасибо!!! Я пробовал искать и в других местах про "умные" TextBlock с такими авто переносами - везде что-то, что я либо не понял, либо не смог применить. Такое просто решение - просто класс!