正如 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
. 我希望评论或多或少地解释了它是如何工作的。
让我知道这是否回答了您的问题并解决了问题。