Laden...

Dependency Property

Erstellt von danielpalme vor 16 Jahren Letzter Beitrag vor 16 Jahren 3.021 Views
danielpalme Themenstarter:in
51 Beiträge seit 2007
vor 16 Jahren
Dependency Property

Folgende Ausgangssituation:
Habe eine Textbox die per Databinding einen Text einer anderen Klasse anzeigt.
Ändert man den Text geändert, so ändert sich auch der Text in der referenzierten Klasse und umgekehrt. Soweit so gut

Nun wollte ich das Verhalten der Textbox erweitern und zwar so, dass ein initialer Text gesetzt werden kann, der angezeigt wird, wenn der eigentliche Text leer ist.
Dazu habe ich folgende Klasse erstellt:

public class InitialTextBox : TextBox
{
        public string InitialText { get; set; }

        public bool HasValue
        {
            get
            {
                return !string.IsNullOrEmpty(Text);
            }
        }
        
        
        public static new readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InitialTextBox), new UIPropertyMetadata(InitialTextBox.TextChanged));

        private static new void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            InitialTextBox initialTextBox = (InitialTextBox)d;
            initialTextBox.Text = (string)e.NewValue;
        }


        public new string Text
        {
            get 
            {
                if ((string)GetValue(TextProperty) != InitialText)
                {
                    return (string)GetValue(TextProperty);
                }
                else
                {
                    return String.Empty;
                }
            }

            set 
            {
                if (!string.IsNullOrEmpty(value))
                {
                    SetValue(TextProperty, value);
                    base.Text = value;
                }
                else
                {
                    base.Text = InitialText;
                }
            }
        }

        protected override void OnGotFocus(System.Windows.RoutedEventArgs e)
        {
            if (!HasValue) base.Text = "";
            base.OnGotFocus(e);
        }

        protected override void OnLostFocus(System.Windows.RoutedEventArgs e)
        {
            if (!HasValue) base.Text = InitialText;
            base.OnLostFocus(e);
        }

        public void Reset()
        {
            this.Text = InitialText;
        }
    }

Ich habe also die "TextProperty" und diverse andere Methoden überschrieben.
Leider werden Änderungen nun in beiden Richtungen nicht mehr übertragen.

Hat jemand eine Idee, was ich falsch mache?

Vielen Dank schon mal!

6.862 Beiträge seit 2003
vor 16 Jahren

Hallo,

bei dir läuft so einiges schief, erstmal ist des ableiten an sich fast unnötig, da du ja nicht die Funktionalität der Textbox erweiterst, sondern du zeigst statt "" nur nen Defaulttext an, doch dazu später mehr, erstmal zu den anderen Sachen.

Ich habe also die "TextProperty" ... überschrieben.

Nein, du hast das eigentliche TextProperty überblendet und registriest dir ein völlig neues TextProperty das nur deiner TextBox gehört und das ist schonmal net so gut. Das Problem daran ist ja dass jedes Control was ne TextBox irgendwie verwendet das TextBox.TextProperty verwendet, und nicht dein InitialTextBox.TextProperty - deshalb könntest du deine TextBox nicht einfach als Ersatz für die normale TextBox verwenden, sondern die steht für sich allein da, zumindest was das TextProperty angeht. Sowas kann man schon machen, aber meistens will man ja dass die abgeleitete TextBox äquivalent zur normalen TextBox verwendet wird. Das heißt du müsstest deine TextBox irgendwie bei der Textbox.TextProperty registrieren und dass kannst du mit AddOwner statt mit Register machen, und dort kannst du auch deine Metadaten mit dem ChangedEventHandler reinhängen. Damit wäre ein potentielles Problem verarztes, kommen wir nun zum viel schlimmeren 🙂

public new string Text
        {
            get 
            {
                if ((string)GetValue(TextProperty) != InitialText)
                {
                    return (string)GetValue(TextProperty);
                }
                else
                {
                    return String.Empty;
                }
            }

            set 
            {
                if (!string.IsNullOrEmpty(value))
                {
                    SetValue(TextProperty, value);
                    base.Text = value;
                }
                else
                {
                    base.Text = InitialText;
                }
            }
        }

Sowas darf man nie machen. Zumindest nicht wenn du deine TextBox in XAML verwenden willst. Dieses Text Property ist ja nur nen Wrapper für das TextProperty Dependency Property und dieser Wrapper darf niemals irgendwelche Verarbeitungslogik haben. Okay, was heißt darf 🙂 Kannst du so schreiben, nützt aber alles nichts, da dieser Wrapper aus XAML niemals aufgerufen wird. Wenn du sowas schreibst:

<InitialTextBox x:Name="myTextBox" Text="Hallo" />

wird das direkt zu:

InitialTextBox myTextBox = new InitialTextBox();
myTextBox.SetValue(TextProperty,"Hallo");

Deine schöne Verarbeitungslogik für den Defaulttext ist umsonst, zumindest wenn du die TextBox aus XAML heraus verwendet.

Nun warum dein DataBinding sich verabschiedet.
"Schuld" sind Zeilen wie

base.Text = "";

oder

base.Text = InitialText;

Damit setzt du einen lokalen Wert für Text, und lokale Werte haben den höchsten Vorrang. Mal die Tabelle der Reihenfolge weil ich die später noch brauch 🙂

  1. Local value
  2. Style triggers
  3. Template triggers
  4. Style setters
  5. Theme style triggers
  6. Theme style setters
  7. Property value inheritance
  8. Default value

Danach kommen noch ein paar Schritte um den wirklichen Wert eines Dependecy Properties zu bestimmen ,aber sobald du erstmal den Local Value gesetzt hast, kannst du den Wert nicht mehr nachträglich durch irgendwelche Styles oder auch DataBinding setzen. Da hilft nur ein ClearValue beim DependencyObject. Dann funktionieren auch wieder alle anderen Möglichkeiten ein Dependency Property dynamisch zu setzen.

Das sind erstmal die großen Knackpunkte die ich bei deinem Code seh.
Aber was wäre eine Antwort wo nur Fehler aufgezählt werden ohne Vorschläge 😉

Wenn du auf ein explizites InitialText Property verzichten kannst, verwende Styles bzw. Templates. Wie oben schon angedeutet, du veränderst ja an sich nur die Anzeige der TextBox, so wie ich es verstanden hab soll der InitialText ja auch net in der per DataBinding gebundenen Variable landen, sondern nur der Text der eingegeben wird oder? Falls ja ist des erst Recht ein Fall das ganze als reine GUI Angelegenheit zu betrachten. Sprich du machst einfach nen Multitrigger auf Text und auf IsFocused deiner TextBox und falls halt die TextBox leer ist und kein Focus hat dann zeigst du deinen InitialText(in meiner überlegung grad Hardcoded im Style was nicht so schön ist, aber würde erstmal gehen, kann man immer noch weiter ausbauen). Der zweite Trigger würde halt schaun wann die TextBox den Focus bekommt und dann den Text wieder auf "" setzen falls nicht was anderes als der InitialText drin steht. Rein theoretisch funktioniert das gut, hat aber seine Tücken wie ich mitbekommen hab wo ich das kurz ausprobiert hab. Wenn du nen Trigger auf den Text machst, kannst du nicht gleichzeitig im Setter den Text setzen, das mag er net. Und wenn du per DataTrigger auf z.B. Text.Length schaust, könntest du gleichzeitig den Text setzen, des gibt aber ne Endlosschleife.

Ehrlich gesagt weiß ich da spontan keine Lösung, aber werd nacher mal bissle rumprobieren, interessieren tut mich das Problem schon. Weil ein Problem was ich noch seh, ist halt die Auswertungsreihenfolge die ich oben in der Tabelle schonmal gepostet hab. Sobald du irgendwie deinen Text in die TextBox schreibst beißt sich das mit dem DataBinding und man muss da bissle rumschiffen. Aber ich glaube Styles sind da noch eher machbar als den lokalen Wert zu setzen, wie du das grad machst, weil dagegen hast keine Chance per DataBinding 🙂

Eine spontane Idee die mir einfällt wäre noch direkt beim Databinding einzugreifen und einen Konverter reinzuhängen der falls "" ansteh, einfach den DefaultText zurückgibt. Glaube echt dass ist noch das einfachste.

Soo, ist ziemlich lang geworden, aber hatte irgendwie grad Bock 🙂 Gerade weil da Probleme im Ursprungscode sind die wahrscheinlich öfter mal auftauchen in Fragen hier im Forum und dann kann ich immer hierhin verweisen 😉

Baka wa shinanakya naoranai.

Mein XING Profil.

danielpalme Themenstarter:in
51 Beiträge seit 2007
vor 16 Jahren

Hallo talla,

vielen Dank für deine ausführliche Antwort. Ich werde mir das ganze in den nächsten Tagen mal ansehen. Heute komme ich wahrscheinlich nicht dazu!.

H
8 Beiträge seit 2007
vor 16 Jahren

Hallo

Es gibt ein fertiges Controll ( InfoTextBox ) mit zumindest ähnlicher Funktion
zum Download (mit Source) auf Kevin Moore's blog.

http://blogs.msdn.com/okoboji/

Hans

danielpalme Themenstarter:in
51 Beiträge seit 2007
vor 16 Jahren

Perfekt. Vielen Dank für den Tipp, genau das brauche ich.
Auch die anderen Teilprogramme des Pakets sind sehr interessant.