views:

840

answers:

3

Ok - I'm pulling my hair out over what I thought was a simple scenario: create a custom Label for bilingual use that contained two additional properties (EnglishText, FrenchText). Currently its structured like this:

Public Class myCustomLabel
    Inherits System.Windows.Controls.Label

    Public myEnglishTextProperty As DependencyProperty = DependencyProperty.Register("myEnglishText", GetType(String), GetType(myCustomLabel), New PropertyMetadata("English", New PropertyChangedCallback(AddressOf TextChanged)))
    Public myFrenchTextProperty As DependencyProperty = DependencyProperty.Register("myFrenchText", GetType(String), GetType(myCustomLabel), New PropertyMetadata("Francais", New PropertyChangedCallback(AddressOf TextChanged)))

    Public Sub New()
        'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
        'This style is defined in themes\generic.xaml
        DefaultStyleKeyProperty.OverrideMetadata(GetType(myCustomLabel), New FrameworkPropertyMetadata(GetType(myCustomLabel)))
    End Sub

    Public Property myEnglishText() As String
        Get
            Return MyBase.GetValue(myFrenchTextProperty)
        End Get
        Set(ByVal value As String)
            MyBase.SetValue(myFrenchTextProperty, value)
        End Set
    End Property

    Public Property myFrenchText() As String
        Get
            Return MyBase.GetValue(myFrenchTextProperty)
        End Get
        Set(ByVal value As String)
            MyBase.SetValue(myFrenchTextProperty, value)
        End Set
    End Property

    Private Sub TextChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        If  DesignerProperties.GetIsInDesignMode(Me) = True Then
            Me.Content = myEnglishText
        Else
            If myUser.Language = "E" Then
                Me.Content = myEnglishText
            Else
                Me.Content = myFrenchText
            End If
        End If
    End Sub
End Class

My test window grid xaml is simple:

<Grid>
        <my:myCustomLabel myEnglishText="English Text" myFrenchText="English Text" Height="25" Width="100" Background="Aqua" Foreground="Black"/>
</Grid>

This seems to work in the development environment - changing the English and French texts change the in the design preview and it works when the app runs and the test window is opened. But only the first time - if I open the test window a second time I receive the following message:

'myEnglishText' property was already registered by 'myCustomLabel'.

I understand now that if I change the dependency property declarations to shared then this problem goes away - but that leads to a host of other problems like the callback function being required to be shared as well - and thus unable to update the Content (which needs to be instantiated with the class). All I really want is the content property to be updated in design time when the english and french labels are changed.

Is there a way around this? Or maybe are dependency properties overkill for what I need?

+1  A: 

Is the reason you don't want the callback method to be shared because you're accessing the "me" instance? If that's all it is, make it shared and use the "d" parameter. I don't know VB well enough to show you the code, but just create a variable of type myCustomLabel and assign "d" to it (with a cast). Then use that variable (say "lbl") instead:

If  DesignerProperties.GetIsInDesignMode(lbl) = True Then
    lbl.Content = myEnglishText
Else
    If myUser.Language = "E" Then
        lbl.Content = myEnglishText
    Else
        lbl.Content = myFrenchText
    End If
End If
Matt Hamilton
Thank you Matt - same answer as below (I set it as Answer because of the completeness) but your's was correct as well. I appreciate the help.
Gatmando
+1  A: 

Also, there's a slight bug in your example code. Try using this:

Public Property myEnglishText() As String
    Get
        Return MyBase.GetValue(myEnglishTextProperty)
    End Get
    Set(ByVal value As String)
        MyBase.SetValue(myEnglishTextProperty, value)
    End Set
End Property

Instead of this:

Public Property myEnglishText() As String
    Get
        Return MyBase.GetValue(myFrenchTextProperty)
    End Get
    Set(ByVal value As String)
        MyBase.SetValue(myFrenchTextProperty, value)
    End Set
End Property
+2  A: 

You are registering your dependency properties as instance variables, and during the instance constructor. So they are getting registered again every time you instantiate the control, which causes an error the second time. As you have found out, dependency properties need to be static (Shared) members:

Public Shared myEnglishTextProperty As DependencyProperty =   
  DependencyProperty.Register("myEnglishText", GetType(String), GetType(myCustomLabel),
  New PropertyMetadata("English", New PropertyChangedCallback(AddressOf TextChanged)))

You probably need to call OverrideMetadata in your shared constructor (type initialiser) rather than your instance constructor as well.

Regarding your issue with the callback needing to be shared: yes, it will be, but one of the arguments to the callback is the label instance. So you can just cast that to label and call an instance method on that:

private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  ((MyLabel)d).TextChanged();
}

private void TextChanged()
{
  // your code here
}

(forgive C# syntax)

itowlson
Thank you itowlson! The dependencyobject "d" was staring me in the face the whole time and I never put two and two together.
Gatmando