Отключите кнопку WPF, но все еще проглатывайте события кликов

У меня есть кнопка в приложении, которая привязана к команде. Эта кнопка находится внутри другого элемента управления, который также реагирует на mouseclicks. Когда кнопка включена, я получаю такое поведение, которое я ожидаю, - нажмите кнопку, и команда запускается, нажмите кнопку за пределами кнопки, но внутри элемента управления контейнера и запускается вместо этого.

К сожалению, когда кнопка отключена (через метод CanExecute команды), клики на кнопке активируются до элемента управления контейнера. Я не хочу этого, я хочу, чтобы клики были проглочены - ни запускать команду, ни пузыриться.

Я попытался преодолеть это, создав новый класс, наследующий от Button, но ни один из следующих методов даже не кажется вызываемым на отключенной кнопке:

  • OnPreviewMouseDown
  • OnPreviewMouseUp
  • OnPreviewMouseLeftButtonDown
  • OnPreviewMouseLeftButtonUp
  • OnMouseDown
  • OnMouseUp
  • OnMouseLeftButtonDown
  • OnMouseLeftButtonUp
  • OnClick

Является ли отключенное управление полностью проигнорировано системой маршрутизации WPF? И если это так, я могу получить поведение, которое я ищу?

Ответ 1

RCGoforth ответ получил мне 90% пути, но решение не должно помещать прямоугольник за кнопку, потому что пузырящее событие поднимается вверх по дереву, а не к братьям и сестрам. В конце я окружил кнопку ContentControl (поскольку прямоугольник не может иметь детей), который проглотил это событие, прежде чем он смог бы продвинуться дальше:

<ContentControl MouseDown="ContentControl_MouseDown">
    <Button Content="Click Test"
            Padding="2"
            Command="{Binding TestCommand}"/>
</ContentControl>

В коде позади:

private void ContentControl_MouseDown(object sender, MouseButtonEventArgs e)
{
    e.Handled = true;
}

Или сделать это целиком в XAML (и увеличить уровень взлома кода...)

<Button>
    <Button.Template>
        <ControlTemplate>
            <Button Content="Click Test"
                    Command="{Binding TestCommand}"/>
        </ControlTemplate>
    </Button.Template>
</Button>

Ответ 2

Это не очень чисто, но вы можете поместить прозрачный прямоугольник за своей кнопкой, который проглатывает события щелчка.

Ответ 3

Что вы хотите сделать, это зарегистрировать обработчик маршрутизируемого события, используя перегрузку, которая принимает параметр handledEventsToo и задает значение true для этого параметра. Таким образом, ваш внешний обработчик получит событие независимо от того, действительно ли кнопка обрабатывает событие. Это будет выглядеть примерно так:

this.AddHandler(Mouse.MouseUpEvent, this.MyMouseUpHandler, true);

И затем в вашем обработчике вы всегда можете проверить, что было нажато, было ли оно обработано и т.д. через MouseButtonEventArgs вы врученный. Например, чтобы проверить, действительно ли другой элемент управления обработал событие:

if(!args.Handled)
{
     // handle it here instead
}

Ответ 4

Более чистое, более многоразовое решение будет реализовывать эту функциональность как присоединенное свойство.

Использование шаблона службы/действия:

namespace Control.Services
{

  public class UIElementService
  {
    public static readonly DependencyProperty HandleMouseEventsProperty = DependencyProperty.RegisterAttached("HandleMouseEvents",
      typeof(bool), typeof(UIElementService), new FrameworkPropertyMetadata(false, UIElementService.HandleMouseEventsPropertyChanged));

    static void HandleMouseEventsPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
      FrameworkElement element = sender as FrameworkElement;
      if (element == null)
        return;

      new HandleMouseEventsAction(element);
    }

    public static bool GetHandleMouseEvents(FrameworkElement target)
    {
      return (bool)target.GetValue(HandleMouseEventsProperty);
    }

    public static void SetHandleMouseEvents(FrameworkElement target, bool value)
    {
      target.SetValue(HandleMouseEventsProperty, value);
    }

    class HandleMouseEventsAction
    {
      UIElement m_Target;
      MouseButtonEventHandler m_Handler;

      internal HandleMouseEventsAction(FrameworkElement source)
      {
        m_Source = source;
        m_Handler = new MouseButtonEventHandler(PreviewMouseLeftButtonUp);

        m_Source.Loaded += OnSource_Loaded;
        m_Source.Unloaded += OnSource_Unloaded;
      }

      void OnSource_Loaded(object sender, RoutedEventArgs e)
      {
        m_Source.AddHandler(Mouse.PreviewMouseUpEvent, m_Handler, true);
      }

      void OnSource_Unloaded(object sender, RoutedEventArgs e)
      {
        m_Source.RemoveHandler(Mouse.PreviewMouseUpEvent, m_Handler);
      }

      void PreviewMouseLeftUIElementUp(object sender, MouseUIElementEventArgs e)
      {
        e.Handled = true;
      }

    }

  }

}

Затем для использования импортируйте пространство имен.

<Button sv:UIElementService.HandleMouseEvents="True" />

или

<ContentControl sv:UIElementService.HandleMouseEvents="True">
    <Button Content="Click Test" Padding="2" Command="{Binding TestCommand}"/>
</ContentControl>

Я не тестировал это (сейчас нет времени). Я считаю, что действие будет по-прежнему получать события мыши, даже если они отключены.

НТН,

Dennis

Ответ 5

Я создал поведение (которое фактически работает), которое вставляет ContentPresenter между Button и его родителем. ContentPresenter проглатывает щелчки мыши, когда Button отключен. Поведение использует пару методов расширения на основе кода этого ответа. Использование его очень просто:

<Button ...>
    <i:Interaction.Behaviors>
        <behaviors:SwallowMouseClicksWhenDisabled />
    </i:Interaction.Behaviors>
</Button>

И вот источник:

// the behavior (could also be an attached behavior)
public class SwallowMouseClicksWhenDisabled : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        var oldParent = AssociatedObject.Parent;

        oldParent.RemoveChild(AssociatedObject);

        var newParent = new ContentPresenter { Content = AssociatedObject };

        oldParent.AddChild(newParent);

        newParent.PreviewMouseDown += OnPreviewMouseEvent;

        newParent.PreviewMouseUp += OnPreviewMouseEvent;
    }

    private void OnPreviewMouseEvent(object sender, MouseButtonEventArgs e)
    {
        e.Handled = AssociatedObject.IsEnabled == false;
    }
}

// the extension methods
public static class DependencyObjectExtensions
{
    public static void AddChild(this DependencyObject parent, UIElement child)
    {
        var panel = parent as Panel;
        if (panel != null)
        {
            panel.Children.Add(child);
            return;
        }

        var decorator = parent as Decorator;
        if (decorator != null)
        {
            decorator.Child = child;
            return;
        }

        var contentPresenter = parent as ContentPresenter;
        if (contentPresenter != null)
        {
            contentPresenter.Content = child;
            return;
        }

        var contentControl = parent as ContentControl;
        if (contentControl != null)
        {
            contentControl.Content = child;
            return;
        }

        // maybe more
    }

    public static void RemoveChild(this DependencyObject parent, UIElement child)
    {
        var panel = parent as Panel;
        if (panel != null)
        {
            panel.Children.Remove(child);
            return;
        }

        var decorator = parent as Decorator;
        if (decorator != null)
        {
            if (Equals(decorator.Child, child))
            {
                decorator.Child = null;
            }
            return;
        }

        var contentPresenter = parent as ContentPresenter;
        if (contentPresenter != null)
        {
            if (Equals(contentPresenter.Content, child))
            {
                contentPresenter.Content = null;
            }
            return;
        }

        var contentControl = parent as ContentControl;
        if (contentControl != null)
        {
            if (Equals(contentControl.Content, child))
            {
                contentControl.Content = null;
            }
            return;
        }

        // maybe more
    }
}