2010-11-17 23 views
14

Ich habe eine Anwendung, die sehr "verbindungsbasiert" ist, d. H. Mehrere Eingänge/Ausgänge.Wie kann ich ein hängendes Kabel in WPF simulieren?

Das UI-Konzept eines "Kabels" ist genau das, was ich suche, um das Konzept für den Benutzer klar zu machen. Einen ähnlichen Ansatz verfolgte Propellerhead in der Reason-Software für Audiokomponenten, dargestellt in this YouTube video (fast forward to 2m:50s).

Ich kann dieses Konzept in GDI arbeiten, indem ich einen Spline von Punkt A nach Punkt B male, es gibt eine elegantere Art, Pfade oder etwas in WPF dafür zu verwenden, aber wo fängst du an? Gibt es eine gute Möglichkeit, die Animation des Kabelschwingens zu simulieren, wenn Sie es greifen und schütteln?

Ich bin auch offen für die Kontrolle Bibliotheken (kommerzielle oder Open Source), wenn dieses Rad bereits für WPF erfunden wurde.

Update: Dank der Links in den Antworten bin ich fast da.

alt text

Ich habe ein BezierCurve programmatisch erstellt, mit Punkt 1 ist (0, 0), Punkt 2 der Boden „hängen“ Punkt zu sein, und Punkt 3 zu sein, wo sich der Mauscursor ist. Ich habe eine PointAnimation für Punkt 2 mit einer ElasticEase Beschleunigungsfunktion erstellt, die angewendet wird, um den "Swinging" -Effekt zu erhalten (d. H. Den Mittelpunkt um ein Bit herum zu springen).

Das einzige Problem ist, die Animation scheint ein wenig zu spät zu laufen. Ich starte das Storyboard jedes Mal, wenn sich die Maus bewegt. Gibt es einen besseren Weg, diese Animation zu machen? Meine Lösung so weit hier befindet:

Bezier Curve Playground

Code:

private Path _path = null; 
private BezierSegment _bs = null; 
private PathFigure _pFigure = null; 
private Storyboard _sb = null; 
private PointAnimation _paPoint2 = null; 
ElasticEase _eEase = null; 

private void cvCanvas_MouseMove(object sender, MouseEventArgs e) 
{ 
    var position = e.GetPosition(cvCanvas); 
    AdjustPath(position.X, position.Y); 
} 

// basic idea: when mouse moves, call AdjustPath and draw line from (0,0) to mouse position with a "hang" in the middle 
private void AdjustPath(double x, double y) 
{ 
    if (_path == null) 
    { 
     _path = new Path(); 
     _path.Stroke = new SolidColorBrush(Colors.Blue); 
     _path.StrokeThickness = 2; 
     cvCanvas.Children.Add(_path); 

     _bs = new BezierSegment(new Point(0, 0), new Point(0, 0), new Point(0, 0), true); 

     PathSegmentCollection psCollection = new PathSegmentCollection(); 
     psCollection.Add(_bs); 

     _pFigure = new PathFigure(); 
     _pFigure.Segments = psCollection; 
     _pFigure.StartPoint = new Point(0, 0); 


     PathFigureCollection pfCollection = new PathFigureCollection(); 
     pfCollection.Add(_pFigure); 

     PathGeometry pathGeometry = new PathGeometry(); 
     pathGeometry.Figures = pfCollection; 

     _path.Data = pathGeometry; 
    } 

    double bottomOfCurveX = ((x/2)); 
    double bottomOfCurveY = (y + (x * 1.25)); 

    _bs.Point3 = new Point(x, y); 

    if (_sb == null) 
    { 
     _paPoint2 = new PointAnimation(); 

     _paPoint2.From = _bs.Point2; 
     _paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY); 
     _paPoint2.Duration = new Duration(TimeSpan.FromMilliseconds(1000)); 
     _eEase = new ElasticEase(); 

     _paPoint2.EasingFunction = _eEase; 
     _sb = new Storyboard(); 

     Storyboard.SetTarget(_paPoint2, _path); 
     Storyboard.SetTargetProperty(_paPoint2, new PropertyPath("Data.Figures[0].Segments[0].Point2")); 

     _sb.Children.Add(_paPoint2); 
     _sb.Begin(this);     
    } 

    _paPoint2.From = _bs.Point2; 
    _paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY); 

    _sb.Begin(this); 
} 
+1

Versuchen Sie, eine Kettenlinie zu zeichnen? http://en.wikipedia.org/wiki/Catenary – Gabe

+0

@Gabe, ja, das scheint die Art der Kurve zu sein, die ich suche – Brandon

+0

Haben Sie http://www.tinaja.com/glib/bezcat gesehen. pdf? – Gabe

Antwort

9

Wenn Sie echte dynamische Bewegung wollen (dh, wenn Sie „schütteln“ die Maus Sie Zeiger kann Wellen erzeugen, die sich entlang der Schnur bewegen), Sie müssen Finite-Elemente-Techniken verwenden. Wenn Sie jedoch mit statischem Verhalten einverstanden sind, können Sie einfach Bezier-Kurven verwenden.

Zuerst beschreibe ich kurz den Finite-Elemente-Ansatz und gehe dann näher auf den statischen Ansatz ein.

Dynamische Ansatz

Teilen Sie Ihre "Schnur" in eine große Anzahl (1000 oder so) "Elemente", die jeweils mit einer Position und Geschwindigkeit Vector. Verwenden Sie das Ereignis CompositionTarget.Rendering jede Elementposition zu berechnen, wie folgt:

  • Berechne die Anziehungskraft auf jedes Element entlang des „Kord“ von benachbarten Elementen, die auf den Abstand zwischen den Elementen proportional ist. Angenommen, das Kabel selbst ist masselos.

  • Berechnen Sie den Nettokraftvektor auf jedem "Element", das aus dem Zug von jedem benachbarten Element entlang der Schnur besteht, zuzüglich der konstanten Schwerkraft.

  • Verwenden Sie eine Massenkonstante, um den Kraftvektor in Beschleunigung umzuwandeln, und aktualisieren Sie die Position und die Geschwindigkeit mithilfe der Bewegungsgleichungen.

  • Zeichnen Sie die Linie mit einem StreamGeometry-Build mit einer BeginFigure gefolgt von einem PolyLineTo. Bei so vielen Punkten gibt es wenig Grund, die zusätzlichen Berechnungen durchzuführen, um eine kubische Bezier-Approximation zu erstellen.

statischen Ansatz

Dividieren Kabel in Ihre vielleicht 30 Segmente, von denen jedes eine kubische Bezier-Annäherung an die Kettenlinie y = a cosh (x/a). Ihre Endkontrollpunkte sollten auf der Kettenlinie liegen, die Parallelen sollten tangential zu den Oberleitungen verlaufen und die Kontrolllinienlängen sollten auf der zweiten Ableitung der Oberleitung basieren.

In diesem Fall werden Sie wahrscheinlich auch eine StreamGeometry rendern wollen, mit BeginFigure und PolyBezierTo, um es zu bauen.

Ich würde dies als benutzerdefinierte Form Unterklasse "Oberleitung" ähnlich wie Rechteck und Ellipse implementieren. In diesem Fall müssen Sie die DefinitionGeometry-Eigenschaft überschreiben. Zur Effizienz würde ich auch CacheDefiningGeometry, GetDefiningGeometryBounds und GetNaturalSize überschreiben.

Sie würden zuerst entscheiden, wie Sie Ihre Oberleitung parametrisieren und dann DependencyProperties für alle Parameter hinzufügen. Stellen Sie sicher, dass Sie die AffectsMeasure- und AffectsRender-Flags in Ihrer FrameworkPropertyMetadata festlegen.

Eine mögliche Parametrierung wäre XOffset, YOffset, Length. Eine andere könnte XOffset, YOffset, SagRelativeToWidth sein. Es würde davon abhängen, an was am einfachsten zu binden wäre.

Sobald Ihre DependencyProperties definiert sind, implementieren Sie Ihre DefinitionGeometry-Eigenschaft, um die kubischen Bezier-Kontrollpunkte zu berechnen, die StreamGeometry zu konstruieren und sie zurückzugeben.

Wenn Sie dies tun, können Sie ein Oberleitungssteuerelement irgendwo ablegen und eine Kettenlinie erhalten.

1

IMHO ‚hängen‘ (physikalisch simuliert) Kabel sind ein Fall davon über tun - Blicke über die Benutzerfreundlichkeit zu begünstigen.

Sind Sie sicher, dass Sie nicht nur die Benutzererfahrung überladen?

In einem Knoten/verbindungsbasierten UI finde ich klar Verbindungen (wie in Quartz Composer: http://ellington.tvu.ac.uk/ma/wp-content/uploads/2006/05/images/Quartz%20Composer_screenshot_011.png) viel wichtiger als Augenschmaus wie geschwungen Kabel, die in eine andere Richtung Kopf (nach unten aufgrund der Schwerkraft) als in dem der tatsächliche Verbindungspunkt ist. (Und in der Zwischenzeit auffressen CPU-Zyklen für die Simulation, die an anderer Stelle sinnvoller sein könnten)

meine nur 0,02 $

+0

sehe ich Was Sie sagen, aber IMO ist es immer wert, ein Risiko auf einer Benutzeroberfläche einzugehen, die perfekt zu Ihrem Konzept passt. Kabel kommunizieren das Konzept sofort; vielleicht würde eine Quartz-ähnliche Ansicht eine großartige alternative Sichtweise ergeben. – Brandon

+0

Die Verbindung ist tot. –

Verwandte Themen