0

I want to somehow make a windows forms control unresponsible - like setting Control.Enabled to False, but without the visual effects of it (I will have some custom "control is busy" indicator, so it will not be unclear to the end-user why the control is unresponsive).

The reason is: I'm writing a busy indicator control for windows forms and want it to be as generic as possible.

The aim is to be able to use it like following.

Dim busy_control As Control = ...
ShowBusyIndicator(busy_control)
BeginDoWork() 'starts a worker thread

'in some OnCompleted-Event:
HideBusyIndicator(busy_control)

My current problem is, I want to be sure the busy_control will not react to any user input at all.

In the current version, I make sure the control looses focus and can never get it again by handling the GotFocus event. Since the Parent of the overlay control is the busy_control, I also set the OnMouseWheel event to handled (else busy_control could be scrolled).

I guess there are more such events. That's why I'd like to "disable" a control without actually setting enabled to false.

Is there any way to do this?

Tymur Gubayev
  • 468
  • 4
  • 14
  • If you want to guarantee it won't take input until your running task is completed, don't multithread it. Just unregister your `OnClick` events to make sure nothing slips through and then run your methods on the UI thread, then re-register the events. This is very likely, however, to confuse your users. Users expect the "disabled" behavior. – CDove Feb 28 '18 at 13:00
  • If I want to show an animated gif (or keep the application responsible), I can't do the work on the UI thread. I also can't disable all the events without some Reflection hacking - which feels dirty. – Tymur Gubayev Feb 28 '18 at 13:06
  • Correct. If you want to keep the application responsive, you can't work on the UI thread. If you just want this form object itself to behave as if it's disabled, you don't need reflection to remove its handlers, but there's a better way. Hang on a sec... – CDove Feb 28 '18 at 13:21
  • Use Graphics.DrawToBitmap() to make a screenshot of it before you disable it. Show it in a PictureBox that you put in front of the control. Pretending that the control is still usable is pretty bad UI. – Hans Passant Feb 28 '18 at 13:25
  • @HansPassant the "busy indicator" makes it obvious the control isn't usable by drawing a half transparent overlay with a spinning circle on it – Tymur Gubayev Feb 28 '18 at 13:29
  • Don't forget about keyboard input. Users can navigate around using Tab, and Alt-Tab, then "click" on buttons using Space. Anything you block for the mouse should be blocked for keyboard too. Even if the controls are completely covered by an image, they will still respond the keyboard events. – Bradley Uffner Feb 28 '18 at 13:49
  • @BradleyUffner that's exactly my question: how would I disable everything? – Tymur Gubayev Feb 28 '18 at 13:53
  • `KeyPreview` on the `Form` should catch the majority of keyboard input and allow you to cancel it. Some kind of overlay image drawn over the entire form should block the mouse. This problem isn't quite as trivial as it seems, since winform controls were not really designed with this in mind at a universal level. It was handled on a control by control basis, as they were developed, if it was a feature they needed. – Bradley Uffner Feb 28 '18 at 13:54

2 Answers2

0

I also recommend that you do not do this... however, if you must...

Just pass the control you want to suppress/disable into this sub. Obviously, when you come to re-enable the control, you will need to remove the picture box that is created... but I will leave you to figure that out.

Private Sub SuppressControl(ByRef CtrlToSuppress As Control)
    ''create an image of the control
    Dim ctrlImage As New Bitmap(CtrlToSuppress.Width, CtrlToSuppress.Height)
    CtrlToSuppress.DrawToBitmap(ctrlImage, CtrlToSuppress.ClientRectangle)
    ''create a picturebox in the same location/size as the control
    Dim PictureBox1 As New PictureBox
    PictureBox1.Left = CtrlToSuppress.Left
    PictureBox1.Top = CtrlToSuppress.Top
    PictureBox1.Width = CtrlToSuppress.Width
    PictureBox1.Height = CtrlToSuppress.Height
    ''Set the anchors to maintain alignment on form resize
    PictureBox1.Anchor = CtrlToSuppress.Anchor
    ''place the image in picturebox
    PictureBox1.Image = ctrlImage
    ''add the picture box to the form
    CtrlToSuppress.Parent.Controls.Add(PictureBox1)
    ''and bring it to the front
    PictureBox1.BringToFront()
    ''Suppress the control
    CtrlToSuppress.Enabled = False
End Sub
Gravitate
  • 2,885
  • 2
  • 21
  • 37
  • in this version the overlay could easily become out of sync with the busy control, if the form is resized so that the control is moved. And like I said, the will be a visual indicator about the control being busy (in this case I'd do something with the ctrlImage), so I don't see any reason why this would be such a terrible idea. – Tymur Gubayev Feb 28 '18 at 15:23
  • If you set the set PictureBox1.Anchor = CtrlToSuppress.Anchor, even if the form moves or is resized... they should stay aligned, unless you are manually moving the control on the form elsewhere in code after it is disabled? I never said it was "a terrible idea", just that I wouldn't recommend it. If you have reason to do it that way (or even if you just want to do it that way for no particular reason), that is up to you. It's just that from outside, it seems like a lot of additional work for something which may cause confusion for users that are used to Winforms "usual" behavior. – Gravitate Feb 28 '18 at 15:32
  • Anchor makes it a bit better, but doesn't work with a docked control. Well, I'm trying to provide an easy-to-use "busy indicator" for my fellow colleagues to use. As for the users, they will see a spinning circle changed color of busy control -- I doubt there will be any confusion. – Tymur Gubayev Feb 28 '18 at 15:43
  • To be honest, I hadn't considered a docked control. Sorry, that is as close as I can get to what you are looking for. – Gravitate Feb 28 '18 at 15:47
  • Actually, it seems to still work with docked controls if you add "PictureBox1.Dock= CtrlToSuppress.Dock". – Gravitate Feb 28 '18 at 15:54
  • If there a 2 controls on a form with Dock = Bottom, they will dock on each other. – Tymur Gubayev Feb 28 '18 at 15:58
  • Oh. Ok. I see what you mean now. Yeah, the only way to avoid that would be to move/resize the picturebox every time the control is moved/resized. – Gravitate Feb 28 '18 at 16:07
-1

Your best bet is probably a transparent Panel. By placing this over your control you want "disabled", you can intercept any attempt to click it, as the Panel will register the click. You can add OnClick handles to the Panel as well.

Since you're already using something like a "spinner", you might want to put your loading indicator on that Panel. That way, the user won't be confused as to why clicking is doing nothing or why the control doesn't look disabled. If not, just make the whole Panel transparent. Information on how to go about controlling transparency on a Panel is found here.

Please note that in the linked example, there's an explanation on how technically you need to do transparency at the Form level due to OS limitations. You'll find references to help you build what you need at that link, depending on the version of Windows you target.

I can't stress enough that it would be better to use your spinner to cover what you disable. It's much more clear (and easier to code, honestly) than making a control just pretend it's not disabled.

CDove
  • 1,940
  • 10
  • 19
  • the spinner is what I called "busy indicator". I have that. It spins. Now I want to make really sure a user can't do anything with the busy control, can't tab into it, can't click or right-click into it, nothing. – Tymur Gubayev Feb 28 '18 at 13:33
  • You will also have to deal with keyboard input. Users can navigate around using Tab, and Alt-Tab, then "click" on buttons using Space. – Bradley Uffner Feb 28 '18 at 13:46