katit
katit

Reputation: 17895

How to get rid of whitespace between Runs in TextBlock?

I have following XAML:

<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
                                               FontSize="10" FontFamily="Arial" Foreground="#414141">        
                                            <Run Text="{Binding LoadsCount}" />        
                                            <Run Text="+" />        
                                            <Run Text="{Binding BrokerLoadsCount}" />
                                        </TextBlock>

And I get display like this: 12 + 11 Somehow it inserts extra space between each Run How do I make it display 12+11 ?

Upvotes: 106

Views: 28049

Answers (8)

LWChris
LWChris

Reputation: 4191

I've started to treat this as an XY problem. Instead of trying to get rid of the spaces when I have multiple Run elements inside a TextBlock, I just put multiple TextBlock elements inside a horizontal StackPanel:

<StackPanel>
  <!-- This doesn't look as intended -->
  <TextBlock>
    <Run Text="S" />
    <Run Text="p" />
    <Run Text="a" />
    <Run Text="c" />
    <Run Text="e" />
    <Run Text="s" />
  </TextBlock>

  <!-- But this one does -->
  <StackPanel Orientation="Horizontal">
    <TextBlock Text="N" />
    <TextBlock Text="o" />
    <TextBlock Text=" " />
    <TextBlock Text="s" />
    <TextBlock Text="p" />
    <TextBlock Text="a" />
    <TextBlock Text="c" />
    <TextBlock Text="e" />
    <TextBlock Text="s" />
  </StackPanel>

  <!-- For visual comparison -->
  <TextBlock Text="No spaces" />
</StackPanel>

Upvotes: 0

Tony G
Tony G

Reputation: 101

Another solution that looks cleaner is to use <TextBlock.Inlines> tag inside the <TextBlock>:

<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
                   FontSize="10" FontFamily="Arial" Foreground="#414141">
            <TextBlock.Inlines>
                <Run Text="{Binding LoadsCount}" />        
                <Run Text="+" />        
                <Run Text="{Binding BrokerLoadsCount}" />
            </TextBlock.Inlines>
</TextBlock>

Upvotes: -1

Vimes
Vimes

Reputation: 11867

I ported Pieter's attached property to WPF (I think it's for UWP).

Example:

<StackPanel>
    <TextBlock Text="Before:" FontWeight="SemiBold"/>
    <TextBlock>
        Foo
        <Run Text="Bar"/>
        <Run>Baz</Run>
    </TextBlock>
    <TextBlock Text="After:" FontWeight="SemiBold" Margin="0,10,0,0"/>
    <TextBlock local:TextBlockHelper.TrimRuns="True">
        Foo
        <Run Text="Bar"/>
        <Run>Baz</Run>
    </TextBlock>
    <TextBlock Text="Use two spaces if you want one:" FontWeight="SemiBold" Margin="0,10,0,0"/>
    <TextBlock local:TextBlockHelper.TrimRuns="True">
        Foo
        <Run Text="  Bar"/>
        <Run>Baz</Run>
    </TextBlock>
</StackPanel>

screenshot

using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

public class TextBlockHelper
{
    public static bool GetTrimRuns(TextBlock textBlock) => (bool)textBlock.GetValue(TrimRunsProperty);
    public static void SetTrimRuns(TextBlock textBlock, bool value) => textBlock.SetValue(TrimRunsProperty, value);

    public static readonly DependencyProperty TrimRunsProperty =
        DependencyProperty.RegisterAttached("TrimRuns", typeof(bool), typeof(TextBlockHelper),
            new PropertyMetadata(false, OnTrimRunsChanged));

    private static void OnTrimRunsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock = d as TextBlock;
        textBlock.Loaded += OnTextBlockLoaded;
    }

    static void OnTextBlockLoaded(object sender, EventArgs args)
    {
        var textBlock = sender as TextBlock;
        textBlock.Loaded -= OnTextBlockLoaded;

        var runs = textBlock.Inlines.OfType<Run>().ToList();
        foreach (var run in runs)
            run.Text = TrimOne(run.Text);
    }

    private static string TrimOne(string text)
    {
        if (text.FirstOrDefault() == ' ')
            text = text.Substring(1);
        if (text.LastOrDefault() == ' ')
            text = text.Substring(0, text.Length - 1);

        return text;
    }
}

Upvotes: 2

Viktor Jasper
Viktor Jasper

Reputation: 83

My solution is to make the default font size nearly invisible(FontSize="1") and then set the font size to the desired size at every each <Run:

<TextBlock HorizontalAlignment="Center"
           VerticalAlignment="Center"
           FontSize="1"
           FontFamily="Arial"
           Foreground="#414141">        

    <Run FontSize="10" Text="{Binding LoadsCount}" />        
    <Run FontSize="10" Text="+" />        
    <Run FontSize="10" Text="{Binding BrokerLoadsCount}" />
</TextBlock>

You may be better off doing it in Code Behind. I have tried previous solutions but in certain situations VS just formatted away the carefully indented code.

Upvotes: 4

helder.tavares.silva
helder.tavares.silva

Reputation: 734

Another option is to comment the space between Run tags, maintaining the code readable and removing the extra space.

<TextBlock HorizontalAlignment="Center"
           VerticalAlignment="Center"
           FontSize="10" FontFamily="Arial" Foreground="#414141">        
    <Run Text="{Binding LoadsCount}" /><!--
 --><Run Text="+" /><!--
 --><Run Text="{Binding BrokerLoadsCount}" />
</TextBlock>

Upvotes: 55

Pieter Nijs
Pieter Nijs

Reputation: 445

I've written a Attached Property to 'bypass' this behavior.

public class TextBlockExtension
{

    public static bool GetRemoveEmptyRuns(DependencyObject obj)
    {
        return (bool)obj.GetValue(RemoveEmptyRunsProperty);
    }

    public static void SetRemoveEmptyRuns(DependencyObject obj, bool value)
    {
        obj.SetValue(RemoveEmptyRunsProperty, value);

        if (value)
        {
            var tb = obj as TextBlock;
            if (tb != null)
            {
                tb.Loaded += Tb_Loaded;
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }

    public static readonly DependencyProperty RemoveEmptyRunsProperty =
        DependencyProperty.RegisterAttached("RemoveEmptyRuns", typeof(bool), 
            typeof(TextBlock), new PropertyMetadata(false));

    public static bool GetPreserveSpace(DependencyObject obj)
    {
        return (bool)obj.GetValue(PreserveSpaceProperty);
    }

    public static void SetPreserveSpace(DependencyObject obj, bool value)
    {
        obj.SetValue(PreserveSpaceProperty, value);
    }

    public static readonly DependencyProperty PreserveSpaceProperty =
        DependencyProperty.RegisterAttached("PreserveSpace", typeof(bool), 
            typeof(Run), new PropertyMetadata(false));


    private static void Tb_Loaded(object sender, RoutedEventArgs e)
    {
        var tb = sender as TextBlock;
        tb.Loaded -= Tb_Loaded;

       var spaces = tb.Inlines.Where(a => a is Run 
            && string.IsNullOrWhiteSpace(((Run)a).Text) 
            && !GetPreserveSpace(a)).ToList();
        spaces.ForEach(s => tb.Inlines.Remove(s));
    }
}

The entire source code and the explanation of it all can be found here. By using this attached property you can keep your XAML formatting just the way you want, but you don't get these whitespaces in your rendered XAML.

Upvotes: 3

Glenn Slayden
Glenn Slayden

Reputation: 18749

One problem with Kevin's nice solution is that the single-line formatting of XAML tags is undone when you apply some of the XAML/XML automatic reformatting functions, e.g. "ctrl-K + ctrl-D". One workaround I found is to format the Run tags as follows:

<TextBlock>
    <Run FontStyle="Italic"
    Text="aaa" /><Run 
    Text="bbb" />
</TextBlock>

Although splitting the tag across lines like this is somewhat awkward, this format will not be altered by automatic reformatting, provided you select the Visual Studio option "Preserve new lines and spaces between attributes" for the XAML text editor:

extra space eliminated between consecutive Run elements in XAML

Upvotes: 25

Kevin DiTraglia
Kevin DiTraglia

Reputation: 26048

The spaces between the run tags cause the spaces, this is the easiest fix.

<TextBlock 
   HorizontalAlignment="Center" 
   VerticalAlignment="Center"
   FontSize="10" 
   FontFamily="Arial" 
   Foreground="#414141">        
      <Run Text="{Binding LoadsCount}" /><Run Text="+" /><Run Text="{Binding BrokerLoadsCount}" />
</TextBlock>

Because anything between the <TextBlock> and </TextBlock> is targeting the text property of the TextBlock the whitespace from the breaks between the runs causes the effect you see. You could also shorten it to this.

<Run Text="{Binding LoadsCount}" />+<Run Text="{Binding BrokerLoadsCount}" />

This MSDN article gives all the specifics on how xaml handles the whitespace

http://msdn.microsoft.com/en-us/library/ms788746.aspx

If you were curious why a break and a ton of tabs translates into a single space

All whitespace characters (space, linefeed, tab) are converted into spaces.

All consecutive spaces are deleted and replaced by one space

Upvotes: 186

Related Questions