I have a custom control (custom ComboBox). It`s work well, when I press "arrow button" it deployed, if I press it again it deployed too, but if it are deployed and I press anywhere in my form - it close but then, when I'm trying to open it - I must press "arrow button" two times. So I need to detect this moment, when I click outside of my combobox.
Code to open ComboBox(call in ButtonClick)
private void OpenComboBox()
{
if (drop_flag)
{
...
popup.Show(this);
}
else
{
drop_flag = true;
}
}
And Close event
private void popup_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
drop_flag = false;
}
So, I want something like this
private ClickedOutsideControl()
{
dropflag = true;
}
Thats not that easy.With basic .net you always need some kind of control / references to intercept clicks by binding click events. For Example if you have an MDI Parent (Mainwindow you can intercept its clicks).
Another way (using windows api) is to hook inso system events See the following stackoverflow post Global mouse event handler.
But you should think twice if you really need this.
In the form, you can pre-process messages sent to controls by using a message filter. Here is my FAILED attempt to implement the desired functionality:
public partial class frmAutoCloseDropDown : Form, IMessageFilter
{
int _lastMsg;
public frmAutoCloseDropDown()
{
InitializeComponent();
Application.AddMessageFilter(this);
}
// THIS ATTEMPT DOES NOT WORK!
public bool PreFilterMessage(ref Message m)
{
const int WM_LBUTTONDOWN = 0x0201;
if (m.Msg!= _lastMsg) {
_lastMsg = m.Msg;
}
if (m.Msg == WM_LBUTTONDOWN) {
// You would have to do this recursively if the combo-boxes were nested inside other controls.
foreach (ComboBox cbo in Controls.OfType<ComboBox>()) {
cbo.DroppedDown = false;
}
}
return false;
}
// Note: Dispose is created inside *.Designer.cs and you have to move it manually to *.cs
protected override void Dispose(bool disposing)
{
if (disposing) {
Application.RemoveMessageFilter(this);
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
}
Why does it not work? Probably because the new messages generated by cbo.DroppedDown = false that are sent to the form to close the drop-down are appended to the message queue and are only processed after the left mouse button click has been processed. This means that even when with PreFilterMessage our attempt to close the drop-down comes too late.
A possible solution would be to re-send WM_LBUTTONDOWN to the right ComboBox. You would have to interpret the parameters of the message to get the mouse coordinates and look to which combobox they are pointing to get its HWnd. My attempt to do this also shows that the behavior also depends on the drop-down style. It also has the undesired effect to close the drown-down you just opened. And what happens if you click in the drop-down itself to select an entry?
Probably you could do it, but the code tends to become very complicated. It's not worth the effort.
Related
Hey stackoverflow community. I have a question that I've been trying to figure out for several days now and I was hoping for some help and general advice.
I have been developing a small application. The main form has a few controls some of which open dialog boxes: an OpenFileDialog, SaveFileDialog, and then a custom dialog for a specific task. The problem arose in the custom dialog box. The basic functionality in a part of the custom dialog box was the ability to click a button that would allow the user to press a key, that key would then be "read" and input into a variable for later use.
My initial thought was to override WndProc and have a check for a bool in it, if the bool was true, the data from a WM_KEYDOWN message would be stored in a variable and the bool would be set to false. I quickly figured out that doesn't work since Dialog Boxes don't get messages like a normal form does.
My question is what is the best/recommended way to achieve this functionality? I've tried some ad hoc workarounds like just using a regular form and disabling the main form while the second form is active, but that didn't work either and I figured it would be better to ask for advice before I continued.
Any help is greatly appreciated.
Edit
Here is some code to demonstrate my current issue
This has quite a few lines removed since I don't think it would be helpful to post hundreds of lines in here
internal class GUI
{
// This class has all the code for the main form
// this is one of the Event Handlers that calls the second dialog box
private static void addItemAbove_Clicked(object sender, EventArgs e)
{
ActionItemDialog aid = new ActionItemDialog();
if(aid.ShowDialog() == DialogResult.OK)
{
ActionList.Items.Insert(list.SelectedIndex, aid.ActionItem);
}
}
}
internal class ActionItemDialog : Form
{
// This class is a custom dialog box for the user to input some data
private bool keyCaptureOn = false;
// This event handler is attached to a button on the dialog box
private void getKey_Clicked(object sender, EventArgs e)
{
getKey.Text = "Press Any Key";
keyCaptureOn = true;
}
// This is the way I was originally trying to get the keypress
protected override void WndProc(ref Message m)
{
if(m.Msg == 0x100)
{
if(keyCaptureOn)
{
// have never been able to make it in this.
}
}
base.WndProc(ref m);
}
}
Also it is worthy to note, I have already tried what all the other questions about this have recommended. I've tried setting KeyPreview to true. I've also made a regular form and used Form.Show() and had it emulate a Modal Dialog but I was still never able to get into that second if statement.
In your ActionItemDialog form, you can add a filter via IMessageFilter to trap keypresses.
When we want to trap a key, we call filter.trapKey = true;, which will fire off a custom Key() event when the user presses a key.
When the form is dismissed, we remove the filter and event subscription.
Here's an example:
public partial class ActionItemDialog : Form
{
private MyFilter filter;
public ActionItemDialog()
{
InitializeComponent();
this.FormClosing += ActionItemDialog_FormClosing;
filter = new MyFilter();
filter.Key += Filter_Key;
Application.AddMessageFilter(filter);
}
private class MyFilter : IMessageFilter
{
public bool trapKey = false;
public delegate void dlgKey(int key);
public event dlgKey Key;
public bool PreFilterMessage(ref Message m)
{
if (trapKey && m.Msg == 0x100)
{
trapKey = false;
Key?.Invoke((int)m.WParam);
return true;
}
return false;
}
}
private void getKey_Click(object sender, EventArgs e)
{
getKey.Text = "Press Any Key";
filter.trapKey = true;
}
private void Filter_Key(int key)
{
getKey.Text = key.ToString();
}
private void ActionItemDialog_FormClosing(object sender, FormClosingEventArgs e)
{
if (filter != null)
{
Application.RemoveMessageFilter(filter);
filter.Key -= Filter_Key;
filter = null;
}
}
}
Note that the filter will only trap key presses when YOUR app is in focus (any form of your app). So if the user clicked to trap a key press, but then switches to another window before pressing the key, then it will not be captured.
To capture a keystroke when ANY application is in focus, you'd have to wire up a low level keyboard hook via WH_KEYBOARD_LL. This would be overkill since I'd expect the key press to be captured only when that app is in focus. This could be an alternate approach, though, if you need to trap key presses globally, as registering a hotkey fails if another app already took the combo you want.
I am using Devexpress PopupMenu to show on right click. Now I want to know before closing of this popup menu, just like Windows ContextMenu Closing event.
PopupMenu has Closeup event, but that fires after closing of it. Actually my goal is to handle when to close the popup menu according to situations.
Is there anyway, I can achieve it?
I found this previous issue - somebody tried to do the same thing using XtraBars.PopupMenu and had to create a subclass of BarManager and override the BarSelectionInfo.ClosePopup event (maybe you can adapt it to your scenario). The example project is attached to the issue and demonstrates selecting a date in the popup menu and the menu staying open.
EDIT:
Here's the relevant code for completeness - whenever the popup is about to close, ClosePopup fires, as per docs for BarManager :
When you place a BarManager on a form at design time, all controls
publish the PopupContextMenu extender property (its caption in the
Properties window looks like 'PopupContextMenu on barManager1')
You can assign the Context menu using this property and implement the override.
In the example, you return from the method based on some condition (cancel the event) - in this case the Tag of the Bar is set to False on an event in the Form and checked in the override.
private void barEditItem1_EditValueChanged(object sender, EventArgs e) {
popupMenu1.Manager.Bars[0].Tag = false;
}
using DevExpress.XtraBars;
using DevExpress.XtraBars.ViewInfo;
public class MyBarManager : BarManager {
protected override BarSelectionInfo CreateSelectionInfo() {
return new MyBarSelectionInfo(this);
}
}
public class MyBarSelectionInfo : BarSelectionInfo {
public MyBarSelectionInfo(BarManager manager)
: base(manager) {
}
public override void ClosePopup(IPopup popup) {
if (!(bool)Manager.Bars[0].Tag) {
Manager.Bars[0].Tag = true;
return;
}
base.ClosePopup(popup);
}
}
When coding a small game, I encountered a problem; my form's KeyDown and KeyUp events don't fire at all.
This is the form's code:
public class GameForm : Form
{
private ControllableBlock player;
public GameForm()
{
KeyDown += Game_KeyDown;
KeyUp += Game_KeyUp;
player = new ControllableBlock();
Controls.Add(player);
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
private void Game_KeyUp(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
}
There's a lot more going on, but I only pasted the relevant code.
I've already tried setting this.KeyPreview = true; and calling this.Focus();, neither works.
The problem is not in ReactToKey() method, I've already set a breakpoint there and the event is never fired.
Edit: After some tests I've come to a conclusion that the problem is within my ControllableBlock.
Yet, I have no idea why, but I'm working on it.
If I comment out everything that's related to the player, the events start firing.
Edit 2: Seems like the problem is me inheriting my ControllableBlock from Control.
If I inherit it from Panel, it works fine.
Why is this?
Can't I fire an event if I inherit from control?
The ControllableBlock class is empty for now, so it doesn't even do anything other than inherits from Control.
Edit 3: Now that I've started a bounty, I'd like to clarify that I'm not looking for a solution on how to make the events fire, I'm looking for a reason on why they don't fire if I inherit from Control.
If your events should be application-wide try to set property KeyPreview to true - it will allow you to fire respective events regardless of focused control.
this.KeyPreview = true;
Otherwise you should attach these events directly to control that will process them.
Edit:
I removed InitializeComponent(); from my form and got behaviour identical to yours.
After implementing solution provided in this question all events started to qork perfectly.
Copy code snippet here:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == Keys.Left) {
// Do your staff for Left Key here
return true;
}
// you need to add if condition to every direction you want to handle
return base.ProcessCmdKey(ref msg, keyData);
}
I was able to reproduce a similar issue (which is hopefully related..)
Explanation:
Controls which return CanSelect==true are selectable for keyboard input
A blank descendent of Control() is selectable, one of Panel() is not
The first selectable control added to a form will get selected
A selected control will steal keyboard events from its parents by default
Certain keys used for navigation within a window require extra steps to be handleable
Check here for a good overview of how windows keyboard input works.
Code to reproduce it:
public class GameForm : Form
{
public GameForm()
{
this.KeyDown += Game_KeyDown;
var tests = new List<Control[]>() {
new[] { new Panel() },
new[] { new Panel(), new Panel() },
new[] { new Control() },
new[] { new Control(), new Panel() },
new[] { new Panel(), new Control() }
};
// When test index 2 to 4 used, keyboard input does not reach form level
Controls.AddRange(tests[0]);
// When uncommented, ensures all keyboard input reaches form level
/*this.KeyPreview = true;
// Additional plumbing required along with KeyPreview to allow arrow and other reserved keys
foreach (Control control in this.Controls)
{
control.PreviewKeyDown += control_PreviewKeyDown;
}*/
}
void control_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
e.IsInputKey = true;
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
// breakpoint here
Debug.WriteLine(e.KeyCode);
}
}
You need to make your control selectable before it can receive the focus.
Try adding the following to your constructor:
this.SetStyle(ControlStyles.Selectable, true);
And ensure that you give your form focus after it has been displayed. Or, override OnMouseDown() and call this.Focus() in it.
try moving the handler setup to the Form_Load event rather than the constructor. Should there not be a call to Initialize() in the constructor? I wouldn't particularly recommend removing it
If ControllableBlock inherits from Panel, it will have more event hookups and better UI interaction setup than a base Control object.
I'm working with a windows form app which I have treeview to show the list of folders , and I have attached NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) event.
and on click of node I call server method to populate the treeview.
Here I could see that NodeMouseClick for one of my tree node is not at all getting triggered.
however for rest of the nodes its working fine with no issues. can anyone tell me what is the exact reason that its not getting triggered.
and I dont want to use After_Select event.
public Form1()
{
InitializeComponent();
Init();
}
private void Init()
{
treeView1.Nodes.Add("root");
for (int i = 0; i < 23; i++)
{
treeView1.Nodes[0].Nodes.Add(i.ToString());
treeView1.Nodes[0].Nodes[i].Nodes.Add("child" + i.ToString());
}
treeView1.Nodes[0].Expand();
}
use treeview of size = 280,369
As I mentioned before in the comments, the workaround is to drop down to the level of the Windows API, intercept mouse messages, and raise the node click event yourself. The code is ugly, but functional.
Add the following code to a new class in your project (I called it CustomTreeView):
class CustomTreeView : System.Windows.Forms.TreeView
{
public event EventHandler<TreeNodeMouseClickEventArgs> CustomNodeClick;
private const int WM_LBUTTONDOWN = 0x201;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_LBUTTONDOWN) // left mouse button down
{
// get the current position of the mouse pointer
Point mousePos = Control.MousePosition;
// get the node the user clicked on
TreeNode testNode = GetNodeAt(PointToClient(mousePos));
// see if the clicked area contained an actual node
if (testNode != null)
{
// A node was clicked, so raise our custom event
var e = new TreeNodeMouseClickEventArgs(testNode,
MouseButtons.Left, 1, mousePos.X, mousePos.Y);
if (CustomNodeClick != null)
CustomNodeClick(this, e);
}
}
// call through to let the base class process the message
base.WndProc(ref m);
}
}
Then change all references to the System.Windows.Forms.TreeView control in your code to the new CustomTreeView class that you just created. This is a subclass of the existing TreeView control that you want to use instead. In case you're not familiar with subclassing, this is the way we modify the existing functionality, or bolt on new functionality to, an existing control. In this case, we've subclassed the original TreeView control to add the CustomNodeClick event that we'll be raising ourselves whenever we detect that a node has been clicked by the user.
Finally, change the event handler method in your form class to listen for the CustomNodeClick event that we're raising, rather than the buggered NodeMouseClick event you were trying to use before.
Compile and run. Everything should work as expected.
try to use AfterSelect Event it must be triggered after any node selection .
I am writing an IM program, and I have the method to make a form flash and stop flashing... question is, how do I implement it?
When a message arrives, I can set the window flashing, but I need to make sure it doesn't have focus. Checking the focued method always seems to return false and so it flashes even when the form is open.
Also, which event to I need to handle to stop it flashing? When the user clicks the form to make it maximise, or switches focus to the form, I need a way of stopping it.
What's the best way?
You can handle the Activated and Deactivate events of your Form, and use them to change a Form-level boolean that will tell your code whether your form has the focus or not, like this:
private bool _IsActivated = false;
private void Form1_Activated(object sender, EventArgs e)
{
_IsActivated = true;
// turn off flashing, if necessary
}
private void Form1_Deactivate(object sender, EventArgs e)
{
_IsActivated = false;
}
When a message arrives, you check _IsActivated to determine if your Form is already the active window, and turn on flashing if it isn't. In the Activated event, you would turn off the flashing if it's on.
The Focused property of your form will always return false if it has any controls on it. This property refers to whether the control in question (the form, in this case) has the focus within your application's form, not whether the application itself has the focus within Windows.
Checking if the form is minimized or not:
if (this.WindowState == FormWindowState.Minimized)
{
MakeFormFlash();
}
else
{
MakeFormStopFlash();
}
Event to trigger when the form is activated by user or code:
this.Activated += new EventHandler(Form_Activated);
Well Focused should be the property to check, so you need to try and work out why that is always returning false.
As for what event to listen to, probably the GotFocus event, though that may not work until you can work out what is wrong with the Focused property.
There are a number of ways you can handle this. Probably the easiest would be to have a flag that you set whenever the form is flashing so this can be reset on re-activation of the form e.g.
Code for base IM window form
private bool IsFlashing;
....
// Code for IM windows
public void OnActivate(EventArgs e)
{
if (IsFlashing)
{
// stop flash
IsFlashing = false;
}
}
public void Flash()
{
// make flash
IsFlashing = true;
}
Then wherever you do your code to handle the new message you would just need to check that the particular conversation window (if you handle multiple ones) that the message is directed at is the current active one:
public void OnNewMessage(AMessage msg)
{
Form convoWindow = FindConvoWindow(msg.Sender);
if (Form.ActiveForm == convoWindow)
{
// update the conversation text
}
else
{
convoWindow.Flash();
}
}