1
votes

I have a custom control with two DependencyProperties. One of type object, which allows the user to add custom content like other controls and one of type string which is used in a Textbox:

public object NoResultContent
{
    get { return (object)GetValue(NoResultContentProperty); }
    set { SetValue(NoResultContentProperty, value); }
}

public static readonly DependencyProperty NoResultContentProperty =
    DependencyProperty.Register("NoResultContent", typeof(object), typeof(AdvancedAutoCompleteBox), new PropertyMetadata(null));

public string FilterText
{
    get { return (string)GetValue(FilterTextProperty); }
    set { SetValue(FilterTextProperty, value); }
}

public static readonly DependencyProperty FilterTextProperty =
    DependencyProperty.Register("FilterText", typeof(string), typeof(AdvancedAutoCompleteBox),
            new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
                        new PropertyChangedCallback(OnFilterTextPropertyChanged), new CoerceValueCallback(CoerceText),
                        true, UpdateSourceTrigger.PropertyChanged));

The ControlTemplate looks like:

<ControlTemplate
    TargetType="{x:Type local:SpecialBox}">
    <StackPanel>
        <TextBox
            Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=FilterText}" />
        <ContentPresenter
            ContentSource="NoResultContent" />
    </StackPanel>
</ControlTemplate>

I'm using it like this:

<Controls:SpecialBox
    Name="Box">
    <Controls:SpecialBox.NoResultContent>
        <Button
            Content="Add value"
            CommandParameter="{Binding ElementName=Box, Path=FilterText}"
            Command="{Binding AddProject}" />
    </Controls:SpecialBox.NoResultContent>
</Controls:SpecialBox>

<TextBlock Text="{Binding ElementName=Box, Path=FilterText}" />

The DataContext of my Window is set to my ViewModel. So Binding to the ICommand works. Providing a constant string as a CommandParameter will pass it as desired to the ICommand.

CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"

will pass "Add value" to my ICommand implementation.

The ElementName Binding, as stated above and following code CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Controls:SpeicalBox}, Path=FilterText}"

does not work. The source can not be found.

CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FilterText}"

does not throw a warning, but always return null.

Further info: The OnFilterTextPropertyChanged Event of my dp is fired on each change. So the value is available, that's why the TextBlocks Text Binding to the SpecialBox works quite well.

Providing a second Property on my ViewModel for the FilterText value would be a workaround, but how am I able to access the local dp from that second property?

2
What do you mean by "not working"? What is the command parameter your command is getting?Rachel
The Binding always return null. Writing a constant string value as a parameter works fineFlorian
First you need to establish whether Button.CommandParameter is actually null or not (the fault might just as well be in bad ICommand implementation). E.g. add a Button.Click handler, set a breakpoint and inspect the Button.Grx70
ICommand works well. The actual problem, is that any Element or relative binding does not work: Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Controls.SpecialBox', AncestorLevel='1''Florian
Ok then. In that case is the code you're provided the actual template that you're using (in particular the ContentPresenter)? Also, what framework version are you using?Grx70

2 Answers

1
votes

The proper way of binding in this case is to use RelativeSource in FindAncestor mode:

CommandParameter="{Binding FilterText,
    RelativeSource={RelativeSource AncestorType=Controls:SpeicalBox}}"

Also, using ElementName should work fine.

Now why it does not work is because you're using TemplateBinding on the TextBox in your template. The TemplateBinding works one-way, so whatever you type in the TextBox is not pushed back to your FilterText property (it was designed to forward properties like Background, BorderBrush etc. to the template more efficiently by being resolved at compile-time unlike normal bindings, which are resolved at run-time). So what you need to do is to replace it with a normal binding with RelativeSource in TemplatedParent mode in your template:

<TextBox Text="{Binding FilterText, RelativeSource={RelativeSource TemplatedParent}}" />

That at least is what resolved the problem I've managed to reproduce based on the code you've provided (and it was my first suspicion). It does however stand in contradiction to your claim that the binding on the TextBlock did actually work. So I suggest you try this out and get back if it does not solve your problem.

0
votes

Changing the ContenPresenter to a ContentControl did the trick

<ContentControl
    Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=NoResultContent}" />