Skip to content

Commit

Permalink
Merge pull request #3073 from MahApps/feature/VSTheme_TabItem_CloseBu…
Browse files Browse the repository at this point in the history
…tton

VS Theme TabItem enhancements
  • Loading branch information
punker76 committed Oct 8, 2017
2 parents 701c92a + 17fb9f3 commit 7466641
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 19 deletions.
4 changes: 4 additions & 0 deletions docs/release-notes/1.6.0.md
Expand Up @@ -43,6 +43,9 @@
- `VS Theme` changes / enhancements
+ Add `StandardGroupBox` style
+ Add `StandardExpander` style
+ `TabControlHelper.CloseButtonEnabled` attached property to show / hide the close button (default is true).
+ `TabControlHelper.CloseTabCommand` attached property which executes if the TabItem will be closed.
+ `TabControlHelper.CloseTabCommandParameter` attached property which will be passed to the CloseTabCommand.

## Breaking Change

Expand Down Expand Up @@ -102,3 +105,4 @@ More informations about the reason of this decision can be found here:
- [#2889](https://github.com/MahApps/MahApps.Metro/issues/2889) [Feature Request] Watermark Wrapping
- [#3070](https://github.com/MahApps/MahApps.Metro/issues/3070) VS GroupBox style
- [#957](https://github.com/MahApps/MahApps.Metro/issues/957) Expander icon in VS theme
- [#1731](https://github.com/MahApps/MahApps.Metro/issues/1731) VS Theme TabItem Question
139 changes: 126 additions & 13 deletions src/MahApps.Metro/MahApps.Metro.Shared/Actions/CloseTabItemAction.cs
Expand Up @@ -3,48 +3,120 @@
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using MahApps.Metro.Controls;

namespace MahApps.Metro.Actions
{
public class CloseTabItemAction : TriggerAction<DependencyObject>
public class CloseTabItemAction : TriggerAction<FrameworkElement>
{
private TabItem associatedTabItem;

private TabItem AssociatedTabItem => this.associatedTabItem ?? (this.associatedTabItem = this.AssociatedObject.TryFindParent<TabItem>());

/// <summary>
/// Identifies the <see cref="Command" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandProperty
= DependencyProperty.Register(nameof(Command),
typeof(ICommand),
typeof(CloseTabItemAction),
new PropertyMetadata(null, (s, e) => OnCommandChanged(s as CloseTabItemAction, e)));

/// <summary>
/// Gets or sets the command that this trigger is bound to.
/// </summary>
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { this.SetValue(CommandProperty, value); }
}

/// <summary>
/// Identifies the <see cref="CommandParameter" /> dependency property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty
= DependencyProperty.Register(nameof(CommandParameter),
typeof(object),
typeof(CloseTabItemAction),
new PropertyMetadata(null,
(s, e) =>
{
var sender = s as CloseTabItemAction;
if (sender?.AssociatedObject != null)
{
sender.EnableDisableElement();
}
}));

/// <summary>
/// Gets or sets an object that will be passed to the <see cref="Command" /> attached to this trigger.
/// </summary>
public object CommandParameter
{
get { return this.GetValue(CommandParameterProperty); }
set { this.SetValue(CommandParameterProperty, value); }
}

[Obsolete("This property will be deleted in the next release.")]
public static readonly DependencyProperty TabControlProperty =
DependencyProperty.Register(
nameof(TabControl),
typeof(TabControl),
typeof(CloseTabItemAction),
new PropertyMetadata(default(TabControl)));
DependencyProperty.Register(nameof(TabControl),
typeof(TabControl),
typeof(CloseTabItemAction),
new PropertyMetadata(default(TabControl)));

[Obsolete("This property will be deleted in the next release.")]
public TabControl TabControl
{
get { return (TabControl)this.GetValue(TabControlProperty); }
set { this.SetValue(TabControlProperty, value); }
}

[Obsolete("This property will be deleted in the next release.")]
public static readonly DependencyProperty TabItemProperty =
DependencyProperty.Register(
nameof(TabItem),
typeof(TabItem),
typeof(CloseTabItemAction),
new PropertyMetadata(default(TabItem)));
DependencyProperty.Register(nameof(TabItem),
typeof(TabItem),
typeof(CloseTabItemAction),
new PropertyMetadata(default(TabItem)));

[Obsolete("This property will be deleted in the next release.")]
public TabItem TabItem
{
get { return (TabItem)this.GetValue(TabItemProperty); }
set { this.SetValue(TabItemProperty, value); }
}

protected override void OnAttached()
{
base.OnAttached();
this.EnableDisableElement();
}

protected override void Invoke(object parameter)
{
var tabControl = this.TabControl;
var tabItem = this.TabItem;
if (this.AssociatedObject == null || (this.AssociatedObject != null && !this.AssociatedObject.IsEnabled))
{
return;
}

var tabControl = this.AssociatedObject.TryFindParent<TabControl>();
var tabItem = this.AssociatedTabItem;
if (tabControl == null || tabItem == null)
{
return;
}

var command = this.Command;
if (command != null)
{
var commandParameter = this.GetCommandParameter();
if (command.CanExecute(commandParameter))
{
command.Execute(commandParameter);
}
}

var closeAction =
new Action(
() =>
Expand Down Expand Up @@ -75,5 +147,46 @@ protected override void Invoke(object parameter)
});
this.BeginInvoke(closeAction);
}

private static void OnCommandChanged(CloseTabItemAction action, DependencyPropertyChangedEventArgs e)
{
if (action == null)
{
return;
}

if (e.OldValue != null)
{
((ICommand)e.OldValue).CanExecuteChanged -= action.OnCommandCanExecuteChanged;
}

var command = (ICommand)e.NewValue;
if (command != null)
{
command.CanExecuteChanged += action.OnCommandCanExecuteChanged;
}

action.EnableDisableElement();
}

private object GetCommandParameter()
{
return this.CommandParameter ?? this.AssociatedTabItem;
}

private void EnableDisableElement()
{
if (this.AssociatedObject == null)
{
return;
}
var command = this.Command;
this.AssociatedObject.IsEnabled = command == null || command.CanExecute(this.GetCommandParameter());
}

private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
this.EnableDisableElement();
}
}
}
Expand Up @@ -3,6 +3,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace MahApps.Metro.Controls
Expand All @@ -20,6 +21,93 @@ public enum UnderlinedType

public static class TabControlHelper
{
/// <summary>
/// Identifies the CloseButtonEnabled attached property.
/// </summary>
public static readonly DependencyProperty CloseButtonEnabledProperty =
DependencyProperty.RegisterAttached("CloseButtonEnabled",
typeof(bool),
typeof(TabControlHelper),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits));

/// <summary>
/// Gets whether a close button should be visible or not.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static bool GetCloseButtonEnabled(UIElement element)
{
return (bool)element.GetValue(CloseButtonEnabledProperty);
}

/// <summary>
/// Sets whether a close button should be visible or not.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static void SetCloseButtonEnabled(UIElement element, bool value)
{
element.SetValue(CloseButtonEnabledProperty, value);
}

/// <summary>
/// Identifies the CloseTabCommand attached property.
/// </summary>
public static readonly DependencyProperty CloseTabCommandProperty =
DependencyProperty.RegisterAttached("CloseTabCommand",
typeof(ICommand),
typeof(TabControlHelper),
new PropertyMetadata(null));

/// <summary>
/// Gets a command for the TabItem which executes if the TabItem will be closed.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static ICommand GetCloseTabCommand(UIElement element)
{
return (ICommand)element.GetValue(CloseTabCommandProperty);
}

/// <summary>
/// Sets a command for the TabItem which executes if the TabItem will be closed.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static void SetCloseTabCommand(UIElement element, ICommand value)
{
element.SetValue(CloseTabCommandProperty, value);
}

/// <summary>
/// Identifies the CloseTabCommandParameter attached property.
/// </summary>
public static readonly DependencyProperty CloseTabCommandParameterProperty =
DependencyProperty.RegisterAttached("CloseTabCommandParameter",
typeof(object),
typeof(TabControlHelper),
new PropertyMetadata(null));

/// <summary>
/// Gets a command parameter for the TabItem that will be passed to the CloseTabCommand.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static object GetCloseTabCommandParameter(UIElement element)
{
return (object)element.GetValue(CloseTabCommandParameterProperty);
}

/// <summary>
/// Sets a command parameter for the TabItem that will be passed to the CloseTabCommand.
/// </summary>
[Category(AppName.MahApps)]
[AttachedPropertyBrowsableForType(typeof(TabItem))]
public static void SetCloseTabCommandParameter(UIElement element, object value)
{
element.SetValue(CloseTabCommandParameterProperty, value);
}

/// <summary>
/// Defines whether the underline below the <see cref="TabItem"/> is shown or not.
/// </summary>
Expand Down
Expand Up @@ -44,8 +44,7 @@ private bool InternalCloseTabCommandCanExecute(object o)
DependencyProperty.Register("CloseButtonEnabled",
typeof(bool),
typeof(MetroTabItem),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits));
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits));

/// <summary>
/// Gets/sets whether the Close Button is visible.
Expand Down Expand Up @@ -103,8 +102,7 @@ public object CloseTabCommandParameter
DependencyProperty.Register("CloseButtonMargin",
typeof(Thickness),
typeof(MetroTabItem),
new FrameworkPropertyMetadata(new Thickness(),
FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits));
new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.Inherits));

/// <summary>
/// Gets/sets the Close Button Margin.
Expand Down
7 changes: 5 additions & 2 deletions src/MahApps.Metro/MahApps.Metro/Styles/VS/TabControl.xaml
Expand Up @@ -145,6 +145,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource BorderBrushNormal}" />
<!-- special property for header font size -->
<Setter Property="Controls:ControlsHelper.HeaderFontSize" Value="{DynamicResource TabItemFontSize}" />
<Setter Property="Controls:TabControlHelper.CloseButtonEnabled" Value="True" />
<Setter Property="Padding" Value="12 5 12 5" />
<Setter Property="Template">
<Setter.Value>
Expand Down Expand Up @@ -178,10 +179,11 @@
<Button x:Name="PART_CloseButton"
VerticalAlignment="Center"
IsTabStop="False"
Style="{DynamicResource StandardTabItemCloseButtonStyle}">
Style="{DynamicResource StandardTabItemCloseButtonStyle}"
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Controls:TabControlHelper.CloseButtonEnabled), Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<Actions:CloseTabItemAction TabControl="{Binding RelativeSource={RelativeSource AncestorType=TabControl}}" TabItem="{Binding RelativeSource={RelativeSource AncestorType=TabItem}}" />
<Actions:CloseTabItemAction Command="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Controls:TabControlHelper.CloseTabCommand), Mode=OneWay}" CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Controls:TabControlHelper.CloseTabCommandParameter), Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Expand Down Expand Up @@ -224,6 +226,7 @@
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="Controls:TabControlHelper.CloseButtonEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="PART_CloseButton" Property="Visibility" Value="Hidden" />
</MultiTrigger>
Expand Down

0 comments on commit 7466641

Please sign in to comment.