I am trying to get a popup to show when the mouse enter a button.
It should then hide if the user leaves the popup.
But for a good user experience, the popup should also stay open if the user goes back on the button.
So when the mouse gets out of the popup, the mouse leave event is triggered, but I don't want to handle it if the mouse entered the button at the same moment.
Is there any way of knowing if the mouse is over another control in the mouse leave event?
I have tried many different approach without success. Tell me if you have a different approach to the problem too!
Set a timer when the MouseLeave event on your popup is fired, along with a boolean, perhaps called closingPopup, to true. The timer will set closingPopup to false and hide the popup. On the MouseEnter event of the button, if closingPopup is true, then stop the timer and set the boolean to false.
That or a variant of that should get you what you want. You can give it a 1/4 second delay that way. Or any amount that seems responsive.
Edit: You should probably also add the same check to MouseLeavefor the button. (Glad I could help!)
This can be achieved by starting a timer when the mouse leaves the button, when the timer ticks, close your popup. If the mouse re-enters the button, stop the timer.
private DispatcherTimer timer = new DispatcherTimer();
public MainPage()
{
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += Timer_Tick;
}
private void Timer_Tick(object sender, EventArgs e)
{
MyPopUp.IsOpen = false;
timer.Stop();
}
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
timer.Stop();
MyPopUp.IsOpen = true;
}
private void Button_MouseLeave(object sender, MouseEventArgs e)
{
timer.Start();
}
With the following markup:
<Button MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave">
<Popup x:Name="MyPopUp">
<Grid Width="100" Height="20" Background="Red">
<TextBlock Text="Hi"/>
</Grid>
</Popup>
</Button>
Related
Using WPF: A Simple Color Picker With Preview, Sacha Barber, 18 Apr 2012 ,
I created a custom control from it:
public class ColorCustomControl : Control
{....}
It is then used as:
<Menu....>
<MenuItem.....>
<pn:ColorCustomControl/>
</MenuItem>
</Menu>
This yields the following picture when the Brushes MenuItem is selected:
Selection of any item in the opened Brushes submenu results in the appropriate action being taken with the Brushes submenu REMAINING OPEN. This is the effect I want.
However, as shown below, selection of any of the three swatches results in a quick flicker of the new swatch -- it replaces the color pattern to the left of "Preview"--followed immediately by closure of the Brushes submenu.
If the Brushes menuitem is again chosen, the most recently selected swatch correctly appears.
I have tried all preview events (i.e., keyboard lost focus, left mouse down, etc.), to try stopping closure of the submenu when a swatch is chosen. Nothing I have found will stop the popup from closing.
How can closure of the Brushes submenu be prevented when selecting a swatch from the visual?
(I strongly suspect that redrawing of the visual, as in InvalidateVisual() when a new swatch image is selected, is forcing closure of the submenu).
Any ideas anybody?
TIA
My suggestion is to stop events propagation from your user control. So in your ColorCustomControl class first of all add a property (it can be a dependency one too if you need):
private bool propagateEvents = true;
public bool PropagateEvents
{
get
{
return propagateEvents;
}
set
{
propagateEvents = value;
}
}
Then add e.Handled = !PropagateEvents; at the end of every mouse event handler; in the end add a Swatch_MouseLeftButtonUp method (it has to handle the event raised by ImgSqaure1, ImgSqaure2 and ImgCircle1).
The result will be:
private void Swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image img = (sender as Image);
ColorImage.Source = img.Source;
e.Handled = !PropagateEvents;
}
private void Swatch_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
e.Handled = !PropagateEvents;
}
private void CanvImage_MouseDown(object sender, MouseButtonEventArgs e)
{
IsMouseDown = true;
e.Handled = !PropagateEvents;
}
private void CanvImage_MouseUp(object sender, MouseButtonEventArgs e)
{
IsMouseDown = false;
e.Handled = !PropagateEvents;
}
and in the user control XAML:
<Image x:Name="ImgSqaure1"
Height="20" Width="20"
Source="Images/ColorSwatchSquare1.png"
Margin="45,0,0,0"
ToolTip="Square swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp"/>
<Image x:Name="ImgSqaure2"
Height="20" Width="20"
Source="Images/ColorSwatchSquare2.png" Margin="5,0,0,0"
ToolTip="Square swatch2"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp"/>
<Image x:Name="ImgCircle1" Height="20" Width="20"
Source="Images/ColorSwatchCircle.png" Margin="5,0,0,0"
ToolTip="Circle swatch1"
MouseLeftButtonDown="Swatch_MouseLeftButtonDown"
MouseLeftButtonUp="Swatch_MouseLeftButtonUp" />
Now all you have to do is set the PropagateEvents property in your menu:
<Menu....>
<MenuItem.....>
<pn:ColorCustomControl PropagateEvents="False" />
</MenuItem>
</Menu>
I hope it can help you.
I have a MetroWindow. It has a Flyout. The Flyout has a button. When the Flyout button is pressed, a new MetroWindow is shown and the flyout is dismissed.
What occurs is:
User pressed button on Flyout.
The button-press opens a new non-modal form and sets the Flyout's IsOpen to false.
The non-modal form opens on top of everything.
The flyout closes.
The main form gets the focus after the flyout closes - hiding the non-modal form.
I would like the focus-change to the main form to not occur when the flyout closes.
I have a method that works, but it's a bit laggy because it has to wait for the flyout animation to finish.
Is there a better way? I do not want to make the new window to be modal or AlwaysOnTop.
private void SearchResultClose(object sender, RoutedEventArgs e)
{
m_EvtResultClosed.Set();
}
private void SearchResultOpenChange(object sender, RoutedEventArgs e)
{
if (foSearchResult.IsOpen)
{
m_EvtResultClosed.Reset();
}
}
and
<controls:Flyout Position="Top" Header="" x:Name="foSearchResult"
Height="275" ClosingFinished="SearchResultClose" IsOpenChanged="SearchResultOpenChange">
and
private void OpenPersonCard(object sender, RoutedEventArgs e)
{
var selected_person = SearchPersonResultsVM.View.CurrentItem as Editable<Person>;
if (selected_person != null)
{
var card = new PersonFileWindow();
card.Person = selected_person;
foSearchResult.IsOpen = false;
// Wait for it to close, and then show the form.
Dispatcher.BeginInvoke(new Action(async () =>
{
await Task.Run(() =>
{
m_EvtResultClosed.WaitOne();
});
card.Show();
}), null);
}
}
where
private AutoResetEvent m_EvtResultClosed = new AutoResetEvent(false);
See the function IsOpenedChanged in MahApps' Flyout.cs and we see it calls Focus() on close whether its animated or not. It is also comment :
// focus the Flyout itself to avoid nasty FocusVisual painting (it's visible until the Flyout is closed)
But I do not know what it is trying to resolve exactly.
https://github.com/MahApps/MahApps.Metro/blob/1.2.4/MahApps.Metro/Controls/Flyout.cs
The simplest hack is adding Focusable="False" to your flyout as:
<controls:Flyout Focusable="False" Position="Top" Header="" x:Name="foSearchResult"Height="275" ClosingFinished="SearchResultClose" IsOpenChanged="SearchResultOpenChange">
It works fine here and I can't tell any nasty FocusVisual painting as commented in the source.
By default, when opened, the Flyout steals focus for itself, or if Focusable==false, for some control within it, even the close button. In any case, focus is not restored to the previous element.
So, just setting Focusable="False" probably won't solve the problem.
Instead, Flyout provides the AllowFocusElement property to suppress this behavior:
<controls:Flyout AllowFocusElement="False" ... >
I am reading one book which says
Rather than setting the DialogResult
by hand after the user clicks a
button, you can designate a button as
the accept button (by setting
IsDefault to true). Clicking that
button automatically sets the
DialogResult of the window to true.
Similarly, you can designate a button
as the cancel button (by setting
IsCancel to true), in which case
clicking it will set the DialogResult
to Cancel.
This is the MainWindow:
<Window x:Class="WpfApplicationWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="400" Height="400">
<StackPanel>
<Button Name="BtnShowDialogStatus" Click="BtnShowDialogStatus_Click">DIALOG RESULT</Button>
</StackPanel>
</Window>
Code for click event:
private void BtnShowDialogStatus_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(new NewWindow().ShowDialog().ToString());
}
And this is the Dialog box which I am opening on the click event:
<Window x:Class="WpfApplicationWPF.NewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NewWindow" Height="300" Width="300">
<StackPanel>
<Button Name="BtnDEfault" IsDefault="True" Click="BtnDEfault_Click">DEFAULT BUTTON</Button>
<Button Name="BtnCancel" IsCancel="True" Click="BtnCancel_Click">CANCEL BUTTON</Button>
</StackPanel>
</Window>
This is the code for it:
private void BtnDEfault_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
I can see it returning the DialogResult only as false no matter I click the default or cancel button.
IsDefault ties the button to the Enter key, so that pressing the Enter key will fire the Click event. It does not mean that the Yes button will return true for the DialogResult.
Refer to the links.It will clear up things for you
http://blog.wpfwonderland.com/2010/03/22/getting-a-dialogresult-from-a-wpf-window/
http://www.wpftutorial.net/Dialogs.html
Hope it helps...
change your code to
private void BtnDEfault_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
this.Close();
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
this.Close();
}
hope this helps
To my understanding setting IsDefault as true and IsCancel as false only enables you to assign what event should occur i.e. the window will fire close event on 'Escape' key for IsCancel and for Enter key for IsDefault=true.
You will need to set the Dialog result from your button click / command action handlers.
Using net 5 this seems to be all the code needed to open a ShowDialog window, and close it.
From the window you have opened.
<Button Margin="10" IsDefault="True" Click="Ok_OnClick" >OK</Button>
<Button Margin="10" IsCancel="True">Cancel</Button>
private void Ok_OnClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
From function to open window.
var requester = new DeleteRequester();// a wpf window
var showDialog = requester.ShowDialog( );
if (showDialog != null && showDialog.Value)
the only reason it is checked for null is to get rid of the blue nag line from re-sharper.
It seems whenever you change the "DialogResult" The window is going to close and the value gets returned. Kind of make sense, why would you change the value if you weren't done.
With what ever your doing with the window, you simply need to close the window to return a false result, or set the DialogResult to true to close the window with a true result.
Simple and basic:
If(ItWorked){DialogResult = true;}// closes window returns true
If(ItsJunk){Close();}// closes window returns false
If(ItsJunk){DialogResult = false;}//closes window returns false
I have a list of toggle-buttons in wpf and I want the user to be able to toggle several buttons by dragging over them. To do this, I used the MouseEnter-Event for each button. This does work, when I press the mousebutton outside the buttons and start dragging. But when I press the mousebutton on a button and start dragging, the MouseEnter-Event is only fired for the first button, where I pressed the mousebutton (also none of the other events like mouseover or mousemove are fired).
Here's the code:
public void AddButton()
{
ToggleButton btn = new ToggleButton();
btn.MouseEnter += VisibilityButton_Enter;
this.gridButtons.Children.Add(btn);
}
private void VisibilityButton_Enter(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed || e.RightButton == MouseButtonState.Pressed)
{
ToggleButton btn = sender as ToggleButton;
btn.IsChecked = !btn.IsChecked;
}
}
I found a solution to use "drag and drop" and the dragover-event, but I think there must be an easier solution?
As Kent mentioned, the ToggleButton captures the mouse. If we handle the PreviewMouseDown event ourselves we can prevent that. The rest is just keeping track of the mouse state so the we don't click twice during a single roll-over. Here is a behavior you can add to your button to enable roll-over clicking.
First add this namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and the corresponding reference to your project.
Then the XAML looks like this (notice the RollOverBehavior):
<Grid>
<ItemsControl>
<ItemsControl.ItemsSource>
<PointCollection>
<Point/>
<Point/>
<Point/>
<Point/>
<Point/>
</PointCollection>
</ItemsControl.ItemsSource>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="25" Height="25">
<i:Interaction.Behaviors>
<local:RollOverBehavior/>
</i:Interaction.Behaviors>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and here is the behavior itself:
public class RollOverBehavior : Behavior<ToggleButton>
{
bool mouseOver;
bool clicked;
protected override void OnAttached()
{
AssociatedObject.PreviewMouseLeftButtonDown += (s, e) =>
{
AssociatedObject.IsChecked = !AssociatedObject.IsChecked;
e.Handled = true;
};
AssociatedObject.MouseEnter += (s, e) =>
{
mouseOver = true;
clicked = false;
};
AssociatedObject.MouseLeave += (s, e) =>
{
mouseOver = false;
};
AssociatedObject.MouseMove += (s, e) =>
{
if (mouseOver && !clicked && e.LeftButton == MouseButtonState.Pressed)
{
AssociatedObject.IsChecked = !AssociatedObject.IsChecked;
clicked = true;
}
};
}
}
The problem is that the default behavior of the ToggleButton is to capture the mouse when the left mouse button is clicked. Because the mouse is captured, all mouse events are sent to the first ToggleButton.
Sounds like what you want to do is override this default behavior such that the mouse isn't captured, but to be honest I couldn't really follow exactly what it is you're trying to achieve.
I had the same problem with normal Buttons. The solution, that worked for me, is to set e.Handled = true in the PreviewMouseButtonDown event (I implemented this too). It seems, that just by down-click with the mouse, the previous action is not fully handled until the mouse button is released, so the MouseEnter event is not able to raise.
I'm implementing drag & drop from a ListBox, but I'm seeing some strange behaviour with a ContextMenu elsewhere in the window. If you open the context menu and then start a drag from the ListBox, the context menu closes but won't open again until after you perform another drag.
Does this make sense? Anybody got any ideas what might be going on?
<ListBox Grid.Row="0" ItemsSource="{Binding SourceItems}" MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}" SelectionMode="Multiple" PreviewMouseLeftButtonDown="HandleLeftButtonDown" PreviewMouseLeftButtonUp="HandleLeftButtonUp" PreviewMouseMove="HandleMouseMove"/>
<ListBox Grid.Row="1" ItemsSource="{Binding DestinationItems}" AllowDrop="True" Drop="DropOnToDestination" />
<Button Grid.Row="2">
<Button.ContextMenu>
<ContextMenu x:Name="theContextMenu">
<MenuItem Header="context 1"/>
<MenuItem Header="context 2"/>
<MenuItem Header="context 3"/>
</ContextMenu>
</Button.ContextMenu>
Button with context menu
</Button>
...
public partial class Window1
{
private bool clickedOnSourceItem;
public Window1()
{
InitializeComponent();
DataContext = new WindowViewModel();
}
private void DropOnToDestination(object sender, DragEventArgs e)
{
var viewModel = (WindowViewModel)e.Data.GetData(typeof(WindowViewModel));
viewModel.CopySelectedItems();
}
private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var sourceElement = (FrameworkElement)sender;
var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement)) as FrameworkElement;
if(hitItem != null)
{
clickedOnSourceItem = true;
}
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
clickedOnSourceItem = false;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
if(clickedOnSourceItem)
{
var sourceItems = (FrameworkElement)sender;
var viewModel = (WindowViewModel)DataContext;
DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
clickedOnSourceItem = false;
}
}
}
It seemed to be something to do with the mouse capture!?
The normal sequence of events during a drag goes something like this...
The PreviewMouseLeftButtonDown
handler gets called and
ListBox.IsMouseCaptureWithin is
false.
The PreviewMouseMove handler
gets called. By this time
ListBox.IsMouseCaptureWithin is true.
During the PreviewMouseMove handler
DragDrop.DoDragDrop gets called and
sometime during this the mouse
capture is released from the ListBox.
But, what seems to happening for a drag started when the context menu is open is...
The PreviewMouseLeftButtonDown
handler gets called and
ListBox.IsMouseCaptureWithin is
false.
The PreviewMouseMove handler gets
called. But this time
ListBox.IsMouseCaptureWithin is
still false.
Sometime after the end of the
PreviewMouseMove handler the
ListBox then gets the mouse capture
(ListBox.IsMouseCaptureWithin
becomes true)
The result of this is that after the drag, the ListBox still has the mouse capture so any clicks on the button to open the context menu are actually going to the listbox not the button.
Adding the following code to the start of the PreviewMouseLeftButtonDown handler seems to help by swallowing up the click that closes that context menu rather than trying to start a drag from it...
if (!contextMenuCloseComplete)
{
sourceElement.CaptureMouse();
return;
}
...with the contextMenuCloseComplete bool getting set in handlers for the context menu's Closed and Opened events.
Does that make sense? Does anyone understand where this mouse capture behaviour is coming from?