How to reset the close reason when close is cancelled - vb.net

Question
Is it possible to reset the CloseReason provided by the FormClosingEventArgs in the FormClosing event of a modal dialog?
Symptoms
Setting the DialogResult of a modal dialog can result in an "incorrect" CloseReason if the close event have previously been cancelled.
Details
(The following code is just sample code to highlight the inconvenience)
Imagine I have a form with two buttons, OK and Cancel, displayed as a modal dialog.
Me.btnOk = New Button With {.DialogResult = Windows.Forms.DialogResult.OK}
Me.btnCancel = New Button With {.DialogResult = Windows.Forms.DialogResult.Cancel}
Me.AcceptButton = Me.btnOk
Me.CancelButton = Me.btnCancel
Any attempts to close the form will be cancelled.
If I click each button (including the [X] - close form button) in the following order, the close reasons will be as following:
Case 1
btnOk::::::::::: None
btnCancel::: None
X::::::::::::::::::: UserClosing
Now, if I repeat the steps you'll see that the UserClosing reason will persist:
btnOk::::::::::: UserClosing
btnCancel::: UserClosing
X::::::::::::::::::: UserClosing
Case 2
X::::::::::::::::::: UserClosing
btnCancel::: UserClosing
btnOk::::::::::: UserClosing
Same here. Once you click the X button the close reason will always return UserClosing.
Sample application
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.Text = "Test"
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
Me.MinimizeBox = False
Me.MaximizeBox = False
Me.ClientSize = New Size(75, 25)
Me.StartPosition = FormStartPosition.CenterScreen
Me.btnOpenDialog = New Button() With {.TabIndex = 0, .Dock = DockStyle.Fill, .Text = "Open dialog"}
Me.Controls.Add(Me.btnOpenDialog)
End Sub
Private Sub HandleOpenDialog(sender As Object, e As EventArgs) Handles btnOpenDialog.Click
Using instance As New CustomDialog()
instance.ShowDialog()
End Using
End Sub
Private WithEvents btnOpenDialog As Button
Private Class CustomDialog
Inherits Form
Public Sub New()
Me.Text = "Custom dialog"
Me.ClientSize = New Size(400, 200)
Me.StartPosition = FormStartPosition.CenterParent
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
Me.MinimizeBox = False
Me.MaximizeBox = False
Me.tbOutput = New RichTextBox() With {.TabIndex = 0, .Bounds = New Rectangle(0, 0, 400, 155), .ReadOnly = True, .ScrollBars = RichTextBoxScrollBars.ForcedBoth, .WordWrap = True}
Me.btnExit = New Button With {.TabIndex = 3, .Text = "Exit", .Bounds = New Rectangle(10, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Left)}
Me.btnOk = New Button With {.TabIndex = 1, .Text = "OK", .Bounds = New Rectangle(237, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Right), .DialogResult = Windows.Forms.DialogResult.OK}
Me.btnCancel = New Button With {.TabIndex = 2, .Text = "Cancel", .Bounds = New Rectangle(315, 165, 75, 25), .Anchor = (AnchorStyles.Bottom Or AnchorStyles.Right), .DialogResult = Windows.Forms.DialogResult.Cancel}
Me.Controls.AddRange({Me.tbOutput, Me.btnExit, Me.btnOk, Me.btnCancel})
Me.AcceptButton = Me.btnOk
Me.CancelButton = Me.btnCancel
End Sub
Private Sub HandleExitDialog(sender As Object, e As EventArgs) Handles btnExit.Click
Me.exitPending = True
Me.Close()
End Sub
Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
If (Not Me.exitPending) Then
e.Cancel = True
Me.tbOutput.Text += (String.Format("DialogResult={0}, CloseReason={1}{2}", Me.DialogResult.ToString(), e.CloseReason.ToString(), Environment.NewLine))
Me.DialogResult = Windows.Forms.DialogResult.None
End If
MyBase.OnFormClosing(e)
End Sub
Private exitPending As Boolean
Private WithEvents btnExit As Button
Private WithEvents btnCancel As Button
Private WithEvents btnOk As Button
Private WithEvents tbOutput As RichTextBox
End Class
End Class
Update
I was of the impression that if either the Form.AcceptButton or Form.CancelButton (IButtonControl) was clicked the close reason would be set to UserClosing, but this is not the case. In the following code you'll see that all it do is setting the DialogResult of the owning form to that of its own DialogResult.
Protected Overrides Sub OnClick(ByVal e As EventArgs)
Dim form As Form = MyBase.FindFormInternal
If (Not form Is Nothing) Then
form.DialogResult = Me.DialogResult
End If
MyBase.AccessibilityNotifyClients(AccessibleEvents.StateChange, -1)
MyBase.AccessibilityNotifyClients(AccessibleEvents.NameChange, -1)
MyBase.OnClick(e)
End Sub
The Control class do have a property named CloseReason but it's defined as Friend, thus not accessible.
I also thought that setting the forms DialogResult would result in a WM message being sent, but all it does is setting a private field.
So I delved into reflector and followed the stack. The following image is a highly simplified illustration.
This is how the CheckCloseDialog method looks like:
Friend Function CheckCloseDialog(ByVal closingOnly As Boolean) As Boolean
If ((Me.dialogResult = DialogResult.None) AndAlso MyBase.Visible) Then
Return False
End If
Try
Dim e As New FormClosingEventArgs(Me.closeReason, False)
If Not Me.CalledClosing Then
Me.OnClosing(e)
Me.OnFormClosing(e)
If e.Cancel Then
Me.dialogResult = DialogResult.None
Else
Me.CalledClosing = True
End If
End If
If (Not closingOnly AndAlso (Me.dialogResult <> DialogResult.None)) Then
Dim args2 As New FormClosedEventArgs(Me.closeReason)
Me.OnClosed(args2)
Me.OnFormClosed(args2)
Me.CalledClosing = False
End If
Catch exception As Exception
Me.dialogResult = DialogResult.None
If NativeWindow.WndProcShouldBeDebuggable Then
Throw
End If
Application.OnThreadException(exception)
End Try
If (Me.dialogResult = DialogResult.None) Then
Return Not MyBase.Visible
End If
Return True
End Function
As you can see the modal message loop checks the DialogResult in every cycle and if the conditions are met it will use the stored CloseReason (as observed) when creating the FormClosingEventArgs.
Summary
Yes, I know that the IButtonControl interface have a PerformClick method which you can call programmatically, but still, IMO this smells like a bug. If clicking a button is not a result of a user action then what is?

It is pretty important to understand why this is behaving the way it does, you are liable to get yourself into trouble when you rely in the CloseReason too much. This is not a bug, it is a restriction due to the way Windows was designed. One core issue is the way the WM_CLOSE message is formulated, it is the one that sets the train in motion, first firing the FormClosing event.
This message can be sent for lots of reasons, you are familiar with the common ones. But that's not where it ends, other programs can send that message as well. You can tell the "flaw" from the MSDN Library article I linked to, the message is missing a WPARAM value that encodes the intent of the message. So there isn't any way for a program to provide a reasonable CloseReason back to you. Winforms is forced to guess at a reason. It is of course an entirely imperfect guess.
That's not where it ends, the DialogResult property is a problem as well. It will force a dialog to close when any code assigns that property. But again the same problem, there isn't any way for such code to indicate the intent of the assignment. So it doesn't, it leaves in internal Form.CloseReason property at whatever value it had before, None by default.
This was "properly" implemented in .NET 1.0, there was only the Closing event and it didn't give a reason at all. But that didn't work out so well either, apps that used it chronically prevented Windows from shutting down. They just didn't know that it was inappropriate to, say, display a message box. The .NET 2.0 FormClosing event was added as a workaround for that. But it needs to work with the imperfect guess.
It is important to rate the CloseReason values, some are very accurate and some are just guesses:
CloseReason.WindowsShutdown - reliable
CloseReason.ApplicationExitCall - reliable
CloseReason.MdiFormClosing - reliable, not very useful
CloseReason.FormOwnerClosing - reliable, not very useful
CloseReason.TaskManagerClosing - complete guess, will be returned when any program sends a WM_CLOSE message, not just Task Manager
CloseReason.UserClosing - complete guess, will also be returned when your program calls the Close() method for example
CloseReason.None - it just doesn't know.
Yes, Winforms not setting the CloseReason back to None when your FormClosing event handler cancels is arguably a bug. But it isn't the kind of bug that actually really matters. Since you can't treat UserClosing and None differently anyway.

I would probably call that a bug.
As you mentioned, the CloseReason property is marked internal (or Friend in VB.Net terms) so one work-around to the problem is using Reflection to reset that value yourself:
Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
If Not exitPending Then
e.Cancel = True
tbOutput.AppendText(String.Format("DialogResult={0}, CloseReason={1}{2}", _
Me.DialogResult.ToString(), e.CloseReason.ToString(), _
Environment.NewLine))
Dim pi As PropertyInfo
pi = Me.GetType.GetProperty("CloseReason", _
BindingFlags.Instance Or BindingFlags.NonPublic)
pi.SetValue(Me, CloseReason.None, Nothing)
End If
MyBase.OnFormClosing(e)
End Sub
No guarantee that this code would work on future versions of WinForms, but I'm guessing it's a safe bet these days. :-)

Private Const WM_SYSCOMMAND As Int32 = &H112
Private Const SC_CLOSE As Int32 = &HF060
'Private Const SC_MAXIMIZE As Int32 = &HF030
'Private Const SC_MINIMIZE As Int32 = &HF020
'Private Const SC_RESTORE As Int32 = &HF120
Private _commandClose As Boolean = False
Protected Overrides Sub WndProc(ByRef m As Message)
If CInt(m.Msg) = WM_SYSCOMMAND Then
If (m.WParam.ToInt32 And &HFFF0) = SC_CLOSE Then _commandClose = True
End If
MyBase.WndProc(m)
End Sub
Private Sub baseClick(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Click
Close()
End Sub
Protected Overrides Sub OnFormClosing(ByVal e As FormClosingEventArgs)
If _commandClose Then DialogResult = ' ...
MyBase.OnFormClosing(e)
End Sub
reference: MSDN - WM_SYSCOMMAND message
hmm, actually, this does work. but unlike the official docs, SC_CLOSE fires for Alt+F4, etc... as well, even though not specifically mentioned.
It does not fire when calling the Form.Close() method. therefore, working as intended.
however, it will still return UserClosing if you call the Close() method, which is by design.
note: SC_SCREENSAVE can be used to detect/prevent screensavers, along with SC_MONITORPOWER. the documentation on that seems a bit vague.

Related

Parent form sometimes does not close (Only in Windows 10)

My main form is frmInvoice. This sub is located inside frmInvoice.
This is one of the Subs that sometimes causes frmDark to not close. frmLookup does not display when this happens. frmDark just stays there covering frmInvoice. It's like it doesn't reach the call to frm.ShowDialog(frmDark), cause when I press the lookup key, it displays the frmLookup, but upon closing frmLookup, frmDark is still there.
No exception is being raised.
Note that this only happens in Windows 10. In Windows 8/7, this never happened. What am I missing?
This happens at different times. Sometimes I could press the lookup key for 20 times and it will display fine. Sometimes, after 1 press of the lookup key and this happens.
Private Sub ItemLookup()
Try
Using frmDark As New Form
With frmDark
.ShowInTaskbar = False
.Icon = Me.Icon
.FormBorderStyle = Windows.Forms.FormBorderStyle.None
.BackColor = Color.Black
.Opacity = 0.95
.WindowState = FormWindowState.Maximized
.Show(Me)
Using frm As New frmLookup
With frm
.Icon = Me.Icon
.ShowDialog(frmDark)
frmDark.Close()
If .DialogResult = Windows.Forms.DialogResult.OK Then
' Do stuff here
End If
End With
End Using
End With
End Using
Catch ex As Exception
ErrMsg(ex)
End Try
End Sub
UPDATE: I'm using .Net Framework 4.8
Thanks
I would suggest rearranging the code like so:
Dim lookupResult As DialogResult
Using frmDark As New Form With {.ShowInTaskbar = False,
.Icon = Me.Icon,
...}
frmDark.Show(Me)
Using frm As New frmLookup With {.Icon = Me.Icon}
lookupResult = frm.ShowDialog(frmDark)
End Using
End Using
If lookupResult = DialogResult.OK Then
'...
End If
Because that code exits the Using block that created frmDark, there should be no way that it can't close.
Also, instead of using a vanilla Form and configuring it on demand, I would suggest that you create a dedicated form type to use as the overlay in that scenario. You can then get rid of all the property assignments.
Having a dedicated overlay form would also allow you to reconfigure things significantly and, in my opinion, better. The overlay form could have a property of type Form. You main form could then create a frmLookup instance and assign it to that property, than call ShowDialog on the overlay form. In the Shown event handler of the overlay form, it could then call ShowDialog on the form in that property. When that call returns, it could assign the result to its own DialogResult property and close itself. The main form would then just get the result from calling ShowDialog on the overlay. That might look like this:
Public Class OverlayForm
Public Property DialogueForm As Form
Private Sub OverlayForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
DialogResult = DialogueForm.ShowDialog()
End Sub
End Class
and this:
Public Class MainForm
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using dialogue As New DialogueForm,
overlay As New OverlayForm With {.DialogueForm = dialogue}
If overlay.ShowDialog() = DialogResult.OK Then
MessageBox.Show("OK")
End If
End Using
End Sub
End Class

VB.NET - How to block user input on a windows form without graying it out?

I am trying to block any sort of user input on a modal dialog that can be used to perform a slow operation that takes a few minutes to finish. The only solution for this which I found so far is to simply set the form's Enabled property to false, however that also grays out the entire form. In addition to that it also for some reason prevents me from showing a wait cursor because that only seems to work while the form is not disabled.
How can I block any user input on my form while the operation is not finished without graying the form out and while still allowing me to set a wait cursor?
I am trying to block any sort of user input
User input is sent to the application in the form of window's messages. You can install a filter to block the messages related to keyboard and mouse input. This is done by defining a class that implements the IMessageFilter Interface and the filter is installed by calling Application.AddMessageFilter and passing an instance of the class.
The following defines a class that implements IMessageFilter. If the application recieves a message defined in the FilteredMessages enum, it will block further processing of that message. The class is self contained and exposes two static methods:
Install
Uninstall
Public Class InputFilter : Implements IMessageFilter
Private Shared sharedInstance As InputFilter
Private handledMessages As FilteredMessages() = CType([Enum].GetValues(GetType(FilteredMessages)), FilteredMessages())
Private Sub New()
End Sub
Private Enum FilteredMessages
#Region "Keyboard"
WM_KEYDOWN = &H100
WM_KEYUP = &H101
WM_SYSKEYDOWN = &H104
WM_SYSKEYUP = &H105
#End Region
#Region "Mouse"
WM_LBUTTONDOWN = &H201
WM_LBUTTONUP = &H202
WM_LBUTTONDBLCLK = &H203
WM_RBUTTONDOWN = &H204
WM_RBUTTONUP = &H205
WM_RBUTTONDBLCLK = &H206
'prevent interaction with title bar
WM_NCLBUTTONDOWN = &HA1
WM_NCLBUTTONUP = &HA2
WM_NCLBUTTONDBLCLK = &HA3
WM_NCRBUTTONDOWN = &HA4
WM_NCRBUTTONUP = &HA5
WM_NCRBUTTONDBLCLK = &HA6
' prevent mouse hover/enter/move events
WM_MOUSEHOVER = &H2A1
WM_MOUSEMOVE = &H200
#End Region
End Enum
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
Return handledMessages.Contains(CType(m.Msg, FilteredMessages))
End Function
Public Shared Sub Install()
If sharedInstance Is Nothing Then
sharedInstance = New InputFilter
Application.AddMessageFilter(sharedInstance)
End If
End Sub
Public Shared Sub Uninstall()
If sharedInstance IsNot Nothing Then
Application.RemoveMessageFilter(sharedInstance)
sharedInstance = Nothing
End If
End Sub
End Class
To test this class create a new WinForm project and add two Buttons, a Label, and a Textbox to the form in the designer. Then replace the form's code with the following code.
Public Class Form1
Private tmr As New Timer() With {.Interval = 10000}
Private tickHandler As EventHandler = Sub(s, e)
tmr.Stop()
RemoveHandler tmr.Tick, tickHandler
InputFilter.Uninstall()
Label1.Text = "filter off"
Application.UseWaitCursor = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
InputFilter.Install()
Application.UseWaitCursor = True
AddHandler tmr.Tick, tickHandler
tmr.Start()
Label1.Text = "filter on"
Label1.Refresh()
TextBox1.Select()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
MessageBox.Show("clicked")
End Sub
End Class
Run the project and click on Button1. The Button1_Click handler installs the filter, activates the WaitCursor and starts a timer. The timer will elapse after ten seconds and will uninstall the filter. While the filter is active, you will not be able to interact with the form using either the keytboard or mouse.
For information on the filtered messages, see Mouse Input Notifications and Keyboard Input Notifications.

Progress Bar will not work, even when all the program does is show a Progress Bar

I have a form which, at present, is doing nothing but opening. The form has 2x controls - a 'Close' button and a Progress Bar. However, when I open the form, I get nothing. The Progress Bar just sits there doing nothing.
I've tried both Marquee (which I understand may not work in Windows 8) and Continuous, but I can't get anywhere with either.
This is how I'm showing the form when the program starts -
Sub main()
Dim ProgressForm As New formProgress
ProgressForm.ShowDialog()
End Sub
And below are the properties for the Progress Bar. Am I missing something that would get this bar working? Thanks.
Additional Information
For my full program, I did originally try using the Block style for the Progress Bar, but I kept getting the following error when trying to update the Progress Bar from a BackgroundWorker. This is why I am trying to get a simple Marquee/Continuous bar working instead.
Additional information: Cross-thread operation not valid: Control 'proWorking' accessed from a thread other than the thread it was created on.
if you use marquee style you have to set marqueeanimationspeed to some value
//
// progressBar1
//
this.progressBar1.Location = new System.Drawing.Point(91, 118);
this.progressBar1.MarqueeAnimationSpeed = 50;
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(100, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 0;
and use continuous style with marqueeanimationsspeed 0 to stop it
For the Progressbar to do something (apart from the Marquee Style) you need to set the Value property. If you have .Minimum=0 and .Maximum=100 then a .Value of 50 means that the Progressbar is half full. If you should use Continuous or Blocks Style depends on the Visual Styles settings and doesn't make a real difference here on Win 7 (maybe it does for example under Win XP).
The Marquee style means that you don't know how far your task has proceeded. The Progressbar then shows a continously moving piece (is only visible at runtime!!). I just tested it in Win 7 and it works.
Here's a little boilerplate I use with a BackgroundWorker and a ProgressBar and Label.
Public Class BackgroundWorkerUI
Private args As New ProgressArgs
Private Sub bw_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
'set ProgressBar style to Marquee
args.Style = ProgressBarStyle.Marquee
bw.ReportProgress(0) 'triggers the progress changed event to update UI on correct thread
'some long operation
Threading.Thread.Sleep(5000)
'Set ProgressBar style to Continuous
args.Style = ProgressBarStyle.Continuous
For i As Integer = 0 To 100
If bw.CancellationPending Then
e.Cancel = True
Exit For
End If
args.Current = i
args.Max = 100
args.Status = String.Format("({0} of {1}) Updating...", args.Current, args.Max)
bw.ReportProgress(0)
'some operation
Threading.Thread.Sleep(100)
Next
End Sub
Private Sub bw_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bw.ProgressChanged
lblStatus.Text = args.Status
If args.Style = ProgressBarStyle.Marquee Then
bar.Style = args.Style
bar.MarqueeAnimationSpeed = 15
Else
bar.Style = ProgressBarStyle.Continuous
bar.Minimum = args.Min
bar.Maximum = args.Max
bar.Value = args.Current
End If
End Sub
Private Sub bw_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
If e.Error IsNot Nothing Then
MessageBox.Show(e.Error.Message, "Background Worker Exception", MessageBoxButtons.OK, MessageBoxIcon.Error)
Else
If e.Cancelled Then
lblStatus.Text = "Operation canceled"
Else
lblStatus.Text = "Done"
End If
End If
End Sub
Private Sub BackgroundWorkerUI_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If bw.IsBusy Then
Dim r = MessageBox.Show("A background process is still running. Are you sure you want to quit?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If r = Windows.Forms.DialogResult.Yes Then
bw.CancelAsync()
End If
e.Cancel = True
End If
End Sub
Private Class ProgressArgs
Inherits EventArgs
Public Property Status As String
Public Property Current As Integer
Public Property Min As Integer
Public Property Max As Integer
Public Property Style As ProgressBarStyle
Public Sub New()
Status = ""
Current = 0
Min = 0
Max = 0
Style = ProgressBarStyle.Continuous
End Sub
End Class
End Class

Cursor.Wait after printDialog

I'm having a little problem. I set the cursor to wait status. After calling the PrintDialog the cursor returns in default status. I can't set the cursor to wait status again. The code is like this:
Cursor.Current = Cursors.WaitCursor
[...]
Dim result As DialogResult = printDialog.ShowDialog()
If result = DialogResult.Cancel Then
Return
End If
Cursor.Current = Cursors.WaitCursor
[...]
I just did a small test with your code. When using your code my VS2012 didn't show up Cursor.Current but did not throw any exception when using it. So I changed it to
Me.Cursor = Cursors.WaitCursor
Dim result As DialogResult = printDialog.ShowDialog()
If result = DialogResult.Cancel Then
Return
End If
' not necesary any more
'Cursor.Current = Cursors.WaitCursor
and the WaitCursor stayed after showing the printDialog.
EDIT: Found a pretty good explanation on difference between Cursor.Current and Cursor!
EDIT2: I changed my code to make use of HourGlass class from #HansPassant's example stated above. WaitCursor now stays even if you enter a textBox. Anyways - I was still able to get loss of the waitCursor when hovering over the border of eg. a textBox.
All in all IMO I think it's not very good to force a waitCursor when it is still possible to enter text aso. Perhaps you may consider disabling controls until some kind of actions has finished and afterwards change cursor back.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Hourglass.Enabled = True
Dim result As DialogResult = PrintDialog1.ShowDialog()
If result = Windows.Forms.DialogResult.Cancel Then
Return
End If
'Cursor.Current = Cursors.WaitCursor
End Sub
Hourglass.vb - I hope I did not make any mistakes when converting it to vb.net
Public Class Hourglass
Implements IDisposable
Public Shared Property Enabled As Boolean
Get
Return Application.UseWaitCursor
End Get
Set(ByVal value As Boolean)
If value = Application.UseWaitCursor Then Return
Application.UseWaitCursor = value
Dim f As Form = Form.ActiveForm
If Not f Is Nothing AndAlso f.Handle <> IntPtr.Zero Then
SendMessage(f.Handle, 32, f.Handle, 1)
End If
End Set
End Property
<System.Runtime.InteropServices.DllImport("user32.dll")>
Private Shared Function SendMessage(hWnd As IntPtr, msg As IntPtr, wp As IntPtr, lp As IntPtr) As IntPtr
End Function
Public Sub Dispose() Implements IDisposable.Dispose
Enabled = False
End Sub
End Class

Mimic Disabled Focus Behaviour in WinForms

In accordance to this Post I'm trying to mimic the behavior of
Enabled = False
without actually disable the Control. (In my case a multiline TextBox)
The next I'm trying to accomplish is to mimic the focus behavior by mouse of a disabled control. If I click on a disabled control it won't get the focus and the control that previously had focus won't loose the focus.
What I came up with so far: I can intercept the WM_SETFOCUS message in WndProc so my control won't recieve focus.
Private Const WM_SETFOCUS = &H7
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If Me.ReadOnly AndAlso (m.Msg = WM_SETFOCUS) Then Exit Sub
MyBase.WndProc(m)
End Sub
The problem with that is, that the previous contol lost the focus, which isn't intended. How do I prevent that even the click by mouse will do anything in the focus behaviour? Is there any way to do this?
Update: 06.08.12
As suggested by Justin I solved the problem by changing it to a label in an autoscroll panel. A minimal code example is as followed:
Imports System.Windows.Forms
Public Class ScrollableDisabledTextBox
Inherits TextBox
Private xLabel As Label
Private xPanel As Panel
Public Sub New()
InizializeComponent()
End Sub
Private Sub InizializeComponent()
xPanel = New Panel
xPanel.AutoScroll = True
xPanel.BorderStyle = BorderStyle.FixedSingle
xLabel = New Label
xLabel.Enabled = False
xLabel.AutoSize = True
xPanel.Controls.Add(xLabel)
Me.Me_SizeChanged()
End Sub
Private Sub Me_EnabledChanged() Handles Me.EnabledChanged
If Me.Enabled Then
Me.Show()
xPanel.Hide()
Else
xPanel.Show()
Me.SendToBack()
Me.Hide()
End If
End Sub
Private Sub Me_TextChanged() Handles Me.TextChanged
xLabel.Text = Me.Text
End Sub
Private Sub Me_SizeChanged() Handles Me.SizeChanged
xPanel.Size = Me.Size
xLabel.MaximumSize = New System.Drawing.Size(xPanel.Size.Width, 0)
End Sub
Private Sub Me_ParentChanged() Handles Me.ParentChanged
xPanel.Location = Me.Location
'If parent changed multiple times, remember to remove panel from old parent!
If Not Me.Parent.Controls.Contains(xPanel) Then
Me.Parent.Controls.Add(xPanel)
End If
End Sub
End Class
I do not believe what you want to do is possible. If you do not have focus, then the scrolling will not work.
However, I posit that you should rethink your original problem. Why not use an AutoSize = true, MaximumSize.Width = ParentWidth label (which could be disabled) inside of a panel that will autoscroll. This sounds like what you are really looking for.