diff --git a/src/MahApps.Metro/Controls/DropDownButton.cs b/src/MahApps.Metro/Controls/DropDownButton.cs index 753da55556..8e26697912 100644 --- a/src/MahApps.Metro/Controls/DropDownButton.cs +++ b/src/MahApps.Metro/Controls/DropDownButton.cs @@ -9,11 +9,13 @@ namespace MahApps.Metro.Controls { - [ContentProperty("ItemsSource")] + [ContentProperty(nameof(ItemsSource))] [TemplatePart(Name = "PART_Button", Type = typeof(Button))] [TemplatePart(Name = "PART_ButtonContent", Type = typeof(ContentControl))] [TemplatePart(Name = "PART_Menu", Type = typeof(ContextMenu))] - public class DropDownButton : ItemsControl + [StyleTypedProperty(Property = nameof(ButtonStyle), StyleTargetType = typeof(Button))] + [StyleTypedProperty(Property = nameof(MenuStyle), StyleTargetType = typeof(ContextMenu))] + public class DropDownButton : ItemsControl, ICommandSource { public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(nameof(Click), @@ -23,29 +25,30 @@ public static readonly RoutedEvent ClickEvent public event RoutedEventHandler Click { - add { this.AddHandler(ClickEvent, value); } - remove { this.RemoveHandler(ClickEvent, value); } + add => this.AddHandler(ClickEvent, value); + remove => this.RemoveHandler(ClickEvent, value); } /// Identifies the dependency property. public static readonly DependencyProperty IsExpandedProperty - = DependencyProperty.Register( - nameof(IsExpanded), - typeof(bool), - typeof(DropDownButton), - new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IsExpandedPropertyChangedCallback)); + = DependencyProperty.Register(nameof(IsExpanded), + typeof(bool), + typeof(DropDownButton), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsExpandedPropertyChangedCallback)); - private static void IsExpandedPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + private static void OnIsExpandedPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { - DropDownButton dropDownButton = (DropDownButton)dependencyObject; - dropDownButton.SetContextMenuPlacementTarget(dropDownButton._contextMenu); + if (dependencyObject is DropDownButton dropDownButton) + { + dropDownButton.SetContextMenuPlacementTarget(dropDownButton.contextMenu); + } } protected virtual void SetContextMenuPlacementTarget(ContextMenu contextMenu) { - if (this._clickButton != null) + if (this.button != null) { - contextMenu.PlacementTarget = this._clickButton; + contextMenu.PlacementTarget = this.button; } } @@ -54,49 +57,46 @@ protected virtual void SetContextMenuPlacementTarget(ContextMenu contextMenu) /// public bool IsExpanded { - get { return (bool)this.GetValue(IsExpandedProperty); } - set { this.SetValue(IsExpandedProperty, value); } + get => (bool)this.GetValue(IsExpandedProperty); + set => this.SetValue(IsExpandedProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ExtraTagProperty - = DependencyProperty.Register( - nameof(ExtraTag), - typeof(object), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(ExtraTag), + typeof(object), + typeof(DropDownButton)); /// /// Gets or sets an extra tag. /// public object ExtraTag { - get { return this.GetValue(ExtraTagProperty); } - set { this.SetValue(ExtraTagProperty, value); } + get => this.GetValue(ExtraTagProperty); + set => this.SetValue(ExtraTagProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty OrientationProperty - = DependencyProperty.Register( - nameof(Orientation), - typeof(Orientation), - typeof(DropDownButton), - new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(Orientation), + typeof(Orientation), + typeof(DropDownButton), + new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the orientation of children stacking. /// public Orientation Orientation { - get { return (Orientation)this.GetValue(OrientationProperty); } - set { this.SetValue(OrientationProperty, value); } + get => (Orientation)this.GetValue(OrientationProperty); + set => this.SetValue(OrientationProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty IconProperty - = DependencyProperty.Register( - nameof(Icon), - typeof(object), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(Icon), + typeof(object), + typeof(DropDownButton)); /// /// Gets or sets the content for the icon part. @@ -104,16 +104,15 @@ public static readonly DependencyProperty IconProperty [Bindable(true)] public object Icon { - get { return this.GetValue(IconProperty); } - set { this.SetValue(IconProperty, value); } + get => this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty IconTemplateProperty - = DependencyProperty.Register( - nameof(IconTemplate), - typeof(DataTemplate), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(IconTemplate), + typeof(DataTemplate), + typeof(DropDownButton)); /// /// Gets or sets the DataTemplate for the icon part. @@ -121,81 +120,84 @@ public static readonly DependencyProperty IconTemplateProperty [Bindable(true)] public DataTemplate IconTemplate { - get { return (DataTemplate)this.GetValue(IconTemplateProperty); } - set { this.SetValue(IconTemplateProperty, value); } + get => (DataTemplate)this.GetValue(IconTemplateProperty); + set => this.SetValue(IconTemplateProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandProperty - = DependencyProperty.Register( - nameof(Command), - typeof(ICommand), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(Command), + typeof(ICommand), + typeof(DropDownButton), + new PropertyMetadata(null, OnCommandPropertyChangedCallback)); + + private static void OnCommandPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + (dependencyObject as DropDownButton)?.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue); + } /// /// Gets or sets the command to invoke when the content button is pressed. /// public ICommand Command { - get { return (ICommand)this.GetValue(CommandProperty); } - set { this.SetValue(CommandProperty, value); } + get => (ICommand)this.GetValue(CommandProperty); + set => this.SetValue(CommandProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandTargetProperty - = DependencyProperty.Register( - nameof(CommandTarget), - typeof(IInputElement), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(CommandTarget), + typeof(IInputElement), + typeof(DropDownButton), + new PropertyMetadata(null)); /// /// Gets or sets the element on which to raise the specified command. /// public IInputElement CommandTarget { - get { return (IInputElement)this.GetValue(CommandTargetProperty); } - set { this.SetValue(CommandTargetProperty, value); } + get => (IInputElement)this.GetValue(CommandTargetProperty); + set => this.SetValue(CommandTargetProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandParameterProperty - = DependencyProperty.Register( - nameof(CommandParameter), - typeof(object), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(CommandParameter), + typeof(object), + typeof(DropDownButton), + new PropertyMetadata(null)); /// /// Gets or sets the parameter to pass to the command property. /// public object CommandParameter { - get { return (object)this.GetValue(CommandParameterProperty); } - set { this.SetValue(CommandParameterProperty, value); } + get => (object)this.GetValue(CommandParameterProperty); + set => this.SetValue(CommandParameterProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ContentProperty - = DependencyProperty.Register( - nameof(Content), - typeof(object), - typeof(DropDownButton)); + = DependencyProperty.Register(nameof(Content), + typeof(object), + typeof(DropDownButton)); /// /// Gets or sets the content of this control. /// public object Content { - get { return (object)this.GetValue(ContentProperty); } - set { this.SetValue(ContentProperty, value); } + get => (object)this.GetValue(ContentProperty); + set => this.SetValue(ContentProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ContentTemplateProperty - = DependencyProperty.Register( - nameof(ContentTemplate), - typeof(DataTemplate), - typeof(DropDownButton), - new FrameworkPropertyMetadata((DataTemplate)null)); + = DependencyProperty.Register(nameof(ContentTemplate), + typeof(DataTemplate), + typeof(DropDownButton), + new FrameworkPropertyMetadata((DataTemplate)null)); /// /// Gets or sets the data template used to display the content of the DropDownButton. @@ -203,17 +205,16 @@ public static readonly DependencyProperty ContentTemplateProperty [Bindable(true)] public DataTemplate ContentTemplate { - get { return (DataTemplate)this.GetValue(ContentTemplateProperty); } - set { this.SetValue(ContentTemplateProperty, value); } + get => (DataTemplate)this.GetValue(ContentTemplateProperty); + set => this.SetValue(ContentTemplateProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ContentTemplateSelectorProperty - = DependencyProperty.Register( - nameof(ContentTemplateSelector), - typeof(DataTemplateSelector), - typeof(DropDownButton), - new FrameworkPropertyMetadata((DataTemplateSelector)null)); + = DependencyProperty.Register(nameof(ContentTemplateSelector), + typeof(DataTemplateSelector), + typeof(DropDownButton), + new FrameworkPropertyMetadata((DataTemplateSelector)null)); /// /// Gets or sets a template selector that enables an application writer to provide custom template-selection logic. @@ -224,17 +225,16 @@ public static readonly DependencyProperty ContentTemplateSelectorProperty [Bindable(true)] public DataTemplateSelector ContentTemplateSelector { - get { return (DataTemplateSelector)this.GetValue(ContentTemplateSelectorProperty); } - set { this.SetValue(ContentTemplateSelectorProperty, value); } + get => (DataTemplateSelector)this.GetValue(ContentTemplateSelectorProperty); + set => this.SetValue(ContentTemplateSelectorProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ContentStringFormatProperty - = DependencyProperty.Register( - nameof(ContentStringFormat), - typeof(string), - typeof(DropDownButton), - new FrameworkPropertyMetadata((string)null)); + = DependencyProperty.Register(nameof(ContentStringFormat), + typeof(string), + typeof(DropDownButton), + new FrameworkPropertyMetadata((string)null)); /// /// Gets or sets a composite string that specifies how to format the content property if it is displayed as a string. @@ -245,110 +245,104 @@ public static readonly DependencyProperty ContentStringFormatProperty [Bindable(true)] public string ContentStringFormat { - get { return (string)this.GetValue(ContentStringFormatProperty); } - set { this.SetValue(ContentStringFormatProperty, value); } + get => (string)this.GetValue(ContentStringFormatProperty); + set => this.SetValue(ContentStringFormatProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ButtonStyleProperty - = DependencyProperty.Register( - nameof(ButtonStyle), - typeof(Style), - typeof(DropDownButton), - new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(ButtonStyle), + typeof(Style), + typeof(DropDownButton), + new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the button content style. /// public Style ButtonStyle { - get { return (Style)this.GetValue(ButtonStyleProperty); } - set { this.SetValue(ButtonStyleProperty, value); } + get => (Style)this.GetValue(ButtonStyleProperty); + set => this.SetValue(ButtonStyleProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty MenuStyleProperty - = DependencyProperty.Register( - nameof(MenuStyle), - typeof(Style), - typeof(DropDownButton), - new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(MenuStyle), + typeof(Style), + typeof(DropDownButton), + new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the "popup" menu style. /// public Style MenuStyle { - get { return (Style)this.GetValue(MenuStyleProperty); } - set { this.SetValue(MenuStyleProperty, value); } + get => (Style)this.GetValue(MenuStyleProperty); + set => this.SetValue(MenuStyleProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowBrushProperty - = DependencyProperty.Register( - nameof(ArrowBrush), - typeof(Brush), - typeof(DropDownButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowBrush), + typeof(Brush), + typeof(DropDownButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush for the button arrow icon. /// public Brush ArrowBrush { - get { return (Brush)this.GetValue(ArrowBrushProperty); } - set { this.SetValue(ArrowBrushProperty, value); } + get => (Brush)this.GetValue(ArrowBrushProperty); + set => this.SetValue(ArrowBrushProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowMouseOverBrushProperty - = DependencyProperty.Register( - nameof(ArrowMouseOverBrush), - typeof(Brush), - typeof(DropDownButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowMouseOverBrush), + typeof(Brush), + typeof(DropDownButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush of the button arrow icon if the mouse is over the drop down button. /// public Brush ArrowMouseOverBrush { - get { return (Brush)this.GetValue(ArrowMouseOverBrushProperty); } - set { this.SetValue(ArrowMouseOverBrushProperty, value); } + get => (Brush)this.GetValue(ArrowMouseOverBrushProperty); + set => this.SetValue(ArrowMouseOverBrushProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowPressedBrushProperty - = DependencyProperty.Register( - nameof(ArrowPressedBrush), - typeof(Brush), - typeof(DropDownButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowPressedBrush), + typeof(Brush), + typeof(DropDownButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush of the button arrow icon if the arrow button is pressed. /// public Brush ArrowPressedBrush { - get { return (Brush)this.GetValue(ArrowPressedBrushProperty); } - set { this.SetValue(ArrowPressedBrushProperty, value); } + get => (Brush)this.GetValue(ArrowPressedBrushProperty); + set => this.SetValue(ArrowPressedBrushProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowVisibilityProperty - = DependencyProperty.Register( - nameof(ArrowVisibility), - typeof(Visibility), - typeof(DropDownButton), - new FrameworkPropertyMetadata(Visibility.Visible, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(ArrowVisibility), + typeof(Visibility), + typeof(DropDownButton), + new FrameworkPropertyMetadata(Visibility.Visible, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the visibility of the button arrow icon. /// public Visibility ArrowVisibility { - get { return (Visibility)this.GetValue(ArrowVisibilityProperty); } - set { this.SetValue(ArrowVisibilityProperty, value); } + get => (Visibility)this.GetValue(ArrowVisibilityProperty); + set => this.SetValue(ArrowVisibilityProperty, value); } static DropDownButton() @@ -356,9 +350,66 @@ static DropDownButton() DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownButton), new FrameworkPropertyMetadata(typeof(DropDownButton))); } + private void OnCommandChanged(ICommand oldCommand, ICommand newCommand) + { + if (oldCommand != null) + { + this.UnhookCommand(oldCommand); + } + + if (newCommand != null) + { + this.HookCommand(newCommand); + } + } + + private void UnhookCommand(ICommand command) + { + CanExecuteChangedEventManager.RemoveHandler(command, this.OnCanExecuteChanged); + this.UpdateCanExecute(); + } + + private void HookCommand(ICommand command) + { + CanExecuteChangedEventManager.AddHandler(command, this.OnCanExecuteChanged); + this.UpdateCanExecute(); + } + + private void OnCanExecuteChanged(object sender, EventArgs e) + { + this.UpdateCanExecute(); + } + + private void UpdateCanExecute() + { + this.CanExecute = this.Command == null || CommandHelpers.CanExecuteCommandSource(this); + } + + /// + protected override bool IsEnabledCore => base.IsEnabledCore && this.CanExecute; + + private bool canExecute = true; + + private bool CanExecute + { + get => this.canExecute; + set + { + if (value == this.canExecute) + { + return; + } + + this.canExecute = value; + this.CoerceValue(IsEnabledProperty); + } + } + private void ButtonClick(object sender, RoutedEventArgs e) { - if (this._contextMenu?.HasItems == true) + CommandHelpers.ExecuteCommandSource(this); + + if (this.contextMenu?.HasItems == true) { this.SetCurrentValue(IsExpandedProperty, true); } @@ -371,27 +422,25 @@ public override void OnApplyTemplate() { base.OnApplyTemplate(); - if (this._clickButton != null) + if (this.button != null) { - this._clickButton.Click -= this.ButtonClick; - this._clickButton.IsEnabledChanged -= this.ButtonIsEnabledChanged; + this.button.Click -= this.ButtonClick; } - this._clickButton = this.GetTemplateChild("PART_Button") as Button; - if (this._clickButton != null) + this.button = this.GetTemplateChild("PART_Button") as Button; + if (this.button != null) { - this._clickButton.Click += this.ButtonClick; - this._clickButton.IsEnabledChanged += this.ButtonIsEnabledChanged; + this.button.Click += this.ButtonClick; } - this._contextMenu = this.GetTemplateChild("PART_Menu") as ContextMenu; + this.contextMenu = this.GetTemplateChild("PART_Menu") as ContextMenu; - if (this._contextMenu != null && this.Items != null && this.ItemsSource == null) + if (this.contextMenu != null && this.Items != null && this.ItemsSource == null) { foreach (var newItem in this.Items) { this.TryRemoveVisualFromOldTree(newItem); - this._contextMenu.Items.Add(newItem); + this.contextMenu.Items.Add(newItem); } } } @@ -403,11 +452,6 @@ protected override void OnMouseRightButtonUp(MouseButtonEventArgs e) e.Handled = true; } - private void ButtonIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) - { - this.SetCurrentValue(IsEnabledProperty, e.NewValue); - } - private void TryRemoveVisualFromOldTree(object item) { if (item is Visual visual) @@ -426,7 +470,7 @@ private void TryRemoveVisualFromOldTree(object item) protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); - if (this._contextMenu == null || this.ItemsSource != null || this._contextMenu.ItemsSource != null) + if (this.contextMenu == null || this.ItemsSource != null || this.contextMenu.ItemsSource != null) { return; } @@ -439,7 +483,7 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) foreach (var newItem in e.NewItems) { this.TryRemoveVisualFromOldTree(newItem); - this._contextMenu.Items.Add(newItem); + this.contextMenu.Items.Add(newItem); } } @@ -449,7 +493,7 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { foreach (var oldItem in e.OldItems) { - this._contextMenu.Items.Remove(oldItem); + this.contextMenu.Items.Remove(oldItem); } } @@ -460,7 +504,7 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { foreach (var oldItem in e.OldItems) { - this._contextMenu.Items.Remove(oldItem); + this.contextMenu.Items.Remove(oldItem); } } @@ -469,7 +513,7 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) foreach (var newItem in e.NewItems) { this.TryRemoveVisualFromOldTree(newItem); - this._contextMenu.Items.Add(newItem); + this.contextMenu.Items.Add(newItem); } } @@ -477,11 +521,11 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) case NotifyCollectionChangedAction.Reset: if (this.Items != null) { - this._contextMenu.Items.Clear(); + this.contextMenu.Items.Clear(); foreach (var newItem in this.Items) { this.TryRemoveVisualFromOldTree(newItem); - this._contextMenu.Items.Add(newItem); + this.contextMenu.Items.Add(newItem); } } @@ -491,7 +535,7 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) } } - private Button _clickButton; - private ContextMenu _contextMenu; + private Button button; + private ContextMenu contextMenu; } } \ No newline at end of file diff --git a/src/MahApps.Metro/Controls/SplitButton.cs b/src/MahApps.Metro/Controls/SplitButton.cs index 94827c7b23..0990e555f7 100644 --- a/src/MahApps.Metro/Controls/SplitButton.cs +++ b/src/MahApps.Metro/Controls/SplitButton.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; @@ -8,14 +9,17 @@ namespace MahApps.Metro.Controls { - [ContentProperty("ItemsSource")] + [ContentProperty(nameof(ItemsSource))] [TemplatePart(Name = "PART_Container", Type = typeof(Grid))] [TemplatePart(Name = "PART_Button", Type = typeof(Button))] [TemplatePart(Name = "PART_ButtonContent", Type = typeof(ContentControl))] [TemplatePart(Name = "PART_Popup", Type = typeof(Popup))] [TemplatePart(Name = "PART_Expander", Type = typeof(Button))] - public class SplitButton : ComboBox + [StyleTypedProperty(Property = nameof(ButtonStyle), StyleTargetType = typeof(Button))] + [StyleTypedProperty(Property = nameof(ButtonArrowStyle), StyleTargetType = typeof(Button))] + public class SplitButton : ComboBox, ICommandSource { + /// Identifies the routed event. public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(nameof(Click), RoutingStrategy.Bubble, @@ -24,49 +28,46 @@ public static readonly RoutedEvent ClickEvent public event RoutedEventHandler Click { - add { this.AddHandler(ClickEvent, value); } - remove { this.RemoveHandler(ClickEvent, value); } + add => this.AddHandler(ClickEvent, value); + remove => this.RemoveHandler(ClickEvent, value); } /// Identifies the dependency property. public static readonly DependencyProperty ExtraTagProperty - = DependencyProperty.Register( - nameof(ExtraTag), - typeof(object), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(ExtraTag), + typeof(object), + typeof(SplitButton)); /// /// Gets or sets an extra tag. /// public object ExtraTag { - get { return this.GetValue(ExtraTagProperty); } - set { this.SetValue(ExtraTagProperty, value); } + get => this.GetValue(ExtraTagProperty); + set => this.SetValue(ExtraTagProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty OrientationProperty - = DependencyProperty.Register( - nameof(Orientation), - typeof(Orientation), - typeof(SplitButton), - new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(Orientation), + typeof(Orientation), + typeof(SplitButton), + new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the orientation of children stacking. /// public Orientation Orientation { - get { return (Orientation)this.GetValue(OrientationProperty); } - set { this.SetValue(OrientationProperty, value); } + get => (Orientation)this.GetValue(OrientationProperty); + set => this.SetValue(OrientationProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty IconProperty - = DependencyProperty.Register( - nameof(Icon), - typeof(object), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(Icon), + typeof(object), + typeof(SplitButton)); /// /// Gets or sets the content for the icon part. @@ -74,16 +75,15 @@ public static readonly DependencyProperty IconProperty [Bindable(true)] public object Icon { - get { return this.GetValue(IconProperty); } - set { this.SetValue(IconProperty, value); } + get => this.GetValue(IconProperty); + set => this.SetValue(IconProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty IconTemplateProperty - = DependencyProperty.Register( - nameof(IconTemplate), - typeof(DataTemplate), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(IconTemplate), + typeof(DataTemplate), + typeof(SplitButton)); /// /// Gets or sets the DataTemplate for the icon part. @@ -91,151 +91,206 @@ public static readonly DependencyProperty IconTemplateProperty [Bindable(true)] public DataTemplate IconTemplate { - get { return (DataTemplate)this.GetValue(IconTemplateProperty); } - set { this.SetValue(IconTemplateProperty, value); } + get => (DataTemplate)this.GetValue(IconTemplateProperty); + set => this.SetValue(IconTemplateProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandProperty - = DependencyProperty.Register( - nameof(Command), - typeof(ICommand), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(Command), + typeof(ICommand), + typeof(SplitButton), + new PropertyMetadata(null, OnCommandPropertyChangedCallback)); + + private static void OnCommandPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + (dependencyObject as SplitButton)?.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue); + } /// /// Gets or sets the command to invoke when the content button is pressed. /// public ICommand Command { - get { return (ICommand)this.GetValue(CommandProperty); } - set { this.SetValue(CommandProperty, value); } + get => (ICommand)this.GetValue(CommandProperty); + set => this.SetValue(CommandProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandTargetProperty - = DependencyProperty.Register( - nameof(CommandTarget), - typeof(IInputElement), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(CommandTarget), + typeof(IInputElement), + typeof(SplitButton), + new PropertyMetadata(null)); /// /// Gets or sets the element on which to raise the specified command. /// public IInputElement CommandTarget { - get { return (IInputElement)this.GetValue(CommandTargetProperty); } - set { this.SetValue(CommandTargetProperty, value); } + get => (IInputElement)this.GetValue(CommandTargetProperty); + set => this.SetValue(CommandTargetProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty CommandParameterProperty - = DependencyProperty.Register( - nameof(CommandParameter), - typeof(object), - typeof(SplitButton)); + = DependencyProperty.Register(nameof(CommandParameter), + typeof(object), + typeof(SplitButton), + new PropertyMetadata(null)); /// /// Gets or sets the parameter to pass to the command property. /// public object CommandParameter { - get { return this.GetValue(CommandParameterProperty); } - set { this.SetValue(CommandParameterProperty, value); } + get => this.GetValue(CommandParameterProperty); + set => this.SetValue(CommandParameterProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ButtonStyleProperty - = DependencyProperty.Register( - nameof(ButtonStyle), - typeof(Style), - typeof(SplitButton), - new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(ButtonStyle), + typeof(Style), + typeof(SplitButton), + new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the button content style. /// public Style ButtonStyle { - get { return (Style)this.GetValue(ButtonStyleProperty); } - set { this.SetValue(ButtonStyleProperty, value); } + get => (Style)this.GetValue(ButtonStyleProperty); + set => this.SetValue(ButtonStyleProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ButtonArrowStyleProperty - = DependencyProperty.Register( - nameof(ButtonArrowStyle), - typeof(Style), - typeof(SplitButton), - new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + = DependencyProperty.Register(nameof(ButtonArrowStyle), + typeof(Style), + typeof(SplitButton), + new FrameworkPropertyMetadata(default(Style), FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// Gets or sets the button arrow style. /// public Style ButtonArrowStyle { - get { return (Style)this.GetValue(ButtonArrowStyleProperty); } - set { this.SetValue(ButtonArrowStyleProperty, value); } + get => (Style)this.GetValue(ButtonArrowStyleProperty); + set => this.SetValue(ButtonArrowStyleProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowBrushProperty - = DependencyProperty.Register( - nameof(ArrowBrush), - typeof(Brush), - typeof(SplitButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowBrush), + typeof(Brush), + typeof(SplitButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush for the button arrow icon. /// public Brush ArrowBrush { - get { return (Brush)this.GetValue(ArrowBrushProperty); } - set { this.SetValue(ArrowBrushProperty, value); } + get => (Brush)this.GetValue(ArrowBrushProperty); + set => this.SetValue(ArrowBrushProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowMouseOverBrushProperty - = DependencyProperty.Register( - nameof(ArrowMouseOverBrush), - typeof(Brush), - typeof(SplitButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowMouseOverBrush), + typeof(Brush), + typeof(SplitButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush of the button arrow icon if the mouse is over the split button. /// public Brush ArrowMouseOverBrush { - get { return (Brush)this.GetValue(ArrowMouseOverBrushProperty); } - set { this.SetValue(ArrowMouseOverBrushProperty, value); } + get => (Brush)this.GetValue(ArrowMouseOverBrushProperty); + set => this.SetValue(ArrowMouseOverBrushProperty, value); } /// Identifies the dependency property. public static readonly DependencyProperty ArrowPressedBrushProperty - = DependencyProperty.Register( - nameof(ArrowPressedBrush), - typeof(Brush), - typeof(SplitButton), - new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); + = DependencyProperty.Register(nameof(ArrowPressedBrush), + typeof(Brush), + typeof(SplitButton), + new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the foreground brush of the button arrow icon if the arrow button is pressed. /// public Brush ArrowPressedBrush { - get { return (Brush)this.GetValue(ArrowPressedBrushProperty); } - set { this.SetValue(ArrowPressedBrushProperty, value); } + get => (Brush)this.GetValue(ArrowPressedBrushProperty); + set => this.SetValue(ArrowPressedBrushProperty, value); } static SplitButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(typeof(SplitButton))); - IsEditableProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(false, null, new CoerceValueCallback(CoerceIsEnabledProperty))); + IsEditableProperty.OverrideMetadata(typeof(SplitButton), new FrameworkPropertyMetadata(false, null, CoerceIsEditableProperty)); + } + + private void OnCommandChanged(ICommand oldCommand, ICommand newCommand) + { + if (oldCommand != null) + { + this.UnhookCommand(oldCommand); + } + + if (newCommand != null) + { + this.HookCommand(newCommand); + } + } + + private void UnhookCommand(ICommand command) + { + CanExecuteChangedEventManager.RemoveHandler(command, this.OnCanExecuteChanged); + this.UpdateCanExecute(); + } + + private void HookCommand(ICommand command) + { + CanExecuteChangedEventManager.AddHandler(command, this.OnCanExecuteChanged); + this.UpdateCanExecute(); + } + + private void OnCanExecuteChanged(object sender, EventArgs e) + { + this.UpdateCanExecute(); + } + + private void UpdateCanExecute() + { + this.CanExecute = this.Command == null || CommandHelpers.CanExecuteCommandSource(this); + } + + /// + protected override bool IsEnabledCore => base.IsEnabledCore && this.CanExecute; + + private bool canExecute = true; + + private bool CanExecute + { + get => this.canExecute; + set + { + if (value == this.canExecute) + { + return; + } + + this.canExecute = value; + this.CoerceValue(IsEnabledProperty); + } } - private static object CoerceIsEnabledProperty(DependencyObject dependencyObject, object value) + private static object CoerceIsEditableProperty(DependencyObject dependencyObject, object value) { // For now SplitButton is not editable return false; @@ -243,8 +298,11 @@ private static object CoerceIsEnabledProperty(DependencyObject dependencyObject, private void ButtonClick(object sender, RoutedEventArgs e) { + CommandHelpers.ExecuteCommandSource(this); + e.RoutedEvent = ClickEvent; this.RaiseEvent(e); + this.SetCurrentValue(IsDropDownOpenProperty, false); } @@ -258,37 +316,30 @@ public override void OnApplyTemplate() { base.OnApplyTemplate(); - if (this._clickButton != null) + if (this.button != null) { - this._clickButton.Click -= this.ButtonClick; - this._clickButton.IsEnabledChanged -= this.ButtonIsEnabledChanged; + this.button.Click -= this.ButtonClick; } - this._clickButton = this.GetTemplateChild("PART_Button") as Button; - if (this._clickButton != null) + this.button = this.GetTemplateChild("PART_Button") as Button; + if (this.button != null) { - this._clickButton.Click += this.ButtonClick; - this._clickButton.IsEnabledChanged += this.ButtonIsEnabledChanged; + this.button.Click += this.ButtonClick; } - if (this._expander != null) + if (this.expanderButton != null) { - this._expander.PreviewMouseLeftButtonDown -= this.ExpanderMouseLeftButtonDown; + this.expanderButton.PreviewMouseLeftButtonDown -= this.ExpanderMouseLeftButtonDown; } - this._expander = this.GetTemplateChild("PART_Expander") as Button; - if (this._expander != null) + this.expanderButton = this.GetTemplateChild("PART_Expander") as Button; + if (this.expanderButton != null) { - this._expander.PreviewMouseLeftButtonDown += this.ExpanderMouseLeftButtonDown; + this.expanderButton.PreviewMouseLeftButtonDown += this.ExpanderMouseLeftButtonDown; } } - private void ButtonIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) - { - this.SetCurrentValue(IsEnabledProperty, e.NewValue); - } - - private Button _clickButton; - private Button _expander; + private Button button; + private Button expanderButton; } } \ No newline at end of file diff --git a/src/MahApps.Metro/Themes/DropDownButton.xaml b/src/MahApps.Metro/Themes/DropDownButton.xaml index e7bd81f9b0..ebfc00ad00 100644 --- a/src/MahApps.Metro/Themes/DropDownButton.xaml +++ b/src/MahApps.Metro/Themes/DropDownButton.xaml @@ -1,4 +1,4 @@ - @@ -54,9 +54,6 @@ VerticalContentAlignment="Stretch" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - Command="{TemplateBinding Command}" - CommandParameter="{TemplateBinding CommandParameter}" - CommandTarget="{TemplateBinding CommandTarget}" FocusVisualStyle="{TemplateBinding FocusVisualStyle}" Foreground="{TemplateBinding Foreground}" RenderOptions.ClearTypeHint="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(RenderOptions.ClearTypeHint), Mode=OneWay}" diff --git a/src/MahApps.Metro/Themes/SplitButton.xaml b/src/MahApps.Metro/Themes/SplitButton.xaml index c848d21460..bcbb635289 100644 --- a/src/MahApps.Metro/Themes/SplitButton.xaml +++ b/src/MahApps.Metro/Themes/SplitButton.xaml @@ -28,9 +28,6 @@ VerticalContentAlignment="Stretch" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - Command="{TemplateBinding Command}" - CommandParameter="{TemplateBinding CommandParameter}" - CommandTarget="{TemplateBinding CommandTarget}" FocusVisualStyle="{TemplateBinding FocusVisualStyle}" Foreground="{TemplateBinding Foreground}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" @@ -156,9 +153,6 @@ VerticalContentAlignment="Stretch" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" - Command="{TemplateBinding Command}" - CommandParameter="{TemplateBinding CommandParameter}" - CommandTarget="{TemplateBinding CommandTarget}" FocusVisualStyle="{TemplateBinding FocusVisualStyle}" Foreground="{TemplateBinding Foreground}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" diff --git a/src/Mahapps.Metro.Tests/Tests/ButtonTest.cs b/src/Mahapps.Metro.Tests/Tests/ButtonTest.cs index d342afbc36..a4990e6d5d 100644 --- a/src/Mahapps.Metro.Tests/Tests/ButtonTest.cs +++ b/src/Mahapps.Metro.Tests/Tests/ButtonTest.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; using MahApps.Metro.Controls; using MahApps.Metro.Tests.TestHelpers; @@ -55,7 +56,7 @@ public async Task SquareButtonButtonTextIsLowerCase() [Fact] [DisplayTestMethodName] - public async Task SquareButtonBespectsButtonHelperContentCharacterCasing() + public async Task SquareButtonRespectsButtonHelperContentCharacterCasing() { await TestHost.SwitchToAppThread(); @@ -67,5 +68,35 @@ public async Task SquareButtonBespectsButtonHelperContentCharacterCasing() Assert.Equal("SomeText", presenter.Content); } + + [Fact] + [DisplayTestMethodName] + public async Task DropDownButtonShouldRespectParentIsEnabledProperty() + { + await TestHost.SwitchToAppThread(); + + var window = await WindowHelpers.CreateInvisibleWindowAsync(); + + window.TheStackPanel.SetCurrentValue(UIElement.IsEnabledProperty, false); + Assert.False(window.TheDropDownButton.IsEnabled); + + window.TheStackPanel.SetCurrentValue(UIElement.IsEnabledProperty, true); + Assert.True(window.TheDropDownButton.IsEnabled); + } + + [Fact] + [DisplayTestMethodName] + public async Task SplitButtonShouldRespectParentIsEnabledProperty() + { + await TestHost.SwitchToAppThread(); + + var window = await WindowHelpers.CreateInvisibleWindowAsync(); + + window.TheStackPanel.SetCurrentValue(UIElement.IsEnabledProperty, false); + Assert.False(window.TheSplitButton.IsEnabled); + + window.TheStackPanel.SetCurrentValue(UIElement.IsEnabledProperty, true); + Assert.True(window.TheSplitButton.IsEnabled); + } } } diff --git a/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml b/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml index da42d7f610..5948ac2989 100644 --- a/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml +++ b/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml @@ -7,10 +7,25 @@ d:DesignHeight="300" d:DesignWidth="300" mc:Ignorable="d"> - + + + + + + + +