Home > Mobile >  Cannot use an attached property in my DataTemplate
Cannot use an attached property in my DataTemplate

Time:09-30

I am relatively new to WPF and am trying to understand Attached Properties to use them in my test application I am using as a learning base. The AP is in it'own source file named AP.cs that lives in namespace WPFPages.ViewModels.

So I have created a simple AP as shown below that stores an int value.

public class AP: DependencyObject
{
    public static int Gettest ( DependencyObject obj )
    {
       return ( int ) obj . GetValue ( testProperty );
    }

    public static void Settest ( DependencyObject obj, int value )
    {
       obj . SetValue ( testProperty, value );
    }
    
    public static readonly DependencyProperty testProperty =
        DependencyProperty . RegisterAttached ( "test", typeof ( int ), typeof ( AP ), new PropertyMetadata ( 2 ), Ontestchanged);

    private static bool Ontestchanged ( object value )
    {
            Console . WriteLine ($"test value changed to {value}");
            return true;
    }
}

In my XAML I have set the value using models:AP.test="21" which works as expected, verfied by my WriteLine debug in the AP.

However, I want to have this value as a column in a ListBox, and therefore have a DataTemplate that has the following entry:

<TextBlock x:Name="tst" 
           Text="{Binding Path=(models:AP.test), RelativeSource={RelativeSource Self}}" 
           Width="55"/>

Sadly, I only see the default set in the AP in my ListBox, NOT the value of 21. I have set it to in my ListBox properties. I have declared the namespace in my XAML files as shown below.

xmlns:models="clr-namespace:WPFPages.ViewModels"

I have read every possible article on attached properties, and have tried more variations on a theme in terms of syntax to make this work, but with no success at all. So far I have spent 3 days trying to get this simple AP to work.

I feel sure it is something simple due to my inexperience with the complexities of Binding, but would greatly appreciate some assistance in getting to the bottom of how I make this work, as I can see how powerful the AP's can be, if only I can make them work of course?.

UPDATE instead of using Comment :

<ListBox x:Name="listboxclass"
    ItemContainerStyle="{StaticResource lvItemStyle1}"
    BorderThickness="2"
    BorderBrush="{StaticResource Blue0}"
    FontWeight="Normal"
    FontSize="14" Margin="0,0,0,164"   >
    <ListBoxItem
          Height="45"
          models:AP.test="21"/>

The relevant Style code is:

Hope this clarifies it. I am happy to get the same value appearing in the column selected for now.

<Style x:Key="lvItemStyle1" TargetType="{x:Type ListBoxItem}">
    <!--<Setter Property="Background" Value="{Binding (models:AP.Background)}"/>-->

    <Setter Property="Template">

          <Setter.Value>
                
                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                      <Grid>
                            <!--<Border x:Name="Bd" 
                                    BorderBrush="{TemplateBinding BorderBrush}" 
                                    BorderThickness="{TemplateBinding BorderThickness}" 
                                    Padding="{TemplateBinding Padding}" 
                                    SnapsToDevicePixels="true">
                                  <Border.Background>
                                        <SolidColorBrush x:Name="borderbckgrnd" Color="{TemplateBinding models:AP.Background}" />
                                  </Border.Background>

                                  <ContentPresenter x:Name="contentpresenter"                                                            
                                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                                  </ContentPresenter>



                            </Border>-->

                            <!--I have tried ALL of these & they ALL cause Exceptions on loading
                                  "{Binding models:AP.test}"
                                  "{Binding Path=(models:AP.test)}"
                                  "{Binding (models:AP.test)}"
                                  "{Binding Source=models:AP.test}"
                                  "{TemplateBinding  models:AP.test}"-->
                            <Border x:Name="Bd" >
                                  <StackPanel Orientation="Horizontal" >
                                        <TextBlock Text="{Binding CustomerId}" 
                                             Height="{Binding ActualHeight, ElementName=NwCustomerDataTemplateBorder1}"  
                                             Width="50" 
                                             Padding="1" 
                                              x:Name="CustomerId"/>
                                        
                                        <!--NB Output window shows :
                                              AP : test value changed to 2
                                              AP : test value changed to 21
                                                    so it is definitely being set.
                                        but the field is empty in the list when run-->
                                        <TextBlock x:Name="tst" 
                                             Text="{Binding Path=models:Attach.test}"
                                              Foreground="Red"                                                           
                                             Width="55"/>
                                        <!--this does  not give error, but does not work either
                                        Height="{Binding Path=models:AP.test, RelativeSource={RelativeSource Self}}"-->
                                        
                                        <TextBlock Text="{Binding CompanyName}" Width="165" Padding="1" x:Name="CompanyName"/>
                                        <TextBlock Text="{Binding ContactName}" Width="135" Padding="1" x:Name="ContactName"/>
                                        <TextBlock Text="{Binding ContactTitle}" Width="40" Padding="1"  x:Name="ContactTitle"/>
                                        <TextBlock Text="{Binding Address}" Width="150" Padding="1"  x:Name="Address"/>
                                        <TextBlock Text="{Binding City }" Width="80" Padding="1"  x:Name="City"/>
                                        <TextBlock Text="{Binding PostalCode}" Width="75" Padding="1"  x:Name="PostalCode"/>
                                        <TextBlock Text="{Binding Country}" Width="75" Padding="1"  x:Name="Country"/>
                                        <TextBlock Text="{Binding Phone}" Width="95" Padding="1"  x:Name="Phone"/>
                                        <TextBlock Text="{Binding Fax}" Width="95" Padding="1"  x:Name="Fax"/>
                                  </StackPanel>
                            </Border>
                            <!-- ... -->

CodePudding user response:

This is how you could bind to the attached property of the parent ListBox in the ItemTemplate:

<ListBox models:AP.test="21">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock x:Name="tst" 
                       Text="{Binding Path=(models:AP.test), 
                           RelativeSource={RelativeSource AncestorType=ListBox}}" 
                       Width="55"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

CodePudding user response:

The attached AP.test property is defined on the ListBoxItem, so you need to refer to that element in order to get the assigned value, otherwise the binding will not work. Your trials do not work because:

  • {Binding models:AP.test} - This is not the correct syntax for binding attached properties, use parentheses ( and ), see binding path syntax.
  • {Binding Path=(models:AP.test)} - Correct syntax, but the attached property will be resolved on the element itself, here the TextBlock, which does not define it.
  • {Binding (models:AP.test)} - Same as above.
  • {Binding Source=models:AP.test} - Sets the attached property as binding source, which is wrong.
  • {TemplateBinding models:AP.test} - Does not work with attached properties.

You can make the binding work by using the attached property binding syntax and refering to the ListBoxItem that defines the value. Since the TextBlock is part of the ListBoxItem template, you can use a RelativeSource binding with TemplateParent.

<TextBlock x:Name="tst"
           Text="{Binding (local:AP.test), RelativeSource={RelativeSource TemplatedParent}}"
           Foreground="Red"
           Width="55" />

Alternatively, you can specify the AncestorType, as ListBoxItem is a parent of a ListBoxItem.

<TextBlock x:Name="tst"
           Text="{Binding (models:AP.test), RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
           Foreground="Red"
           Width="55" />
  • Related