Permalink
Browse files

Added two new controls.

  • Loading branch information...
1 parent af2ada9 commit 948a44e1f2817b974a9466f423a948d8a2a64dad @kaisellgren committed Jun 10, 2012
Showing with 343 additions and 9 deletions.
  1. +76 −0 Styles/SplitButton.xaml
  2. +24 −0 UserControls/MenuButton.cs
  3. +21 −9 UserControls/Panel.xaml
  4. +222 −0 UserControls/SplitButton.cs
View
@@ -0,0 +1,76 @@
+<ResourceDictionary
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mwt="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
+ xmlns:userControls="clr-namespace:GG.UserControls">
+
+ <Style TargetType="{x:Type userControls:SplitButton}" BasedOn="{StaticResource {x:Type Button}}">
+ <Setter Property="Control.Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type userControls:SplitButton}">
+ <mwt:ButtonChrome x:Name="Chrome" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" RenderPressed="{TemplateBinding ButtonBase.IsPressed}" SnapsToDevicePixels="True">
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition/>
+ <ColumnDefinition Width="Auto"/>
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" Margin="{TemplateBinding Control.Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" />
+ <Rectangle Grid.Column="1" Width="1" Fill="{TemplateBinding Foreground}" Opacity="0.4" Margin="0 4 0 4"/>
+ <Grid x:Name="SplitElement" Grid.Column="2" Background="Transparent">
+ <ContextMenuService.ContextMenu>
+ <ContextMenu ItemsSource="{Binding ButtonMenuItemsSource, RelativeSource={RelativeSource TemplatedParent}}" Foreground="{TemplateBinding Foreground}" FlowDirection="{TemplateBinding FlowDirection}"/>
+ </ContextMenuService.ContextMenu>
+ <Path Data="M 0,0 L 8,0 L 4,4 Z" Fill="{TemplateBinding Foreground}" Margin="2 0 4 0" VerticalAlignment="Center"/>
+ </Grid>
+ </Grid>
+ </mwt:ButtonChrome>
+ <ControlTemplate.Triggers>
+ <Trigger Property="UIElement.IsKeyboardFocused" Value="True">
+ <Setter Property="mwt:ButtonChrome.RenderDefaulted" TargetName="Chrome" Value="True" />
+ </Trigger>
+ <Trigger Property="ToggleButton.IsChecked" Value="True">
+ <Setter Property="mwt:ButtonChrome.RenderPressed" TargetName="Chrome" Value="True" />
+ </Trigger>
+ <Trigger Property="UIElement.IsEnabled" Value="False">
+ <Setter Property="Control.Foreground" Value="#FFADADAD" />
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="{x:Type userControls:MenuButton}" BasedOn="{StaticResource {x:Type Button}}">
+ <Setter Property="Control.Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type userControls:SplitButton}">
+ <mwt:ButtonChrome x:Name="Chrome" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" RenderPressed="{TemplateBinding ButtonBase.IsPressed}" SnapsToDevicePixels="True">
+ <Grid x:Name="SplitElement" Background="Transparent">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition/>
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <ContextMenuService.ContextMenu>
+ <ContextMenu ItemsSource="{Binding ButtonMenuItemsSource, RelativeSource={RelativeSource TemplatedParent}}" Foreground="{TemplateBinding Foreground}" FlowDirection="{TemplateBinding FlowDirection}"/>
+ </ContextMenuService.ContextMenu>
+ <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" Margin="{TemplateBinding Control.Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" />
+ <Path Grid.Column="1" Data="M 0,0 L 8,0 L 4,4 Z" Fill="{TemplateBinding Foreground}" Margin="2 0 4 0" VerticalAlignment="Center"/>
+ </Grid>
+ </mwt:ButtonChrome>
+ <ControlTemplate.Triggers>
+ <Trigger Property="UIElement.IsKeyboardFocused" Value="True">
+ <Setter Property="mwt:ButtonChrome.RenderDefaulted" TargetName="Chrome" Value="True" />
+ </Trigger>
+ <Trigger Property="ToggleButton.IsChecked" Value="True">
+ <Setter Property="mwt:ButtonChrome.RenderPressed" TargetName="Chrome" Value="True" />
+ </Trigger>
+ <Trigger Property="UIElement.IsEnabled" Value="False">
+ <Setter Property="Control.Foreground" Value="#FFADADAD" />
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary>
View
@@ -0,0 +1,24 @@
+namespace GG.UserControls
+{
+ /// <summary>
+ /// Implements a "menu button" for WPF.
+ /// </summary>
+ public class MenuButton : SplitButton
+ {
+ /// <summary>
+ /// Initializes a new instance of the MenuButton class.
+ /// </summary>
+ public MenuButton()
+ {
+ DefaultStyleKey = typeof(MenuButton);
+ }
+
+ /// <summary>
+ /// Called when the button is clicked.
+ /// </summary>
+ protected override void OnClick()
+ {
+ OpenButtonMenu();
+ }
+ }
+}
View
@@ -2,11 +2,17 @@
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:local="clr-namespace:GG"
xmlns:userControls="clr-namespace:GG.UserControls"
+ xmlns:converters="clr-namespace:GG.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
+ <UserControl.Resources>
+ <converters:NullToVisibilityConverter x:Key="nullToVisibilityConverter" />
+ </UserControl.Resources>
+
<UserControl.Template>
<ControlTemplate>
<DockPanel>
@@ -15,15 +21,21 @@
<DockPanel Style="{DynamicResource PanelHeaderStyle}">
<ContentPresenter DockPanel.Dock="Left" Style="{DynamicResource PanelHeaderTitleStyle}" Content="{TemplateBinding userControls:Panel.Header}" />
<WrapPanel DockPanel.Dock="Right" HorizontalAlignment="Right">
- <ComboBox ToolTip="Copy a previous commit message. The entire message will be copied."
- ItemsSource="{Binding RecentCommitMessages}"
- DisplayMemberPath="CroppedMessage"
- Name="RecentCommitMessages"
- SelectionChanged="OnRecentCommitMessagesSelectionChanged"
- Width="150" Margin="0,4,4,4">
- </ComboBox>
- <Button Margin="0,3,3,3"
- Command="{Binding CommitCommand}" CommandParameter="{Binding Text, ElementName=CommitMessageTextBox}">
+ <Grid>
+ <ComboBox ToolTip="Copy a previous commit message. The entire message will be copied."
+ ItemsSource="{Binding RecentCommitMessages}"
+ DisplayMemberPath="CroppedMessage"
+ Name="RecentCommitMessages"
+ SelectionChanged="OnRecentCommitMessagesSelectionChanged"
+ Width="135" Margin="0,4,4,4">
+ </ComboBox>
+ <WrapPanel Margin="8,9,0,0" Visibility="{Binding SelectedItem, ElementName=RecentCommitMessages, Converter={StaticResource nullToVisibilityConverter}}">
+ <Image Source="../Resources/Icons/MessageWrite.png" Stretch="None" Margin="0,0,4,0" />
+ <TextBlock IsHitTestVisible="False" Text="Copy a message" Foreground="#333" />
+ </WrapPanel>
+ </Grid>
+ <Button Margin="0,3,3,3"
+ Command="{Binding CommitCommand}" CommandParameter="{Binding Text, ElementName=CommitMessageTextBox}">
<WrapPanel>
<Image Source="../Resources/Icons/RepositoryWrite.png" Width="16" Margin="0,0,4,0" />
<TextBlock>Commit</TextBlock>
View
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace GG.UserControls
+{
+ /// <summary>
+ /// Implements a "split button" for WPF.
+ /// </summary>
+ [TemplatePart(Name = SplitElementName, Type = typeof(UIElement))]
+ public class SplitButton : Button
+ {
+ /// <summary>
+ /// Stores the public name of the split element.
+ /// </summary>
+ private const string SplitElementName = "SplitElement";
+
+ /// <summary>
+ /// Stores a reference to the split element.
+ /// </summary>
+ private UIElement _splitElement;
+
+ /// <summary>
+ /// Stores a reference to the ContextMenu.
+ /// </summary>
+ private ContextMenu _contextMenu;
+
+ /// <summary>
+ /// Stores the initial location of the ContextMenu.
+ /// </summary>
+ private Point _contextMenuInitialOffset;
+
+ /// <summary>
+ /// Stores the backing collection for the ButtonMenuItemsSource property.
+ /// </summary>
+ private ObservableCollection<object> _buttonMenuItemsSource = new ObservableCollection<object>();
+
+ /// <summary>
+ /// Gets the collection of items for the split button's menu.
+ /// </summary>
+ public Collection<object> ButtonMenuItemsSource { get { return _buttonMenuItemsSource; } }
+
+ /// <summary>
+ /// Gets or sets a value indicating whetherthe mouse is over the split element.
+ /// </summary>
+ protected bool IsMouseOverSplitElement { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the SplitButton class.
+ /// </summary>
+ public SplitButton()
+ {
+ DefaultStyleKey = typeof(SplitButton);
+ }
+
+ /// <summary>
+ /// Called when the template is changed.
+ /// </summary>
+ public override void OnApplyTemplate()
+ {
+ // Unhook existing handlers
+ if (null != _splitElement)
+ {
+ _splitElement.MouseEnter -= new MouseEventHandler(SplitElement_MouseEnter);
+ _splitElement.MouseLeave -= new MouseEventHandler(SplitElement_MouseLeave);
+ _splitElement = null;
+ }
+ if (null != _contextMenu)
+ {
+ _contextMenu.Opened -= new RoutedEventHandler(ContextMenu_Opened);
+ _contextMenu.Closed -= new RoutedEventHandler(ContextMenu_Closed);
+ _contextMenu = null;
+ }
+
+ // Apply new template
+ base.OnApplyTemplate();
+
+ // Hook new event handlers
+ _splitElement = GetTemplateChild(SplitElementName) as UIElement;
+ if (null != _splitElement)
+ {
+ _splitElement.MouseEnter += new MouseEventHandler(SplitElement_MouseEnter);
+ _splitElement.MouseLeave += new MouseEventHandler(SplitElement_MouseLeave);
+
+ _contextMenu = ContextMenuService.GetContextMenu(_splitElement);
+ if (null != _contextMenu)
+ {
+ _contextMenu.Opened += new RoutedEventHandler(ContextMenu_Opened);
+ _contextMenu.Closed += new RoutedEventHandler(ContextMenu_Closed);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when the Button is clicked.
+ /// </summary>
+ protected override void OnClick()
+ {
+ if (IsMouseOverSplitElement)
+ {
+ OpenButtonMenu();
+ }
+ else
+ {
+ base.OnClick();
+ }
+ }
+
+ /// <summary>
+ /// Called when a key is pressed.
+ /// </summary>
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (null == e)
+ {
+ throw new ArgumentNullException("e");
+ }
+
+ if ((Key.Down == e.Key) || (Key.Up == e.Key))
+ {
+ // WPF requires this to happen via BeginInvoke
+ Dispatcher.BeginInvoke((Action) (() => OpenButtonMenu()));
+ }
+ else
+ {
+ base.OnKeyDown(e);
+ }
+ }
+
+ /// <summary>
+ /// Opens the button menu.
+ /// </summary>
+ protected void OpenButtonMenu()
+ {
+ if ((0 < _buttonMenuItemsSource.Count) && (null != _contextMenu))
+ {
+ _contextMenu.HorizontalOffset = 0;
+ _contextMenu.VerticalOffset = 0;
+ _contextMenu.IsOpen = true;
+ }
+ }
+
+ /// <summary>
+ /// Called when the mouse goes over the split element.
+ /// </summary>
+ /// <param name="sender">Event source.</param>
+ /// <param name="e">Event arguments.</param>
+ private void SplitElement_MouseEnter(object sender, MouseEventArgs e)
+ {
+ IsMouseOverSplitElement = true;
+ }
+
+ /// <summary>
+ /// Called when the mouse goes off the split element.
+ /// </summary>
+ /// <param name="sender">Event source.</param>
+ /// <param name="e">Event arguments.</param>
+ private void SplitElement_MouseLeave(object sender, MouseEventArgs e)
+ {
+ IsMouseOverSplitElement = false;
+ }
+
+ /// <summary>
+ /// Called when the ContextMenu is opened.
+ /// </summary>
+ /// <param name="sender">Event source.</param>
+ /// <param name="e">Event arguments.</param>
+ private void ContextMenu_Opened(object sender, RoutedEventArgs e)
+ {
+ // Offset the ContextMenu correctly
+ _contextMenuInitialOffset = TranslatePoint(new Point(0, ActualHeight), _contextMenu);
+ UpdateContextMenuOffsets();
+
+ // Hook LayoutUpdated to handle application resize and zoom changes
+ LayoutUpdated += new EventHandler(SplitButton_LayoutUpdated);
+ }
+
+ /// <summary>
+ /// Called when the ContextMenu is closed.
+ /// </summary>
+ /// <param name="sender">Event source.</param>
+ /// <param name="e">Event arguments.</param>
+ private void ContextMenu_Closed(object sender, RoutedEventArgs e)
+ {
+ // No longer need to handle LayoutUpdated
+ LayoutUpdated -= new EventHandler(SplitButton_LayoutUpdated);
+
+ // Restore focus to the Button
+ Focus();
+ }
+
+ /// <summary>
+ /// Called when the ContextMenu is open and layout is updated.
+ /// </summary>
+ /// <param name="sender">Event source.</param>
+ /// <param name="e">Event arguments.</param>
+ private void SplitButton_LayoutUpdated(object sender, EventArgs e)
+ {
+ UpdateContextMenuOffsets();
+ }
+
+ /// <summary>
+ /// Updates the ContextMenu's Horizontal/VerticalOffset properties to keep it under the SplitButton.
+ /// </summary>
+ private void UpdateContextMenuOffsets()
+ {
+ // Calculate desired offset to put the ContextMenu below and left-aligned to the Button
+ Point currentOffset = new Point();
+ Point desiredOffset = _contextMenuInitialOffset;
+ _contextMenu.HorizontalOffset = desiredOffset.X - currentOffset.X;
+ _contextMenu.VerticalOffset = desiredOffset.Y - currentOffset.Y;
+ // Adjust for RTL
+ if (FlowDirection.RightToLeft == FlowDirection)
+ {
+ _contextMenu.HorizontalOffset *= -1;
+ }
+ }
+
+ }
+}

0 comments on commit 948a44e

Please sign in to comment.