I am in the process of migrating a VB6 app to .Net and we must provide like-for-like functionality at all times. In one form is a treeview with checkboxes that has three levels of nodes. The first level serves only to group the next level down and they are not checkable. The second level nodes are checkable by the user and when checked or unchecked all its children follow suit. At all levels, clicking a node or its checkbox will mean it becomes selected, regardless of whether or not the check state is affected.
The third level is the crux of the problem (although the issue itself manifests on all treeview checkboxes): this level contains two 'types' of node, one which can be checked and unchecked by the user (if the parent is checked) and one type which cannot be checked or unchecked by the user regardless of the state of the parent, but its state mirrors that of its parent.
In normal use this all works as expected. However, if you quickly click one of the third level nodes (which is not supposed to be directly checkable) twice, it appears to change its check state. But if you examine the underlying value of the Checked property, it remains unaffected, so it seems it is simply a display issue. If discovered, this anomaly will be an issue for our clients as users may think they can do something that they cannot leading to expensive confusion.
I am fresh out of ideas on this one - has anyone else observed this behaviour or know about it and are there workarounds/solutions to it? I can't help feeling I've missed something really obvious but after a day and a half I now have tunnel vision. Here's some code to demonstrate the problem. Create a form with a treeview (big enough to see what's going on) and two buttons then drop this in:
Private _node As TreeNode = Nothing
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
MessageBox.Show(_node.Text & " : " & _node.Checked.ToString)
_node = Nothing
End Sub
Private Sub InitialiseTreeview()
TreeView1.Nodes.Clear()
Dim ran As New Random
Randomize()
For i As Int32 = 1 To 5
Dim TLNode As New TreeNode
Dim children As Int32 = 0
children = ran.Next(1, 5)
TLNode.Text = "Top Level Node " & i.ToString
For j As Int32 = 1 To children
TLNode.Nodes.Add("Child Node " & j.ToString)
Next
TreeView1.Nodes.Add(TLNode)
Next
TreeView1.ExpandAll()
End Sub
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
InitialiseTreeview()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
InitialiseTreeview()
End Sub
Run it, and click on a node ONCE. Click Button 1 and it will tell you the node text and it's checked state. Now click the same nodes checkbox twice, fast, observe the state of the checkmark and click button 1 again. You'll see what I mean. Button 2 generates a fresh set of tree nodes.
Yes, this is a bug introduced by the Vista version of the native TreeView control. When it sees the double-click event, it will automatic toggle the check state of the item. Without telling the .NET TreeView wrapper about it, the Before/AfterCheck event won't run. This hasn't been fixed in the .NET wrapper and probably never will.
Working around this bug requires preventing the native control from seeing the double-click message. 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, replacing the existing TreeView.
Public Class MyTreeView
Inherits TreeView
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
'' Filter the WM_LBUTTONDBLCLK message
If m.Msg <> &H203 Then MyBase.WndProc(m)
End Sub
End Class
Related
I am generating Auto Serial number in DataGridView using below code:
Public Class Form1
Dim table As New DataTable()
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
table.Columns.Add("Sl", Type.GetType("System.Int32"))
table.Columns.Add("Id", Type.GetType("System.Int32"))
table.Columns.Add("Name", Type.GetType("System.String"))
table.Columns.Add("Amount", Type.GetType("System.Int32"))
DataGridView1.DataSource = table
DataGridView1.Columns(0).ReadOnly = True
DataGridView1.Columns(2).ReadOnly = True
End Sub
Private Sub DgvRowCountChanged()
For Each dgvr As DataGridViewRow In Me.DataGridView1.Rows
dgvr.Cells(0).Value = dgvr.Index + 1
Next
End Sub
Private Sub DataGridView1_RowsAdded(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewRowsAddedEventArgs) Handles DataGridView1.RowsAdded
Me.DgvRowCountChanged()
End Sub
Private Sub DataGridView1_RowsRemoved(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewRowsRemovedEventArgs) Handles DataGridView1.RowsRemoved
Me.DgvRowCountChanged()
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Index As Integer
If DataGridView1.RowCount > 1 Then
Index = DataGridView1.CurrentCell.RowIndex
DataGridView1.Rows.RemoveAt(Index)
End If
End Sub
End Class
Serial number appears, but when I click on next column it disappears. Why is that happening?
Do this, make life easy:
Ditch all that code
Make a new form
Add a new item of type DataSet to your project, and give it a nice name (not DataSet1)
Double click the dataset, so its design surface appears
Right click the surface, choose "Add new.. DataTable", give it a more imaginative name than DataTable1. I'll assume you choose License (serial number? name? seems licensey)
Right click the new datatable and choose "Add New.. Column"
Name the new column Sl, and use the properties grid to give it a type of System.Int32
Repeat for your other columns
Save
Open the Data Sources window on the View menu (Other Windows item)
Open the new blank form you made in step 2
Expand every node you can see in the Data Sources window
Drag the node representing your datatable (the one with an icon looking like a datagridview next toit) out of the data sources window and drop it on the form
Remove (delete) the bindingnavigator it created (you wont need it)
CLick the datagridview, CLick the small arrow that appeared in the top right of the control to show the popup menu, choose Edit Columns
Make whatever columns you want read only
Set other properties like sizes of columns, fill weights, header texts etc
Open the code of your form. add a single row of data to the datatable, in the constructor, after the initializecomponent() call:
Me.myImaginativeDataSetName.License.AddLicenseRow(1, 1, "Name Blah", 1234)
That's it. It looks like a lot because I've broken it down into the absolute step by step - about the only thing that isn't there is reminding you to take a breath every few steps because you'll be so blown away how easy it makes your life when you get the IDE to write code for you, ;)
OK, it's maybe not that exciting... But you already use the Forms designer to write reams of code for you, so this is how you leverage the other tools so you don't have to work with un-typed datasets all the time. Ugh.
The dataset this creates has a full suite of nicely named properties; don't use the basic stringy stuff ever again:
'yes - do this
For Each ro as LicenseRow in myDataSet.License
If ro.IsNameNull Then ro.Name = "Default Name" & ro.Sl
Next ro
'no - heck no
For Each ro as DataRow in myDataSet.Tables("License").Rows
If ro.IsNull("Naem") Then ro.item("Name") = "Default Name" & Convert.ToInt32(ro.Item("Sl"))
Next ro
See how much cleaner the first one is? ro.Sl is a nice Integer property, no casting or converting, no incessant Tables this or Columns/Rows that, Intellisense helps you out becaise it's all strongly named stuff, no typos in string column names like I made in the second...
It looks like youre trying to prevent the user from adding rows with this:
If DataGridView1.RowCount > 1 Then
Index = DataGridView1.CurrentCell.RowIndex
DataGridView1.Rows.RemoveAt(Index)
End If
End Sub
If so, click the datagridview on the form designer and in the properties grid set AllowUserToAddRows to false. If youre also trying to prevent deletion set the same on AllowUserToDeleteRows
I Intended to display an PictureBox in my form when the mouse hovered over another control. I then wanted to use a separate event for when the mouse left the control. This event would remove the displayed PictureBox from controls. However, because my events are private subs, I can't directly access the name of the control in the latter event. A solution to this would be a method that removes the most recently added control. If no such method exists, or there is an alternative way of approaching this problem, any help would be appreciated.
I tried simply using Controls.Remove(), but this requires a parameter. The name of the control as a string did not work either, as the parameter must be a control itself.
Private Sub Tile_MouseEnter(Sender As Object, e As EventArgs)
Dim CloseUpPic As New PictureBox With {Properties}
CloseUpPic.Image = Sender.Image
Controls.Add(CloseUpPic)
Refresh()
End Sub
Private Sub Tile_MouseLeave(Sender As Object, e As EventArgs)
Me.Controls.Remove()
End Sub
The program won't compile due to .Remove() needing a parameter
I expected for the Control to be created and displayed when the mouse entered the tile, and to cease to exist when the mouse left the tile.
For future reference, controls have Tag property which allows you to store whatever you like. In this case, you can store a reference to the newly created PictureBox. Furthermore, the "Sender" parameter tells you which control was the source of the event. You can cast sender to a control, then store the reference. Then, in the leave event, you can cast sender to a control, cast the .Tag to a control, and finally remove it:
Private Sub Tile_MouseEnter(Sender As Object, e As EventArgs)
Dim ctl As Control = DirectCast(Sender, Control)
Dim CloseUpPic As New PictureBox With {Properties}
CloseUpPic.Image = Sender.Image
Controls.Add(CloseUpPic)
ctl.Tag = CloseUpPic
Refresh()
End Sub
Private Sub Tile_MouseLeave(Sender As Object, e As EventArgs)
Dim ctl As Control = DirectCast(Sender, Control)
Dim ctlToRemove As Control = DirectCast(ctl.Tag, Control)
Me.Controls.Remove(ctlToRemove)
End Sub
I ended up using the following code to solve my issue:
For Each Closeup In Controls.OfType(Of CloseUp)
Controls.Remove(Closeup)
Next
I created a new class of my own called Closeup, that inherits PictureBox. I then looped through each Closeup in controls (There was only one but this code works for multiple controls), and removed them.
In my program (Winforms), i use usercontrols as pages. I do this in the following way:
I have a panel on my Form1 in which i load usercontrols, on these usercontrols are my actual controls (buttons, labels, checkboxes etc). So actually i'm using the user controls as "sub" pages in my form.
The usercontrols are declared at the start of runtime, so they are "live" when i load them into the panel. This has allways worked fine, untill i ran into a problem yesterday.
Yesterday I used a datagridview on one of those usercontrols and during the ParentChanged of this control i call a Sub which changes the backcollor of this data grid view. (The sub itself is a public sub which is located in a module)
Like this:
Private Sub Init_ParentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.ParentChanged
GetRecipe(RecipeDGV, "Bla")
End Sub
Public Sub GetRecipe(ByVal Data As DataGridView, ByVal RecipeID As String)
For r As Integer = 0 To Recipe_Mem.Rows.Count - 1
For c As Integer = 0 To Recipe_Mem.Columns.Count - 1
Data(c, r).Style.BackColor = getPresetColorByID(CInt(Data(c, r).Value))
Data(c, r).ToolTipText = getPresetNameByID(CInt(Data(c, r).Value))
NEXT
NEXT
End Sub
When i run my program, i can see that my dgv gets the data from the database (which happens in the same Sub). But there is no color change in the cells.
Now, something i've noticed is that when i add a button to the user control, and use the click event of this button to call the same sub for the coloring, it does work).
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
GetRecipe(RecipeDGV, "Bla")
End Sub
Any idea why this works on a button click event, but it doesn't in the usercontrol parent changed event? it looks like during the parent changed event there is some kind of repaint event of the dgv. how do i solve this?
Have you tried wrapping the gridview in an ajax control ( with triggers on the ParentChanged event?
I'm a little puzzled by the "RecipeID" argument being passed as it isn't used.
VB.net .. Currently I have two controls, one richtextbox and a textbox. The RTB is readonly, and also HideSelection is set to false.
The textbox is generic, it allows for input (to send data).
I want to be able to select things in the richtextbox without losing focus in the textbox. There is a client called 'mushclient' that does this, and it works pretty well. The text is still selected, but it doesn't lose the focus on the chatbar to type in.
I don't exactly know however how to prevent 'focus' though. At the moment it breaks flow when you are in game but want to copy something, you'll have to click the textbox again to start typing again. I understand I could setfocus after clicking the RTB, but this feels overall a bit odd. I was wondering if there is a more elegant solution.
Thanks!
This seems to work well for me. The TextBox does loose focus, but as soon as the Mouse_UP event fires, the selected text is copied to the clipboard and focus is sent back to the text box.
Public Class Form1
Dim LostFocusControl As Control
Private Sub RichTextBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles RichTextBox1.MouseUp
If RichTextBox1.SelectedText.Length > 0 Then
Clipboard.SetText(RichTextBox1.SelectedText)
End If
If Not IsNothing(LostFocusControl) Then
LostFocusControl.Focus()
End If
End Sub
Private Sub ControlLostFocus(sender As Object, e As EventArgs) Handles TextBox1.LostFocus
LostFocusControl = Sender
End Sub
End Class
The code is a bit longer than it could be, but this makes it easier if later on you want to change the control that focus is returned to. To change the control that you want to return focus to, just change the name of the control that the handler is subscribed to e.g
Change
Private Sub ControlLostFocus(sender As Object, e As EventArgs) Handles TextBox1.LostFocus
To
Private Sub ControlLostFocus(sender As Object, e As EventArgs) Handles Listbox1.LostFocus
or whatever the name of the control is that you want to return focus to.
VB Windows form Application.. I am developing an application of which part of the program is around configuration settings allowing the user to enter configuration items. When the menubar item for configuration menu is clicked on the main form the menu opens. This is fine but the mainform should not become active again until the configuration menu has closed. This does not happen right now and the mainform simply comes to the foreground and the configuration form goes to the background... I realize that coding an event on the Child form to handle this would not work because the child window loses control and the main form gains control.. I thought of coding a function as follows on the main form but it does not seem logical because i would have to add to it for everyform and do checking to make sure the child is actually open before trying to close it..
Private Sub Form1_GotFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.GotFocus
MailSettingsWindow.Close()
RentalSettingsWindow.Close()
End Sub
I did away with the above sub routine and used the below code as per the recomendation of using showdialog which works just as i was looking for.
Private Sub MailingAndEmailSettingsToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MailingAndEmailSettingsToolStripMenuItem.Click
Dim MailConfig As New MailSettingsWindow()
MailSettingsWindow.Showdialog()
End Sub
I did away with the above sub routine and used the below code as per the recommendation of using ShowDialog which works just as I was looking for.
My code is as follows:
Private Sub MailingAndEmailSettingsMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles MailingAndEmailSettingsMenuItem.Click
Dim MailConfig As New MailSettingsWindow()
MailSettingsWindow.ShowDialog()
End Sub