在WPF中将Shape转换为可重用的几何体

我试图将System.Windows.Shapes.Shape对象转换为System.Windows.Media.Geometry对象。

使用Geometry对象,我将使用自定义图形控件多次渲染它,具体取决于一组数据点。 这要求Geometry对象的每个实例都有一个唯一的TranslateTransform对象。

现在,我正以两种不同的方式处理这个问题,但似乎都没有正常工作。 我的自定义控件使用以下代码来绘制几何:

 //Create an instance of the geometry the shape uses. Geometry geo = DataPointShape.RenderedGeometry.Clone(); //Apply transformation. TranslateTransform translation = new TranslateTransform(dataPoint.X, dataPoint.Y); geo.Transform = translation; //Create pen and draw geometry. Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness); dc.DrawGeometry(DataPointShape.Fill, shapePen, geo); 

我还尝试了以下替代代码:

 //Create an instance of the geometry the shape uses. Geometry geo = DataPointShape.RenderedGeometry; //Apply transformation. TranslateTransform translation = new TranslateTransform(dataPoint.X, dataPoint.Y); dc.PushTransform(translation); //Create pen and draw geometry. Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness); dc.DrawGeometry(DataPointShape.Fill, shapePen, geo); dc.Pop(); //Undo translation. 

不同之处在于第二个代码段不会克隆或修改Shape.RenderedGeometry属性。

奇怪的是,我偶尔可以查看WPF设计器中用于数据点的几何。 但是,行为不一致,很难弄清楚如何使几何体始终出现 。 此外,当我执行我的应用程序时,数据点永远不会出现指定的几何。

编辑:
我已经弄清楚如何生成几何体的外观。 但这仅适用于设计模式。 执行以下步骤:

  • 重建项目。
  • 转到MainWindow.xaml并单击自定义形状对象,以便将形状的属性加载到Visual Studio的属性窗口中。 等到属性窗口呈现形状的样子。
  • 修改数据点集合或属性以查看正确呈现的几何体。

以下是我希望控件最终看起来像现在的样子: 在此处输入图像描述

如何将Shape对象转换为Geometry对象以进行多次渲染?

非常感谢您的帮助!


让我给出我的问题的完整背景,以及理解我的控件如何设置的所有必要代码。 希望这可能表明我将Shape对象转换为Geometry对象的方法存在哪些问题。

MainWindow.xaml

               

DataPoint.cs
该类只有两个DependencyProperties (X和Y),并在更改任何属性时发出通知。 此通知用于通过UIElement.InvalidateVisual()触发重新呈现。

 public class DataPoint : DependencyObject, INotifyPropertyChanged { public static readonly DependencyProperty XProperty = DependencyProperty.Register("XProperty", typeof(double), typeof(DataPoint), new FrameworkPropertyMetadata(0.0d, DataPoint_PropertyChanged)); public static readonly DependencyProperty YProperty = DependencyProperty.Register("YProperty", typeof(double), typeof(DataPoint), new FrameworkPropertyMetadata(0.0d, DataPoint_PropertyChanged)); private static void DataPoint_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { DataPoint dp = (DataPoint)sender; dp.RaisePropertyChanged(e.Property.Name); } public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } public double X { get { return (double)GetValue(XProperty); } set { SetValue(XProperty, (double)value); } } public double Y { get { return (double)GetValue(YProperty); } set { SetValue(YProperty, (double)value); } } } 

LineGraph.cs
这是控制。 它包含数据点集合,并提供重新呈现数据点的机制(对WPF设计器很有用)。 特别重要的是上面发布的逻辑,它位于UIElement.OnRender()方法的内部。

 public class LineGraph : FrameworkElement { public static readonly DependencyProperty DataPointShapeProperty = DependencyProperty.Register("DataPointShapeProperty", typeof(Shape), typeof(LineGraph), new FrameworkPropertyMetadata(default(Shape), FrameworkPropertyMetadataOptions.AffectsRender, DataPointShapeChanged)); public static readonly DependencyProperty DataPointsProperty = DependencyProperty.Register("DataPointsProperty", typeof(ObservableCollection), typeof(LineGraph), new FrameworkPropertyMetadata(default(ObservableCollection), FrameworkPropertyMetadataOptions.AffectsRender, DataPointsChanged)); private static void DataPointShapeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { LineGraph g = (LineGraph)sender; g.InvalidateVisual(); } private static void DataPointsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { //Collection referenced set or unset. LineGraph g = (LineGraph)sender; INotifyCollectionChanged oldValue = e.OldValue as INotifyCollectionChanged; INotifyCollectionChanged newValue = e.NewValue as INotifyCollectionChanged; if (oldValue != null) oldValue.CollectionChanged -= g.DataPoints_CollectionChanged; if (newValue != null) newValue.CollectionChanged += g.DataPoints_CollectionChanged; //Update the point visuals. g.InvalidateVisual(); } private void DataPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { //Collection changed (added/removed from). if (e.OldItems != null) foreach (INotifyPropertyChanged n in e.OldItems) { n.PropertyChanged -= DataPoint_PropertyChanged; } if (e.NewItems != null) foreach (INotifyPropertyChanged n in e.NewItems) { n.PropertyChanged += DataPoint_PropertyChanged; } InvalidateVisual(); } private void DataPoint_PropertyChanged(object sender, PropertyChangedEventArgs e) { //Re-render the LineGraph when a DataPoint has a property that changes. InvalidateVisual(); } public Shape DataPointShape { get { return (Shape)GetValue(DataPointShapeProperty); } set { SetValue(DataPointShapeProperty, (Shape)value); } } public ObservableCollection DataPoints { get { return (ObservableCollection)GetValue(DataPointsProperty); } set { SetValue(DataPointsProperty, (ObservableCollection)value); } } public LineGraph() { //Provide instance-specific value for data point collection instead of a shared static instance. SetCurrentValue(DataPointsProperty, new ObservableCollection()); } protected override void OnRender(DrawingContext dc) { if (DataPointShape != null) { Pen shapePen = new Pen(DataPointShape.Stroke, DataPointShape.StrokeThickness); foreach (DataPoint dp in DataPoints) { Geometry geo = DataPointShape.RenderedGeometry.Clone(); TranslateTransform translation = new TranslateTransform(dp.X, dp.Y); geo.Transform = translation; dc.DrawGeometry(DataPointShape.Fill, shapePen, geo); } } } } 

编辑2:
在回答Peter Duniho的回答时 ,我想提供另一种方法来向Visual Studio说谎以创建自定义控件。 要创建自定义控件,请执行以下步骤:

  • 在名为Themes的项目的根目录中创建文件夹
  • 在名为Generic.xaml Themes文件夹中创建资源字典
  • 在控件的资源字典中创建样式。
  • 应用控件的C#代码中的样式。

Generic.xaml
这是Peter描述的SimpleGraph一个例子。

                            

最后,在SimpleGraph构造函数中应用这样的样式:

 public SimpleGraph() { DefaultStyleKey = typeof(SimpleGraph); DataPointGeometry = (Geometry)FindResource("defaultGraphGeometry"); } 

我认为你可能没有以最好的方式接近这一点。 根据您发布的代码,您似乎尝试手动完成WPF自动处理的相当好的事情。

主要棘手的部分(至少对我来说……我不是WPF专家)是你似乎想要使用一个实际的Shape对象作为图形数据点图形的模板,我并不完全确定最好的允许以编程方式或声明方式替换该模板的方法,而不暴露控制图上定位的底层转换机制。

所以这里是一个忽略这个特定方面的例子(我将在下面评论替代方案),但我相信这些方面可以满足您的确切需求。

首先,我创建一个自定义ItemsControl类(在Visual Studio中,我通过撒谎告诉VS我想要添加一个UserControl ,它让我在项目中获得一个基于XAML的项目…我立即用“ItemsControl”替换“UserControl”在.xaml和.xaml.cs文件中):

XAML:

                    

C#:

 public partial class SimpleGraph : ItemsControl { public Geometry DataPointGeometry { get { return (Geometry)GetValue(DataPointShapeProperty); } set { SetValue(DataPointShapeProperty, value); } } public static DependencyProperty DataPointShapeProperty = DependencyProperty.Register( "DataPointGeometry", typeof(Geometry), typeof(SimpleGraph)); public SimpleGraph() { InitializeComponent(); DataPointGeometry = (Geometry)FindResource("defaultGraphGeometry"); } } 

这里的关键是我有一个ItemsControl类,默认的ItemTemplate有一个Path对象。 该对象的几何体绑定到控件DataPointGeometry属性,其RenderTransform绑定到数据项的XY值作为转换变换的偏移量。

一个简单的Canvas用于ItemsPanel ,因为我只需要一个绘制东西的地方,没有任何其他布局function。 最后,如果调用者没有提供默认几何,则有一个资源定义要使用的默认几何。

关于那个来电者……

以下是一个如何使用上述内容的简单示例:

                      

在上面,唯一真正有趣的是我声明了PathGeometry资源,然后将该资源绑定到控件的DataPointGeometry属性。 这允许程序为图形提供自定义几何。

WPF通过隐式数据绑定和模板处理其余部分。 如果任何DataPoint对象的值发生更改,或者数据集合本身被修改,则图表将自动更新。

这是它的样子:

图形截图

我会注意到上面的例子只允许你指定几何。 其他形状属性在数据模板中是硬编码的。 这似乎与你要求做的略有不同。 但请注意,这里有一些替代方案可以满足您的需求,而无需在示例中重新引入所有额外的手动绑定/更新代码:

  1. 只需添加其他属性,以类似于DataPointGeometry属性的方式绑定到模板Path对象。 例如DataPointFillDataPointStroke等。

  2. 继续并允许用户指定Shape对象,然后使用该对象的属性来填充绑定到模板对象属性的特定属性。 这主要是给来电者带来方便; 如果有的话,它在图形控件本身中增加了一些复杂性。

  3. Go-hog并允许用户指定一个Shape对象,然后通过使用XamlWriter为对象创建一些XAML,将其转换为模板,将必要的Transform元素添加到XAML并将其包装在DataTemplate声明中(例如,将XAML作为内存中的DOM加载以修改XAML),然后使用XamlReader将XAML作为模板加载,然后将其分配给ItemTemplate属性。

选项#3对我来说似乎最复杂。 事实上很复杂,我没有费心去做一个使用它的例子……我做了一点研究,在我看来它应该有效,但我承认我没有validation它确实如此。 但就呼叫者的绝对灵活性而言,它肯定是黄金标准。