0

我正在创建一个DataGrid将定义(通过用户输入)将在同一窗口上显示的曲线的 X 和 Y 值。曲线将由Path包含一系列QuadraticBezierCurve对象的 a 定义。

但是,虽然用户将输入路径将通过的点的坐标,但aQuadraticBezierCurve的数据包含有关其端点和控制点的信息,该控制点不是它将通过的点(直线除外) . 我已经计算出如何计算曲线必须经过的三个坐标所定义的控制点,但现在我需要创建这条曲线。

是否可以将 绑定DataGrid到已处理的“缓冲区”数据集(尽管是代码隐藏),并且此结果绑定到QuadraticBezierCurve?

还是我需要根据用户输入删除现有路径并构建一个新路径?

4

2 回答 2

0

通过绑定ItemsSource到集合,您的每一行都DataGrid将绑定到该集合中的一个项目。假设 MVVM,每个项目都是一个视图模型。该视图模型可以具有用户输入的值的属性,并且任何更改都可能导致在公开控制点的另一个中重新计算。UI 的另一部分可以绑定到相同的属性,因此会自动刷新。

于 2013-05-18T17:21:40.887 回答
0

正如 Kent 所说,在 MVVM 方法中,您可以定义具有两个集合的 ViewModel,一个用于用户输入数据,一个用于公开路径可以绑定到的转换后的项目。然后您需要定义何时需要刷新路径的集合。这意味着使用用户输入的坐标手动订阅CollectionChanged集合事件(仅当用户可以添加或删除点时)和PropertyChanged每个用户坐标的事件(在任何情况下)。这是否需要,取决于您是否要允许用户更改列表中已有的坐标。

坐标在添加到列表后不会更改

如果没有,还有一种更直接的方法,即使用 ValueConverter。由于您当时想要实现的是直接转换,我建议使用内置的 WPF 机制进行转换。这些适用于 MVVM 和非 MVVM 方法。

定义一个转换器,将坐标转换为存储在 DataGrid 的每个项目中的坐标ItemsSource到路径段。这包含您已经计算出的数学,仅此而已:

public class CoordinateToPathSegment : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var coordinate = value as CurveViewModel.Coordinate;
        var segment = new QuadraticBezierSegment();

        // Set properties of quadratic bezier element

        return segment;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

您在应用程序/窗口/控件的资源中公开此转换器的实例。有关转换器的一般信息,请参见此处:WPFTutorial: Converters

然后将它们连接在一起,如下所示:

 <Path Data="{Binding ItemsSource, 
                      ElementName=CoordinateGrid, 
                      Mode=OneWay, 
                      Converter={StaticResource CoordinateToSegmentConverter}}" />

这样,您的路径始终与数据网格同步,并且用户输入坐标会自动转换为路径段,而无需您自己关心刷新。同样:请注意,已经存在的 UserCoordinate 的更改不会转发到 UI,因为一旦坐标转换为 a PathSegment,两者之间就不再有引用。只有在 DataGrid 中添加/删除坐标时,路径才会刷新。

在将坐标添加到列表后,用户可以更改坐标

如果您想允许用户更改已定义坐标的单个值,还有更多内容。然后你需要得到肯特提议的方式。原理大概是这样的:

public class CurveViewModel : ViewModelBase
{
    private Collection<PathSegment> _segments;
    private readonly ObservableCollection<Coordinate> _userInputCoordinates;

    public ObservableCollection<Coordinate> UserInputCoordinates
    {
        get { return _userInputCoordinates; }
    }

    public Collection<PathSegment> Segments
    {
        get { return _segments; }
        private set
        {
            _segments = value;
            OnPropertyChanged(() => Segments);
        }
    }

    public CurveViewModel()
    {
        _userInputCoordinates = new ObservableCollection<Coordinate>();

        // Subscribe to refresh the path on adding/deleting ne coordinates
        UserInputCoordinates.CollectionChanged += UserInputCoordinates_CollectionChanged;
    }

    private void UserInputCoordinates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Add:
                var newItems = args.NewItems.OfType<INotifyPropertyChanged>();
                foreach (var coordinate in newItems)
                {
                    // Subscribe to property change of a particular coordinate to refresh the 
                    // curve when user changes the values of an already existing coordinate data set
                    coordinate.PropertyChanged += Coordinate_PropertyChanged;
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                var oldItems = args.OldItems.OfType<INotifyPropertyChanged>();
                foreach (var coordinate in oldItems)
                {
                    // Unsubscribe to avoid memory leaks
                    coordinate.PropertyChanged -= Coordinate_PropertyChanged;
                }
                break;
        }

        // This refreshes the path when a coordinate has been added/removed
        RefreshPath();
    }

    private void Coordinate_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        RefreshPath();
    }

    private void RefreshPath()
    {
        var segments = new Collection<PathSegment>();

        foreach (var userInputCoordinate in UserInputCoordinates)
        {
            var segment = new QuadraticBezierSegment();
            // Set properties here
            segments.Add(segment);
        }

        Segments = segments;
    }

    public class Coordinate : ViewModelBase
    {
        private double _xStart;

        public double XStart
        {
            get { return _xStart; }
            set
            {
                _xStart = value;
                OnPropertyChanged("XStart");
            }
        }

        // Analogous properties for YStart, XEnd, YEnd, XControl, YControl
    }
}

然后你将Data路径绑定到Segments. 我希望评论或多或少地解释了它是如何工作的。

让我知道这是否回答了您的问题并解决了问题。

于 2013-05-19T08:45:45.257 回答