I’m trying to make a button that is styled to change its layout to make itself smaller based upon how much space is available.
These buttons will always have a stackpanel with an image and a textblock within it. I’m aiming to be able to define 3-4 different styles for these buttons, each resulting in a different button size.
For each button image, I have 4 versions if the image in different resolutions. 16, 32, 48 and 64. These images are stored in different folders; i.e. Images\Icons\16\Add.png and Images\Icons\32\Add.png. Both of those images are the same, just different sizes. I’ve chosen to use multiple sizes because I found that just having one image and scaling its size caused it to be rather blurry.
To facilitate choosing different sized images, I’ve created a converter which I use when binding the image source:
class ImagePathResolutionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Image sender = (Image)value;
int imageSize = (parameter != null) ? (parameter is int) ? (int)parameter : (parameter is string) ? Int32.Parse((string)parameter) : 16 : 16;
Uri returnImage = new Uri(String.Format(@"Images\Icons\{0}\{1}", imageSize, sender.Tag as string), UriKind.Relative);
return returnImage;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The binding looks like this:
<Setter Property="Source"
Value="{Binding .,
Converter={StaticResource ImagePathResolutionConverter},
RelativeSource={RelativeSource Self},
ConverterParameter='16'}"
/>
Using this set up means that I can pass 16, 32, 48 or 64 to the ConverterParameter, and the appropriate image path will be returned.
Now, for this example, I have two styles;
Big:
<!-- Big Button Container Style -->
<Style TargetType="Button" x:Key="MultiResButton_Big">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Style.Resources>
<!-- Button.StackPanel Style -->
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Vertical" />
<Style.Resources>
<!-- Button.StackPanel.Image Style -->
<Style TargetType="Image">
<Setter Property="Stretch" Value="None" />
<Setter Property="Margin" Value="5 2 5 0" />
<Setter Property="Source" Value="{Binding ., Converter={StaticResource ImagePathResolutionConverter}, RelativeSource={RelativeSource Self}, ConverterParameter='32'}" />
</Style>
<!-- Button.StackPanel.TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="5 0 5 2" />
</Style>
</Style.Resources>
</Style>
</Style.Resources>
</Style>
… and Small
<!-- Small Button Container Style -->
<Style TargetType="Button" x:Key="MultiResButton_Small">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Style.Resources>
<!-- Button.StackPanel Style -->
<Style TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal" />
<Style.Resources>
<!-- Button.StackPanel.Image Style -->
<Style TargetType="Image">
<Setter Property="Stretch" Value="None" />
<Setter Property="Margin" Value="5 2" />
<Setter Property="Source" Value="{Binding ., Converter={StaticResource ImagePathResolutionConverter}, RelativeSource={RelativeSource Self}, ConverterParameter='16'}" />
</Style>
<!-- Button.StackPanel.TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0 5 2 5" />
</Style>
</Style.Resources>
</Style>
</Style.Resources>
</Style>
The XAML for one of these buttons is :
<Button Style="{StaticResource ResourceKey=MultiResButton_Big}">
<StackPanel>
<Image Tag="Multi.png" />
<TextBlock Text="Button Text" />
</StackPanel>
</Button>
At the moment, I can set a button to either or these, and the results are a big or small button. Now I just need to work out how to determin when to use which, and how to do so programatically.
The way I see it, I have two options: Combine both styles into one and use a trigger of some sort, OR create a styleSelector class and let it choose which style to use.
My problem is that I’m at a loss as to how to programatically determine which style to use. I’ve found the ‘RenderSize.Width’ gets set to 0 when the button is pushed beyond the visible range of the container. My issue is that when 5 buttons are outside the visible bounds of the container, setting the 5 to use the smaller style won’t help… I’ll have to also use the smaller size for other visible buttons in order to ‘make room’ for the 5 that are outside the bounds.
I’d be happy with setting the style for all buttons within a container… the moment one buttons renderSize.Width = 0, knock all of the sibling buttons down to on style size lower…
Is what I’m attempting even possible? Would I have to create a custom container in order to override the rendering of all of the child controls? I would rather avoid that if I can… but I can’t see any other possible way…
As an example of what I’m trying to accomplish, open Word or Excel 2007/2010, and slowly resize the window to make it smaller. As you so so, the buttons in the ribon ‘shrink’ to make more room.
I would appreciate any thoughts on whether I’m approaching this from the right direction.
You can use a second converter to bind the style.
Create a IMultiValueConverter that takes two values, ActualWidth and the element that the calculations will be based on (in my example, I am using Window):
Then, change your button to use the converter to choose the style:
I added the FallbackValue because otherwise, I could not see either style from design view.