Skip to main content

WPF Flat Button FOR REAL!


Three different solutions are available to create a "flat button" from a standard button. A flat button is one which only pops up when you point your mouse to it or gives it focus by tabbing with the keyboard. Watch the video, try the one that best suits you, and leave your comments. Note that in all three solutions, events of your created button will work as per normal.

In the video, buttons in the first column contain a Polygon. Those in the second column contain an Image, and those in the third contain simple text.

Solution 1: Property-based WPF Flat Buttons

To get these flat buttons, all you have to do is set both Background and BorderBrush properties of a standard button to Transparent. These buttons also show natively the fade-in and fade-out effects when they gain focus or are pointed to.

<Button Background="Transparent" BorderBrush="Transparent" />

However, these flat buttons fail when Windows is using the Classic theme or a High Contrast theme (as you can see from the video). If your application is targeted to work on all themes, try Solution 2 or 3.

Solution 2: Style-based WPF Flat Buttons (non-animated)

Add the following to your application's resources (usually Window.Resources or Page.Resources):

<Style x:Key="FlatButton" TargetType="Button">
 <Setter Property="IsTabStop" Value="False" />
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="Button">
    <Grid>

     <Button> <!-- Inherit properties here with TemplateBinding -->
      <Button.Style>
       <Style TargetType="Button">
        <Style.Triggers>
         <MultiTrigger>

          <MultiTrigger.Conditions>
           <Condition Property="IsFocused" Value="False" />
           <Condition Property="IsMouseOver" Value="False" />
          </MultiTrigger.Conditions>

          <Setter Property="Opacity" Value="0" />

         </MultiTrigger>
        </Style.Triggers>
       </Style>
      </Button.Style>
     </Button>

     <Label HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="3" IsHitTestVisible="False">
      <ContentPresenter />
     </Label>

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

With this style in place, create your button as follows:

XAML:
<Button Style="{StaticResource FlatButton}" />

Code-behind:
Button myButton = new Button();
myButton.Style = (Style)this.FindResource("FlatButton");

The inner button in the style definition will not automatically inherit some properties such as Background that you set to the above button. If you need to apply any such property that doesn't inherit automatically, you will have to inherit it by editing the button inside the style definition as follows, with Background as an example:

<Button Background="{TemplateBinding Background}" />
 <!-- Inherit properties here with TemplateBinding -->

However, you must not inherit the Opacity property! If you do that, the button will completely fail at being flat. You may add an Opacity property to the inner button to help yourself visualize the buttons during design-time. But you must remove the Opacity property of inner button before runtime, or your created buttons will be stuck at the added opacity value permanently.

If you want to control the runtime opacity, simply set the Opacity property of your created button (there is no need to inherit it to the inner button). Alternatively, for a global effect, you may also change the value in the MultiTrigger's Setter (or) use solution 3 (edit the DoubleAnimation's by setting the duration to "0:0:0" and setting the opacities to your desired values).

Solution 3: Style-based WPF Animated Flat Buttons

Add the following to your application's resources (usually Window.Resources or Page.Resources):

<Style x:Key="AnimatedFlatButton" TargetType="Button">
 <Setter Property="IsTabStop" Value="False" />
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="Button">
    <Grid>

     <Button Opacity="0.1"> <!-- Inherit properties here with TemplateBinding -->
      <Button.Style>
       <Style TargetType="Button">
        <Style.Triggers>
         <MultiTrigger>

          <MultiTrigger.Conditions>
           <Condition Property="IsFocused" Value="False" />
           <Condition Property="IsMouseOver" Value="False" />
          </MultiTrigger.Conditions>

          <MultiTrigger.EnterActions>
           <BeginStoryboard>
            <Storyboard>
             <DoubleAnimation To="0.0" Duration="0:0:0.5" Storyboard.TargetProperty="Opacity" />
            </Storyboard>
           </BeginStoryboard>
          </MultiTrigger.EnterActions>

          <MultiTrigger.ExitActions>
           <BeginStoryboard>
            <Storyboard>
             <DoubleAnimation To="1.0" Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" />
            </Storyboard>
           </BeginStoryboard>
          </MultiTrigger.ExitActions>

         </MultiTrigger>
        </Style.Triggers>
       </Style>
      </Button.Style>
     </Button>

     <Label HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="3" IsHitTestVisible="False">
      <ContentPresenter />
     </Label>

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

With this style in place, create your button as follows:

XAML:
<Button Style="{StaticResource AnimatedFlatButton}" />

Code-behind:
Button myButton = new Button();
myButton.Style = (Style)this.FindResource("AnimatedFlatButton");

The inner button in the style definition will not automatically inherit some properties such as Background that you set to the above button. If you need to apply any such property that doesn't inherit automatically, you will have to inherit it by editing the button inside the style definition as follows, with Background as an example:

<Button Opacity="0.1" Background="{TemplateBinding Background}" />
 <!-- Inherit properties here with TemplateBinding -->

You may edit the Opacity property to help yourself visualize when designing your form. But remember to set it back to zero or 0.1 because an opacity too high will give the unwelcome visual effect of all your flat buttons fading into flatness when you launch the application window.

If you want to control the runtime opacity, simply set the Opacity property of your created button (there is no need to inherit it to the inner button). To control the fade speed of the buttons, edit the DoubleAnimation's by setting the duration to your desired values. You may also change the opacity values in the DoubleAnimation's if desired.

What if I don't want the button to remain visible after I click?

Simple! Just replace the <MultiTrigger> opening tag with the following tag and delete the <MultiTrigger.Conditions /> tag including its contents. Remember to change all remaining instances of the term MultiTrigger with Trigger. (Note: this is applicable for solutions 2 and 3 only.)

<Trigger Property="IsMouseOver" Value="False">

Admittedly, a true flat button must be "not flat" only when the mouse is pointing at it. I overlooked this the first time and enabled my WPF flat buttons to be "not flat" when they are focused (after click or by pressing Tab key) even if the mouse is not pointing at it.

With this simple change, that anomaly can be removed and you can have a true flat button. When user navigates controls on the form using Tab key, a dotted rectangle appears around the button label to indicate focus. Hence the button itself can remain flat, only showing up when the user points mouse at it.

Backstory:
After searching everywhere on the Internet for a perfect solution, I could only find half-baked solutions of either inheriting the toolbar button style (which looks very ugly with a simple blue box) or hacking up the button and defining your own style (which makes you lose the standard button look and gradients).

I ended up spending two days trying different ways and coming up with a good solution that actually works. The solutions 2 and 3 were created and perfected by me before I stumbled upon solution 1 here. I'm glad I didn't see solution 1 before. Initially I was surprised that the problem could be very easily solved with solution 1, but upon further testing, I found it unreliable if an application is to be deployed in an environment which may use the Classic theme or its derivatives.

Remember to leave your comments if you found it useful or encountered problems.

Comments

Popular posts from this blog

Disable auto save in JetBrains IDE software (IntelliJ IDEA, PyCharm, PhpStorm)

JetBrains provides the following IDE software:
IntelliJ IDEAPhpStormPyCharmRubyMineWebStormAppCodeCLion Google also provides Android Studio which is powered by the IntelliJ platform.

If you come from a different IDE such as Eclipse, you will be unpleasantly surprised to find that JetBrains-branded IDEs automatically save everything the moment you look away. The proponents argue that as you work on your project, you should not have to worry about saving files. But to others, this auto-save behavior which is enabled by default is a curse that catches them by surprise, and a shocking departure from the workflow they are very much used to.

You can change the behavior by altering some settings.

Stop having to click Unblock on every downloaded file

CAUTION: The blocking of downloaded files in Windows is a security and safety feature to help prevent your computer from being infected by viruses and other malware. Only disable this feature if you know what you're doing.

I had been plagued by this annoyance since the days of Windows Vista. Any downloaded file, no matter what browser I use, gets tagged as "blocked" by Windows. You can open downloaded documents even though they are blocked, but when you run a downloaded application (such as a setup file) you're presented with a "Security Warning" before you're allowed to run it. It's worse if you extract a downloaded ZIP file with the Windows' built-in ZIP management. Every extracted file is blocked by default.

Being a geek who finds unnecessary "security" prompts annoying, the first thing I do in Windows is to disable the User Account Control (UAC). But I couldn't quite figure out how to disable blocking of downloaded files until …

Install Windows 10 Tech Preview when setup says media driver missing

Skip to the end for instructions.
I had been trying to install Windows 10 Pro Technical Preview onto a VHD, as I have Windows 7 Ultimate and can run an operating system from VHD (this feature is apparently limited to Enterprise and Ultimate editions).

While I had successfully managed to install it on a virtual machine in Oracle VirtualBox, I was dissatisfied with the sluggish performance of a VM. At the same time, I also did not want to dedicate a real hard disk partition for the sole purpose of installing Windows 10 TP.

Hence the decision to install into a VHD, which is a very neat solution that lets me keep Windows 10 TP separate from my real hard disk partitions and still enjoy the full native speeds of my real hardware. I had previously installed Windows 8 RC successfully into a VHD so I knew what I was up against.

Or so I thought. As soon as I booted up the setup disk (the ISO I had downloaded) and clicked on Install Now, I received this very unhelpful error asking for device dri…