What is the best way to simulate a Click with MouseUp & MouseDown events or otherwise?
I would use a Button
control and overwrite the Button.Template
to just show the content directly.
<ControlTemplate x:Key="ContentOnlyTemplate" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
<Button Template="{StaticResource ContentOnlyTemplate}">
<Label Content="Test"/>
</Button>
Here is a behavior you can add to any element so that it will raise the ButtonBase.Click
event using the normal button logic:
public class ClickBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
e.Handled = true;
AssociatedObject.CaptureMouse();
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
if (!AssociatedObject.IsMouseCaptured) return;
e.Handled = true;
AssociatedObject.ReleaseMouseCapture();
if (AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)) != null)
AssociatedObject.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
};
}
}
Notice the use of mouse capture/release and the input hit test check. With this behavior in place, we can write click handlers like this:
<Grid>
<Rectangle Width="100" Height="100" Fill="LightGreen" ButtonBase.Click="Rectangle_Click">
<i:Interaction.Behaviors>
<utils:ClickBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
</Grid>
and the code behind:
private void Rectangle_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Code-behind: Click");
}
It's easy enough to convert this to all code-behind; the important part is the capture and click logic.
If you are not familiar with behaviors, install the Expression Blend 4 SDK and add this namespace:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
and add System.Windows.Interactivity
to your project.
Edit:
Here's how to attach the click behavior to an element in code-behind and add a handler for the click event:
void AttachClickBehaviors()
{
AttachClickBehavior(rectangle1, new RoutedEventHandler(OnAttachedClick));
}
void OnAttachedClick(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Attached: Click");
}
// Reusable: doesn't need to be in the code-behind.
static void AttachClickBehavior(FrameworkElement element, RoutedEventHandler clickHandler)
{
Interaction.GetBehaviors(element).Add(new ClickBehavior());
element.AddHandler(ButtonBase.ClickEvent, clickHandler);
}
Ok, just thought i'd play around with attached events and see where i'll get with it, the following seems to work in most cases, could use some refinement/debugging:
public static class Extensions
{
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
"Click",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(UIElement)
);
public static void AddClickHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement element = d as UIElement;
if (element != null)
{
element.MouseLeftButtonDown += new MouseButtonEventHandler(element_MouseLeftButtonDown);
element.MouseLeftButtonUp += new MouseButtonEventHandler(element_MouseLeftButtonUp);
element.AddHandler(Extensions.ClickEvent, handler);
}
}
public static void RemoveClickHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement element = d as UIElement;
if (element != null)
{
element.MouseLeftButtonDown -= new MouseButtonEventHandler(element_MouseLeftButtonDown);
element.MouseLeftButtonUp -= new MouseButtonEventHandler(element_MouseLeftButtonUp);
element.RemoveHandler(Extensions.ClickEvent, handler);
}
}
static void element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UIElement uie = sender as UIElement;
if (uie != null)
{
uie.CaptureMouse();
}
}
static void element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
UIElement uie = sender as UIElement;
if (uie != null && uie.IsMouseCaptured)
{
uie.ReleaseMouseCapture();
UIElement element = e.OriginalSource as UIElement;
if (element != null && element.InputHitTest(e.GetPosition(element)) != null) element.RaiseEvent(new RoutedEventArgs(Extensions.ClickEvent));
}
}
}
Usage:
<TextBlock local:Extensions.Click="TextBlock_Click" Text="Test"/>
Edit: Changed it to use mouse-capture instead of storing sender/source which did not get cleared out sufficiently. Remaining problem is that you cannot add the event in code using UIElement.AddHandler(Extensions.ClickEvent, handler)
because that omits the attachments of the other mouse events which are needed for raising the click event (you need to use Extensions.AddClickHandler(object, handler)
instead).
Here is a simple solution that I use on a fairly regular basis. It is easy, and works in all sorts of scenarios.
In your code, place this.
private object DownOn = null;
private void LeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (DownOn == sender)
{
MessageBox.Show((sender as FrameworkElement).Name);
}
DownOn = null;
}
private void LeftButtonDown(object sender, MouseButtonEventArgs e)
{
DownOn = sender;
}
Now all you have to do is setup handlers for all of the controls that you care about i.e.
<Border Background="Red" MouseLeftButtonUp="LeftButtonUp" MouseLeftButtonDown="LeftButtonDown".../>
<Border Background="Blue" MouseLeftButtonUp="LeftButtonUp" MouseLeftButtonDown="LeftButtonDown".../>
If you have a ton of controls, and don't want to do it all in XAML, you can do it in your controls / window constructor programatically.
This will cover most of your scenarios, and it can easily be tweaked to handle special cases.