23. Mai 2016
Sebastian Thurm
0

WPF-Tricks: Icons zur Laufzeit färben

Icons helfen Nutzern sich in einer Anwendungsoberfläche zurecht zu finden. Gut gemachte Icons sind schnell erfassbar und leisten damit einen wichtigen Beitrag zur intuitiven Bedienbarkeit. Sie gehören daher in jede Anwendung – können Entwickler aber auch vor unerwartete Probleme stellen.

Das Problem:

Auf einem Button soll ein Icon erscheinen. Je nach Zustand (normal, hover, disabled) sollen sich Vorder- und Hintergrundfarbe des Controls ändern und damit auch die Farbe des Icons. In meinem Beispiel sorgt die relativ dunkle Hintergrundfarbe beim Hover dafür, dass die Farbe des Icons von schwarz zu weiß wechseln muss. Da die Hintergrundfarbe durch die CI des Kunden vorgegeben wird, lässt sie sich auch nicht ohne Weiteres ändern.

buttons@saxonia-systems

Je nach Zustand (normal, hover, disabled) soll auch die Farbe des Icons wechseln.

Üblicherweise braucht es jetzt verschiedene Icon-Sets und die Grafiken müssen zur Laufzeit ausgetauscht werden. Wäre es nicht toll, man könnte die Icons einfach zur Laufzeit beliebig einfärben?

So geht’s:

Wir ersetzen das Control-Template des Buttons per Style. Der eigentliche Inhalt wird dabei hinter einem Border und einem Rectangle versteckt. Wichtig ist, dass beide genau so groß wie der originale Content sind. Die Hintergrundfarbe des Borders wird per Template-Binding auf die Hintergrundfarbe des Buttons gebunden. Die Füllfarbe des Rectangles binden wir auf die Vordergrundfarbe. In dieser Farbe wird dann der Inhalt des Buttons sichtbar.

Den jetzt verdeckten Content-Presenter des Buttons verwenden wir, mit Hilfe eines Visualbrush, als Opacity-Mask für das Rectangle. Das Rectangle wird nun „ausgestanzt“: An den Stellen, an denen der Content sichtbar ist, bleibt das Rectangle ebenfalls sichtbar. An allen anderen wird es transparent. Der Inhalt des Buttons wird also in der gesetzten Vordergrundfarbe sichtbar!

button-saxonia-systems-test

Hier eine gekürzte Version des Control-Templates (einige Properties fehlen):

<Border
 Background="{TemplateBinding Background}" 
 BorderBrush="{TemplateBinding BorderBrush}" 
 BorderThickness="{TemplateBinding BorderThickness}">
 <Grid>
  <ContentPresenter x:Name="MyContentPresenter" 
            Content="{TemplateBinding Content}"/>

  <Border Background="{TemplateBinding Background}">
   <Rectangle 
    Fill="{TemplateBinding Foreground}" 
    Width="{Binding ElementName=MyContentPresenter, Path=ActualWidth}" 
    Height="{Binding ElementName=MyContentPresenter, Path=ActualHeight}">
    <Rectangle.OpacityMask>
     <VisualBrush Stretch="None" Visual="{Binding ElementName=MyContentPresenter}"/>
    </Rectangle.OpacityMask>
   </Rectangle>
  </Border>
 </Grid>
</Border>

Der Button kann weiterhin mit beliebigem Content befüllt werden:

<Button>
 <StackPanel Orientation="Horizontal">
  <Image  Source="{StaticResource Icon}" Margin="2" />
  <TextBlock VerticalAlignment="Center">los gehts!</TextBlock>
 </StackPanel>
</Button>

(Am Ende des Artikels findet befindet sich der komplette Style)

Fazit:

Mit diesem kleinen Trick ist es möglich, Icons zur Laufzeit praktisch beliebig einzufärben. Verschiedenfarbige Icon-Sets werden damit unnötig. Das Prinzip ist sehr leichtgewichtig, hat aber auch Einschränkungen: Graustufige oder Mehrfarbige Icons, lassen sich bspw. nicht verwenden.

Das Prinzip ist natürlich nicht nur für Buttons sinnvoll, sondern kann auch bei beliebigen anderen Controls Anwendung finden. Durch das Binding der Farbe wären letztendlich sogar Farbverläufe oder gar Animationen machbar.

ende@saxonia-systems


Der komplette Style:

<Style TargetType="Button">
 <Setter Property="Background" Value="WhiteSmoke" />
 <Setter Property="Foreground" Value="Black" />
 <Setter Property="SnapsToDevicePixels" Value="True" />
 <Setter Property="FontWeight" Value="Normal"/>
 <Setter Property="MinWidth" Value="50"/>
 <Setter Property="Padding" Value="1"/>
 <Setter Property="BorderBrush" Value="LightGray"/>
 <Setter Property="BorderThickness" Value="1"/>
 <Setter Property="VerticalContentAlignment" Value="Center"/>
 <Setter Property="HorizontalContentAlignment" Value="Center"/>
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="{x:Type Button}">
    <Border
     Background="{TemplateBinding Background}" 
     BorderBrush="{TemplateBinding BorderBrush}" 
     BorderThickness="{TemplateBinding BorderThickness}" 
     MinWidth="{TemplateBinding MinWidth}">
     <Grid>
      <ContentPresenter x:Name="MyContentPresenter" 
       Content="{TemplateBinding Content}" 
       HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
       VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
       Margin="{TemplateBinding Padding}"/>

      <Border Background="{TemplateBinding Background}">
       <Rectangle 
        Fill="{TemplateBinding Foreground}" 
        Margin="{TemplateBinding Padding}"
        Width="{Binding ElementName=MyContentPresenter, Path=ActualWidth}" 
        Height="{Binding ElementName=MyContentPresenter, Path=ActualHeight}" 
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
        <Rectangle.OpacityMask>
         <VisualBrush Stretch="None" Visual="{Binding ElementName=MyContentPresenter}"/>
        </Rectangle.OpacityMask>
       </Rectangle>
      </Border>
     </Grid>
    </Border>
    <ControlTemplate.Triggers>
     <Trigger Property="IsMouseOver" Value="True">
      <Setter Property="Background" Value="{DynamicResource Brushes50HzOrange158}" />
      <Setter Property="Foreground" Value="White" />
     </Trigger>

     <Trigger Property="IsPressed" Value="True">
      <Setter Property="Background" Value="{DynamicResource Brushes50HzRotOrange179}" />
      <Setter Property="Foreground" Value="White" />
     </Trigger>

     <Trigger Property="IsEnabled" Value="False">
      <Setter Property="Foreground" Value="{DynamicResource Brushes50HzGrau430}"/>
     </Trigger>

    </ControlTemplate.Triggers>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

 

Sebastian Thurm ist Dipl.-Ing. für Computervisualistik und arbeitet als Frontend-Entwickler und UX-Berater für die Saxonia Systems AG, Dresden. Er war in den letzten Jahren in verschiedenen Projekten im Bereich Handel, Industrie und Energiewirtschaft tätig.

Xing 

TeilenTweet about this on TwitterShare on Facebook0Share on Google+0Share on LinkedIn0