Wpf Equivalent to Textrenderer

How to measure multi-line text in WPF?

You may create a FormattedText instance and set its MaxTextWidth property, which makes the text wrap. Then either get the Bounds of its geometry, or perhaps only its height from the Extent property:

var testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";

var formattedText = new FormattedText(
testString,
CultureInfo.InvariantCulture,
FlowDirection.LeftToRight,
new Typeface("Segoe UI"),
20,
Brushes.Black);

formattedText.MaxTextWidth = 200;

var bounds = formattedText.BuildGeometry(new Point(0, 0)).Bounds;
var height = formattedText.Extent;

Not sure however why height and bounds.Height are not exactly equal.

How to calculate WPF TextBlock width for its known font size and characters?

Use the FormattedText class.

I made a helper function in my code:

private Size MeasureString(string candidate)
{
var formattedText = new FormattedText(
candidate,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(this.textBlock.FontFamily, this.textBlock.FontStyle, this.textBlock.FontWeight, this.textBlock.FontStretch),
this.textBlock.FontSize,
Brushes.Black,
new NumberSubstitution(),
VisualTreeHelper.GetDpi(this.textBlock).PixelsPerDip);

return new Size(formattedText.Width, formattedText.Height);
}

It returns device-independent pixels that can be used in WPF layout.

Measuring text in WPF

The most low-level technique (and therefore giving the most scope for creative optimisations) is to use GlyphRuns.

It's not very well documented but I wrote up a little example here:

http://smellegantcode.wordpress.com/2008/07/03/glyphrun-and-so-forth/

The example works out the length of the string as a necessary step before rendering it.

Binding Objects to a WPF datagrid with unknown-at-compile-time columns

Have you considered creating a DataGrid effect by using an ItemsControl? Something like:

<ItemsControl ItemsSource="{Binding}" Name="IC1"  VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<Border
BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
SnapsToDevicePixels="True">
<ScrollViewer
Padding="{TemplateBinding Control.Padding}"
Focusable="False">
<ItemsPresenter
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="9*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding Path=id}"/>
<ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=columnName}" Width="100" />
<TextBox Text="{Binding Path=value}" Width="100" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

I populated with some test data (sorry, but in VB):

Property PersonList As New ObservableCollection(Of Person)

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
For x As Integer = 1 To 500
Dim P1 As New Person With {.id = "SE" & x}
P1.Add(New DataObject With {.columnName = "First Name", .value = "Simon"})
P1.Add(New DataObject With {.columnName = "Last Name", .value = "Evans"})
P1.Add(New DataObject With {.columnName = "DOB", .value = "03/03/1980"})
Dim P2 As New Person With {.id = "RE" & x}
P2.Add(New DataObject With {.columnName = "First Name", .value = "Ruth"})
P2.Add(New DataObject With {.columnName = "Last Name", .value = "Evans"})
P2.Add(New DataObject With {.columnName = "DOB", .value = "11/02/1979"})
PersonList.Add(P1)
PersonList.Add(P2)
Next
IC1.DataContext = PersonList
End Sub

That's 1,000 rows, but because the control uses virtulisation, there is no lag.

EDIT

No idea if the is the best way, but I would suggest adding an Int width property to your DataObject class and binding the width of the TextBlocks & TextBoxes in the second ItemsControl to this.

Then using the below code snippet to calculate the maximum width required (kudos to WPF equivalent to TextRenderer):

public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)
{
FormattedText ft = new FormattedText(text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
fontSize,
Brushes.Black);
return new Size(ft.Width, ft.Height);
}

(I used the font details for the Window, but you can, of course, specify different details if you are styling the text boxes.)

I then wrote this little bit to go through the data and set the widths (sorry, back to VB):

    If PersonList.Count > 0 Then
Dim MaxLengths(PersonList(0).Count - 1) As Integer
For i As Integer = 0 To MaxLengths.Count - 1
MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
Next
For Each P As Person In PersonList
For i As Integer = 0 To P.Count - 1
Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
Next
Next
For Each P As Person In PersonList
For i As Integer = 0 To P.Count - 1
P(i).width = MaxLengths(i)
Next
Next
End If

TextOptions.TextFormattingMode affecting text with bold font weight

The problem is not that the bold text is too short, it is that the normal text is too long.

There's history behind this, WPF originally shipped at .NET 3.0 supporting only the "Ideal" mode for scaling text. This mode supports true resolution independent text scaling, a line of text will have a predictable length in inches on various display devices with different dots-per-inch resolution. This was not received well, it caused massive complaints from WPF programmers that didn't like the blurry text that this produces. This is visible in your screen-shot. Note how the left stem of the bold letter m is too fat in Ideal mode but not in Display mode.

At .NET 4.0, the WPF team supported a new way to render text, called "Display". Which renders text the way GDI does it, applying font hinting rules to tweak the letter shape so it coincides better with the pixel grid of the monitor. This tends to stretch the letters, especially when their stem only has a single pixel. The smaller the point size, the more pronounced this becomes. The text is highly readable because of it, but true resolution independent rendering is lost.

Winforms also went through a similar evolution, from Graphics.DrawString() to TextRenderer.DrawText().

This blog post from the WPF team has the details.

The answer to your question is thus No.

Finding out whether string can fit into rectangle on the screen

Use Graphics.MeasureString. You'll need to either have a graphics object or create one, like this, for example.

MeasureString returns a Sizef, which you can easily compare to your rectangle, which also has a Size property.

Edit: For WPF, you can also look into this: WPF equivalent to TextRenderer



Related Topics



Leave a reply



Submit