gpt4 book ai didi

c# - 在 WPF 中将形状转换为可重用的几何图形

转载 作者:行者123 更新时间:2023-11-30 14:53:51 27 4
gpt4 key购买 nike

我正在尝试转换 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属性(property)。

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

EDIT:
I have figured out how to generate the appearance of the geometry. But this only works in design-mode. Execute these steps:

  • Rebuild project.
  • Go to MainWindow.xaml and click in the custom shape object so that the shape's properties load into Visual Studio's property window. Wait until the property window renders what the shape looks like.
  • Modify the data points collection or properties to see the geometry rendered properly.

Here is what I want the control to ultimately look like for now: enter image description here



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

非常感谢您的帮助!

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

MainWindow.xaml
<Window x:Class="CustomControls.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Grid>
<local:LineGraph>
<local:LineGraph.DataPointShape>
<Ellipse Width="10" Height="10" Fill="Red" Stroke="Black" StrokeThickness="1" />
</local:LineGraph.DataPointShape>
<local:LineGraph.DataPoints>
<local:DataPoint X="10" Y="10"/>
<local:DataPoint X="20" Y="20"/>
<local:DataPoint X="30" Y="30"/>
<local:DataPoint X="40" Y="40"/>
</local:LineGraph.DataPoints>
</local:LineGraph>
</Grid>

数据点.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<DataPoint>), typeof(LineGraph), new FrameworkPropertyMetadata(default(ObservableCollection<DataPoint>), 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<DataPoint> DataPoints
{
get { return (ObservableCollection<DataPoint>)GetValue(DataPointsProperty); }
set { SetValue(DataPointsProperty, (ObservableCollection<DataPoint>)value); }
}

public LineGraph()
{ //Provide instance-specific value for data point collection instead of a shared static instance.
SetCurrentValue(DataPointsProperty, new ObservableCollection<DataPoint>());
}

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);
}
}
}
}

EDIT 2:
In response to this answer by Peter Duniho, I would like to provide the alternate method to lying to Visual Studio in creating a custom control. For creating the custom control execute these steps:

  • Create folder in root of project named Themes
  • Create resource dictionary in Themes folder named Generic.xaml
  • Create a style in the resource dictionary for the control.
  • Apply the style from the control's C# code.

Generic.xaml
Here is an example of for the SimpleGraph described by Peter.

<ResourceDictionary 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Style TargetType="local:SimpleGraph" BasedOn="{StaticResource {x:Type ItemsControl}}">
<Style.Resources>
<EllipseGeometry x:Key="defaultGraphGeometry" Center="5,5" RadiusX="5" RadiusY="5"/>
</Style.Resources>
<Style.Setters>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Path Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointFill}"
Stroke="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointStroke}"
StrokeThickness="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointStrokeThickness}"
Data="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SimpleGraph}}, Path=DataPointGeometry}">
<Path.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Path.RenderTransform>
</Path>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</ResourceDictionary>

Lastly, apply the style like so in the SimpleGraph constructor:

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

最佳答案

我认为你可能没有以最好的方式解决这个问题。根据您发布的代码,您似乎正在尝试手动执行 WPF 在自动处理方面相当擅长的事情。

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

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

首先,我创建了一个自定义 ItemsControl类(在 Visual Studio 中,我通过撒谎并告诉 VS 我想添加一个 UserControl 来做到这一点,这让我在项目中获得了一个基于 XAML 的项目......我立即在两个 .xaml 中将“UserControl”替换为“ItemsControl”和 .xaml.cs 文件):

XAML:

<ItemsControl x:Class="TestSO28332278SimpleGraphControl.SimpleGraph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestSO28332278SimpleGraphControl"
mc:Ignorable="d"
x:Name="root"
d:DesignHeight="300" d:DesignWidth="300">

<ItemsControl.Resources>
<EllipseGeometry x:Key="defaultGraphGeometry" Center="5,5" RadiusX="5" RadiusY="5" />
</ItemsControl.Resources>

<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Path Data="{Binding ElementName=root, Path=DataPointGeometry}"
Fill="Red" Stroke="Black" StrokeThickness="1">
<Path.RenderTransform>
<TranslateTransform X="{Binding X}" Y="{Binding Y}"/>
</Path.RenderTransform>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>

</ItemsControl>

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目的。该对象的几何图形绑定(bind)到控件 DataPointGeometry属性(property)及其 RenderTransform绑定(bind)到数据项的 XY值作为平移变换的偏移量。

一个简单的 Canvas用于 ItemsPanel ,因为我只需要一个地方来绘制东西,没有任何其他布局功能。最后,有一个资源定义了要使用的默认几何图形,以防调用者不提供。

关于那个来电者……

这是一个如何使用上述内容的简单示例:
<Window x:Class="TestSO28332278SimpleGraphControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO28332278SimpleGraphControl"
Title="MainWindow" Height="350" Width="525">

<Window.Resources>
<PathGeometry x:Key="dataPointGeometry"
Figures="M 0.5000,0.0000
L 0.6176,0.3382
0.9755,0.3455
0.6902,0.5618
0.7939,0.9045
0.5000,0.7000
0.2061,0.9045
0.3098,0.5618
0.0245,0.3455
0.3824,0.3382 Z">
<PathGeometry.Transform>
<ScaleTransform ScaleX="20" ScaleY="20" />
</PathGeometry.Transform>
</PathGeometry>
</Window.Resources>

<Grid>
<Border Margin="3" BorderBrush="Black" BorderThickness="1">
<local:SimpleGraph Width="450" Height="300" DataPointGeometry="{StaticResource dataPointGeometry}">
<local:SimpleGraph.Items>
<local:DataPoint X="10" Y="10" />
<local:DataPoint X="25" Y="25" />
<local:DataPoint X="40" Y="40" />
<local:DataPoint X="55" Y="55" />
</local:SimpleGraph.Items>
</local:SimpleGraph>
</Border>
</Grid>
</Window>

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

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

这是它的样子:

Graph screenshot

我会注意到上面的例子只允许你指定几何。其他形状属性在数据模板中硬编码。这似乎与您要求做的略有不同。但请注意,您在这里有一些替代方案可以满足您的需求,而无需在示例中重新引入所有额外的手动绑定(bind)/更新代码:
  • 只需添加其他属性,绑定(bind)到模板 Path对象的方式类似于 DataPointGeometry属性(property)。例如。 DataPointFill , DataPointStroke
  • 继续并允许用户指定 Shape对象,然后使用该对象的属性来填充绑定(bind)到模板对象属性的特定属性。这主要是为调用者提供方便;如果有的话,这在图形控件本身中会增加一些复杂性。
  • 全力以赴并允许用户指定 Shape对象,然后您可以使用 XamlWriter 将其转换为模板要为对象创建一些 XAML,请添加必要的 Transform XAML 中的元素并将其包装在 DataTemplate 中声明(例如,通过将 XAML 作为内存中的 DOM 加载来修改 XAML),然后使用 XamlReader然后将 XAML 作为模板加载,然后您可以将其分配给 ItemTemplate属性(property)。

  • 选项#3 对我来说似乎是最复杂的。实际上太复杂了,以至于我没有费心使用它来制作一个示例的原型(prototype)……我做了一些研究,在我看来它应该可以工作,但我承认我没有亲自验证它是否有效。但就调用者的绝对灵 active 而言,这肯定是黄金标准。

    关于c# - 在 WPF 中将形状转换为可重用的几何图形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28332278/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com