Block focus/select in a ReadOnly textbox - vb.net

Someone knows how to block the focus/select in a read-only textbox (ReadOnly = true), without using enabled = false?
Thanks!

Controls have a GotFocus Event. You can add an event handler for this event and give another control focus, for example by calling Select() on another control or by using SelectNextControl:
Private Sub MyTextBox_GotFocus(sender as Object, e as EventArgs) _
Handles MyTextBox.GotFocus
MyTextBox.Parent.SelectNextControl(MyTextBox, True, True, True, True)
End Sub
Alternately, you can create a custom control that inherits TextBox and set ControlStyles.Selectable to False.
Public Class NonSelectableTextBox Inherits TextBox
Public Sub New()
SetStyle(ControlStyles.Selectable, false)
End Sub
End Class
Setting ControlStyles.Selectable to false will make the TextBox mimic the behavior of other controls which have this bit set to False:
Label
Panel
GroupBox
PictureBox
ProgressBar
Splitter
LinkLabel (when there is no link present in the control)

I'm not sure I understand fully why you would want that. A read only text box allows selection to allow users to copy the text in there for other purposes. What I assume from your question is that you don't want the TextBox to accept input focus when a user is tabbing through controls, which I've seen to be a more common requirement.
You can achieve this via code:
TextBox1.TabStop = False
to ensure that tab doesn't direct focus to the readonly textbox. You can also achieve this in the designer using the same property as the screenshot shows.

Related

Use a virtual Keyboard on focused Textboxes and DataGridView Cells

In my Form I have various Textboxes that I write into with an in Form keyboard I created using Buttons. I have this code in Form.Load, which uses an event handler to determine which Textbox has the Focus:
For Each control As Control In Me.Controls
If control.GetType.Equals(GetType(TextBox)) Then
Dim textBox As TextBox = control
AddHandler textBox.Enter, Sub() FocussedTextbox = textBox
End If
Next
Then I use this on each button to write a specific character:
Private Sub btnQ_Click(sender As Object, e As EventArgs) Handles btnQ.Click
If btnQ.Text = "Q" Then
FocussedTextbox.Text += "Q"
ElseIf btnQ.Text = "q" Then
FocussedTextbox.Text += "q"
End If
End Sub
Up to that point I'm good and everything works as intended. The problem is I also have a DataGridView I want to write into but can't focus on it selected cells as I do on Textboxes.
I tried this:
For Each control As Control In Me.Controls
If control.GetType.Equals(GetType(TextBox)) Then
Dim textBox As TextBox = control
AddHandler textBox.Enter, Sub() FocussedTextbox = textBox
ElseIf control.GetType.Equals(GetType(DataGridViewCell)) Then
Dim DGVC As DataGridView = control
AddHandler DGVC.CellBeginEdit, Sub() FocussedTextbox = DGVC
End If
Next
But it just selects my last Textbox.
I declared the variable FocussedTextbox as Control so it's not specific to Textbox but any control.
Any help will be greatly appreciated.
To add text to the current ActiveControl using Buttons, these Button must not steal the focus from the ActiveControl (otherwise they become the ActiveControl).
This way, you can also avoid all those FocusedTextbox = textBox etc. and remove that code.
You just need Buttons that don't have the Selectable attribute set. You can use a Custom Control derived from Button and remove ControlStyles.Selectable in its constructor using the SetStyle method:
Public Class ButtonNoSel
Inherits Button
Public Sub New()
SetStyle(ControlStyles.Selectable, False)
End Sub
End Class
Replace your Buttons with this one (or, well, just set the Style if you're already using Custom Controls).
To replace the existing Buttons with this Custom Control:
Add a new class object to your Project, name it ButtonNoSel, copy all the code above inside the new class to replace the two lines of code you find there.
Build the Project. You can find the ButtonNoSel Control in your ToolBox now. Replace your Buttons with this one.
Or, open up the Form's Designer file and replace (CTRL+H) all System.Windows.Forms.Button() related to the Virtual KeyBoard with ButtonNoSel.
Remove the existing event handlers, these are not needed anymore.
Add the same Click event handler in the Constructor of the class that hosts those Buttons (a Form or whatever else you're using).
You can then remove all those event handlers, one for each control, that you have now; only one event handler is needed for all:
Public Sub New()
InitializeComponent()
For Each ctrl As Control In Me.Controls.OfType(Of ButtonNoSel)
AddHandler ctrl.Click, AddressOf KeyBoardButtons_Click
Next
End Sub
Of course, you also don't need to add event handlers to any other control, this is all that's required.
Now, you can filter the Control types you want your keyboard to work on, e.g., TextBoxBase Controls (TextBox and RichTextBox), DataGridView, NumericUpDown etc.
Or filter only special cases that need special treatment (e.g., MonthCalendar).
To add the char corresponding to the Button pressed, you can use SendKeys.Send(): it will insert the new char in the current insertion point, so you don't need any other code to store and reset the caret/cursor position as it happens if you set the Text property of a Control.
In this example, I'm checking whether the ActiveControl is a TextBoxBase Control, then just send the char that the clicked Button holds.
If it's a DataGridView, first send F2 to enter Edit Mode, then send the char.
You could also just send a char (so, no filter would be required), but in this case, you'll replace, not add to, the existing value of that Cell.
Private Sub KeyBoardButtons_Click(sender As Object, e As EventArgs)
Dim selectedButton = DirectCast(sender, Control)
Dim keysToSend = String.Empty
If TypeOf ActiveControl Is TextBoxBase Then
keysToSend = selectedButton.Text
ElseIf TypeOf ActiveControl Is DataGridView Then
Dim ctrl = DirectCast(ActiveControl, DataGridView)
If TypeOf ctrl.CurrentCell IsNot DataGridViewTextBoxCell Then Return
SendKeys.Send("{F2}")
keysToSend = selectedButton.Text
Else
' Whatever else
End If
If Not String.IsNullOrEmpty(keysToSend) Then
SendKeys.Send(keysToSend)
End If
End Sub
► Note that {F2} is sent just once: when the Cell enters Edit Mode, the ActiveControl is a DataGridViewTextBoxEditingControl, hence a TextBox Control, handled by the TextBoxBase filter.
This is how it works (using just the code posted here):

DataBinding - Toggle Enabled Based on Input

I'm using Visual Basic .NET developing a windows form application and I want to toggle the enabled property of a Button based on if there is anything in a TextBox. I attempted to setup the DataBinding using the following:
ButtonBack.DataBindings.Add("Enabled", Me.TextBoxValue, "TextLength")
This successfully disables the Button, but whenever anything is typed in the TextBox, the Button never gets enabled. I would much rather do this via DataBindings if possible as opposed to setting the Enabled property manually in the TextChanged or Validating event of the TextBox.
I usually implement this kind of custom property binding by "subclassing" the control that requires the "new" property, as in your case, TextBox needs an HasLength Boolean property (or whatever name you want to give it).
Here's MyTextBox.vb:
Public Class MyTextBox
Inherits TextBox
Public Event HasLengthChanged As EventHandler
Private _HasLength As Boolean
Public Property HasLength() As Boolean
Get
Return _HasLength
End Get
Set(ByVal value As Boolean)
If value <> _HasLength Then
_HasLength = value
OnHasLengthChanged()
End If
End Set
End Property
Public Sub New()
_HasLength = False
End Sub
Protected Sub OnHasLengthChanged()
RaiseEvent HasLengthChanged(Me, New EventArgs())
End Sub
Protected Overrides Sub OnTextChanged(e As EventArgs)
MyBase.OnTextChanged(e)
HasLength = Not (String.IsNullOrEmpty(Text))
End Sub
End Class
And here's Form1.vb:
Public Class Form1
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
ButtonBack.DataBindings.Add("Enabled", Me.MyTextBox1, "HasLength", True, DataSourceUpdateMode.OnPropertyChanged)
End Sub
End Class
Of course, you need a Form with a MyTextBox instance and a regular Button instance.
With this method, as soon as the user types something in the textbox, the button becomes enabled, and as soon as the textbox becomes empty, the button is disabled. Please note that I used the DataSourceUpdateMode.OnPropertyChanged so that the update is done as you type, and not only when the textbox loses focus.
EDIT: I thought I'd add a bit of background about the PropertyNameChanged pattern in .NET.
There are different ways to let observers know that a property has changed on an object, and most of the time we use the INotifyPropertyChanged interface, but for controls, the recommended method is still to use the PropertyNameChanged pattern, which is recognized by .NET, and thus the Binding object can do its job and know when the desired property has changed.
INotifyPropertyChanged is recommended for data objects that participate in binding.
By the way, the PropertyNameChanged works in both Windows Forms and WPF.
As reference MSDN:
https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-apply-the-propertynamechanged-pattern
EDIT 2: I'm a C# programmer, and I've done my best to apply the residual knowledge that I have of VB.NET. Any error is really the result of my being too rusty in VB. Sorry for any inconvenience, but I can assure you that the code runs just fine here.
As #LarsTech stated in his comment, you need to use a property of the TextBox Class that raises changed notifications. The Text property is the appropriate one to use.
Your request to do this strictly via the binding is possible, but it will still involve using an event handler. As such, I don't see much value in this approach over handling the TextBoxValue.TextChanged event.
One way of doing this is to use the Binding.Format Event to convert the string of the Text Property to a boolean based on if it contains any characters.
Dim b As Binding = ButtonBack.DataBindings.Add("Enabled", Me.TextBoxValue, "Text", True, DataSourceUpdateMode.OnPropertyChanged)
AddHandler b.Format, Sub(s As Object, args As ConvertEventArgs) args.Value = Not String.IsNullOrEmpty(CStr(args.Value))
Because the binding is two way, if your code changes the ButtonBack.Enabled property, it will send the string representation of the property back to TextBoxValue and its text will read either "True" or "False". This would not be a desirable consequence. To rectify this possibility, the Binding.Parse Event will need to be handled.
AddHandler b.Parse, Sub(s As Object, args As ConvertEventArgs) args.Value = Me.TextBoxValue.Text

Enabled but unselectable menu item

In WinForms application I need some "caption" in dynamically created ContextMenuStrip.
That caption is changable text composed in ContextMenuStrip_Opening event handler.
For that purpose I'm trying to use ToolStripControlHost with label in it, like this:
Dim labelItem As ToolStripControlHost = New ToolStripControlHost(New Label)
...
labelItem.BackColor = Color.Transparent
labelItem.ForeColor = Color.FromKnownColor(KnownColor.HotTrack)
labelItem.ToolTipText = "mytooltiptext"
mycontextmenu.Items.Add(labelItem)
That work almost OK, but...
I try to disable that "labelItem" to avoid clicks and keypresses and then it becomes gray automatically what is unwanted and also then tooltiptext is not showed.
If "labelItem" is enabled then color is OK, item cannot be selected with keys but can be with mouse and on mouse click it takes focus to itself. That is also unwanted but shows tooltiptext.
Is here a way in described situation to get "labelItem" to be enabled and able to show tooltiptext but be unselectable? In short... How to make an item like is described which would be in color (enabled) but would not accept mouse clicks and take a focus?
Don't disable the item. Set the disabled state image and then in the click event handler, just ignore the case for the item you don't want to be active.
Enabled and Disabled are predetermined definitions for for the appearance and behavior of a control. Disabled will always mean the control can't be clicked.
If you need alternate behavior, you'll need to write it yourself. I would suggest tracking two global variables in your form: whether or not your item should be active in a boolean and which object currently has focus in an object. Then use these to write your click event behavior. For example:
Public Class Form1
Public RunEvent As Boolean
Public HasFocus As Object
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If RunEvent Then
'Do something
Else
HasFocus.Focus()
End If
End Sub
End Class

Close panel if clicked outside

I'm making a custom ComboBox, and I'm struggling to make it's dropdown (a Panel) close when the user clicks outside. As everyone probably knows, using LostFocus event is not enough because when the user clicks on eg.: a scroolbar or the form itself, the control doesn't lose focus. I tried using IMessageFilter, but I don't think I understood how it works. I also tried using the Capture property, forcing the dropdown to capture the mouse, but that also forces the mouse click to happen on the dropdown (Panel) itself, meaning that if the user had clicked on an item on the dropdown, the click won't work.
Let me clarify:
I'm making a custom control, a UserControl, which is compiled to a .DLL, and can be dragged'n dropped from the Toolbox onto forms, just like any other Winforms control.
The control is a Combobox.
I want to make the Combobox's dropdown (which is a panel, created at runtime) close when the user clicks outside of it, just like a normal ComboBox.
And what is the problem? Detecting clicks outside of the panel and then closing it.
-> Using LostFocus event: Not enough. If the user clicks on a blank space on the form, LostFocus is not fired. I know I can place a LostFocus event on my form and set the ActiveContrl to Nothing, but that would require that users (devs) of my custom ComboBox place the same code on every form they use the control.
-> Using Capture property: Has given the best results so far. There are still some problems though. When Capture is set to True, the mouse is indeed captured by the control which has that property set to True. Therefore, no mouse activity is sensed by any other control on the app, but only the control which has the mouse captured. It'll only release the mouse when the user performs a click. This leads to some problems:
If the user clicks on an item (a Button) on the dropdown, and the dropdown (a Panel) has its Capture property set to true, the click will be "ignored", as it'll be handled by the dropdown, and not by the Buttonon which the user actually wanted to click.
If the user clicks on the dropdown's scroolbar, the click will also be "ignored", just like explained above.
When user moves the mouse above the Buttons inside the dropdown (the ComboBox's items), they are not highlighted, because no MouseEnter is fired for them, as the mouse is Captured by the dropdown.
There is a way to solve the first issue: You can find on which button is the mouse pointing using Control.MousePosition, and then force a click on it.
I haven't been able to find a way to solve the 2nd and 3rd issue though. One possible solution I thought of was setting the dropdown's Capture property to False when the mouse enters on it. So, this is how it would work:
User clicks on the Combobox. Dropdown opens, captures the mouse;
User then moves the mouse inside Combobox's dropdown. Capture is set to false, thus making it possible for the user to click on any button inside the dropdown or on the dropdown's scroolbar, and so forth. Buttons inside the dropdown are also properly highlighted as user moves the mouse above them;
User moves the mouse outside Combobox's dropdown, Capture is again set to true, and now any click the user perform outside of it will be "ignored", and the dropdown will be closed. Just like a normal Combobox.
But when I tried to do this, another issue arrived: when a control has its Capture property set to True, it'll constantly fire MouseEnter events. MouseEnter event doesn't use the real mouse pointer location. Even if the mouse pointer is actually outside a Control, that Control will think the mouse is actually inside of it if its Capture property is set to True.
Edit2: here is the code for handling some different types of events (should work for all cases now).
Public Class Form1
Private Shared mouseNotify() As Int32 = {&H201, &H204, &H207} ' WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN
Private Shared scrollNotify() As Int32 = {&H114, &H115} ' WM_HSCROLL, WM_VSCROLL
Private Shared scrollCommands() As Int32 = {0, 1, 2, 3, 4, 5} ' SB_LINEUP, SB_LINEDOWN, SB_PAGEUP, SB_PAGEDOWN, SB_THUMBTRACK, SB_THUMBPOSITION
Private Sub baseLoad(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
AutoScroll = True
Controls.Add(myPanel)
Controls.Add(myTextBox4)
myPanel.myTextBox1.Focus()
End Sub
Private myTextBox4 As New customTextBox(300)
Private myPanel As New customPanel
Protected Overrides Sub OnScroll(ByVal se As ScrollEventArgs)
MyBase.OnScroll(se)
ActiveControl = Nothing
End Sub
Protected Overrides Sub OnMouseWheel(ByVal e As MouseEventArgs)
MyBase.OnMouseWheel(e)
ActiveControl = Nothing
End Sub
Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
MyBase.OnResize(e)
ActiveControl = Nothing
End Sub
Protected Overrides Sub OnMove(ByVal e As System.EventArgs)
MyBase.OnMove(e)
ActiveControl = Nothing
End Sub
Friend Shared Function isOverControl(ByRef theControl As Control) As Boolean
Return theControl.ClientRectangle.Contains(theControl.PointToClient(Cursor.Position))
End Function
Protected Overrides Sub WndProc(ByRef m As Message)
If mouseNotify.Contains(CInt(m.Msg)) Then
If Not isOverControl(myPanel) Then
ActiveControl = Nothing
Else
myPanel.myTextBox1.Focus()
End If
End If
MyBase.WndProc(m)
End Sub
Friend Class customPanel : Inherits Panel
Friend myTextBox1 As New customTextBox(20)
Private myTextBox2 As New customTextBox(60)
Private myTextBox3 As New customTextBox(200)
Friend Sub New()
AutoScroll = True
Location = New Point(0, 100)
Size = New Size(200, 100)
Controls.Add(myTextBox1)
Controls.Add(myTextBox2)
Controls.Add(myTextBox3)
End Sub
Protected Overrides Sub OnLeave(ByVal e As EventArgs)
MyBase.OnLeave(e)
myTextBox1.Text = "false"
myTextBox2.Text = "false"
BackColor = Color.Green
End Sub
Protected Overrides Sub OnEnter(ByVal e As EventArgs)
myTextBox1.Text = "true"
myTextBox2.Text = "true"
BackColor = Color.Gold
MyBase.OnEnter(e)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
If mouseNotify.Contains(CInt(m.Msg)) Then
If isOverControl(Me) Then Form1.WndProc(m)
End If
MyBase.WndProc(m)
End Sub
End Class
Friend Class customTextBox : Inherits TextBox
Friend Sub New(ByVal y As Integer)
Location = New Point(10, y)
Size = New Size(100, 30)
End Sub
Protected Overrides Sub OnLeave(ByVal e As EventArgs)
MyBase.OnLeave(e)
BackColor = Color.Blue
End Sub
Protected Overrides Sub OnEnter(ByVal e As EventArgs)
BackColor = Color.Red
MyBase.OnEnter(e)
End Sub
End Class
End Class
If it doesn't work in all cases, you may have to attach events to all the controls on your form that can't/won't receive focus on mouse events.
also, this works slightly differently depending on the type of control. richtextbox use OnHScroll and OnVscroll for example, instead of OnScroll. you can also get the thumb position with CInt(m.WParam.ToInt32 >> 16), only valid for SB_THUMBTRACK, SB_THUMBPOSITION.
also, here are some interesting techniques: Handling a click event anywhere inside a panel in C#
this was actually taken from the MSDN page for WndProc: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.wndproc%28v=vs.110%29.aspx
and the page for NativeWindow Class: http://msdn.microsoft.com/en-us/library/system.windows.forms.nativewindow.aspx

Disable mouse scroll wheel in combobox VB.NET

Does anyone know of a way to disable the mouse scroll wheel when a control such as a combobox or listbox has focus? For my purposes, combobox is all I need the answer for.
I have a combobox set to trigger a SQL query on SelectedIndexChanged, and accidentally scrolling the wheel while the combobox has focus causes about six SQL queries to fire off simultaneously.
I've found a mix response, put this code in the MouseWheel event:
Dim mwe As HandledMouseEventArgs = DirectCast(e, HandledMouseEventArgs)
mwe.Handled = True
That's all. You don't need to create a new class, if you have your project in an advanced state.
The ComboBox control doesn't let you easily override behavior of the MouseWheel event. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form.
Friend Class MyComboBox
Inherits ComboBox
Protected Overrides Sub OnMouseWheel(ByVal e As MouseEventArgs)
Dim mwe As HandledMouseEventArgs = DirectCast(e, HandledMouseEventArgs)
mwe.Handled = True
End Sub
End Class
Beware that this also disables the wheel in the dropdown list.
If you subclass the control it's possible (apologies for the C#)
public class NoScrollCombo : ComboBox
{
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
protected override void WndProc(ref Message m)
{
if (m.HWnd != this.Handle)
{
return;
}
if (m.Msg == 0x020A) // WM_MOUSEWHEEL
{
return;
}
base.WndProc(ref m);
}
}
One such option would be to add a handler to the comboBox, and within that comboBox, resolve the situation. I'm not sure how your code is set up, but I'm assuming if you knew when the event was happening, you could set up some kind of conditional to prevent the queries from happening
'''Insert this statement where your form loads
AddHandler comboBoxBeingWatched.MouseWheel, AddressOf buttonHandler
Private Sub buttonHandler(ByVal sender As System.Object, ByVal e As System.EventArgs)
'''Code to stop the event from happening
End Sub
In this way, you'd be able to maintain the user being able to scroll in the comboBox, but also be able to prevent the queries from happening
Combining all the answers on this thread, the best solution if you don't want to create a custom control is to handle the mousewheel event. The below will also allow the list to be scrolled if it is dropped down.
Assuming your combobox is called combobox1:
If Not ComboBox1.DroppedDown Then
Dim mwe As HandledMouseEventArgs = DirectCast(e, HandledMouseEventArgs)
mwe.Handled = True
End If
I had the exact same issue, but found that simply changing the focus of the control after the query executed to another control such as the "Query" button itself worked better than perfect. It also allowed me to still scroll the control until the SelectedIndex actually changed and was only one line of code.
Just put this in the mousewheel event or in a single handler for all the controls this applies to, maybe call it wheelsnubber.
DirectCast(e, HandledMouseEventArgs).Handled = True