How to Dynamically size a data bound control by it's content
I've seen this question posted on the web, and I have been personally asked to do this a couple of times.
There comes a time when we will need to have buttons (or radioButtons, any control) that needs to have it's width dynamically assigned based on the content within, for example a button with the text "MyButton, should be wide enough to display all the text, but another button next to it should have the same padding but a smaller width if it's content was "Small". This gets even more confusing when we have a list of items that are data bound. You cannot set individual widths for items that are not yet created.
I'm going to show you how you can accomplish this using a MVVM Converter (https://msdn.microsoft.com/en-us/library/system.windows.data.binding.converter(v=vs.110).aspx).
First let's define our list:
public ObservableCollection<string> ButtonNames
{
get { return _buttonNames; }
}
private ObservableCollection<string> _buttonNames = new ObservableCollection<string>();
Let's add some strings to our ButtonNames dependency property:
_buttonNames.Add("Short");
_buttonNames.Add("LongerName");
OnPropertyChanged("ButtonNames");
Pretty simple.. now we'll create an ItemsControl to bind to our ButtonNames collection:
<ItemsControl ItemsSource="{Binding ButtonNames}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Width="50" Height="30" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If we run this, we see our problem:
The buttons are stuck with the wrong width.(say that fast three times!)
To fix this, I am going to bind the width of our button to the string we are using for content. The problem is, the property is expecting an int, So we need a converter:
class WidthHelper : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int nameLength = value.ToString().Length;
return (14 * nameLength) + 20;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
What's great about the implemented Convert function is that the value coming in is an object, so we can pass in a string.. or whatever we want. As long as we match the targetType.. we're good. So as you can see I multiply 14 (that's the font size I am using) times the number of characters in the name, I then put a 10 pixel buffer on each side of the text (+20).
I can now wire up ourconverter to our ItemsControl data template:
<Button Content="{Binding}" Width="{Binding Converter,Converter={StaticResource WidthHelper}}" Height="30" />
Our buttons now display with the dynamic width:
Pretty cool!
Hope this helps!
Comments
- Anonymous
December 18, 2014
Kind of makes you wish that there was a method named something like MeasureString that would figure out the dimensions a string of text would take up if rendered using a given font. :)