How to manage lost focus across a group of WinForms controls?











up vote
0
down vote

favorite












I'm trying to reliably detect when focus leaves a group of controls. So if I tab or click between two controls in the group, that's considered not leaving, but as soon as I click on a control outside the group, it raises an event. Here's how I'm currently doing it:



public class LeaveTracker
{
private HashSet<Control> trackedControls = new HashSet<Control>();
public void Track(Control ctrl)
{
ctrl.Leave += Ctrl_Leave;
trackedControls.Add(ctrl);
}

private void Ctrl_Leave(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
if (LeftControlGroup != null)
LeftControlGroup(this, EventArgs.Empty);
}
public event EventHandler LeftControlGroup;
}


Sometimes it works perfectly, but occasionally it seems to stop raising events. Is there a more reliable way to do this?










share|improve this question
























  • If you can place these controls on a panel, you can just use the Leave event of the panel instead.
    – Ahmed Abdelhameed
    Nov 21 at 21:25










  • Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
    – JayV
    Nov 21 at 21:26










  • There are other controls that are intermixed that aren't part of the group.
    – Bryce Wagner
    Nov 21 at 21:35










  • It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
    – LarsTech
    Nov 21 at 21:36










  • @AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
    – Bryce Wagner
    Nov 21 at 21:37















up vote
0
down vote

favorite












I'm trying to reliably detect when focus leaves a group of controls. So if I tab or click between two controls in the group, that's considered not leaving, but as soon as I click on a control outside the group, it raises an event. Here's how I'm currently doing it:



public class LeaveTracker
{
private HashSet<Control> trackedControls = new HashSet<Control>();
public void Track(Control ctrl)
{
ctrl.Leave += Ctrl_Leave;
trackedControls.Add(ctrl);
}

private void Ctrl_Leave(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
if (LeftControlGroup != null)
LeftControlGroup(this, EventArgs.Empty);
}
public event EventHandler LeftControlGroup;
}


Sometimes it works perfectly, but occasionally it seems to stop raising events. Is there a more reliable way to do this?










share|improve this question
























  • If you can place these controls on a panel, you can just use the Leave event of the panel instead.
    – Ahmed Abdelhameed
    Nov 21 at 21:25










  • Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
    – JayV
    Nov 21 at 21:26










  • There are other controls that are intermixed that aren't part of the group.
    – Bryce Wagner
    Nov 21 at 21:35










  • It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
    – LarsTech
    Nov 21 at 21:36










  • @AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
    – Bryce Wagner
    Nov 21 at 21:37













up vote
0
down vote

favorite









up vote
0
down vote

favorite











I'm trying to reliably detect when focus leaves a group of controls. So if I tab or click between two controls in the group, that's considered not leaving, but as soon as I click on a control outside the group, it raises an event. Here's how I'm currently doing it:



public class LeaveTracker
{
private HashSet<Control> trackedControls = new HashSet<Control>();
public void Track(Control ctrl)
{
ctrl.Leave += Ctrl_Leave;
trackedControls.Add(ctrl);
}

private void Ctrl_Leave(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
if (LeftControlGroup != null)
LeftControlGroup(this, EventArgs.Empty);
}
public event EventHandler LeftControlGroup;
}


Sometimes it works perfectly, but occasionally it seems to stop raising events. Is there a more reliable way to do this?










share|improve this question















I'm trying to reliably detect when focus leaves a group of controls. So if I tab or click between two controls in the group, that's considered not leaving, but as soon as I click on a control outside the group, it raises an event. Here's how I'm currently doing it:



public class LeaveTracker
{
private HashSet<Control> trackedControls = new HashSet<Control>();
public void Track(Control ctrl)
{
ctrl.Leave += Ctrl_Leave;
trackedControls.Add(ctrl);
}

private void Ctrl_Leave(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
if (LeftControlGroup != null)
LeftControlGroup(this, EventArgs.Empty);
}
public event EventHandler LeftControlGroup;
}


Sometimes it works perfectly, but occasionally it seems to stop raising events. Is there a more reliable way to do this?







c# winforms






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 at 21:29









Ahmed Abdelhameed

5,48971943




5,48971943










asked Nov 21 at 21:17









Bryce Wagner

429210




429210












  • If you can place these controls on a panel, you can just use the Leave event of the panel instead.
    – Ahmed Abdelhameed
    Nov 21 at 21:25










  • Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
    – JayV
    Nov 21 at 21:26










  • There are other controls that are intermixed that aren't part of the group.
    – Bryce Wagner
    Nov 21 at 21:35










  • It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
    – LarsTech
    Nov 21 at 21:36










  • @AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
    – Bryce Wagner
    Nov 21 at 21:37


















  • If you can place these controls on a panel, you can just use the Leave event of the panel instead.
    – Ahmed Abdelhameed
    Nov 21 at 21:25










  • Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
    – JayV
    Nov 21 at 21:26










  • There are other controls that are intermixed that aren't part of the group.
    – Bryce Wagner
    Nov 21 at 21:35










  • It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
    – LarsTech
    Nov 21 at 21:36










  • @AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
    – Bryce Wagner
    Nov 21 at 21:37
















If you can place these controls on a panel, you can just use the Leave event of the panel instead.
– Ahmed Abdelhameed
Nov 21 at 21:25




If you can place these controls on a panel, you can just use the Leave event of the panel instead.
– Ahmed Abdelhameed
Nov 21 at 21:25












Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
– JayV
Nov 21 at 21:26




Have you tried putting the sets of controls inside another custom User Control? I believe that User Control will have its own Leave event
– JayV
Nov 21 at 21:26












There are other controls that are intermixed that aren't part of the group.
– Bryce Wagner
Nov 21 at 21:35




There are other controls that are intermixed that aren't part of the group.
– Bryce Wagner
Nov 21 at 21:35












It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
– LarsTech
Nov 21 at 21:36




It's really hard to picture how that is supposed to work. You have a screen shot of your form that shows these "groups"?
– LarsTech
Nov 21 at 21:36












@AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
– Bryce Wagner
Nov 21 at 21:37




@AhmedAbdelhameed This question is in no way a duplicate of the mouse leaving event. This is about focus, not mouse pointer.
– Bryce Wagner
Nov 21 at 21:37












1 Answer
1






active

oldest

votes

















up vote
-1
down vote













Apparently, the Leave event gets triggered before the control actually loses focus. You can confirm that by checking the value of (sender as Control).ContainsFocus in your event handler.



Either use the Control.LostFocus event instead:



private void Ctrl_LostFocus(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}


Or if you want to use the Leave event, you can wait for a little while until the control actually loses focus. This also worked fine for me:



private async void Ctrl_Leave(object sender, EventArgs e)
{
while ((sender as Control).ContainsFocus)
await Task.Delay(50);

foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}




A better alternative to using await is to wrap the code under Ctrl_Leave in a BeginInvoke delegate as suggested by Hans Passant:



private void Ctrl_Leave(object sender, EventArgs e)
{
BeginInvoke((Action)(() =>
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}));
}





share|improve this answer























  • The Leave event replaced that event. Read the notes in your link.
    – LarsTech
    Nov 21 at 21:48










  • @LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
    – Ahmed Abdelhameed
    Nov 21 at 21:56








  • 1




    It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
    – Hans Passant
    Nov 21 at 22:22










  • @HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
    – Ahmed Abdelhameed
    Nov 21 at 22:35








  • 1




    The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
    – Hans Passant
    Nov 21 at 22:39











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














 

draft saved


draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53420594%2fhow-to-manage-lost-focus-across-a-group-of-winforms-controls%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
-1
down vote













Apparently, the Leave event gets triggered before the control actually loses focus. You can confirm that by checking the value of (sender as Control).ContainsFocus in your event handler.



Either use the Control.LostFocus event instead:



private void Ctrl_LostFocus(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}


Or if you want to use the Leave event, you can wait for a little while until the control actually loses focus. This also worked fine for me:



private async void Ctrl_Leave(object sender, EventArgs e)
{
while ((sender as Control).ContainsFocus)
await Task.Delay(50);

foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}




A better alternative to using await is to wrap the code under Ctrl_Leave in a BeginInvoke delegate as suggested by Hans Passant:



private void Ctrl_Leave(object sender, EventArgs e)
{
BeginInvoke((Action)(() =>
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}));
}





share|improve this answer























  • The Leave event replaced that event. Read the notes in your link.
    – LarsTech
    Nov 21 at 21:48










  • @LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
    – Ahmed Abdelhameed
    Nov 21 at 21:56








  • 1




    It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
    – Hans Passant
    Nov 21 at 22:22










  • @HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
    – Ahmed Abdelhameed
    Nov 21 at 22:35








  • 1




    The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
    – Hans Passant
    Nov 21 at 22:39















up vote
-1
down vote













Apparently, the Leave event gets triggered before the control actually loses focus. You can confirm that by checking the value of (sender as Control).ContainsFocus in your event handler.



Either use the Control.LostFocus event instead:



private void Ctrl_LostFocus(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}


Or if you want to use the Leave event, you can wait for a little while until the control actually loses focus. This also worked fine for me:



private async void Ctrl_Leave(object sender, EventArgs e)
{
while ((sender as Control).ContainsFocus)
await Task.Delay(50);

foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}




A better alternative to using await is to wrap the code under Ctrl_Leave in a BeginInvoke delegate as suggested by Hans Passant:



private void Ctrl_Leave(object sender, EventArgs e)
{
BeginInvoke((Action)(() =>
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}));
}





share|improve this answer























  • The Leave event replaced that event. Read the notes in your link.
    – LarsTech
    Nov 21 at 21:48










  • @LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
    – Ahmed Abdelhameed
    Nov 21 at 21:56








  • 1




    It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
    – Hans Passant
    Nov 21 at 22:22










  • @HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
    – Ahmed Abdelhameed
    Nov 21 at 22:35








  • 1




    The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
    – Hans Passant
    Nov 21 at 22:39













up vote
-1
down vote










up vote
-1
down vote









Apparently, the Leave event gets triggered before the control actually loses focus. You can confirm that by checking the value of (sender as Control).ContainsFocus in your event handler.



Either use the Control.LostFocus event instead:



private void Ctrl_LostFocus(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}


Or if you want to use the Leave event, you can wait for a little while until the control actually loses focus. This also worked fine for me:



private async void Ctrl_Leave(object sender, EventArgs e)
{
while ((sender as Control).ContainsFocus)
await Task.Delay(50);

foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}




A better alternative to using await is to wrap the code under Ctrl_Leave in a BeginInvoke delegate as suggested by Hans Passant:



private void Ctrl_Leave(object sender, EventArgs e)
{
BeginInvoke((Action)(() =>
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}));
}





share|improve this answer














Apparently, the Leave event gets triggered before the control actually loses focus. You can confirm that by checking the value of (sender as Control).ContainsFocus in your event handler.



Either use the Control.LostFocus event instead:



private void Ctrl_LostFocus(object sender, EventArgs e)
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}


Or if you want to use the Leave event, you can wait for a little while until the control actually loses focus. This also worked fine for me:



private async void Ctrl_Leave(object sender, EventArgs e)
{
while ((sender as Control).ContainsFocus)
await Task.Delay(50);

foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}




A better alternative to using await is to wrap the code under Ctrl_Leave in a BeginInvoke delegate as suggested by Hans Passant:



private void Ctrl_Leave(object sender, EventArgs e)
{
BeginInvoke((Action)(() =>
{
foreach (var ctrl in trackedControls)
if (ctrl.ContainsFocus)
return;
Debug.Print("The tracked controls are not focused anymore!");
//..
}));
}






share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 21 at 22:52

























answered Nov 21 at 21:46









Ahmed Abdelhameed

5,48971943




5,48971943












  • The Leave event replaced that event. Read the notes in your link.
    – LarsTech
    Nov 21 at 21:48










  • @LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
    – Ahmed Abdelhameed
    Nov 21 at 21:56








  • 1




    It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
    – Hans Passant
    Nov 21 at 22:22










  • @HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
    – Ahmed Abdelhameed
    Nov 21 at 22:35








  • 1




    The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
    – Hans Passant
    Nov 21 at 22:39


















  • The Leave event replaced that event. Read the notes in your link.
    – LarsTech
    Nov 21 at 21:48










  • @LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
    – Ahmed Abdelhameed
    Nov 21 at 21:56








  • 1




    It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
    – Hans Passant
    Nov 21 at 22:22










  • @HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
    – Ahmed Abdelhameed
    Nov 21 at 22:35








  • 1




    The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
    – Hans Passant
    Nov 21 at 22:39
















The Leave event replaced that event. Read the notes in your link.
– LarsTech
Nov 21 at 21:48




The Leave event replaced that event. Read the notes in your link.
– LarsTech
Nov 21 at 21:48












@LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
– Ahmed Abdelhameed
Nov 21 at 21:56






@LarsTech I'm aware of that but it doesn't work for this particular case. I was in the process of updating my answer with more info + a workaround for using the Leave event when you left your comment. Please, check the updated answer.
– Ahmed Abdelhameed
Nov 21 at 21:56






1




1




It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
– Hans Passant
Nov 21 at 22:22




It is getting somewhere, but is still too awkward. All you need to do is use this.BeginInvoke() in the event handler. That makes the invoked code run later, after the focus has changed and the ContainsFocus property is accurate. You get it now by accident from await.
– Hans Passant
Nov 21 at 22:22












@HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
– Ahmed Abdelhameed
Nov 21 at 22:35






@HansPassant How is it 'by accident' if the await is repeated until ContainsFocus is false? I agree with you though that BeginInvoke is a better idea. I actually learned to use it in such situations from some of your answers but for some reason, didn't think of it this time :D
– Ahmed Abdelhameed
Nov 21 at 22:35






1




1




The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
– Hans Passant
Nov 21 at 22:39




The awaiter for an async void event handler uses BeginInvoke() to run the continuation. That kind of accident. Mostly a fatal accident to the programmer's brain region :)
– Hans Passant
Nov 21 at 22:39


















 

draft saved


draft discarded



















































 


draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53420594%2fhow-to-manage-lost-focus-across-a-group-of-winforms-controls%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

What visual should I use to simply compare current year value vs last year in Power BI desktop

How to ignore python UserWarning in pytest?

Alexandru Averescu