2008-10-13 5 views
11

Je crée un contrôle pour WPF, et j'ai une question pour vous, les gourous WPF.Zones de défilement imbriquées

Je veux que mon contrôle puisse se développer pour s'adapter à une fenêtre redimensionnable.

Dans mon contrôle, j'ai une zone de liste que je veux développer avec la fenêtre. J'ai également d'autres contrôles autour de la zone de liste (boutons, texte, etc). Je veux être en mesure de définir une taille minimale sur mon contrôle, mais je veux que la taille de la fenêtre puisse être réduite en créant des barres de défilement pour afficher le contrôle.

Cela crée des zones de défilement imbriquées: une pour la zone de liste et un ScrollViewer envelopper le contrôle entier. Maintenant, si la zone de liste est définie sur la taille automatique, elle n'aura jamais de barre de défilement car elle est toujours dessinée en taille réelle dans ScrollViewer.

Je veux seulement que le contrôle défile si le contenu ne peut pas être plus petit, sinon je ne veux pas faire défiler le contrôle; Au lieu de cela, je veux faire défiler la zone de liste à l'intérieur du contrôle.

Comment puis-je modifier le comportement par défaut de la classe ScrollViewer? J'ai essayé d'hériter de la classe ScrollViewer et de remplacer les classes MeasureOverride et ArrangeOverride, mais je n'ai pas pu déterminer comment mesurer et organiser correctement l'enfant. Il semble que l'arrangement doit affecter le ScrollContentPresenter d'une manière ou d'une autre, et non l'enfant du contenu.

Toute aide ou suggestion serait grandement appréciée.

+0

Grande question. Nous avons nous-mêmes rencontré un problème très similaire. – cplotts

Répondre

0

Bien que je ne recommande pas la création d'une interface utilisateur qui nécessite des barres de défilement externes vous pouvez accomplir ce assez facilement:

<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> 

Je ne recommande pas vraiment. WPF fournit des systèmes de mise en page exceptionnels, comme Grid, et vous devriez essayer de permettre à l'application de se redimensionner au besoin. Peut-être que vous pouvez définir un MinWidth/MinHeight sur la fenêtre elle-même pour éviter ce redimensionnement?

+0

La difficulté est que je veux être en mesure de réduire la fenêtre ci-dessous la taille minimale sans recadrer les contrôles. –

+0

Avez-vous * besoin * de laisser la fenêtre rétrécir en dessous de cette taille minimale? Qu'est-ce que vous essayez d'accomplir en permettant cela? –

+0

Non, ne pas le faire, mais tout le problème disparaît si je ne le fais pas. Je veux rétrécir sous la taille minimale pour permettre des mises en page flexibles. Je suis en train de construire une application qui contient des commandes "ressemblant à des fenêtres" qui peuvent être dimensionnées et positionnées. –

2

Votre problème se pose, car Control s dans un ScrollViewer ont un espace pratiquement illimité disponible. Par conséquent, votre ListBox pense qu'il peut éviter le défilement en prenant toute la hauteur nécessaire pour afficher tous ses éléments. Bien sûr, dans votre cas, ce comportement a l'effet secondaire indésirable d'exercer le ScrollViewer trop.

L'objectif est donc d'obtenir le ListBox à utiliser la visible hauteur dans le ScrollViewer ssi il y a assez et une certaine hauteur minimale autrement. Pour y parvenir, le moyen le plus direct est d'hériter de ScrollViewer et de surcharger MeasureOverride() pour passer un availableSize de taille appropriée (c'est-à-dire le availableSize donné gonflé à la taille minimale au lieu de l'infini "habituel") au Visual s trouvé en utilisant VisualChildrenCount et GetVisualChild(int).

+0

+1 pour l'explication concernant l'espace disponible illimité. – Ridcully

12

J'ai créé une classe pour contourner ce problème:

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); 
    } 
} 

Il vaudra toujours la taille désirée (0,0), même si l'élément contenant veut être plus grand. Utilisation:

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

C'est la solution qui a fonctionné pour nous. – cplotts

+0

Solution propre, simple et élégante –

1

J'utilise la solution de Daniel. Cela fonctionne très bien. Je vous remercie.

Puis j'ai ajouté deux propriétés de dépendance booléenne à la classe décorateur: KeepWidth et . Ainsi, la nouvelle fonctionnalité peut être supprimée pour une dimension.

Cela nécessite un changement de 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); 
} 
0

Créer une méthode dans le code-behind qui définit MaxHeight de la zone de liste à la hauteur de ce que le contrôle est le contenant et d'autres contrôles. Si la Listbox a des contrôles/marges/padding au-dessus ou en dessous de ceux-ci, soustrayez leurs hauteurs de la hauteur du conteneur assignée à MaxHeight. Appelez cette méthode dans les fenêtres principales "chargé" et "fenêtre redimensionner" les gestionnaires d'événements.

Cela devrait vous donner le meilleur des deux mondes. Vous donnez à la ListBox une taille "fixe" qui la fera défiler malgré le fait que la fenêtre principale possède sa propre barre de défilement.

0

pour 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); 
    } 
} 

Code:

<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>