2010-11-17 8 views
14

Tengo una aplicación que está muy "basada en la conexión", es decir, múltiples entradas/salidas.¿Cómo puedo simular un cable colgante en WPF?

El concepto de interfaz de usuario de un "cable" es exactamente lo que estoy buscando para aclarar el concepto al usuario. Propellerhead tomó un enfoque similar en su software Reason para componentes de audio, ilustrado en this YouTube video (fast forward to 2m:50s).

Puedo hacer que este concepto funcione en GDI pintando una spline del punto A al punto B, tiene que haber una forma más elegante de usar Paths o algo en WPF para esto, pero ¿dónde empiezas? ¿Hay una buena manera de simular la animación del balanceo del cable cuando lo agarras y lo agitas?

También estoy abierto a bibliotecas de control (comerciales o de código abierto) si esta rueda ya se ha inventado para WPF.

Actualización: Gracias a los enlaces en las respuestas hasta el momento, estoy casi allí.

alt text

He creado un BezierCurve mediante programación, con el punto 1 siendo (0, 0), punto 2, siendo la parte inferior "colgar" punto y el punto 3 es dondequiera que el cursor del ratón. Creé un PointAnimation para el Punto 2 con una función de aceleración ElasticEase aplicada para darle el efecto "Balanceo" (es decir, rebotar el punto medio alrededor de un bit).

El único problema es que la animación parece llegar un poco tarde. Estoy comenzando el Storyboard cada vez que se mueve el mouse, ¿hay una mejor manera de hacer esta animación? Mi solución hasta el momento se encuentra aquí:

Bezier Curve Playground

Código:

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

¿Estás tratando de dibujar una curva de catenaria? http://en.wikipedia.org/wiki/Catenary – Gabe

+0

@Gabe, sí, ese parece ser el tipo de curva que estoy buscando – Brandon

+0

¿Has visto http://www.tinaja.com/glib/bezcat. pdf? – Gabe

Respuesta

9

Si quieres verdadera dinámica de movimiento (es decir, cuando se "sacuda" del ratón, puntero puede crear ondas que viajan a lo largo del cable), deberá usar técnicas de elementos finitos. Sin embargo, si estás de acuerdo con el comportamiento estático, puedes simplemente usar las curvas de Bezier.

Primero describiré brevemente el enfoque de elementos finitos, luego entraré en más detalles sobre el enfoque estático.

enfoque dinámico

Divida su "cordón" en un gran número (1.000 o menos) "elementos", cada uno con una posición y vector de velocidad. Utilice el evento CompositionTarget.Rendering para calcular cada posición de elemento como sigue:

  • Calcular la tracción en cada elemento a lo largo del "cordón" de elementos adyacentes, que es proporcional a la distancia entre elementos. Supongamos que el cable en sí no tiene masa.

  • Calcule el vector neto de fuerza en cada "elemento" que consiste en el tirón de cada elemento adyacente a lo largo del cable, más la fuerza constante de la gravedad.

  • Usa una constante de masa para convertir el vector de fuerza en aceleración, y actualiza la posición y velocidad usando las ecuaciones de movimiento.

  • Dibuje la línea usando una compilación StreamGeometry con un BeginFigure seguido de un PolyLineTo. Con tantos puntos, hay pocas razones para hacer los cálculos adicionales para crear una aproximación bezier cúbica.

enfoque estático

dividir su cable en tal vez 30 segmentos, cada uno de los cuales es una aproximación de Bezier cúbico a la catenaria y = a cosh (x/a). Sus puntos de control final deben estar en la curva de catenaria, los paralelos deben ser tangentes a las catenarias, y las longitudes de línea de control establecidas en función de la segunda derivada de la catenaria.

En este caso, probablemente también desee renderizar un StreamGeometry, utilizando BeginFigure y PolyBezierTo para compilarlo.

Implementaría esto como una subclase de forma personalizada "Catenaria" similar a Rectángulo y Elipse. En ese caso, todo lo que tiene que anular es la propiedad DefiningGeometry. Para mayor eficiencia, también anularía CacheDefiningGeometry, GetDefiningGeometryBounds y GetNaturalSize.

Primero debe decidir cómo parametrizar su catenaria y luego agregar DependencyProperties para todos sus parámetros. Asegúrese de establecer los indicadores AffectsMeasure y AffectsRender en su FrameworkPropertyMetadata.

Una posible parametrización sería XOffset, YOffset, Length. Otro podría ser XOffset, YOffset, SagRelativeToWidth. Dependería de lo que sería más fácil de vincular.

Una vez que se definen sus DependencyProperties, implemente su propiedad DefiningGeometry para calcular los puntos de control de bezier cúbicos, construir StreamGeometry y devolverlos.

Si lo hace, puede colocar un control Catenary en cualquier lugar y obtener una curva de catenaria.

1

mi humilde opinión 'colgante' (simulados) físicamente los cables son un caso de exceso de hacerlo - a favor de las miradas más de usabilidad.

¿Estás seguro de que no solo estás abarrotando la experiencia del usuario?

En una UI/nodo basado en la conexión me parece claras conexiones (como en Quartz Composer: http://ellington.tvu.ac.uk/ma/wp-content/uploads/2006/05/images/Quartz%20Composer_screenshot_011.png) mucho más importante que los ojos dulces como los cables de vaivén que se dirigen en una dirección diferente (hacia abajo debido a la gravedad) que donde el punto de conexión es. (Y en el tiempo medio comer hasta CPU ciclos para la simulación que podría ser más útil en otro lugar)

Sólo mi $ 0,02

+0

Veo lo que dices, pero IMO siempre vale la pena arriesgarse con una interfaz de usuario que se alinea perfectamente con tu concepto. Los cables comunican el concepto de inmediato; quizás una vista de cuarzo haría una gran vista alternativa. – Brandon

+0

El enlace está muerto. –

Cuestiones relacionadas