2010-11-21 4 views
1

J'ai une toile dans un scrollviewer. Pour pouvoir utiliser le scrollviewer, j'ai remplacé la méthode MeasureOverride du canevas pour retourner la taille de tous les enfants.ScrollViewer avec Canvas, redimensionner automatiquement le canevas au contenu incluant l'espace négatif

Cela fonctionne très bien, sauf que le canevas ne peut contenir que des éléments dans l'espace positif pour que le scrollviewer fonctionne correctement.

Je voudrais que l'utilisateur soit capable de faire glisser des éléments sans que la restriction de ceux-ci doive être sur le côté positif de l'origine de la toile. Donc, fondamentalement, je veux être en mesure de positionner les éléments n'importe où, comme à (-200, 10) ou (500, -20), et être en mesure de faire défiler de l'élément le plus à gauche (-200) au plus à droite (500), et de haut en bas. Pour compliquer les choses, le canevas peut être mis à l'échelle en utilisant LayoutTransform, et je voudrais inclure la zone de vue dans les barres de défilement, donc non seulement les limites min/max des enfants sont prises en compte par les barres de défilement, mais aussi le min/max de la zone de vue actuelle.

Est-ce que quelqu'un sait comment faire ce travail?

Répondre

0

Je pense que le ScrollViewer suppose que l'origine sera à (0, 0) - après tout, il n'y a aucun moyen de le dire autrement; Canvas est le seul panneau à connaître les origines spécifiées et non nulles. Une option simple pourrait être d'oublier un ScrollViewer, et d'implémenter votre propre fonctionnalité de panoramique - peut-être en faisant glisser, ou peut-être avec un gros bouton "scroll left" sur le côté gauche de la vue, un "scroll right" bouton sur la droite, etc - et l'implémenter en termes de transformation sur le contenu. Mais si vous avez besoin de vous en tenir à la métaphore de la barre de défilement classique, je pense que vous pourriez créer votre propre panneau (ou remplacer ArrangeOverride, ce qui revient au même) et décaler toutes les positions. Donc, si vous avez un élément à (20, -20) et un autre à (0, 5), vous devrez tout décaler de 20 vers le bas pour le faire rentrer dans un espace à base zéro; et quand vous étendez vos enfants, le premier irait à (20, 0) et le second à (0, 25).

Mais maintenant le défilement est bizarre. Si l'utilisateur fait défiler tout en bas et extrait quelque chose du bord inférieur, la vue reste en place; même avec le droit. Mais si on les fait défiler jusqu'en haut et que l'on fait glisser quelque chose du bord supérieur, soudainement tout saute vers le bas, parce que VerticalOffset du ScrollViewer était zéro et est toujours zéro, mais zéro signifie quelque chose de différent à cause de votre décalage de disposition.

Vous pouvez contourner l'étrangeté de défilement en associant les commandes HorizontalOffset et VerticalOffset. Si votre ViewModel corrige automatiquement ces propriétés (et lance PropertyChanged) dès qu'un contrôle passe à "négatif", vous pouvez alors faire défiler la vue vers le même contenu logique, même si vous dites maintenant au moteur de rendu de WPF que tout est ailleurs. Par exemple, votre ViewModel suivrait la position de défilement logique (zéro, avant de faire glisser l'élément négatif), et signalerait la position de défilement zéro à ScrollViewer (donc après le glisser, vous diriez au ScrollViewer que son VerticalOffset est maintenant 20).

1

Aujourd'hui, je travaille sur ce problème que :) Heureux de partager le code qui fonctionne:

public void RepositionAllObjects(Canvas canvas) 
{ 
    adjustNodesHorizontally(canvas); 
    adjustNodesVertically(canvas); 
} 

private void adjustNodesVertically(Canvas canvas) 
{ 
    double minLeft = Canvas.GetLeft(canvas.Children[0]); 
    foreach (UIElement child in canvas.Children) 
    { 
     double left = Canvas.GetLeft(child); 
     if (left < minLeft) 
      minLeft = left; 
    } 

    if (minLeft < 0) 
    { 
     minLeft = -minLeft; 
     foreach (UIElement child in canvas.Children) 
      Canvas.SetLeft(child, Canvas.GetLeft(child) + minLeft); 
    } 
} 

private void adjustNodesHorizontally(Canvas canvas) 
{ 
    double minTop = Canvas.GetTop(canvas.Children[0]); 
    foreach (UIElement child in canvas.Children) 
    { 
     double top = Canvas.GetTop(child); 
     if (top < minTop) 
      minTop = top; 
    } 

    if (minTop < 0) 
    { 
     minTop = -minTop; 
     foreach (UIElement child in canvas.Children) 
      Canvas.SetTop(child, Canvas.GetTop(child) + minTop); 
    } 
} 

Appelez dès maintenant RepositionAllObjects méthode pour réaligner les objets en cas de besoin.

Bien sûr, vous devez avoir votre propre toile dérivée de Canvas. Et cette méthode est nécessaire (vous pouvez modifier si vous le souhaitez):

public static Rect GetDimension(UIElement element) 
    { 
     Rect box = new Rect(); 
     box.X = Canvas.GetLeft(element); 
     box.Y = Canvas.GetTop(element); 
     box.Width = element.DesiredSize.Width; 
     box.Height = element.DesiredSize.Height; 
     return box; 
    } 

    protected override Size MeasureOverride(Size constraint) 
    { 
     Size availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity); 
     double minX = 900000; //some dummy high number 
     double minY = 900000; //some dummy high number 
     double maxX = 0; 
     double maxY = 0; 

     foreach (UIElement element in this.Children) 
     { 
      element.Measure(availableSize); 

      Rect box = GetDimension(element); 
      if (minX > box.X) minX = box.X; 
      if (minY > box.Y) minY = box.Y; 
      if (maxX < box.X + box.Width) maxX = box.X + box.Width; 
      if (maxY < box.Y + box.Height) maxY = box.Y + box.Height; 
     } 

     if (minX == 900000) minX = 0; 
     if (minY == 900000) minY = 0; 

     return new Size { Width = maxX - minX, Height = maxY - minY }; 
    } 

Maintenant, tout ce que vous devez faire est envelopper cette toile dans un ScrollViewer.

J'espère que ça aide!

Questions connexes