Отключите кнопку 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
}
}