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

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

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

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

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

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

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

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

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

Любая помощь / предложения будут очень благодарны.

Отличный вопрос. Мы сами столкнулись с очень похожей проблемой.

cplotts 29.03.2010 19:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
1
5 001
7

Ответы 7

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

<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 в самом окне, чтобы предотвратить это изменение размера?

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

Chris Jester-Young 13.10.2008 23:11

Вы имеют позволяете окну уменьшаться ниже этого минимального размера? Чего вы пытаетесь достичь, позволяя это?

Bob King 13.10.2008 23:12

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

Chris Jester-Young 13.10.2008 23:45

Тогда приведенный выше код должен работать на вас. Просто убедитесь, что ваши части находятся в столбцах с правильным размером «*» и «Авто», и убедитесь, что внешний ScrollViewer использует «Авто» для Horizontal / VerticalScrollbarVisibility.

Bob King 13.10.2008 23:47

И вы можете оставить их как всегда «Видимые», чтобы предотвратить скачок, когда нужно внезапно стать видимым.

Bob King 13.10.2008 23:47

Ваше решение не работает. Добавьте несколько элементов в ListBox, и вы увидите, что список никогда не становится прокручиваемым; прокручивается всегда родительский ScrollViewer. Я думаю, что Джош Джи (как и я) искал решение, в котором внешний ScrollViewer будет использоваться только в случае необходимости для MinWidth / MinHeight.

Daniel 15.10.2009 15:43

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

Поэтому цель состоит в том, чтобы заставить ListBox использовать высоту видимый внутри ScrollViewer, если ее достаточно, и определенную минимальную высоту в противном случае. Для достижения этого самый прямой способ - унаследовать от ScrollViewer и переопределить MeasureOverride(), чтобы передать availableSize подходящего размера (то есть заданный availableSize, увеличенный до минимального размера вместо «обычной» бесконечности) в Visual, найденные с помощью VisualChildrenCount и GetVisualChild(int).

+1 за объяснение относительно неограниченного доступного места.

Ridcully 16.09.2010 17:25

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

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>

Чистое, простое, элегантное решение

Scott B 09.03.2012 20:57

Я использовал решение Даниэльs. Это прекрасно работает. Спасибо.

Затем я добавил к классу декоратора два логических свойства зависимости: 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);
}

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

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

для 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>

В итоге я объединил ответ Дэниелс и ответ Хайнера. Я решил опубликовать решение полностью, чтобы людям было легче его принять в случае необходимости. Вот мой класс декоратора:

public class RestrictDesiredSizeDecorator : Decorator
{
    public static readonly DependencyProperty KeepWidth;
    public static readonly DependencyProperty KeepHeight;

    #region Dependency property setters and getters
    public static void SetKeepWidth(UIElement element, bool value)
    {
        element.SetValue(KeepWidth, value);
    }

    public static bool GetKeepWidth(UIElement element)
    {
        return (bool)element.GetValue(KeepWidth);
    }

    public static void SetKeepHeight(UIElement element, bool value)
    {
        element.SetValue(KeepHeight, value);
    }

    public static bool GetKeepHeight(UIElement element)
    {
        return (bool)element.GetValue(KeepHeight);
    }
    #endregion

    private Size _lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

    static RestrictDesiredSizeDecorator()
    {
        KeepWidth = DependencyProperty.RegisterAttached(
            nameof(KeepWidth),
            typeof(bool),
            typeof(RestrictDesiredSizeDecorator));

        KeepHeight = DependencyProperty.RegisterAttached(
            nameof(KeepHeight),
            typeof(bool),
            typeof(RestrictDesiredSizeDecorator));
    }

    protected override Size MeasureOverride(Size constraint)
    {
        Debug.WriteLine("Measure: " + constraint);

        var keepWidth = GetValue(KeepWidth) as bool? ?? false;
        var keepHeight = GetValue(KeepHeight) as bool? ?? false;

        var innerWidth = keepWidth ? constraint.Width : Math.Min(this._lastArrangeSize.Width, constraint.Width);
        var innerHeight = keepHeight ? constraint.Height : 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);
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        Debug.WriteLine("Arrange: " + arrangeSize);

        if (_lastArrangeSize != arrangeSize)
        {
            _lastArrangeSize = arrangeSize;
            base.MeasureOverride(arrangeSize);
        }

        return base.ArrangeOverride(arrangeSize);
    }
}

и вот как я использую его в xaml:

<ScrollViewer>
    <StackPanel Orientation = "Vertical">
        <Whatever />

        <decorators:RestrictDesiredSizeDecorator MinWidth = "100" KeepHeight = "True">
            <TextBox
                Text = "{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
                Height = "Auto"
                MaxHeight = "360"
                VerticalScrollBarVisibility = "Auto"
                HorizontalScrollBarVisibility = "Auto"
                AcceptsReturn = "True"
                AcceptsTab = "True"
                TextWrapping = "WrapWithOverflow"
                />
        </decorators:RestrictDesiredSizeDecorator>

        <Whatever />
    </StackPanel>
</ScrollViewer

Вышеупомянутое создает текстовое поле, которое будет увеличиваться по вертикали (пока не достигнет MaxHeight), но будет соответствовать ширине родителя без увеличения внешнего ScrollViewer. Изменение размера окна / ScrollViewer до ширины менее 100 заставит внешний ScrollViewer отображать горизонтальные полосы прокрутки. Также можно использовать другие элементы управления с внутренними ScrollViewers, включая сложные сетки.

Другие вопросы по теме