Quite a simple question, when the selected index of a list view is changed, the event fires twice, once for deselection and a second time to select the next item.
I need to use the event when selecting or deselecting at different times however whan deselecting only to reselect a moment later it makes half my ui flash from enabled being on to off and back on again, it also causes a fair bit of code to run so I just need a way of avoiding the deselection firing if it was another item that was clicked and not blank space (for deselection)
Dave R said to use a 100ms timer here : Am I missing something with my ListView selection event handling
which sounds like it would work but seems quite untidy or generally a bad way of doing it.
My only other idea was to use the click event and then find the item at the location? but I'd rather not go to the hassle
thanks in advance!
-EDIT-
I've just thought that the click event would fire first so I could set a flag that skips selection index changed code if the click event happened on an item and then resets the flag after it's been used therefore skipping the deselection? I'll have a look now but again doesnt feel like a very efficient or easy way of doing something that sounds quite simple?
Use the ItemSelectionChanged event instead - the ListViewItemSelectionChangedEventArgs can tell you which item caused it to fire, and whether it's selected or not.
i just tried another solution which is potentially without any delay, it worked for me:
If ListView1.Items(ListView1.FocusedItem.Index).Selected = False Then
'This is the deselected value
MsgBox("Deselected: " & ListView1.Items(ListView1.FocusedItem.Index).SubItems(0).Text)
Else
'This is the new selected value
MsgBox("Selected: " & ListView1.Items(ListView1.FocusedItem.Index).SubItems(0).Text)
End If
The following solution works even with a delay of 1 ms. To be sure it works probably you can choose a higher delay, 10 ms for example, but a delay of 100 ms will make it a bit laggy on selecting "nothing". Here's the C#-Code:
public class FixedListView : ListView
{
private Timer _ItemSelectionChangedTimer = new Timer();
private Timer _SelectedIndexChangedTimer = new Timer();
private ListViewItemSelectionChangedEventArgs _ItemSelectionChangedEventArgs;
private EventArgs _SelectedIndexChangedEventArgs;
public FixedListView()
{
this._ItemSelectionChangedTimer.Interval = 1;
this._SelectedIndexChangedTimer.Interval = 1;
this._ItemSelectionChangedTimer.Tick += (sender, e) =>
{
this.OnItemSelectionChanged(this._ItemSelectionChangedEventArgs);
this._ItemSelectionChangedEventArgs = null;
};
this._SelectedIndexChangedTimer.Tick += (sender, e) =>
{
this.OnSelectedIndexChanged(this._SelectedIndexChangedEventArgs);
this._SelectedIndexChangedEventArgs = null;
};
}
protected override void OnItemSelectionChanged(ListViewItemSelectionChangedEventArgs e)
{
if (this._ItemSelectionChangedTimer.Enabled)
{
this._ItemSelectionChangedTimer.Stop();
base.OnItemSelectionChanged(e);
}
else
{
this._ItemSelectionChangedEventArgs = e;
this._ItemSelectionChangedTimer.Start();
}
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (this._SelectedIndexChangedTimer.Enabled)
{
this._SelectedIndexChangedTimer.Stop();
base.OnSelectedIndexChanged(e);
}
else
{
this._SelectedIndexChangedEventArgs = e;
this._SelectedIndexChangedTimer.Start();
}
}
}
And here is the VB-Code:
Public Class FixedListBox
Inherits ListView
Public Sub New()
Me._ItemSelectionChangedTimer.Interval = 1
Me._SelectedIndexChangedTimer.Interval = 1
AddHandler Me._ItemSelectionChangedTimer.Tick, _
Sub(sender, e)
Me.OnItemSelectionChanged(Me._ItemSelectionChangedEventArgs)
Me._ItemSelectionChangedEventArgs = Nothing
End Sub
AddHandler Me._SelectedIndexChangedTimer.Tick, _
Sub(sender, e)
Me.OnSelectedIndexChanged(Me._SelectedIndexChangedEventArgs)
Me._SelectedIndexChangedEventArgs = Nothing
End Sub
End Sub
Private _ItemSelectionChangedTimer As New Timer()
Private _SelectedIndexChangedTimer As New Timer()
Private _ItemSelectionChangedEventArgs As ListViewItemSelectionChangedEventArgs
Private _SelectedIndexChangedEventArgs As EventArgs
Protected Overrides Sub OnItemSelectionChanged(e As ListViewItemSelectionChangedEventArgs)
If Me._ItemSelectionChangedTimer.Enabled Then
Me._ItemSelectionChangedTimer.Stop()
MyBase.OnItemSelectionChanged(e)
Else
Me._ItemSelectionChangedEventArgs = e
Me._ItemSelectionChangedTimer.Start()
End If
End Sub
Protected Overrides Sub OnSelectedIndexChanged(e As EventArgs)
If Me._SelectedIndexChangedTimer.Enabled Then
Me._SelectedIndexChangedTimer.Stop()
MyBase.OnSelectedIndexChanged(e)
Else
Me._SelectedIndexChangedEventArgs = e
Me._SelectedIndexChangedTimer.Start()
End If
End Sub
End Class
You can use the control like a normal ListView but SelectedIndexChanged and ItemSelectionChanged will fire only once.
Have fun...
Just check in the SelectedIndexChanged event whether the focused item is null and exit.
ListView^ item = listView1-> FocusedItem; //get selected item
if (item == nullptr){return;) // this line exits when deselection event fires
String^ data1 = Convert::ToString ( item-> SubItems [0] ); // get your data from columns like so
MessageBox::Show (data1); // display
note that you could grab data under several columns by changing index provided in SubItems
And using timers and delays will just incur overhead especially with large databases causing your application to slow Code in visual C++ .NET but the same theory applies for C# and others
Enjoy!!
Related
I'm looking for a best way to implement common Windows keyboard shortcuts (for example Ctrl+F, Ctrl+N) in my Windows Forms application in C#.
The application has a main form which hosts many child forms (one at a time). When a user hits Ctrl+F, I'd like to show a custom search form. The search form would depend on the current open child form in the application.
I was thinking of using something like this in the ChildForm_KeyDown event:
if (e.KeyCode == Keys.F && Control.ModifierKeys == Keys.Control)
// Show search form
But this doesn't work. The event doesn't even fire when you press a key. What is the solution?
You probably forgot to set the form's KeyPreview property to True. Overriding the ProcessCmdKey() method is the generic solution:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == (Keys.Control | Keys.F)) {
MessageBox.Show("What the Ctrl+F?");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
On your Main form
Set KeyPreview to True
Add KeyDown event handler with the following code
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.N)
{
SearchForm searchForm = new SearchForm();
searchForm.Show();
}
}
The best way is to use menu mnemonics, i.e. to have menu entries in your main form that get assigned the keyboard shortcut you want. Then everything else is handled internally and all you have to do is to implement the appropriate action that gets executed in the Click event handler of that menu entry.
You can even try this example:
public class MDIParent : System.Windows.Forms.Form
{
public bool NextTab()
{
// some code
}
public bool PreviousTab()
{
// some code
}
protected override bool ProcessCmdKey(ref Message message, Keys keys)
{
switch (keys)
{
case Keys.Control | Keys.Tab:
{
NextTab();
return true;
}
case Keys.Control | Keys.Shift | Keys.Tab:
{
PreviousTab();
return true;
}
}
return base.ProcessCmdKey(ref message, keys);
}
}
public class mySecondForm : System.Windows.Forms.Form
{
// some code...
}
If you have a menu then changing ShortcutKeys property of the ToolStripMenuItem should do the trick.
If not, you could create one and set its visible property to false.
From the main Form, you have to:
Be sure you set KeyPreview to true( TRUE by default)
Add MainForm_KeyDown(..) - by which you can set here any shortcuts you want.
Additionally,I have found this on google and I wanted to share this to those who are still searching for answers. (for global)
I think you have to be using user32.dll
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0x0312)
{
/* Note that the three lines below are not needed if you only want to register one hotkey.
* The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */
Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF); // The key of the hotkey that was pressed.
KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF); // The modifier of the hotkey that was pressed.
int id = m.WParam.ToInt32(); // The id of the hotkey that was pressed.
MessageBox.Show("Hotkey has been pressed!");
// do something
}
}
Further read this http://www.fluxbytes.com/csharp/how-to-register-a-global-hotkey-for-your-application-in-c/
Hans's answer could be made a little easier for someone new to this, so here is my version.
You do not need to fool with KeyPreview, leave it set to false. To use the code below, just paste it below your form1_load and run with F5 to see it work:
protected override void OnKeyPress(KeyPressEventArgs ex)
{
string xo = ex.KeyChar.ToString();
if (xo == "q") //You pressed "q" key on the keyboard
{
Form2 f2 = new Form2();
f2.Show();
}
}
In WinForm, we can always get the Control Key status by:
bool IsCtrlPressed = (Control.ModifierKeys & Keys.Control) != 0;
The VB.NET version of Hans' answer.
(There's a ProcessCmdKey function template in Visual Studio.)
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
If (keyData = (Keys.Control Or Keys.F)) Then
' call your sub here, like
SearchDialog()
Return True
End If
Return MyBase.ProcessCmdKey(msg, keyData)
End Function
End Class
I try to add code to Hardware Back Button on WP8.1
Private Sub onBackPressed(sender As Object, e As BackPressedEventArgs)
some code
End Sub
But when I press Back - my app just closes.
onBackPressed event is not happening at all, how can I fixed it?
Did you register for that event?
You need to first add this youself
HardwareButtons.BackPressed += HardwareButtons_BackPressed; ( sorry c# )
cfr http://invokeit.wordpress.com/2014/04/14/backbutton-handling-with-winprt-and-windows-phone-8-1-wpdev/
I know that for C# its easy.
Add this to the constructor:
HardwareButtons.BackPressed += this.Hardware_BackButton_Pressed;
And use:
public void Hardware_BackButton_Pressed(object sender, BackPressedEventArgs e)
{
// do things here..
}
In a DataGridView, pressing SHIFT and SPACE will by default select the entire row. The only solution I've found (referenced at vb.net DataGridView - Replace Shortcut Key with typed character) is to turn off the row select feature. While that works, it's not ideal, because I would still like to be able to select the whole row using the row selector (for example, to delete the row), and by changing the SelectionMode property to anything other than RowHeaderSelect I lose that ability. Is there a way to trap just the SHIFT+SPACE combination and replace it with a simple SPACE? It seems like none of the key events even recognize that keystroke when the control's MutiSelect property is set to True and the SelectionMode property is set to RowHeaderSelect, so I can't use those.
ETA: I thought maybe turning off MultiSelect and changing the selection mode to CellSelect, then adding an event handler for the RowHeaderMouseClick event would work...nope.
The best way I figured out how to accomplish this was to inherit from DataGridView and override the ProcessCmdKey method. Then you can intercept the Shift+Space and just send on Space. Just add this class to your project and switch all of your DataGridViews to MyDataGridViews. My solution draws inspiration from this DataGridView keydown event not working in C# SO question (that also explains why Zaggler's solution doesn't work) and this SendKeys in ProcessCmdKey: change Shift-Space to just a Space Bytes.com post. Sorry, but it's in C#.
class MyDataGridView : System.Windows.Forms.DataGridView
{
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData)
{
if (keyData == (System.Windows.Forms.Keys.Space | System.Windows.Forms.Keys.Shift))
{
// DataGridView is dumb and will select a row when the user types Shift+Space
// if you have the DGV set so that you can click a row header to select a row (for example, to delete the row)
// this method will intercept Shift+Space and just send on Space so that the DGV properly handles this.
// For example, if I type "ME TYPING IN ALL CAPS" it ends up looking like "METYPINGINALLCAPS".
// Or if I type "Note: I have some OS thing to talk about" it looks like "Note:Ihave some OSthing to talk about"
byte[] keyStates = new byte[255];
UnsafeNativeMethods.GetKeyboardState(keyStates);
byte shiftKeyState = keyStates[16];
keyStates[16] = 0; // turn off the shift key
UnsafeNativeMethods.SetKeyboardState(keyStates);
System.Windows.Forms.SendKeys.SendWait(" ");
keyStates[16] = shiftKeyState; // turn the shift key back on
UnsafeNativeMethods.SetKeyboardState(keyStates);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
[System.Security.SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods
{
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetKeyboardState(byte[] keystate);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int SetKeyboardState(byte[] keystate);
}
}
This was my solution:
private void dataGridView_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
dataGridView.SelectionMode = DataGridViewSelectionMode.CellSelect;
}
private void dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
dataGridView.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect;
}
Here this works fine for me....
Private Sub DataGridView1_KeyDown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles DataGridView1.KeyDown
'Lets see what keys we have down shall we?'
If My.Computer.Keyboard.ShiftKeyDown And e.KeyCode = Keys.Space Then
DataGridView1.CurrentCell.Selected = False
End If
End Sub
Here's another way..
Private Sub DataGridView1_KeyDown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles DataGridView1.KeyDown
'Lets see what keys we have down shall we?'
If My.Computer.Keyboard.ShiftKeyDown And e.KeyCode = Keys.Space Then
'SendKeys.Send(Keys.Space)
DataGridView1.CurrentCell.Selected = False
End If
End Sub
Just experiment with them and I hope something works out for you?
Dows anyone know how I can build a timeout feature into a windows forms app.
The app is event driven, but I am thinking of somehow using a timer which counts down for say 10minutes, and one the timer ticks then we time out the user.
The problem I have is how can I reset the timer each time the mouse is moved or clicked.
Any help appreciated.
Cheers
you can use System.Windows.Forms.Timer.
you can drag it from your toolbox to the designer surface.
Use the properties window to set the Interval Property to the time span you want(miliseconds), the Enabled property should be set to false.
on the for load set the timer Enabled property to true.
(The event handler in the sample are written using c# - sorry about that)
private void Form1_Load(object sender, EventArgs e)
{
timer1.Enabled = true;
}
Double click the timer tick event to register to the event, and close the form on the timer tick
private void timer1_Tick(object sender, EventArgs e)
{
Close();
}
In setting timer.Interval to 0 it does not work ?
Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
Me.Timer1.Stop()
Me.Timer1.Start()
End Sub
As bad as it seems, I think the best way for that is to use a system.timer object with a set interval of a few milliseconds at most.
What I saw once is the use of a global variable that would get the time of the last action and that variable would be set to Now (using a global function for example) each time an action is performed. In your timer elapsed event, you check if now if bigger that the last action with your 10 minutes limit and act accordingly.
As for multi-form app, you could either use a different timer on each form , or only have the timer run on your main form.
Hope that helps.
Problem: I have a document class which contains a list of objects. These objects raise events such as SolutionExpired, DisplayExpired etc. The document needs to respond to this.
Documents can sometimes exchange objects, but a single object should never be 'part' of more than one document.
My document class contains a bunch of methods which serve as event handlers. Whenever an object enters the document, I use AddHandler to set up the events, and whenever an object is removed from the document I use RemoveHandler to undo the damage. However, there are cases where it's difficult to make sure all the steps are properly taken and I might thus end up with rogue event handlers.
Long story short; how do I remove all the handlers that are pointing to a specific method? Note, I don't have a list of potential event sources, these could be stored anywhere.
Something like:
RemoveHandler *.SolutionExpired, AddressOf DefObj_SolutionExpired
You can use Delegate.RemoveAll(). (The part you're interested in is in button2_Click)
public void Form_Load(object sender, EventArgs e)
{
button1.Click += new EventHandler(button1_Click);
button1.Click += new EventHandler(button1_Click);
button2.Click += new EventHandler(button2_Click);
TestEvent += new EventHandler(Form_TestEvent);
}
event EventHandler TestEvent;
void OnTestEvent(EventArgs e)
{
if (TestEvent != null)
TestEvent(this, e);
}
void Form_TestEvent(object sender, EventArgs e)
{
MessageBox.Show("TestEvent fired");
}
void button2_Click(object sender, EventArgs e)
{
Delegate d = TestEvent as Delegate;
TestEvent = Delegate.RemoveAll(d, d) as EventHandler;
}
void button1_Click(object sender, EventArgs e)
{
OnTestEvent(EventArgs.Empty);
}
You should note that it doesn't alter the contents of the delegates you pass in to it, it returns an altered delegate. Consequently, you won't be able to alter the events on a button you've dropped on a form from the form, as button1.Click can only have += or -= used on it, not =. This won't compile:
button1.Click = Delegate.RemoveAll(d, d) as EventHandler;
Also, be sure that wherever you're implementing this you're watching out for the potential of race conditions. You could end up with some really strange behavior if you're removing handlers from an event that is being called by another thread!
public class TheAnswer
{
public event EventHandler MyEvent = delegate { };
public void RemoveFromMyEvent(string methodName)
{
foreach (var handler in MyEvent.GetInvocationList())
{
if (handler.Method.Name == methodName)
{
MyEvent -= (EventHandler)handler;
}
}
}
}
EDIT 2: Apologies for my misunderstanding--I see that you were pretty clear about not having access to the event sources in your original post.
The simplest way I can think of to solve this problem involves implementing a Shared dictionary of object-to-document bindings. When an object enters a document, check the dictionary for an existing binding to another document; if present, remove handlers that refer to the old document before adding them for the new. Either way, update the dictionary with the new binding.
I think in most cases the performance and memory impacts would be negligible: unless you're dealing with many tens of thousands of small objects and frequently exchange them between documents, the memory overhead of each key/value pair and performance hit for each lookup operation should be fairly small.
As an alternative: if you can detect (in the document event handlers) that the sender of the event is no longer relevant to the document, you can detach the events there.
These seem like the kind of ideas you might have already rejected--but maybe not!
Use Delegate.RemoveAll (maybe using reflection if the Delegate instance is private).