Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strategy for implementing a complex curve editor with XAML/WPF

I want to implement a rather complex CurveEditor that has to support the usual requirements like:

  • freely scalable and moveable axis
  • different interpolation types per curve point (Linear, Cubic, Spline)
  • Tangents (joined and broken)
  • Selecting one or several points to edit (move, scale, delete) via Fence or Click
  • Only show handles and highlights for selected curve points

sample illustration of the CurveEditorComponent

I don't want to manipulate actual WPF curves but an existing model with key/value/tangents sets and sample the precise shape of the curve from our implementation.

I already gathered some experience on implementing custom UserControls and Templates. But I want to make sure, I don't miss any apparent solution. I planned to have this general XAML-tree:

  • CurveEditor - Window holding all content
    • MainThumb : Enable dragging and scaling the editor range
    • XAxis : UserControl rending some scale on the left side
    • YAxis : UserControl rending some scale on the bottom
    • Curves : Canvas holding the curves
      • Curve : UserControl for a single curve
        • CurveName - Label of the curve
        • CurveLine - DrawingVisual that will render the actual curve by sampling the internal implementation of the spline function.
        • CurveEditPoints - Canvas that holds all edit points
          • CurveEditPoint - UserControl for a single edit point
            • LeftTangent - UserControl for the left tangent handle
              • LeftTangentThumb - For modifying the handle
            • RightTangent - UserControl for the right tangent handle
              • RightTangentThumb - For modifying the handle
          • CurvePointCenter - Visualisation of the actual point, select state and interpolation type.
            • CurvePointThumb - Thumb to select and drag point around

I know, this is quite a complex question and I am not asking for an actual implementation. I am interested in the following questions:

  1. Can you recommend any tutorials or books that might help me (I already got Illustrated WPF, WPF Control Development Unleashed, and a couple of other)
  2. Should minor elements like the Tangents be individual UserControls?
  3. What container is best suited for hosting the individual "Curves", "EditPoints" and "Tangents". Right now, I use Canvas and Canvas.SetLeft/SetTop to position the children, but that feels "strange".
  4. Should I use "Shapes" like Path or DrawingVisual-Classes to implement actual representation. Path is straight forward, but I am concerned about performance with hundreds of CurvePoints.
  5. Should I use Transforms to rotate the tangents or is just fine to do some triangulation math in the code behind files?
  6. Does the structure roughly make sense, or do you suggest a completely different approach?
like image 805
pixtur Avatar asked Mar 11 '11 18:03

pixtur


1 Answers

  1. you seem to have the right tools at hand, WPF Unleashed is excellent, but I guess you have that one already.

  2. make individual UserControls in one of these cases:

    • you are using the same xaml all over the place (DRY)
    • you xaml file gets too big (get some components out)
    • you need to inherit from some class
  3. this depends on how much codebehind you want.
    you can, as you suggested in your comment, use an ItemsControl as a container for wherever you need selection between multiple items. so this could also be done on the level of Curves, not just on the level of points on the curve. Depending on how you want to handle drawing of the actual lines and curves you can even have an ItemsControl for those. (on a side note: you will not have virtualization out of the box though, as your items won't have a constant height)

  4. Path is OK with hundreds of CurvePoints. If you have 10.000, I'd say you could get problems.
  5. can't imagine how a transform should be used here, maybe inside an Adorner.
  6. your structure looks good. you will be able to implement all of this. I will suggest though how I would do it:

first of all use MVVM.

  • CurveEditor
    ListBox(Panel=Canvas)(ItemsSource=Curves)(ItemTemplate=CurveControl)

  • CurveControl

    • Canvas(Background=Transparent) <= I'm not sure if standard is white, but you don't want to overlap other Curves...
      • CurveName
      • ListBox(Panel=Canvas(Background=Transparent))(ItemsSource=CurveParts)
      • ListBox(Panel=Canvas(Background=Transparent))(ItemsSource=CurvePoints)(ItemTemplate=>EditPointControl)
  • EditPointControl

    • Canvas
      • Thumb(Template = Ellipse) (Name=CenterHandle) (with some Visualstates for Selection and hiding of Tangents)
      • Thumb(Template = Ellipse) (Name=LeftHandle)
      • Thumb(Template = Ellipse) (Name=RightHandle)
      • Line (Binding X/Y to Centerpoint and LeftHandlePoint)
      • Line (Binding X/Y to Centerpoint and RightHandlePoint)

I have stated to set ItemTemplate for the ListBox. You can however style the listbox however you want (I think the standard style includes a border, you might want to remove that or set bordersize=0) and set instead of ItemTemplate the ItemContainerStyle and bind to IsSelected so you have control over IsSelected from your ViewModel (look here for what I mean).

So the viewmodel looks like this:

- CurveEditorViewModel
    - ObservableCollection<CurveViewModel> Curves


- CurveViewModel
    - string Label
    - (Point LabelPlacement)
    - bool IsSelected
    - ObservableCollection<CurvePointViewModel> CurvePoints
    - ObservableCollection<CurvePartViewModel> CurveParts


- CurvePointViewModel
    - Point Position
    - bool IsSelected
    - Point LeftHandle
    - Point RightHandle

- CurvePartViewModel
    - CurvePointViewModel StartPoint
    - CurvePointViewModel EndPoint
    - Path CurvePath

in here you can subscribe to CurvePointViewModel's PropertyChanged and recalculate the Path you're exposing.

I'd probably improve on it as I go but that'd be my first guess.

There are some details you might want to watch out for. eg: the style for the thumbs might be a visible circle in the middle and an invisible bigger one around that with background=transparent. that way you can have your visible circle small, but have the user use the tumb in an area around the small circle.

EDIT:

here is an Example for the Thumb:

        <Thumb Width="8" Height="8" Cursor="Hand" Margin="-4">
            <Thumb.Template>
                <ControlTemplate TargetType="Thumb">
                    <Grid>
                        <Ellipse Fill="Transparent" Margin="-6"/>
                        <Ellipse Stroke="Red" StrokeThickness="2"/>
                    </Grid>
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>

as you want to position this at a specific point on a canvas setting the Margin to minus half the width and height will place the center of the circle on that point. Furthermore, having that inner ellipse with a transparent fill and Margin of -6 you will get a 6px bigger radius around the inner (smaller) circle where you can drag the thumb.

like image 138
Markus Hütter Avatar answered Jan 02 '23 09:01

Markus Hütter