Вложенные области прокрутки - PullRequest
11 голосов
/ 13 октября 2008

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

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

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

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

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

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

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

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

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

Ответы [ 7 ]

13 голосов
/ 15 октября 2009

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

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 голосов
/ 03 апреля 2012

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

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

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

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

0 голосов
/ 26 октября 2018

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

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, включая сложные сетки.

0 голосов
/ 16 июня 2017

для 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>
0 голосов
/ 20 мая 2014

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

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

0 голосов
/ 13 октября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...