将 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。
从有关如何实现动态 D3 类型的讨论中收集到的示例。我修改为使用 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 手动创建和设置真实属性。现在,这只会以一种方式起作用。如果您碰巧更改了属性,它不会更新附加的属性。但是,这应该不是问题,因为我们应该只更新我们的数据对象。
添加这个类
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。所以不要指望改变 Children 会正确地重新绑定。但是,如果您确实直接更改子项,而不是 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;