Вложенные области прокрутки

Я создаю элемент управления для WPF, и у меня есть вопрос для вас, гуру WPF.

Я хочу, чтобы мой элемент управления мог расширяться, чтобы соответствовать изменяемому размеру.

В моем контроле у ​​меня есть окно списка, которое я хочу расширить с помощью окна. У меня также есть другие элементы управления вокруг окна списка (кнопки, текст и т.д.).

Я хочу иметь возможность установить минимальный размер моего элемента управления, но я хочу, чтобы окно могло быть меньше размерами, создавая полосы прокрутки для просмотра элемента управления.

Это создает вложенные области прокрутки: один для списка и ScrollViewer, который обертывает весь элемент управления.

Теперь, если в поле списка установлен автоматический размер, он никогда не будет иметь полосу прокрутки, потому что он всегда отображается в полном размере в ScrollViewer.

Я хочу, чтобы элемент управления прокручивал, если содержимое не может быть меньше, иначе я не хочу прокручивать элемент управления; вместо этого я хочу прокрутить список внутри элемента управления.

Как изменить поведение класса ScrollViewer по умолчанию? Я пробовал наследовать от класса ScrollViewer и переопределять классы MeasureOverride и ArrangeOverride, но я не мог понять, как правильно измерить и уладить ребенка. Похоже, что аранжировка должна каким-то образом влиять на ScrollContentPresenter, а не на фактический контент.

Любая помощь/предложения будут высоко оценены.

Ответ 1

Я создал класс для решения этой проблемы:

public class RestrictDesiredSize : Decorator
{
    Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

    protected override Size MeasureOverride(Size constraint)
    {
        Debug.WriteLine("Measure: " + constraint);
        base.MeasureOverride(new Size(Math.Min(lastArrangeSize.Width, constraint.Width),
                                      Math.Min(lastArrangeSize.Height, constraint.Height)));
        return new Size(0, 0);
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        Debug.WriteLine("Arrange: " + arrangeSize);
        if (lastArrangeSize != arrangeSize) {
            lastArrangeSize = arrangeSize;
            base.MeasureOverride(arrangeSize);
        }
        return base.ArrangeOverride(arrangeSize);
    }
}

Он всегда возвращает желаемый размер (0,0), даже если содержащий элемент хочет быть больше. Использование:

<local:RestrictDesiredSize MinWidth="200" MinHeight="200">
     <ListBox />
</local>

Ответ 2

Возникает проблема, потому что Control внутри ScrollViewer имеет практически неограниченное пространство. Поэтому ваш внутренний ListBox считает, что он может избежать прокрутки, взяв полную высоту, необходимую для отображения всех ее элементов. Конечно, в вашем случае это поведение имеет нежелательный побочный эффект чрезмерного использования внешнего ScrollViewer.

Цель состоит в том, чтобы использовать ListBox для использования видимой высоты в ScrollViewer, если этого достаточно, и в противном случае будет минимальная высота. Чтобы достичь этого, самым прямым способом является наследование от ScrollViewer и переопределить MeasureOverride(), чтобы передать соответствующий размер availableSize (который представляет собой заданный availableSize, взорванный до минимального размера вместо "обычной" бесконечности), в Visual, найденный с помощью VisualChildrenCount и GetVisualChild(int).

Ответ 3

Я использовал Daniel решение. Это отлично работает. Спасибо.

Затем я добавил два свойства зависимостей boolean к классу декоратора: KeepWidth и KeepHeight. Таким образом, новая функция может быть подавлена ​​для одного измерения.

Это требует изменения в MeasureOverride:

protected override Size MeasureOverride(Size constraint)
{
    var innerWidth = Math.Min(this._lastArrangeSize.Width, constraint.Width);
    var innerHeight = Math.Min(this._lastArrangeSize.Height, constraint.Height);
    base.MeasureOverride(new Size(innerWidth, innerHeight));

    var outerWidth = KeepWidth ? Child.DesiredSize.Width : 0;
    var outerHeight = KeepHeight ? Child.DesiredSize.Height : 0;
    return new Size(outerWidth, outerHeight);
}

Ответ 4

Хотя я бы не рекомендовал создавать пользовательский интерфейс, который требует внешних полос прокрутки, вы можете выполнить это довольно легко:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >    
    <ScrollViewer HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ListBox Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" MinWidth="200"/>
            <Button Grid.Row="0" Grid.Column="1" Content="Button1"/>
            <Button Grid.Row="1" Grid.Column="1" Content="Button2"/>
            <Button Grid.Row="2" Grid.Column="1" Content="Button3"/>
        </Grid>
    </ScrollViewer>
</Window>

Я действительно этого не рекомендую. WPF предоставляет исключительные системы компоновки, такие как Grid, и вы должны попытаться разрешить приложению изменять размеры по мере необходимости. Возможно, вы можете установить MinWidth/MinHeight в самом окне, чтобы предотвратить это изменение размера?

Ответ 5

Создайте метод в коде, который устанавливает ListBox MaxHeight на высоту любого элемента управления, содержащего его, и других элементов управления. Если в Listbox есть какие-либо элементы управления/поля/надписи над или под ним, вычтите их высоты из высоты контейнера, назначенной MaxHeight. Вызовите этот метод в обработчиках событий "загруженные" и "оконные изменения" в главных окнах.

Это должно дать вам лучшее из обоих миров. Вы даете ListBox "фиксированный" размер, который заставит его прокручиваться, несмотря на то, что главное окно имеет свою собственную полосу прокрутки.

Ответ 6

для 2 ScrollViewer

   public class ScrollExt: ScrollViewer
{
    Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

    public ScrollExt()
    {

    }
    protected override Size MeasureOverride(Size constraint)
    {
        base.MeasureOverride(new Size(Math.Min(lastArrangeSize.Width, constraint.Width),
                                      Math.Min(lastArrangeSize.Height, constraint.Height)));
        return new Size(0, 0);
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        if (lastArrangeSize != arrangeSize)
        {
            lastArrangeSize = arrangeSize;
            base.MeasureOverride(arrangeSize);
        }
        return base.ArrangeOverride(arrangeSize);
    }
}

код:

  <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <Grid >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Background="Beige" Width="600" Text="Example"/>
            <Grid Grid.Column="1" x:Name="grid">
                    <Grid Grid.Column="1" Margin="25" Background="Green">
                    <local:ScrollExt HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                        <Grid Width="10000" Margin="25" Background="Red" />
                    </local:ScrollExt>
                    </Grid>
                </Grid>
        </Grid>
    </ScrollViewer>