Reputation: 15413
Anyone know of a WPF split button that looks like a regular button in both the Win7 theme and the Win8 theme? I'm using one that looks good for Win7 but sticks out like a sore thumb in Win8:
I've tried the WPF Splitbutton project on codeplex, the Banana Splitbutton and the splitbutton in the Extended WPF Toolkit.
Is there anything out there that gives an Win7-themed button in Win7 and a Win8-themed button in Win8?
I need the control to have a bindable Command property and show a context menu when the down arrow is pressed.
Upvotes: 2
Views: 4519
Reputation: 15413
I went with Sten's approach: nesting a button inside another button. I used a user control to make it re-usable, putting all the elements in the control template so it could put arbitrary content inside the button.
<UserControl
x:Class="VidCoder.Controls.SplitButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Loaded="SplitButton_OnLoaded">
<UserControl.Template>
<ControlTemplate TargetType="{x:Type UserControl}">
<Button
HorizontalAlignment="Left" VerticalAlignment="Top" Name="mainButton" ContextMenuService.Placement="Bottom"
Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Button.Content>
<StackPanel Orientation="Horizontal" UseLayoutRounding="True">
<ContentPresenter Margin="{TemplateBinding Padding}" />
<Rectangle Width="1" Fill="#111111" Margin="0,2" />
<Button Click="OnArrowClick">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid Background="Transparent" Name="buttonGrid">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Button.Template>
<Button.Content>
<Path Data="M 0,0 L 8,0 L 4,4 Z" Fill="{TemplateBinding Foreground}" Margin="4 0 3 0" VerticalAlignment="Center"/>
</Button.Content>
</Button>
</StackPanel>
</Button.Content>
<Button.ContextMenu>
<ContextMenu Name="buttonMenu" ItemsSource="{Binding Path=MenuItemsSource, RelativeSource={RelativeSource TemplatedParent}}" />
</Button.ContextMenu>
</Button>
</ControlTemplate>
</UserControl.Template>
</UserControl>
The codebehind exposes the menu item collection and Command property:
public partial class SplitButton : UserControl
{
private Button button;
private ObservableCollection<object> menuItemsSource = new ObservableCollection<object>();
public Collection<object> MenuItemsSource { get { return this.menuItemsSource; } }
public SplitButton()
{
InitializeComponent();
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof (ICommand),
typeof (SplitButton),
new UIPropertyMetadata(null, OnCommandChanged));
public ICommand Command
{
get
{
return (ICommand) GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
private static void OnCommandChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
if (eventArgs.NewValue != eventArgs.OldValue)
{
var splitButton = dependencyObject as SplitButton;
if (splitButton.button != null)
{
splitButton.button.Command = eventArgs.NewValue as ICommand;
}
}
}
private void OnArrowClick(object sender, RoutedEventArgs e)
{
var buttonMenu = ContextMenuService.GetContextMenu(this.button);
if (this.menuItemsSource.Count > 0 && buttonMenu != null)
{
buttonMenu.IsOpen = !buttonMenu.IsOpen;
buttonMenu.PlacementTarget = this.button;
buttonMenu.Placement = PlacementMode.Bottom;
}
}
private void SplitButton_OnLoaded(object sender, RoutedEventArgs e)
{
this.button = this.Template.FindName("mainButton", this) as Button;
if (this.Command != null)
{
this.button.Command = this.Command;
}
}
}
In use:
<controls:SplitButton HorizontalAlignment="Left" VerticalAlignment="Top" Command="{Binding TestCommand}">
<controls:SplitButton.MenuItemsSource>
<MenuItem Header="ham" Command="{Binding TestCommand2}" />
<MenuItem Header="sandwiches" />
<MenuItem Header="yum" />
</controls:SplitButton.MenuItemsSource>
<TextBlock Padding="4" Text="Testing" />
</controls:SplitButton>
Upvotes: 2
Reputation: 11040
You can always make your own out of two standard buttons and it's not that hard - just but a button within the button, customize the style of the inner one to lack border, have the drop-down triangle and have "transparent" background (not the same as {x:null} background which causes click-through)
Roughly something like this:
<Button Style="outerStyle">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Label>Click Me</Label>
<Button Style="{innerStyleWithTransparentBackground}"> ▼ </Button>
</StackPanel>
</Button.Content>
</Button>
If you use a lot of split buttons (do it anyway) you can put this construct in a user control with a couple routed events, otherwise you can nest it in your other code.
It's not the most elegant solution but it's probably one of the simplest ones you'd find.
Upvotes: 1
Reputation: 522
The "sticking out like a sore thumb" is a result of how wpf controls resolve their style. By default (meaning if you have not supplied a Style via the Style property), the splitbutton will go up the resource tree until it finds a style with the TargetType of splitButton and an implicit style x:Key of "{x:Type SplitButton}". If it can't find a style for your splitbutton, it will look in the theme file for whatever windows theme you are currently using. In win7 this is usually Aero.NormalColor.xaml. I'm not sure for win8 but I'd imagine Metro.NormalColor.xaml (correct me if I'm wrong). If it can't find a style in the theme dictionary, it will look in Generic.xaml. Anyways, what you want to do is define an implicit style for your control at a level in the resource tree that make sense (probably at Window if it is a standalone or Page if the xaml is hosted in a browser/>. This will be the style that is always used (when style is not explicitly defined).
Example: http://msdn.microsoft.com/en-us/library/ms745683.aspx#styling_basics
Upvotes: 0