I'm in the process of making a custom control -- specifically a pie chart. I want the markup to look something like this example:
<c:PieChart>
<!-- These dependency properties are never set -->
<c:Slice Value="{Binding RedCount}" />
<c:Slice Value="{Binding BlueCount}" />
<c:Slice Value="{Binding GreenCount}" />
</c:PieChart>
PieChart
derives from Control
:
[ContentProperty("Slices")]
public class PieChart : Control
{
public PieChart()
{
Slices = new ObservableCollection<Slice>();
}
public ObservableCollection<Slice> Slices { get; private set; }
}
The above XAML causes the Slices
property to be populated with three instances of Slice
.
Here's a snippet from Slice
:
public class Slice : ContentControl
{
public static DependencyProperty ValueProperty
= DependencyProperty.Register(
"Value", typeof(double), typeof(Slice),
new PropertyMetadata((p,a) => ((Slice)p).OnValueChanged(a)));
private void OnValueChanged(DependencyPropertyChangedEventArgs e)
{
// This code never called!
}
}
The problem is that the Value
property on Slice
is never set. If I put the dependency property on the PieChart
and copy the binding expression, then the value is set, so I'm confident that I understand the basics of dependency properties and bindings.
So, what do I have to do to have my dependency property set a value on the child item? As these items are in my own collection, are they somehow not in the logical tree recognised by whatever magic makes bindings work?
A simple 'do this' answer would be helpful, but I'd really like to understand what's going wrong here, so I'll accept the most insightful, explanatory answer posted.
EDIT I didn't make it clear but there is some ambient object on the DataContext
of the PieChart
that contains the properties RedCount
, etc. In the past, when I've had a typo in a binding's path (or some other mistake) I've seen warnings in the VS output window at runtime. In this case, I see nothing.
-
I think the problem you're seeing is that the bindings are trying to use the wrong source. I suspect something like this will work for you:
<c:PieChart> <!-- These dependency properties are never set --> <c:Slice Value="{Binding RedCount, RelativeSource={AncestorType c:PieChart}}" /> <c:Slice Value="{Binding BlueCount, RelativeSource={AncestorType c:PieChart}}" /> <c:Slice Value="{Binding GreenCount, RelativeSource={AncestorType c:PieChart}}" /> </c:PieChart>
I think that's close, but I might be way off. I can't really look it up right now. But it sounds good to me. (Sorry if it's completely wrong :) )
MojoFilter : After reading the question again, I don't think those values are coming from the PieChart class in the first place... which makes this completely pointless. You're welcome.itowlson : Picky point -- I think that should be {Binding RelativeSource={RelativeSource AncestorType...}} (i.e. you need to repeat RelativeSource inside the RelativeSource expression). Yeah, annoying I know.Drew Noakes : Thanks but no, the properties are defined on some ambient data context object. If the bindings were failing, I'd see warning messages in the VS output window, but none are displayed. I'll edit the post to mention this. -
It looks like the
Slice
controls aren't getting hooked up to the visual tree. They are just member variables, and WPF doesn't know it's meant to be displaying them as child elements of thePieChart
. A couple of possible approaches:Have
PieChart
callAddLogicalChild
to add theSlice
objects. You will need to handle the possibility ofSlice
s being added to or removed from the collection at runtime. This is pretty low level: MSDN advises, "For control authors, manipulating the logical tree at this level is not the recommended practice, unless none of the content models for available base control classes are appropriate for your control scenario. Consider subclassing at the level ofContentControl
,ItemsControl
, andHeaderedItemsControl
."Make
PieChart
aPanel
. TheSlice
s will then be its children, but note that theChildren
property is weakly typed, so people could add anything to aPieChart
. This is not inherently a bad or a good thing; it's a design consideration (see note below). But it's probably not what you want in your case.Make
PieChart
anItemsControl
, and overrideGetContainerForItemOverride
,IsItemItsOwnContainerOverride
and possiblyPrepareContainerForItemOverride
to create/respectSlice
controls. (Cf.ListBox
andListBoxItem
,ComboBox
andComboBoxItem
, etc.) A nice feature of this is that users can then use anItemsSource
and aDataTemplate
(like aListBox
) instead of having to create the child controls by hand. (But users can still create fixedSlice
s in XAML per your syntax, just as they can createListBoxItems
in XAML.)You could even consider a hybrid approach, e.g. factoring out the radial layout functionality into a
RadialLayoutPanel
and the pie slice generation into anItemsControl
which uses aRadialLayoutPanel
as itsItemsPanel
.
0 comments:
Post a Comment