views:

1445

answers:

1

I am trying to measure an object immediately after changing the DataContext, but the binding for the object is not getting updated soon enough. Here's my code:

// In MeasureOverride(Size)
m_inputWidth = 0.0;

Size elemSize = new Size(double.PositiveInfinity, RowHeight);
MapElementView ruler = new MapElementView();

// Measure inputs
foreach (MapElementViewModel elem in m_vm.InputElements)
{
   ruler.DataContext = elem;
   ruler.Measure(elemSize);
   m_inputWidth = Math.Max(m_inputWidth, ruler.DesiredSize.Width);
}

I want the bindings for the View object to update so that I can measure how large the View needs to be to display the ViewModel. I am reusing the same View to measure because I am virtualizing the data.

Does anyone know how to force the binding to update when the DataContext changes?

Note that the binding does update eventually.

The View contains a TextBlock that is the main element that changes size based on the ViewModel. I have looked at the BindingExpression for the TextProperty on this element immediately after changing the DataContext, but calling UpdateTarget() does not fix the problem and BindingExpression.DataItem appears to be null.

EDIT: The status of the BindingExression is Unattached. The trick is to figure out how to attach it.

+2  A: 

Well, if after setting the DataContext, you did an Invoke on the Dispatcher at the DataBind priority, it should cause them all to be updated.

Since this code is being executed inside the MeasureOverride method, you can't do an Invoke on the Dispatcher. Instead, I would make a flag that indicated if the ruler width had been measured, and if not, do a BeginInvoke on the method that calculates those widths. Then, when the widths are calculated, call InvalidateMeasure to force a second layout pass.

This is going to require an additional layout pass every time one of those widths changes. You will need to reset the flag to false whenever the textboxes have to be remeasured.

private bool isRulerWidthValid = false;

protected override Size MeasureOverride(Size available)
{
    ... // other code for measuring
    if (!isRulerWidthValid)
    { 
        Dispatcher.BeginInvoke(new Action(CalculateRulerSize));
        ... // return some temporary value here
    }

    ... // do your normal measure logic
}

private void CalculateRulerSize(Size available)
{
    Size elemSize = new Size(double.PositiveInfinity, RowHeight);
    m_inputWidth = 0.0;

    foreach (MapElementViewModel elem in m_vm.InputElements)
    {
       ruler.DataContext = elem;
       ruler.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.DataBind);
       ruler.Measure(elemSize);
       m_inputWidth = Math.Max(m_inputWidth, ruler.DesiredSize.Width);
    }

    // invalidate measure again, as we now have a value for m_inputwidth
    isRulerWidthValid = true;
    InvalidateMeasure();
}
Abe Heidebrecht
Good idea. I'll try that.
Josh G
I got the following exception: Cannot perform this operation while dispatcher processing is suspended.It appears that WPF locks the Dispatcher during layout. I running this code in MeasureOverride()
Josh G