web-dev-qa-db-fra.com

Comment faire fonctionner scrollviewer avec Height réglé sur Auto dans WPF?

J'ai appris que si la hauteur d'une ligne de la grille, où réside la ScrollViewer, est définie sur Auto, la barre de défilement verticale ne prendra pas effet, car la taille réelle de la ScrollViewer peut être supérieure à la hauteur visible. Donc, afin de faire fonctionner la barre de défilement, je dois définir la hauteur soit sur un nombre fixe, soit sur la hauteur de l'étoile.

Cependant, j'ai maintenant cette exigence, à savoir que deux vues différentes résident dans deux lignes de grille et que je dispose d'un bouton bascule pour basculer entre ces deux vues: lorsqu'une vue est affichée, l'autre est masquée/disparaît. J'ai donc défini deux lignes, les deux hauteurs sont définies comme suit: Auto. Et je lie la visibilité de la vue dans chaque ligne à une propriété booléenne de mon ViewModel (l'une est convertie de True à Visible et l'autre de True à Collapsed. L'idée est de savoir si la visibilité d'une vue est Collapsed/view sera changé en 0 automatiquement.

La vue show/hidden fonctionne bien. Cependant, dans une vue, j'ai une ScrollViewer, qui, comme je l'ai mentionné, ne fonctionne pas lorsque la hauteur de la ligne est définie sur Auto. Quelqu'un peut-il me dire comment satisfaire à cette exigence tout en maintenant la ScrollViewer automatiquement »? Je suppose que je peux définir la hauteur en code-behind. Mais comme j'utilise MVVM, cela nécessiterait une communication/notification supplémentaire. Y a-t-il un moyen plus simple de faire cela? 

12
tete

Changez la hauteur de Auto à *, si vous le pouvez.

Exemple:

    <Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="200" Width="525">
    <StackPanel Orientation="Horizontal"  Background="LightGray">

        <Grid Width="100">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll1">
                <Border Height="300" Background="Red" />
            </ScrollViewer>
            <TextBlock Text="{Binding ElementName=_scroll1, Path=ActualHeight}" Grid.Row="1"/>
        </Grid>

        <Grid Width="100">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
                <ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll2">
                    <Border Height="300" Background="Green" />
                </ScrollViewer>
            <TextBlock Text="{Binding ElementName=_scroll2, Path=ActualHeight}" Grid.Row="1"/>
        </Grid>
    </StackPanel>
</Window>
11

Dans MVVM, la méthode qui a fonctionné pour moi a été de lier la hauteur de la variable ScrollViewer à la variable ActualHeight du contrôle parent (qui est toujours de type UIElement).

ActualHeight est une propriété en lecture seule qui est définie uniquement après que le contrôle a été dessiné à l'écran. Cela peut changer si la fenêtre est redimensionnée. 

<StackPanel>
    <ScrollViewer Height="{Binding Path=ActualHeight, 
           RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}">
        <TextBlock Text=Hello"/>
    </ScrollViewer>
</StackPanel>

Mais que se passe-t-il si le contrôle parent a une hauteur infinie?

Si le contrôle parent a une hauteur infinie, nous avons un problème plus important. Nous devons continuer à régler la taille de tous les parents jusqu'à ce que nous atteignions un contrôle avec une hauteur non infinie.

Snoop est absolument précieux pour cela:

 enter image description here

Si la "Hauteur" de l'un des éléments XAML est 0 ou NaN, vous pouvez le définir sur l'une des options suivantes:

  • Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
  • VerticalAlignment="Stretch"
  • Height="Auto"

Astuce: Utilisez VerticalAlignment="Stretch" si vous êtes l'enfant d'une Grid avec un <RowDefinition Height="*"> et le Binding RelativeSource... ailleurs si cela ne fonctionne pas.


Si vous êtes intéressé, voici toutes mes tentatives précédentes pour résoudre ce problème:

Annexe A: tentative précédente 1

Peut aussi utiliser ceci:

Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"

Annexe B: tentative précédente 2

Informations utiles: voir WPF - Hauteur auto en combinaison avec MaxHeight

Si rien ne semble fonctionner, c'est probablement parce que la ActualHeight du parent est 0 (donc rien n'est visible) ou énorme (le visualiseur de défilement n'a donc jamais besoin d'apparaître). C'est plus un problème s'il y a des grilles profondément imbriquées, avec un visionneur de défilement juste en bas.

  • Utilisez Snoop pour rechercher la ActualHeight du parent StackPanel. Dans les propriétés, filtrez avec le Word "Actual", ce qui ramène ActualHeight et ActualWidth.
  • Si ActualHeight est égal à zéro, donnez-lui une hauteur minimale en utilisant MinHeight, afin que nous puissions au moins voir quelque chose.
  • Si ActualHeight est si énorme qu’il se détache du bord de l’écran (16 000), attribuez-lui une hauteur maximale raisonnable à l’aide de MaxHeight pour que les barres de défilement apparaissent.

Une fois que les barres de défilement apparaissent, nous pouvons alors les nettoyer:

  • Liez la Height de la StackPanel ou la Grid à la ActualHeight du parent.

Enfin, mettez une ScrollViewer dans cette StackPanel.

Annexe C: tentative précédente 3

Il se trouve que cela peut parfois échouer:

Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"

La raison? Si la liaison échoue, la hauteur sera nulle et rien ne sera vu. La liaison peut échouer si nous lions un élément non accessible. La liaison échouera si nous allons up l'arborescence visuelle, puis down à un nœud feuille (par exemple, jusqu'à la grille parente, puis jusqu'à la ActualHeight d'une ligne attachée à cette grille). C'est pourquoi la liaison à la ActualWidth d'une RowDefinition ne fonctionnera tout simplement pas.

Annexe D: tentative précédente 4

J'ai finalement réussi à faire fonctionner cela en m'assurant que Height=Auto pour tous les éléments parents de notre part correspondait au premier élément <Grid> du contrôle UserControl.

20
Contango

Vous pouvez soit définir une hauteur fixe sur votre ScrollViewer, mais vous devez également considérer que la deuxième ligne de votre grille aura aussi cette hauteur puisque le premier enfant de la ligne sera ScrollViewer et que la hauteur de la ligne est auto, ou vous liez la hauteur à ScrollViewer. un autre contrôle dans votre mise en page. Nous ne savons pas à quoi ressemble votre mise en page.

À la fin, si vous n'aimez ni l'un ni l'autre, réglez la hauteur de la ligne sur * comme suggéré par swiszcz ou hackez wpf et écrivez votre propre panneau personnalisé qui permettra de mettre en page tout ce qui est possible dans chaque univers parallèle ou quelque chose comme ça. :)

0
dev hedgehog

Ce que je découvre, c'est que vous devez placer votre ScrollViewer dans un conteneur qui a le Height=Auto ou vous obtenez son parent Heigh Actual Size et l'appliquer à ce conteneur.

Dans mon cas, j'ai UserControl comme

  <Grid Margin="0,0,0,0" Padding="0,2,0,0">
                    <ScrollViewer Height="Auto" ZoomMode="Disabled" IsVerticalScrollChainingEnabled="True"  VerticalAlignment="Top"
                     HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Disabled"
                     VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Visible"> 
                        <ListView  ItemsSource="{x:Bind PersonalDB.View, Mode=OneWay}" x:Name="DeviceList"
                           ScrollViewer.VerticalScrollBarVisibility="Hidden" 
                    ItemTemplate="{StaticResource ContactListViewTemplate}"
                    SelectionMode="Single"
                    ShowsScrollingPlaceholders="False"
                    Grid.Row="1" 
                    Grid.ColumnSpan="2"
                    VerticalAlignment="Stretch"
                    BorderThickness="0,0,0,0"
                    BorderBrush="DimGray">
                            <ListView.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <ItemsStackPanel AreStickyGroupHeadersEnabled="False" />
                                </ItemsPanelTemplate>
                            </ListView.ItemsPanel>
                            <ListView.GroupStyle>
                                <GroupStyle>
                                    <GroupStyle.HeaderTemplate>
                                        <DataTemplate x:DataType="local1:GroupInfoList">
                                            <TextBlock Text="{x:Bind Key}" 
                                       Style="{ThemeResource TitleTextBlockStyle}"/>
                                        </DataTemplate>
                                    </GroupStyle.HeaderTemplate>
                                </GroupStyle>
                            </ListView.GroupStyle>
                        </ListView>
                    </ScrollViewer>
                </Grid> 

Et je l’ajoute dinamiquement à ContentControl qui est dans un Page.

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,12,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="70"  /> 
            <RowDefinition Height="*" MinHeight="200"  />
        </Grid.RowDefinitions> 
 <Grid Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"     >
            <ContentControl x:Name="UIControlContainer"  />
        </Grid>
    </Grid>

Notez que Heigh de la Row est *

Lorsque je remplis ContentControl j'utilise ce code dans l'événement Loaded

  UIControlContainer.Content = new UIDeviceSelection() { 
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch,
Height = UIControlContainer.ActualHeight,
Width = UIControlContainer.ActualWidth
};

Et aussi lorsque ContentControl change sa taille, vous devez mettre à jour la taille de UserControl.

 UIControlContainer.SizeChanged += UIControlContainer_SizeChanged;

 private void UIControlContainer_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (UIControlContainer.Content != null)
            {
                if (UIControlContainer.Content is UserControl)
                {
                    (UIControlContainer.Content as UserControl).Height = UIControlContainer.ActualHeight;
                    (UIControlContainer.Content as UserControl).Width = UIControlContainer.ActualWidth;
                }
            }
        }

Prendre plaisir!

P.S. En fait, je l'ai fait pour UWP.

0