Concise question:
Is it possible to measure the contents of an unselected Tab?
Problem Summary:
Imagine you have a TabControl with two tabs. The first tab contains a Grid with a TextBlock in it. You select the second tab. After some time passes, the TextBlock's Text field changes to a very long string. You want to measure the size of the first tab's content before it is visible.
If you simply do a measure, it will not catch the fact that the string has changed - you can see in the WPF Visualizer that the TextBlock has the new string in its Text field, but the TextBlock refuses to re-measure. If you directly measure the string, you can detect the new desired size, but this is not a good solution; I want to be able to measure the total content of the first tab, not just strings.
Anyway, apologies for the long example code, this is hard to reduce further. When the window appears, the code waits for two seconds and then swaps tabs. It then changes the string. The measure loop changes the background to blue when it detects the string size change, and red when it detects the actual content size change. Red only happens when you switch tabs, but I'd like to be able to get Red to occur without having to switch back to the first tab.
Code Behind:
public MainWindow()
{
InitializeComponent();
// this worker waits a bit so the first tab renders,
// then it switches tabs and changes the string.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Thread.Sleep(2000);
Application.Current.Dispatcher.BeginInvoke(new Action(delegate
{
TestTabControl.SelectedIndex = 1;
TestBlock.Text = "This is a long string that ought to change the measure of the textblock to sizes never before seen by human eyes";
this.Background = Brushes.Green;
}));
};
worker.RunWorkerAsync();
// This worker constantly measures the text block
worker = new BackgroundWorker();
worker.DoWork += delegate
{
while (true)
{
if (Application.Current != null)
{
Application.Current.Dispatcher.BeginInvoke(new Action(delegate
{
MeasureFirstTab();
}));
}
else
{
break;
}
Thread.Sleep(100);
}
};
worker.RunWorkerAsync();
}
// Turn the background red when the tab width changes
public void MeasureFirstTab()
{
FirstTabContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (MeasureString(TestBlock.Text).Width > 500)
{
this.Background = Brushes.Blue;
}
if (FirstTabContent.DesiredSize.Width > 500)
{
this.Background = Brushes.Red;
}
}
private Size MeasureString(string candidate)
{
var formattedText = new FormattedText(
candidate,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(TestBlock.FontFamily, TestBlock.FontStyle, TestBlock.FontWeight, TestBlock.FontStretch),
TestBlock.FontSize,
Brushes.Black);
return new Size(formattedText.Width, formattedText.Height);
}
}
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl Name="TestTabControl">
<TabItem Header="Changes">
<Grid Name="FirstTabContent">
<TextBlock Name="TestBlock" Text="small"/>
</Grid>
</TabItem>
<TabItem Header="Short">
<TextBlock Text="Short"/>
</TabItem>
</TabControl>
</Grid>