使用XAML/WPF实现复杂曲线编辑器的策略

pix*_*tur 7 architecture wpf user-interface xaml user-controls

我想实现一个相当复杂的CurveEditor,它必须支持通常的要求,例如:

  • 可自由伸缩和可移动的轴
  • 每个曲线点的不同插值类型(线性,立方,样条)
  • 切线(连接和断开)
  • 通过围栏或单击选择一个或多个点进行编辑(移动,缩放,删除)
  • 仅显示所选曲线点的手柄和高光

CurveEditorComponent的示例图

我不想操纵实际的WPF曲线,但是现有的带有键/值/切线的模型设置并从我们的实现中采样曲线的精确形状.

我已经收集了一些实现自定义UserControls和模板的经验.但我想确保,我不会错过任何明显的解决方案.我计划使用这个通用的XAML树:

  • CurveEditor - 包含所有内容的窗口
    • MainThumb:启用拖动和缩放编辑器范围
    • XAxis:UserControl在左侧以某种比例递增
    • YAxis:UserControl在底部有一定比例
    • 曲线:画布保持曲线
      • 曲线:用于单条曲线的UserControl
        • CurveName - 曲线的标签
        • CurveLine - DrawingVisual将通过对样条函数的内部实现进行采样来呈现实际曲线.
        • CurveEditPoints - 包含所有编辑点的画布
          • CurveEditPoint - 单个编辑点的UserControl
            • LeftTangent - 左切线控制柄的UserControl
              • LeftTangentThumb - 用于修改句柄
            • RightTangent - 用于右切线控制柄的UserControl
              • RightTangentThumb - 用于修改句柄
          • CurvePointCenter - 实际点的可视化,选择状态和插值类型.
            • CurvePointThumb - 用于选择和拖动点的拇指

我知道,这是一个非常复杂的问题,我不是要求实际实施.我对以下问题感兴趣:

  1. 你能推荐一些可能对我有帮助的教程或书籍吗(我已经有了Illustrated WPF,WPF Control Development Unleashed,还有其他一些)
  2. 像Tangents这样的次要元素应该是单独的UserControls吗?
  3. 什么容器最适合托管个人"曲线","EditPoints"和"Tangents".现在,我使用Canvas和Canvas.SetLeft/SetTop定位孩子,但感觉"奇怪".
  4. 我应该使用像Path或DrawingVisual-Classes这样的"形状"来实现实际表示.路径是直截了当的,但我担心数百个CurvePoints的性能.
  5. 我应该使用变换来旋转切线还是可以在文件后面的代码中进行一些三角测量数学运算?
  6. 结构是否大致有意义,或者您是否建议采用完全不同的方法?

Mar*_*ter 7

  1. 你似乎有正确的工具,WPF Unleashed很棒,但我猜你已经有了.

  2. 在以下某种情况下制作单独的UserControl:

    • 你在整个地方使用相同的xaml(DRY)
    • 你的xaml文件太大了(得到一些组件)
    • 需要继承一些类
  3. 这取决于你想要多少代码.
    您可以按照评论中的建议,使用ItemsControl作为容器,以便在多个项目之间进行选择.所以这也可以在曲线水平上完成,而不仅仅是曲线上的点水平.根据您想要处理实际线条和曲线的绘制方式,您甚至可以为这些线条和曲线设置ItemsControl.(在旁注:你不会开箱即用,因为你的物品不会有恒定的高度)

  4. 路径可以使用数百个CurvePoints.如果你有10.000,我会说你可能会遇到问题.
  5. 我无法想象如何在这里使用变换,也许在Adorner中.
  6. 你的结构看起来不错.你将能够实现所有这一切.我会建议我怎么做:

首先使用MVVM.

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

  • CurveControl

    • 画布(背景=透明)<=我不确定标准是否为白色,但您不想与其他曲线重叠...
      • CurveName
      • 列表框(面板=画布(背景=透明))(的ItemsSource = CurveParts)
      • 列表框(面板=画布(背景=透明))(的ItemsSource = CurvePoints)(的ItemTemplate => EditPointControl)
  • EditPointControl

    • 帆布
      • Thumb(Template = Ellipse)(Name = CenterHandle)(带有一些用于选择和隐藏切线的Visualstates)
      • Thumb(Template = Ellipse)(Name = LeftHandle)
      • Thumb(Template = Ellipse)(Name = RightHandle)
      • 线(将X/Y绑定到Centerpoint和LeftHandlePoint)
      • 线(将X/Y绑定到Centerpoint和RightHandlePoint)

我已声明为ListBox设置ItemTemplate.但是,您可以风格,但是你想在列表框(我认为标准样式包括边界,你可能想要删除或设置bordersize = 0),并设置ItemTemplate中的,而不是ItemContainerStyle并绑定到IsSelected让你从可以控制IsSelected您视图模型(看看这里为我的意思).

所以viewmodel看起来像这样:

- 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
Run Code Online (Sandbox Code Playgroud)

在这里,您可以订阅CurvePointViewModel的PropertyChanged并重新计算您正在公开的路径.

我可能会改进它,但这是我的第一个猜测.

您可能需要注意一些细节.例如:拇指的样式可能是中间的可见圆圈,而围绕它的不可见的较大的圆圈,背景=透明.这样你可以让你的可见圆圈变小,但让用户在小圆圈周围的区域使用tumb.

编辑:

这是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>
Run Code Online (Sandbox Code Playgroud)

如果你想将它定位在画布上的特定点上,将边距设置为负宽度和高度的一半将圆圈的中心放在该点上.此外,如果内部椭圆具有透明填充和-6的边距,您将在内部(较小)圆周围获得6px更大的半径,您可以在其中拖动拇指.