Home > Enterprise >  C# WPF: Insert Shape Into TextBlock
C# WPF: Insert Shape Into TextBlock

Time:03-28

I am looking to insert a coloured square (with a border) into a TextBlock in WPF. The colour of the square needs to be set dynamically, and so ideally this should happen in the code-behind, not XAML.

I'm guessing the best way to do this is with a InlineUIContainer, but I can't work out how to position a Rectangle such that it aligns with the text, and is sized appropriately to the font size.

So far I have:

Color myColor = GetMyColor();

TextBlock textBlock = new TextBlock();
textBlock.Inlines.Add(new Run("My color: "));

// Attempt with a Canvas and Rectangle
Canvas canvas = new Canvas();
canvas.Children.Add(new Rectangle() { Height = 6, Width = 6, Fill = new SolidColorBrush(color.Value) });

textBlock.Inlines.Add(new InlineUIContainer(canvas));

// Hacky version that looks terrible
textBlock.Inlines.Add(new Run("  ") { Background = new SolidColorBrush(myColor) });

The problem here is that the Rectangle is created from the text baseline, hanging down. I would like it to be vertically centred relative to the text, square (i.e. aspect ratio of 1), and ideally automatically sized to the font size.

I'd wondered if a Viewbox was somehow useful, or some combination of VerticalAlignment properties, but I couldn't make them work. Any suggestions would be greatly appreciated.

CodePudding user response:

Depending on the size of square you want, you could try using unicode characters 0x25A0 or 0x25AA.

Here's an example defined in Xaml, but you could achieve the same effect in code behind too.

<TextBlock FontFamily="Segoe UI">
    <Run Text="ABC" />
    <Run Foreground="Red" Text="&#x25a0;" />
    <Run Foreground="Green" Text="&#x25aa;" />
</TextBlock>

<TextBlock FontFamily="Tahoma">
    <Run Text="ABC" />
    <Run Foreground="Red" Text="&#x25a0;" />
    <Run Foreground="Green" Text="&#x25aa;" />
</TextBlock>

enter image description here

Note that different font families render these characters with different proportion compared to the hight of the regular letters.

CodePudding user response:

You can use a ContentControl and a DataTemplate.
A UserControl or custom Control or ContentControl is also a good solution, especially if you like to add a behavior.

The following example uses a ContentControl and a DataTemplate to display a centered Rectangle next to a text, where the shape's color and the text are dynamic values. The size of the shape is relative to the FontSize applied to the ContentControl.
The final size of the shape can be adjusted by setting a Margin on the Viewbox or by attaching a IValueConverter to the Height binding of the Viewbox:

MainWindow.xaml

<Window>
  <Window.Resources>
    <DataTemplate x:Key="DataModelTemplate" 
                  DataType="{x:Type DataModel}">
      <DockPanel HorizontalAlignment="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=HorizontalContentAlignment}">
        <TextBlock x:Name="TextLabel"
                   FontSize="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=FontSize}"
                   Text="{Binding TextValue}"
                   VerticalAlignment="Center" />
        <Viewbox Height="{Binding ElementName=TextLabel, Path=ActualHeight}"
                 Margin="8"
                 Stretch="Uniform">
          <Rectangle Width="10"
                     Height="10"
                     Fill="{Binding Color}" />
        </Viewbox>
      </DockPanel>
    </DataTemplate>
  </Window.Resources>

  <StackPanel>
    <ContentControl x:Name="TextControl1"
                    ContentTemplate="{StaticResource DataModelTemplate}"
                    FontSize="50" />
    <ContentControl x:Name="TextControl2"
                    ContentTemplate="{StaticResource DataModelTemplate}"
                    FontSize="20" />
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  
    this.TextControl1.Content = new DataModel("@Test 1", Brushes.Yellow);
    this.TextControl2.Content = new DataModel("@Test 2", Brushes.Red);
  }
}

DataModel.cs

class DataModel : INotifyPropertyChanged
{
  public DataModel(string textValue, Brush color) 
  {
    this.TextValue = textValue;
    this.Color = color;
  }

  public string TextValue { get; }
  public Brush Color { get; }
}
  • Related