WPF: 4 options for a button with an icon and text

I think that everyone (or almost everyone), from those who come to WPF from WinForms, at first is confused about the functionality of standard controls.
It would seem - here he is - a familiar control.
It is very similar to an old friend from WinForms. Even the signature of ordinary methods either completely coincides or underwent a slight transformation (well, for example, the Enabled property received the prefix Is). The controls have a lot of settings, from the parameters of visualization ripples in the eyes.
But with a little closer acquaintance and an attempt to pull the usual ways of building an interface on XAML, that same confusion comes.

How so? Well, does the button have no Image property? You're kidding, right?

The thing is that WPF (more precisely, XAML) has a completely different ideology of interface organization. Basic controls represent only basic (sorry for the tautology) functionality. The simplicity of the basic controls is offset by the powerful mechanisms of templates and styles.
There are third-party component libraries, but they are most often either useless, hopelessly outdated, or highly paid.

Not so long ago, I once again ran into the need to solve this very simple (it would seem) problem. I tormented all of Google with queries like “XAML button with image”, “WPF button image text”, etc.

Among the dozens of results reviewed, there were obvious both obvious (and at the same time inconvenient) solutions, as well as more sophisticated ones.

Small digression number 1
After the very first experiments, it became obvious that XAML and icons in the form of png are incompatible things. I won’t spend too much time on why - there is enough literature on this subject, I’ll just say that in the end it turns out ugly, and uncomfortable, and non-functional. Pictures are blurry, superimposed effects and animations look depressing, etc ...
But do not worry - there are tens and hundreds of resources with vector images on the network.
The best I have found is SyncFusion Metro Studio 2 (no ads). This is a free product that has 1700 vector icons and means to display these icons in XAML. The result is obtained in the form of a complex element, from which it is enough to copy only Path, which describes the geometry of the icon itself.
With this element I do this - I add to the projectResourceDictionary with the name Icons.xaml and put in it all the icons I need:


But let's get back to ways to implement a button with an icon.

The first and most obvious way is to describe the desired Content buttons directly in the form code



Well, it would seem that everything is in place. There is a button, it has an icon and text. Those who believe that everything is so simple and try to organize a beautiful and stylish interface with this approach will soon be disappointed. And here's why: a simple style setting leads to XAML swelling by duplicating the description of the parameters of the elements directly in the form code. 10 buttons - 10 identical descriptions. A simple change of the type “let's color the text on the buttons in green” turns into a tedious copy-paste and even more swelling of the form.

The second obvious way is to inherit from Button

And let's write "your button with blackjack and you yourself know what else"?
We will inherit from Button and add DependencyProperty for ContentControl , through which the content for the icon can be set from the XAML form. I will not dwell on the implementation details (there will be a link to the sources below, it can be read there), but I will describe the minuses - the contents of the button will have to be set from the constructor of the heir, in C #. From here we get a lot of obvious and non-obvious problems, not to mention the fact that it does not smell very good.

The third obvious way is to create a UserControl .

Create a UserControl , on which we put just one button. In UserControl, create a DependencyProperty , through which we will set the icon for the ContentControl , which lies in the button. This method deserves a medal for maximum clumsiness. He inherits almost all the shortcomings of the previous methods, and adds many of his own. In the form code, we get a UserControl , but lose the button. We lose along with all the properties and events. The author of the idea suggests pulling out everything that was lost through the very DependencyProperty , in general, you understand. It becomes unclear what we were fighting for.

Fourth Method - AttachedProperty

I refer this method to non-obvious and sophisticated. In the original article, the author suggests setting the picture through AttachedProperty . Looking ahead, I’ll say that I chose this method for use in my product and I will describe it in the most detail. It is not without some drawbacks at the development stage (I will describe below), but still I liked it more than others. In the original, the author used the icon in the form of a png picture, but I modified the method for using the vector icon and added buns.

So, quite a bit of theory. What is this AttachedProperty ?

Each XAML developer encountered Attached properties when, for example, setting the Grid.Column property to the control .
If in a nutshell - then its idea is a bit like Extension from Linq. You can register a property whose value can be set to any DependencyObject . It looks something like this (example from MSDN):

public class AquariumObject
{
    public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(
		"IsBubbleSource",
		typeof(Boolean),
		typeof(AquariumObject),
		new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
    );
    public static void SetIsBubbleSource(UIElement element, Boolean value)
    {
    	element.SetValue(IsBubbleSourceProperty, value);
    }
    public static Boolean GetIsBubbleSource(UIElement element)
    {
	return (Boolean)element.GetValue(IsBubbleSourceProperty);
    } 
}


This code registers the IsBubleSource property . As a result, any DependencyObject , for example the same Button , can be set to its value:


The general purpose of this code is to set the IsBubbleSource property for a button to the SetIsBubbleSource method , which sets the value. Upon receipt of the value, respectively, we get into the GetIsBubbleSource method . This all happens automatically, just write the methods with the names Set and Get, the rest is up to the platform.

Despite the fact that not so little code has been written, Button itself is neither hot nor cold from such an operation - it just becomes a repository of a separate value that can be set and asked. Of course, you can implement tricky logic in the SetIsBubbleSource and GetIsBubbleSource methods that will cause element toButton , get the contents out of it, and perform various operations with the contents, but it smells bad again, you don’t need to do that.

Getting to the practical part

A small digression 2
In the original, the author uses the EyeCandy class name and the project namespace, but it is too long and I hope that the abbreviation - namespace Ext and class name E will be forgiven.

We add the following class to the WPF project:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Ext
{
	public class E
	{
		public static readonly DependencyProperty IconProperty;
		static E()
		{
			var metadata = new FrameworkPropertyMetadata(null);
			IconProperty = DependencyProperty.RegisterAttached("Icon",
	typeof(FrameworkElement),
	typeof(E), metadata);
		}
		public static FrameworkElement GetIcon(DependencyObject obj)
		{
			return (FrameworkElement)obj.GetValue(IconProperty);
		}
		public static void SetIcon(DependencyObject obj, FrameworkElement value)
		{
			obj.SetValue(IconProperty, value);
		}
       }
}

What is going on here? We registered the Attached Icon property of type FrameworkElement with a default value of null.

Now create a template for our button. I will not dwell on the explanation of “what patterns are and how they work” - if suddenly someone does not know this - there is a lot of information on the network.
So, we add ResourceDictionary with the name Styles.xaml to our project (if suddenly there is no styles resource in the project yet). In this ResourceDictionary, add the following code:


This ResourceDictionary entry describes the template for any button in our project. This template sets the default look for the WPF button, but its content is redefined. The content is StackPanel , which contains ContentControl and TextBlock , i.e. exactly the same as in the very first example. In addition, in the template, I set the following behavior for the icon - if the button is set to IsEnabled == False , then the icon receives 50% transparency, and becomes similar to inactive.

Add 4 simple buttons to our form. Assign each button its own text, for example, like this: Content = "Button 1" .
We launch the application.


Identical icons on each button of the application are not what we are aiming for, and it is here that we put into operation our secret weapon - the Ext.E class and the AttachedProperty mechanism .

So, go to our Styles.xaml resource file and add a new namespace to it :

xmlns:Ext="clr-namespace:Ext"

After that, we go down below and in the button template we find the line in which the ContentControl is created and its contents are set:

Replace the second line:

This line makes ContentControl access the Ext.E.Icon property of the button and get its contents from it. After that, it remains to add code that sets the value of the Ext.E.Icon property to the button itself. This is done in the code of the form on which the button is created.


The primitive version of the button with the icon is ready. By changing the IconTriangle value to the names of other resources, you can set various icons on the buttons. At the same time, unlike the first three methods, we retain at the button all its innate abilities for styling (with the exception of the ability to change the Content structure , of course). The content of the button is not set from C #, and all the properties with the events remained in their place.

Let's go a little further

If we try to use this button in a real project, we will encounter this:
  • Icon size is not adjustable.
  • The orientation of the button (vertical or horizontal) is not adjustable.


More precisely, everything is customizable, but only in the template, i.e. for all buttons at once, but cloning patterns is a rake and a real inconvenience. We did not fight for this.
Extend the class Ext.E. Add two more AttachedProperty there
  • IconSize of type double
  • Orientation type Orientation

The source code will be in one archive at the end of the article, so I will not duplicate the similar methods of the Ext.E class in the article.

I will describe only the changes that need to be made in the Button template .
The sizes of ContentControl are associated with the IconSize value :


The orientation of the StackPanel is associated with the value of Orientation.


As a result, the button received additional parameters, and we can write like this:


As a result of simple manipulations, you can get such a zoo (the first button IsEnabled = "False" ):
"
And finally, I’ll mention restrictions

They all relate to the process and development tools:
- The XAML designer VisualStudio 2010 reacts to a similar description of the button in some way:

- Blend 4 and VisualStudio 2012 behave better, but also with features:
  • After changing the Ext.E class, it is better to reload the development environment. Without this, changes are most often not defined and designers swear that added or changed properties do not exist.
  • According to the algorithm that I have not installed, the values ​​set to the button using AttachedProperty are visible to the designer or not. More often not visible, and the form looks something like this:




But this concerns only Designer, in the application runtime everything works as it should.

Perhaps (and even most likely) I described the bike, but the fact that in two days of searching I did not find a more acceptable free implementation suggests that everything is not all right on this front.
In addition, I got the opportunity to understand a little bit about the mechanism for expanding standard controls in a non-standard way and there are a lot of applications for this mechanism.

Thank you for attention.

UPD: The article was updated due to the fact that a solution was found using the same icon on different buttons.
UPD2: Thank you onikiychuka for a sensible suggestion.

Source

Useful links:

WPF Control Development - 3 Ways to build an ImageButton
blogs.msdn.com/b/knom/archive/2007/10/31/wpf-control-development-3-ways-to-build-an-imagebutton.aspx
Using Attached Properties to Create a WPF Image Button
www.hardcodet.net/2009/01/create-wpf-image-button-through-attached-properties
Custom Dependency
Properties
msdn.microsoft.com/en-us/library/ms753358.aspx SyncFusion Metro Studio 2
www.syncfusion.com/downloads/metrostudio

Also popular now: