I'm working on an encryption program and it uses a "PIN" to calculate some stuff for the encryption. I have a textbox where the user can insert the "PIN". I'd like to prevent people from entering anything but numbers. I added this on the KeyPress event:
If Not Char.IsControl(e.KeyChar) Then
If Not Char.IsNumber(e.KeyChar) Then
MsgBox("Invalid character", , "WARNING!")
TextBox3.Clear()
End If
End If
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox. The typed character appears in the textbox. When I write another one it works the same as before, but it only replaces the last character instead of writing another one. Is there something I'm missing because that looks like a bug to me?
Set the ES_NUMBER windows style for your TextBox:
Public Class Form1
Public Const GWL_STYLE As Integer = (-16)
Public Const ES_NUMBER As Integer = &H2000
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer) As Integer
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal handle As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer
Public Sub SetNumbersOnlyTextBox(ByVal TB As TextBox)
SetWindowLong(TB.Handle, GWL_STYLE, GetWindowLong(TB.Handle, GWL_STYLE) Or ES_NUMBER)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
SetNumbersOnlyTextBox(TextBox3)
End Sub
End Class
It shows the msgbox and it doesn't write to the textbox until i close th emsgbox.
Yes, that's what modal dialogs do. They block the caller from updates until closed. That's the point; the user cannot interact with the parent until they clear the modal child.
Why not simply clear the textbox first? Better yet; don't show an annoying dialog at all. Simply disallow the user from entering invalid characters by setting e.Handled to true. However, it's a bit trickier than it sounds as you need to allow for the backspace and delete keys, disable pasting, etc.
Here's an example of a NumericTextbox: http://msdn.microsoft.com/en-us/library/ms229644(v=vs.80).aspx
You just need to set the Handled property to true instead of clear:
e.Handled = True
As MarkPM notes above, if its a key you don't want you can set e.handle=true (as you intercept the key on the keypress event) to have the system eat it.
Along with this, in stead of a pop-up, you can have a label on the form that says "Only numbers can be entered here" or something like that. Set it up so that the color of the text is red. Also set it up so the label is not normally visible.
Finally, also in the keypress event, beyond setting e.handle=true for unwanted keys, when an unwanted key comes along make the label that says "Only numbers can be entered here" visible - you can also set up a timed event to turn the label's visibility off after a few seconds. You can also throw a Beep() into the mix if you like :-)
This is less invasive then a pop-up and moves things along nicely for the user.
Related
We kept a menu item short cut key (Ctrl+Shift+X) to pop up another form in Windows VB application. But when user tried to cut using (Ctrl+X) text from any field the above short cut action is firing and popping up another form.
No sure anything we missed or we should not use (Ctrl+Shift+X) as shortcut.
Me.mnuTools.Shortcut = System.Windows.Forms.Shortcut.CtrlShiftX
worst case we can change short cut but we wish to know is it something wrong using the short cut.
Please provide your views.
I can't answer why it thinks CTRL + X should call a CTRL + SHIFT + X hotkey, but I do believe I have a workaround.
When a cut action is performed Windows sends a WM_CUT message to the focused control. If we override ProcessCmdKey() we can stop the CTRL + X combination from reaching any child controls of the form and instead send the WM_CUT message ourselves.
Untested, but I believe it should work:
<DllImport("user32.dll")> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr)
End Function
Private Const WM_CUT As Integer = &H300
Protected Overrides Function ProcessCmdKey (ByRef msg As Message, keyData As Keys) As Boolean
If keyData = (Keys.ControlKey Or Keys.X) AndAlso _
Me.ActiveControl IsNot Nothing Then
SendMessage(Me.ActiveControl.Handle, WM_CUT, IntPtr.Zero, IntPtr.Zero) 'Send the WM_CUT message to the currently focused control.
Return True 'Indicate that we've handled the message and stop the key strokes from reaching any child controls.
End If
End Function
EDIT:
Future readers: I am aware that you can define the hotkeys on your own and call the respective methods in KeyDown or ProcessCmdKey, but I used the method above so that the menu item still can display its CTRL + SHIFT + X hint.
I am currently working on a TextBox using VB.NET 2015 that is read-only and only inserts characters by a button click event. I want to hide or disable the iBeam inside the TextBox to let the user know that it is only accessible by the button click and not by manual typing on the actual keyboard. I have tried changing its ReadOnly property to True and cursor property to cursors other than the iBeam but they don't seem to work.
Is there another way, may it be a code or a property that disables the iBeam in the TextBox when its accessed?
This image is my example of an on-screen keyboard. As you can see, the iBeam on the TextBox is visible as soon as I click on one of the on-screen keys.
Use the HideCaret() API call from the GotFocus() event of your TextBox:
Private Declare Function HideCaret Lib "user32.dll" (ByVal hWnd As IntPtr) As Boolean
Private Sub TextBox1_GotFocus(sender As Object, e As EventArgs) Handles TextBox1.GotFocus
HideCaret(TextBox1.Handle)
End Sub
I need to know how I can send a message to any text box of windows.
If a focus the google chrome url textbox, then I will "auto paste" the message, or if I focus a Word Document string, or notepad, or anything!
I got a code ho sends by setting the iHwnd, findwindow and findwindowex, but I need to set any time I want to change the final program, and thats why I need an automatic program "focus based".
Here is what I have so far...
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim iHwnd As IntPtr = FindWindow("notepad", vbNullString)
Dim iHwndChild As IntPtr = FindWindowEx(iHwnd, IntPtr.Zero, "Edit", vbNullString)
SendMessage(iHwndChild, WM_SETTEXT, 0, "Hello World!")
End Sub
Sorry for my bad english!
SendMessage is always going to require a specific window handle, or broadcast to all top level windows. To continue with your current code, you could first try to retrieve the active window's handle with GetActiveWindow or similar function.
Alternately, you could experiment with the SendKeys class to send your text. SendKeys always targets the currently active control (as if the user were typing directly on the keyboard), so you don't need to concern yourself with finding window handles or titles.
This is more a design than a programming question (well, maybe not). I have 4 CheckedListBoxes which are filled with Data from an SQLite-database (Visual Studio 2010), and some of the entries exceed the width of the Box. I know that I can include a horizontal scrollbar in a CheckedListBox but everybody hates horizontal scrollbars (very ugly), so I tried to find an option to automatically wrap the text that doesn't fit.
So if there is any solution to have the text wrapped when the width of the box is too small it would be awesome.
I could extend the window size but it is already over 1000px in width and some of the users use computers made of wood with 1024x768 solution, so that's not really an option.
Datagrid would be another option but I thought there must be an easier solution. Any hints?
Edit: Sorry, it's Windows Forms.
You can write your own CheckedListBox pretty easily using a panel with some actual CheckBoxes on it so you can do the other things you'd expect such as disable certain ones, fix the way it cuts off drop characters, iterate them and so forth.
The problem with wrap, is a) determining the Text Extent of long text so you know how tall to make each checkbox, and b) having to keep a cumulative items height so you know where to add the next one. Of course, once you support wrap, you have to be able to adjust them all which involves moving them when a text change causes one in the middle to grow/shrink.
The panel's AutoScroll handles all the scrolling for you, which includes adding a HSCroll as needed, which is not always desirable. One way to overcome this, which might work for the actual CheckedListBox you are using, is to eat the HScroll instead.
<DllImport("user32.dll")> _
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Then in form load or perhaps after you have populated it:
ShowScrollBar(myControl.Handle, ScrollBarDirection.SB_HORZ, False)
You can also just subclass the existing CheckedListBox to eat the scrollbar in OnClientSizeChanged
Public Class CheckedListBox2
Inherits CheckedListBox
' optionally remove the scroll bar
Public Property VerticalScrollOnly As Boolean
' PInvokes
<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function GetWindowLong(ByVal hWnd As IntPtr,
ByVal nIndex As Integer) As Integer
End Function
<DllImport("user32.dll")>
Private Shared Function ShowScrollBar(hWnd As IntPtr,
wBar As Integer,
bShow As Boolean) _
As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
'// window style constants for scrollbars
Private Const WS_VSCROLL As Integer = &H200000
Private Const WS_HSCROLL As Integer = &H100000
Private Const GWL_STYLE As Integer = -16
Private Enum ScrollBarDirection
SB_HORZ = 0
SB_VERT = 1
SB_CTL = 2
SB_BOTH = 3
End Enum
' eat the HScroll when it shows up
Protected Overrides Sub OnClientSizeChanged(e As EventArgs)
Dim HScrollVis As Boolean
HScrollVis = IsHScrollVisible(Me)
If VerticalScrollOnly AndAlso HScrollVis Then
ShowScrollBar(MyBase.Handle, ScrollBarDirection.SB_HORZ, False)
End If
MyBase.OnClientSizeChanged(e)
End Sub
Friend Shared Function IsHScrollVisible(ByVal ctl As Control) As Boolean
Dim wndStyle As Integer = GetWindowLong(ctl.Handle, GWL_STYLE)
Return ((wndStyle And WS_HSCROLL) <> 0)
End Function
End Class
Preface: I know this is an unusual/improper way to do this. I can do this with a "real" ShowDialog(), background worker/thread, and so on. I'm not looking for help doing it that way; I am trying to do specifically what I describe here, even if it is ugly. If this is impossible for X reason, please let me know though.
I have created a fancy progress dialog for some of our long running operations. I need to have this dialog shown on a new thread while having processing continue on the calling (UI in most cases) thread.
This has 3 real requirements:
Prevent user interaction with the calling form (similar to ShowDialog(this))
Keep the progress dialog above the main window (it can fall behind now)
Allow the main thread to continue processing
What I have looks like this (and works just fine so far, as far as running goes, except for those issues above):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
I have tried a few ways to do this:
ShowDialog() on BackgroundWorker / Thread
Action.BeginInvoke() which calls a function
ProgressForm.BeginInvoke(... method that calls ShowDialog... )
Wrapping main form in a class that implements IWin32Window so it can be called cross-threaded and passed to ShowDialog() - this one failed somewhere later one, but at least causes ShowDialog() to not barf immediately.
Any clues or wisdom on how to make this work?
Solution (For Now)
The call to EnableWindow is what did what I was looking for.
I do not experience any crashes at all
Changed to use ManualResetEvent
I set TopMost, because I couldn't always guarantee the form would end up on top otherwise. Perhaps there is a better way.
My progress form is like a splash screen (no sizing, no toolbar, etc), perhaps that accounts for the lack of crashes (mentioned in answer)
Here is another thread on the EnableWindow topic (didn't reference for this fix, tho)
Getting the progress window consistently displayed on top of the (dead) form is the difficult requirement. This is normally handled by using the Form.Show(owner) overload. It causes trouble in your case, WF isn't going to appreciate the owner form belonging to another thread. That can be worked around by P/Invoking SetWindowLong() to set the owner.
But now a new problem emerges, the progress window goes belly-up as soon as it tries to send a message to its owner. Somewhat surprisingly, this problem kinda disappears when you use Invoke() instead of BeginInvoke() to update progress. Kinda, you can still trip the problem by moving the mouse over the border of the disabled owner. Realistically, you'll have to use TopMost to nail down the Z-order. More realistically, Windows just doesn't support what you are trying to do. You know the real fix, it is at the top of your question.
Here's some code to experiment with. It assumes you progress form is called dlgProgress:
Imports System.Threading
Public Class ShowProgress
Implements IDisposable
Private Delegate Sub UpdateProgressDelegate(ByVal pct As Integer)
Private mOwnerHandle As IntPtr
Private mOwnerRect As Rectangle
Private mProgress As dlgProgress
Private mInterlock As ManualResetEvent
Public Sub New(ByVal owner As Form)
Debug.Assert(owner.Created)
mOwnerHandle = owner.Handle
mOwnerRect = owner.Bounds
mInterlock = New ManualResetEvent(False)
Dim t As Thread = New Thread(AddressOf dlgStart)
t.SetApartmentState(ApartmentState.STA)
t.Start()
mInterlock.WaitOne()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
mProgress.BeginInvoke(New MethodInvoker(AddressOf dlgClose))
End Sub
Public Sub UpdateProgress(ByVal pct As Integer)
mProgress.Invoke(New UpdateProgressDelegate(AddressOf dlgUpdate), pct)
End Sub
Private Sub dlgStart()
mProgress = New dlgProgress
mProgress.StartPosition = FormStartPosition.Manual
mProgress.ShowInTaskbar = False
AddHandler mProgress.Load, AddressOf dlgLoad
AddHandler mProgress.FormClosing, AddressOf dlgClosing
EnableWindow(mOwnerHandle, False)
SetWindowLong(mProgress.Handle, -8, mOwnerHandle)
Application.Run(mProgress)
End Sub
Private Sub dlgLoad(ByVal sender As Object, ByVal e As EventArgs)
mProgress.Location = New Point( _
mOwnerRect.Left + (mOwnerRect.Width - mProgress.Width) \ 2, _
mOwnerRect.Top + (mOwnerRect.Height - mProgress.Height) \ 2)
mInterlock.Set()
End Sub
Private Sub dlgUpdate(ByVal pct As Integer)
mProgress.ProgressBar1.Value = pct
End Sub
Private Sub dlgClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)
EnableWindow(mOwnerHandle, True)
End Sub
Private Sub dlgClose()
mProgress.Close()
mProgress = Nothing
End Sub
'--- P/Invoke
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
If IntPtr.Size = 4 Then
Return SetWindowLongPtr32(hWnd, nIndex, dwNewLong)
Else
Return SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
End If
End Function
Private Declare Function EnableWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal enabled As Boolean) As Boolean
Private Declare Function SetWindowLongPtr32 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
Private Declare Function SetWindowLongPtr64 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
End Class
Sample usage:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Using dlg As New ShowProgress(Me)
For ix As Integer = 1 To 100
dlg.UpdateProgress(ix)
System.Threading.Thread.Sleep(50)
Next
End Using
End Sub
I know it's a bit dirty but can't you just do the work in the dialog??
I mean something like
Dialog.MyShowDialog(callback);
and do all the work in callback as well as the UI update.
That way you'll retain the ShowDialog behaivour while allowing different code to be called.
I wrote a blog post on this topic a while ago (dealing with splash forms, but the idea is the same). The code is in C#, but I will try to convert it an post it here (coming...).