Laden...

DependencyProperty Coerce & Changed & TextBox

Erstellt von WarLorD_XaN vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.647 Views
W
WarLorD_XaN Themenstarter:in
113 Beiträge seit 2006
vor 13 Jahren
DependencyProperty Coerce & Changed & TextBox

Hallo,

Ich habe ein CustomControl mit einer Value DependencyProperty [decimal] und einer Maximum DependencyProperty [decimal]. In der CoerceCallback der Value Property führe ich die Begrenzung der Value auf das Maximum durch.

In meinem Control Template habe ich nun den Value Wert an die Text Property einer TextBox gebunden und konvertiere den string per IValueConverter in einen decimal.

Mein Problem sieht nun wie folgt aus:
Gebe ich das erste mal einen Wert der größer ist als das Maximum in die TexBox ein wird dieser von dem Coerce Callback auf dasMaximum begrenzt und der Text in der TextBox aktualisiert.
Gebe ich nun das 2. mal eine Wert der größer ist als das Maximum ein wird dieser zwar durch den Coerce Callback begrenzt, jdeoch wird der string in der TextBox nicht aktualisiert.
-> Das heißt ich habe am Ende einen Wert der größer ist als das Maximum in der TextBox stehen mein Value Property ist jedoch genau auf das Maximum gesetzt.

Ich habe herausgefunden das beim 2. setzen der Text Property der PropertyChangedCallback der Value Property nicht aufgerufen wird.

Beispielcode:


public class MyControl : Control
{
    public static readonly DependencyProperty ValueProperty;
    public static readonly DependencyProperty MaximumProperty;

    static MyControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));

        //--    initialize the dependency properties
        ValueProperty = DependencyProperty.Register("Value", typeof (decimal), typeof (MyControl), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnValueChanged), ValueCoerce));

        MaximumProperty = DependencyProperty.Register("Maximum", typeof(decimal), typeof(MyControl));
    }

    private static object ValueCoerce(DependencyObject Obj, object Value)
    {
        MyControl myControl = (MyControl)Obj;
        decimal temp = (decimal) Value;

        if(temp > myControl.Maximum)
        {
            temp = myControl.Maximum;
        }

        return temp;
    }

    private static void OnValueChanged(DependencyObject Obj, DependencyPropertyChangedEventArgs E)
    {
        //--    wird beim 2. setzen nicht mehr aufgerufen
    }

    public decimal Value
    {
        get
        {
            return (decimal)this.GetValue(ValueProperty);
        }
        set
        {
            this.SetValue(ValueProperty, value);
        }
    }

    public decimal Maximum
    {
        get
        {
            return (decimal)this.GetValue(MaximumProperty);
        }
        set
        {
            this.SetValue(MaximumProperty, value);
        }
    }
}


<ControlTemplate TargetType="{x:Type local:MyControl}">
                    <ControlTemplate.Resources>
                        <local:DecimalStringConverter x:Key="decimalStringConverter" />
                    </ControlTemplate.Resources>

                    <TextBox Grid.RowSpan="2" VerticalAlignment="Center" Margin="0,0,5,0" TextAlignment="Right" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource decimalStringConverter}}" />
                </ControlTemplate>

Wäre echt dankbar um jegliche Hilfe.

mfg,
XaN

L
862 Beiträge seit 2006
vor 13 Jahren

Ich vermute dass sich deine TextBox nur aktualisiert wenn sie eine Änderung des DepdendencyProperties feststellt.

Beispiel:

Value=50
Maximum=100

Benutzer gibt eine 0 ein. -> CoerceValue beschränkt auf 100. -> Wert ändert sich von 50 auf 100. -> TextBox aktualisiert sich.
Benutzer tippt auf die 0. (Wert wäre jetzt wieder 500)-> CoerceValue beschränkt auf 100. -> Da der Wert vorher allerdings schon 100 war wird er nicht mehr geändert. Dadruch mekrt die TextBox nicht dass sie sich aktualisieren muss.

Versuche mal folgendes. Gib nach dem ersten CoerceValue mal einen legalen Wert an und teste ob der CoerceValue dann wieder funktioniert (um zu testen ob meine Theorie stimmt).
Fall sie stimmt musst du wohl oder übel den Wert des DependencyProperties auf Maximum-1 ändern und die Änderung dann wieder rückgängig machen.

W
WarLorD_XaN Themenstarter:in
113 Beiträge seit 2006
vor 13 Jahren

Hallo Lector,

Danke für deine Antwort.

Versuche mal folgendes. Gib nach dem ersten CoerceValue mal einen legalen Wert an und teste ob der CoerceValue dann wieder funktioniert (um zu testen ob meine Theorie stimmt).

Hab ich ausprobiert und das Coerce funktioniert.

Ich habe nun den Code um das von dir vorgeschlagene ergänzt - und siehe da es funktionert:


public class MyControl : Control
{
    //....

    private static object ValueCoerce(DependencyObject Obj, object Value)
    {
        MyControl myControl = (MyControl)Obj;
        decimal temp = (decimal) Value;

        if(myControl.overwriteValue != null)
        {
            myControl.overwriteValue = null;
        }

        if(temp > myControl.Maximum)
        {
            temp = myControl.Maximum;
            myControl.overwriteValue = temp;
            return temp - 1;
        }

        return temp;
    }

    private TextBox textBox;
    private decimal? overwriteValue;

    public MyControl()
    {
        this.overwriteValue = null;
    }

    private void TextBoxTextChanged(object Sender, TextChangedEventArgs E)
    {
        if (this.overwriteValue != null)
        {
            this.Value = this.overwriteValue.Value;
        }
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        if (this.Template == null)
        {
            return;
        }

        this.textBox = this.Template.FindName("PART_TextBox", this) as TextBox;

        if (textBox == null)
        {
            throw new InvalidOperationException("PART_TextBox is missing in the Template.");
        }

        this.textBox.TextChanged += this.TextBoxTextChanged;
    }

    //...
}

Ich frage mich ob es da nicht eine "schönere" Lösung gibt, z.B. das ich das Control irgendwie zwinge das Changed auszulösen. Durch das setzen auf Maximum - 1 erreiche ich ja im Endeffekt nichts anderes, aber mich wundert dass es seitens WPF keine Möglicheit gibt diese Gleichheitsprüfung die offenbar intern abläuft zu umgehen.

mfg,
XaN

L
862 Beiträge seit 2006
vor 13 Jahren

Ob es eine 'schönere' Lösung gibt habe ich mich auch schon gefragt. Das gleiche Problem tritt übrigens auch im Zusammenhang mit der Validierung auf. Wenn ich möchte dass sich die Validierung von einen bestimmten D.-Properties ändert muss ich den Wert ändern und neu setzen. Bei INotifyPropertyChanged genügt es hier einfach manuell den Changed-Event zu feuern.

Irgendwo hier im Forum wurde das gleiche Problem bereits in diesem Zusammenhang festgestellt.

Bei meinen Projekt habe ich mir für diese Fälle eine eigene Basisklasse gestrickt welche den Wert eines bestimmten Properties ändert und wieder rücksetzt. Sauber ist das nicht aber eine andere Möglichkeit ist leider nicht bekannt.

W
WarLorD_XaN Themenstarter:in
113 Beiträge seit 2006
vor 13 Jahren

Das mit INotifyPropertyChanged wäre in diesem Fall schon besser, aber durch die DependencyProperties hat man ja noch mehr Vorteile wie Animationen, Trigger, Styles usw.
Deshalb komme ich da auch nicht herum.

Meinst du das Thema:

Dependency Property, ChangedCallback + Validation

Schade dass es hierfür keine "schönere" Lösung gibt.

Trotzdem vielen Dank für deine Hilfe.

mfg,
XaN

L
862 Beiträge seit 2006
vor 13 Jahren

Hier wurde die Problematik bereits beschrieben:

[gelöst] Validierungs-Problem