You do not need a custom form control. You just need to draw a cloud with a tail of the desired size, and put the text inside.
Take the original image and remove the text from it:

This will be our background. Only it is necessary that he resized not equally. The standard idea is to select several areas:

The angular areas should not change at all, the upper and lower side should stretch horizontally, the left and right side - vertically, and the center - in both directions. Since the top and bottom side images will still stretch in width, you can make them one pixel wide, and just like the left and right side bars - one pixel high. The central area can be done at all 1x1.
To implement this in WPF, let's get UserControl :
<UserControl x:Class="ChatInterface.Bubble" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <!-- сетка 3х3 --> <Grid.ColumnDefinitions> <ColumnDefinition Width="11"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="17"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="11"/> <RowDefinition Height="*"/> <RowDefinition Height="11"/> </Grid.RowDefinitions> <!-- левый верхний уголок --> <Image Source="bubbleNW.png" Grid.Row="0" Grid.Column="0" RenderOptions.EdgeMode="Aliased" Stretch="None"/> <!-- верхняя полоса --> <Image Source="bubbleN.png" Grid.Row="0" Grid.Column="1" RenderOptions.EdgeMode="Aliased" Stretch="Fill"/> <!-- правый верхний уголок --> <Image Source="bubbleNE.png" Grid.Row="0" Grid.Column="2" RenderOptions.EdgeMode="Aliased" Stretch="None"/> <!-- левая полоса --> <Image Source="bubbleW.png" Grid.Row="1" Grid.Column="0" RenderOptions.EdgeMode="Aliased" Stretch="Fill"/> <!-- центр --> <Image Source="bubbleC.png" Grid.Row="1" Grid.Column="1" RenderOptions.EdgeMode="Aliased" Stretch="Fill"/> <!-- и т. д. --> <Image Source="bubbleE.png" Grid.Row="1" Grid.Column="2" RenderOptions.EdgeMode="Aliased" Stretch="Fill"/> <Image Source="bubbleSW.png" Grid.Row="2" Grid.Column="0" RenderOptions.EdgeMode="Aliased" Stretch="None"/> <Image Source="bubbleS.png" Grid.Row="2" Grid.Column="1" RenderOptions.EdgeMode="Aliased" Stretch="Fill"/> <Image Source="bubbleSE.png" Grid.Row="2" Grid.Column="2" RenderOptions.EdgeMode="Aliased" Stretch="None"/> </Grid> </UserControl>
Now, you need to impose on this text. An absolutely correct way would be to make a decorator with a newly created control in the background, but this requires advanced knowledge of WPF, so we will go in a simpler way: we will create another UserControl . A small trick is that the cloud must be the same size as the text, so you have to use Binding, and so that Bubble does not affect the layout itself, stick it, for example, in Canvas :
<UserControl x:Class="ChatInterface.BubbleWithText" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ChatInterface" Name="root"> <Grid> <Canvas> <local:Bubble Height="{Binding ActualHeight, ElementName=tb}" Width="{Binding ActualWidth, ElementName=tb}"/> </Canvas> <TextBlock Name="tb" Padding="11" TextWrapping="Wrap" Text="{Binding Text, ElementName=root}"/> </Grid> </UserControl>
In the code-behind you need to put the Text property:
public partial class BubbleWithText : UserControl { public BubbleWithText() { InitializeComponent(); } #region dp string Text public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(BubbleWithText)); #endregion }
Now you can shove our items into the StackPanel :
<StackPanel Width="150"> <local:BubbleWithText Text="Я думаю, она дала тебе неправильный номер"/> <local:BubbleWithText Text="А может быть и нет, кто их разберёт?"/> </StackPanel>
and get the following:

Of course, it is better to draw the original images yourself (or request from the designer), and in no case save to JPEG.
If you want to do more correctly, use the decorator:
public class BubbleDecorator : Decorator { Bubble bubble = new Bubble(); public BubbleDecorator() { bubble.SetBinding(Bubble.WidthProperty, new Binding("ActualWidth") { Source = this }); bubble.SetBinding(Bubble.HeightProperty, new Binding("ActualHeight") { Source = this }); } protected override Visual GetVisualChild(int index) { if (Child != null) { if (index == 0) return bubble; if (index == 1) return Child; } throw new IndexOutOfRangeException("Wrong child index"); } protected override Size ArrangeOverride(Size arrangeSize) { bubble.Arrange(new Rect(arrangeSize)); return base.ArrangeOverride(arrangeSize); } protected override int VisualChildrenCount { get { return Child == null ? 0 : 2; } } }
We get the following XAML:
<StackPanel Width="150"> <local:BubbleDecorator HorizontalAlignment="Right" Margin="2"> <TextBlock Padding="13" TextWrapping="Wrap" Text="Я думаю, она дала тебе неправильный номер"/> </local:BubbleDecorator> <local:BubbleDecorator HorizontalAlignment="Right" Margin="2"> <TextBlock Padding="13" TextWrapping="Wrap" Text="А может быть и нет, кто их разберёт?"/> </local:BubbleDecorator> <local:BubbleDecorator HorizontalAlignment="Right" Margin="2"> <TextBlock Padding="13" TextWrapping="Wrap" Text="Приффки!!1"/> </local:BubbleDecorator> </StackPanel>
