This style will do the job. The trick is to redefine a control template for the label. The content is then put inside a clipping canvas and aligned to the right of the canvas. The minimum width of the content is the width of the canvas so the content text will be left aligned if there is enough space and right aligned when clipped.
The ellipses is triggered to be on if the width of the content is bigger than the canvas.
<Style x:Key="LeftEllipsesLabelStyle"
TargetType="{x:Type Label}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Padding"
Value="5" />
<Setter Property="HorizontalContentAlignment"
Value="Left" />
<Setter Property="VerticalContentAlignment"
Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Label}">
<Grid >
<Grid.Resources>
<LinearGradientBrush x:Key="HeaderBackgroundOpacityMask" StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Black" Offset="0.5"/>
<GradientStop Color="Transparent" Offset="1"/>
</LinearGradientBrush>
</Grid.Resources>
<Canvas x:Name="Canvas"
ClipToBounds="True"
DockPanel.Dock="Top"
Height="{Binding ElementName=Content, Path=ActualHeight}">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
Canvas.Right="0"
Canvas.ZIndex="0"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
MinWidth="{Binding ElementName=Canvas, Path=ActualWidth}"
SnapsToDevicePixels="true"
x:Name="Content"
>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Content="{Binding RelativeSource={RelativeSource AncestorType=Label}, Path=Content}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
>
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="{Binding FontSize, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/>
<Setter Property="FontWeight" Value="{Binding FontWeight, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/>
<Setter Property="FontStyle" Value="{Binding FontStyle, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/>
<Setter Property="FontFamily" Value="{Binding FontFamily, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<Label
x:Name="Ellipses"
Canvas.Left="0"
Canvas.ZIndex="10"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontStyle="{TemplateBinding FontStyle}"
VerticalContentAlignment="Center"
OpacityMask="{StaticResource HeaderBackgroundOpacityMask}"
Background="{TemplateBinding Background}"
Foreground="RoyalBlue"
Height="{Binding ElementName=Content, Path=ActualHeight}"
Content="...   ">
<Label.Resources>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Value="true">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource GteConverter}">
<Binding ElementName="Canvas" Path="ActualWidth"/>
<Binding ElementName="Content" Path="ActualWidth"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Resources>
</Label>
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There are a couple of utility classes here
GteConverter
<c:GteConverter x:Key="GteConverter"/>
which is
public class RelationalValueConverter : IMultiValueConverter
{
public enum RelationsEnum
{
Gt,Lt,Gte,Lte,Eq,Neq
}
public RelationsEnum Relations { get; protected set; }
public RelationalValueConverter(RelationsEnum relations)
{
Relations = relations;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if(values.Length!=2)
throw new ArgumentException(@"Must have two parameters", "values");
var v0 = values[0] as IComparable;
var v1 = values[1] as IComparable;
if(v0==null || v1==null)
throw new ArgumentException(@"Must arguments must be IComparible", "values");
var r = v0.CompareTo(v1);
switch (Relations)
{
case RelationsEnum.Gt:
return r > 0;
break;
case RelationsEnum.Lt:
return r < 0;
break;
case RelationsEnum.Gte:
return r >= 0;
break;
case RelationsEnum.Lte:
return r <= 0;
break;
case RelationsEnum.Eq:
return r == 0;
break;
case RelationsEnum.Neq:
return r != 0;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and
public class GtConverter : RelationalValueConverter
{
public GtConverter() : base(RelationsEnum.Gt) { }
}
public class GteConverter : RelationalValueConverter
{
public GteConverter() : base(RelationsEnum.Gte) { }
}
public class LtConverter : RelationalValueConverter
{
public LtConverter() : base(RelationsEnum.Lt) { }
}
public class LteConverter : RelationalValueConverter
{
public LteConverter() : base(RelationsEnum.Lte) { }
}
public class EqConverter : RelationalValueConverter
{
public EqConverter() : base(RelationsEnum.Eq) { }
}
public class NeqConverter : RelationalValueConverter
{
public NeqConverter() : base(RelationsEnum.Neq) { }
}
Here is it working.