我正在尝试绘制用户的输入数据,这些数据最终将变成图形中的一系列矩形(大小和位置不同,不重叠)。我读过的所有示例要么仅绘制点数可变的绘图线,要么在XAML中的形状中包含硬编码。但是我不知道数据需要多少个矩形。我的理想情况是遵循MVVM并简单地将XAML绑定到我可以修改的ObservableCollection上,但是我看到的大多数示例似乎都使用了隐藏代码,而是直接访问ChartPlotter。这是我绘制了一些简单的矩形并对其进行了修改后可以正常工作的内容:
VisualizationWindow.xaml
<Window x:Class="View.VisualizationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
Title="VisualizationWindow" MinHeight="300" MinWidth="500" Height="300" Width="500">
<Grid>
<d3:ChartPlotter Name="Chart">
<d3:RectangleHighlight Name="Rect1" Bounds="-1,-1.5,.5,2" StrokeThickness="3" Fill="Blue" ToolTip="Blue!"></d3:RectangleHighlight>
<d3:RectangleHighlight Name="Rect2" Bounds="1,1.5,2,.5" StrokeThickness="1" Fill="Red" ToolTip="Red!"></d3:RectangleHighlight>
</d3:ChartPlotter>
</Grid>
</Window>
VisualizationWindow.xaml.cs
public partial class VisualizationWindow : Window
{
public VisualizationWindow(ListViewModel vm)
{
InitializeComponent();
this.DataContext = vm;
Chart.Viewport.Visible = new Rect(-1, -1, .5, .5);
Rect1.Bounds = new Rect(0,0,.3,.3);
}
}
动态数据显示上的文档几乎不存在。我很想知道,如果D3不能优雅地做到这一点,其他库是否可以更轻松地做到这一点。
最佳答案
将ItemsSource处理添加到现有类。
似乎他们已尽一切努力使ChartPlotter仅接受IPlotterElements。没有ItemsSource属性,因此Children将始终返回实际元素。 RectangleHighlight的Bounds属性是不可绑定的,并且该类是密封的,禁止使用任何方法重写该属性。
我们可以从该类派生“注入” ItemsSource处理。它不会像真正的交易那样工作,因为没有任何可靠的方法可以使Children属性反映数据绑定。但是,我们仍然可以通过这种方式分配ItemsSource。
我们需要一些东西。我们将需要实际的ItemsSource属性。一种对它被设置作出反应的方法。而且,如果我们想绑定到dataObjects,则是一种处理DataTemplates的方法。我还没有深入研究现有的源代码。但是,我确实提出了一种无需DataTemplateSelector即可处理DataTemplates的方法。但是,除非您修改我的示例,否则它也不能与DataTemplateSelector一起使用。
该答案假设您知道绑定的工作原理,因此我们无需过多掌握就可以跳到初始类。
Xaml首先:
<local:DynamicLineChartPlotter Name="Chart" ItemsSource="{Binding DataCollection}">
<local:DynamicLineChartPlotter .Resources>
<DataTemplate DataType{x:Type local:RectangleHighlightDataObject}>
<d3:RectangleHighlight
Bounds="{Binding Bounds}"
StrokeThickness="{Binding StrokeThickness}"
Fill="{Binding Fill}"
ToolTip="{Binding ToolTip}"
/>
</DataTemplate>
</local:DynamicLineChartPlotter .Resources>
</local:DynamicLineChartPlotter >
类:
public class RectangleHighlightDataObject
{
public Rect Bounds { get; set; }
public double StrokeThickness { get; set; }
public Brush Fill { get; set; }
public String ToolTip { get; set; }
}
public class VisualizationWindow
{
public VisualizationWindow()
{
DataCollection.Add(new RectangleHighlightDataObject()
{
Bounds = new Rect(-1,-1.5,.5,2),
StrokeThickness = 3,
Fill = Brushes.Blue,
ToolTip = "Blue!"
});
DataCollection.Add(new RectangleHighlightDataObject()
{
Bounds = new Rect(1,1.5,2,.5),
StrokeThickness = 1,
Fill = Brushes.Red,
ToolTip = "Red!"
});
}
public ObservableCollection<RectangleHighlightDataObject> DataCollection =
new ObservableCollection<RectangleHighlightDataObject>();
}
您必须使用ChartPlotter的派生类而不是实现ItemsSource。
从a discussion on how to implement dynamic D3 types中收集了一个示例。我修改为使用DataTemplates代替实际的对象元素,这是为了支持OP中的数据绑定。
public class DynamicLineChartPlotter : Microsoft.Research.DynamicDataDisplay.ChartPlotter
{
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource",
typeof(IEnumerable),
typeof(DynamicLineChartPlotter),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Bindable(true)]
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
if (value == null)
ClearValue(ItemsSourceProperty);
else
SetValue(ItemsSourceProperty, value);
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
IEnumerable oldValue = (IEnumerable)e.OldValue;
IEnumerable newValue = (IEnumerable)e.NewValue;
if (e.OldValue != null)
{
control.ClearItems();
}
if (e.NewValue != null)
{
control.BindItems((IEnumerable)e.NewValue);
}
}
private void ClearItems()
{
Children.Clear();
}
private void BindItems(IEnumerable items)
{
foreach (var item in items)
{
var template = GetTemplate(item);
if (template == null) continue;
FrameworkElement obj = template.LoadContent() as FrameworkElement;
obj.DataContext = item;
Children.Add((IPlotterElement)obj);
}
}
private DataTemplate GetTemplate(object item)
{
foreach (var key in this.Resources.Keys)
{
if (((DataTemplateKey)key).DataType as Type == item.GetType())
{
return (DataTemplate)this.Resources[key];
}
}
return null;
}
}
现在,这是您撞到砖墙的地方。
RectangleHighlight Bounds属性不能是数据绑定的。您也不能从他们那里得到解决此问题的方法。
我们可以通过拉出数据模板并生成静态RectangleHighlight来破解自己的方法,但是如果数据值发生变化,我们就可以解决。
那么,如何解决呢?
好吧,我们可以使用附加属性!
使用附加属性
我们将创建一个静态类来处理附加属性。它响应OnPropertyChanged来手动创建和设置real属性。现在,这只能以一种方式起作用。如果您碰巧更改了该属性,它将不会更新附加的属性。但是,这应该不成问题,因为我们应该只更新数据对象。
添加课程
public class BindableRectangleBounds : DependencyObject
{
public static DependencyProperty BoundsProperty = DependencyProperty.RegisterAttached("Bounds", typeof(Rect), typeof(BindableRectangleBounds), new PropertyMetadata(new Rect(), OnBoundsChanged));
public static void SetBounds(DependencyObject dp, Rect value)
{
dp.SetValue(BoundsProperty, value);
}
public static void GetBounds(DependencyObject dp)
{
dp.GetValue(BoundsProperty);
}
public static void OnBoundsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs args)
{
var property = dp.GetType().GetProperty("Bounds");
if (property != null)
{
property.SetValue(dp, args.NewValue, null);
}
}
}
然后从更改XAML行
Bounds="{Binding Bounds}"
至
local:BindableRectangleBounds.Bounds="{Binding Bounds}"
响应集合已更改。
到目前为止,一切都很好。但是OP注意到,如果他对分配了ItemsSource的集合进行了更改,则控件中没有任何更改。那是因为我们仅在分配ItemsSource时添加子代。现在,除非确切知道ItemsControl如何实现ItemsSource。我知道我们可以通过在集合更改时注册ObservableCollection的事件来解决此问题。每当集合发生更改时,我都会提供一种重新绑定控件的简单方法。不过,这仅在ItemsSource是由ObservableCollection分配的情况下才有效。但是我认为如果没有ObservableCollection,ItemsControl会遇到同样的问题。
但是,我们仍然没有重新分配dataContext。因此,不要指望改变孩子会正确地重新绑定。但是,如果您确实直接更改了子级,而不是直接在ItemsControl中更改ItemsSource,则会丢失绑定。因此,我很可能会步入正轨。我们失去的唯一怪癖是,在设置ItemsSource后,当您引用Children时,ItemsControl将返回ItemsSource。我还没有解决这个问题。我唯一能想到的就是隐藏children属性,但这并不是一件好事,而且如果您将控件引用为ChartPlotter,将无法正常工作。
添加以下内容
public DynamicLineChartPlotter()
{
_HandleCollectionChanged = new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DynamicLineChartPlotter control = (DynamicLineChartPlotter)d;
IEnumerable oldValue = (IEnumerable)e.OldValue;
IEnumerable newValue = (IEnumerable)e.NewValue;
INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
INotifyCollectionChanged oldCollection = e.OldValue as INotifyCollectionChanged;
if (e.OldValue != null)
{
control.ClearItems();
}
if (e.NewValue != null)
{
control.BindItems((IEnumerable)e.NewValue);
}
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control._HandleCollectionChanged;
control._Collection = null;
}
if (collection != null)
{
collection.CollectionChanged += control._HandleCollectionChanged;
control._Collection = newValue;
}
}
void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ClearItems();
BindItems(_Collection);
}
NotifyCollectionChangedEventHandler _HandleCollectionChanged;
IEnumerable _Collection;
关于c# - 可变数目的矩形的动态数据显示图,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15396972/