Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

wpf trigger dependency property refresh manually

I have a custom UserControl subclassing from RichTextBox. This class has a dependency property, Equation, that is bound two-way.

When the user drops an item onto the control I change Equation. This properly propagates the change to the other end of the binding, which triggers a property changed notification, but the UI is not changing. If I change the binding to a different object and back it then displays the updated Equation.

How can I force the refresh without changing the binding? Right now I'm setting Equation=null and then back which works, but that seems hackish. There must be something more elegant.

Here are relevant portions of the control. What I would like to happen is for the OnEquationChanged callback to be called after I change Equation (Equation.Components.Add(txt)).

public class EquationTextBox : RichTextBox
{
    protected override void OnDrop(DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.StringFormat))
        {
            string str = (string)e.Data.GetData(DataFormats.StringFormat);

            EquationText txt = new EquationText(str);
            //// Preferred /////
            Equation.Components.Add(txt);
            //// HACK /////
            Equation eqn = this.Equation;
            eqn.Components.Add(txt);
            this.Equation = null;
            this.Equation = eqn;
            ///////////////
            Console.WriteLine("Dropping " + str);
        }
    }

    public Equation Equation
    {
        get { return (Equation)GetValue(EquationProperty); }
        set { SetValue(EquationProperty, value); }
    }

    private static void onEquationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        string prop = e.Property.ToString();
        EquationTextBox txtBox = d as EquationTextBox;
        if(txtBox == null || txtBox.Equation == null)
            return;

        FlowDocument doc = txtBox.Document;
        doc.Blocks.Clear();
        doc.Blocks.Add(new Paragraph(new Run(txtBox.Equation.ToString())));
    }

    public static readonly DependencyProperty EquationProperty =
        DependencyProperty.Register("Equation",
                                    typeof(Equation),
                                    typeof(EquationTextBox),
                                    new FrameworkPropertyMetadata(null,
                                                                  FrameworkPropertyMetadataOptions.AffectsRender,
                                                                  new PropertyChangedCallback(onEquationChanged)));

    private bool mIsTextChanged;
}

}

Here is the property on the other end of the two-way binding. The equation_PropertyChanged event is getting called in the above code as a result of Equation.Components.Add(txt);

public Equation Equation
{
    get{ return mEquation; }
    set { mEquation = value; NotifyPropertyChanged(); }
}

private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    NotifyPropertyChanged("Equation");
}

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Edit --------------------------

Per the comments, I tried using a dispatcher like this (note that this is my first attempt at using a dispatcher)

        string str = (string)e.Data.GetData(DataFormats.StringFormat);

        EquationText txt = new EquationText(str);
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
            {
                Equation.Components.Add(txt); 
                NotifyPropertyChanged("Equation");
            }));

but still no UI update.

Edit 2 --------------------------

The 2-way binding is done in XAML

<l:EquationTextBox x:Name="ui_txtVariableEquation" Grid.Row="0" Grid.Column="2" 
                             Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                   AllowDrop="True"
                                   Equation="{Binding SelectedVariableVM.Variable.Equation, Mode=TwoWay}">
                </l:EquationTextBox>

Info relevant to the Components object (with in the Equation class)

public class Equation : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Equation()
    {
        mComponents = new ObservableCollection<EquationComponent>();

        mComponents.CollectionChanged += new NotifyCollectionChangedEventHandler(components_CollectionChanged);
    }

    public Equation(string eqn) : this()
    {
        mComponents.Add(new EquationText(eqn));
    }

    public ObservableCollection<EquationComponent> Components
    {
        get{ return mComponents; }
        set{ mComponents = value; NotifyPropertyChanged();}
    }

    public override string ToString()
    {
        string str = "";
        for(int i=0; i<mComponents.Count; i++)
            str += mComponents[i].ToString();

        return str;
    }

    private void components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        NotifyPropertyChanged("Components");
    }

    private ObservableCollection<EquationComponent> mComponents;

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Variable : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Variable(string name = "var", VariableType type = VariableType.UnknownType) :
        this(name, "", 0, type)
    {
    }

and ...

    public class Variable : INotifyPropertyChanged
{
    public Variable(string name, string unit, object val, VariableType type)
    {
        mEquation = new Equation(name + " = " + val.ToString() + 
        mEquation.PropertyChanged += new PropertyChangedEventHandler(equation_PropertyChanged);

    }

    ...
    public Equation Equation
    {
        get{ return mEquation; }
        set { mEquation = value; NotifyPropertyChanged(); }
    }

    private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyPropertyChanged("Equation");
    }

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }


    private Equation mEquation;
    ...
}

Variable.equation_PropertyChanged is called when the event is raised inside of the Equation class

like image 789
ryan0270 Avatar asked Aug 13 '26 22:08

ryan0270


1 Answers

I think the problem is that the value produced by the binding is not actually changing (it's still the same Equation object). If the DP value doesn't change, then your DP change handler will not be called.

Perhaps, in your DP change handler, you should subscribe to the new equation's PropertyChanged event and then rebuild your document when an underlying property changes:

private static void onEquationChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var txtBox = d as EquationTextBox;
    if (txtBox == null)
        return;

    var oldEquation = e.OldValue as Equation;
    if (oldEquation != null)
        oldEquation.PropertyChanged -= txtBox.OnEquationPropertyChanged;

    var newEquation = e.NewValue as Equation;
    if (newEquation != null)
        newEquation.PropertyChanged += txtBox.OnEquationPropertyChanged;

    txtBox.RebuildDocument();
}

private void OnEquationPropertyChanged(object sender, EventArgs e)
{
    RebuildDocument();
}

private void RebuildDocument()
{
    FlowDocument doc = this.Document;

    doc.Blocks.Clear();

    var equation = this.Equation;
    if (equation != null)
        doc.Blocks.Add(new Paragraph(new Run(equation.ToString())));
}
like image 179
Mike Strobel Avatar answered Aug 16 '26 16:08

Mike Strobel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!