正如 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,因为一旦坐标转换为 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。我希望 cmets 或多或少地解释一下它是如何工作的。
如果这回答了您的问题并解决了问题,请告诉我。