I have a lot of radio buttons named after numbers between 0 and 20. And pretty much the same code to run when they're selected. If I could somehow use something like "checkedRadioButton.value" things would be a lot easier.
I have some pictures to show, when radiobutton1 is selected picture1 will be shown. I get the pictures with
OpenFileDialog1.Multiselect = True
OpenFileDialog1.ShowDialog()
and want to use something like
PictureBox1.ImageLocation = OpenFileDialog1.FileNames(checkedRadioButton.value)
One option is to set the Tag property of each control to the associated value. Tag is a general-purpose property intended to store arbitrary data, so you can use it for whatever you like. It is type Object, so you'll need to cast whatever you get from it. To get the associated Integer value from the checked RadioButton on the form you could do this:
Dim number = CInt(Me.Controls.OfType(Of RadioButton)().First(Function(rb) rb.Checked).Tag)
If your RadioButtons are in some other container, e.g. a Panel or GroupBox, then you must use that container's Controls collection instead.
EDIT:
As the end game here seems to be to get a file path, it would make sense to assign the file path to the Tag property in the first place. You can then get the file path directly from the RadioButton, e.g.
PictureBox1.ImageLocation = CStr(Me.Controls.OfType(Of RadioButton)().First(Function(rb) rb.Checked).Tag)
Another option is to create your own derived control and add a dedicated property, e.g.
Public Class FilePathRadioButton
Inherits RadioButton
Public Property FilePath As String
End Class
You can then use that class instead of the standard RadioButton class and use the FilePath property instead of the Tag property. There's then no need for a cast:
PictureBox1.ImageLocation = Me.Controls.OfType(Of RadioButton)().First(Function(rb) rb.Checked).FilePath
Related
I have a form that alters the content of a class within a list box. The information is updated correctly, but my ToString override on my object doesn't refresh - meaning the old ToString doesn't change. How would I fix this?
Here's my object:
Public Class Destination
Public strDestinationName As String
Public strAddress As String
Public intQuality As Integer
Public intPrice As Integer
Public Overrides Function ToString() As String
Return strDestinationName
End Function
End Class
Here's the code where it should be updated
Dim selectedDestination As Destination
selectedDestination = CType(ListForm.lbNames.SelectedItem, Destination)
selectedDestination.strDestinationName = tbName.Text
selectedDestination.strAddress = tbAddress.Text
selectedDestination.intPrice = cbPrice.SelectedIndex
selectedDestination.intQuality = cbQuality.SelectedIndex
Me.Close()
Regardless of how you add items to a ListBox, it is the ListBox that actually displays the data. In your case, it appears that you are adding Destination objects to the ListBox somehow, given that the SelectedItem is a Destination object. Given that you have written that ToString method, you are presumably relying on that to produce the text that the ListBox displays for each item. You are now expecting to be able to change the value of the strDestinationName field of one of the items and have the ListBox reflect that change. How exactly do you think that is going to happen?
The ToString method has to be called in order to get the new value and who do you think is going to call it? It would be the ListBox that calls it because it is the ListBox that displays the result. When you change that field, you are expecting the ListBox to call your ToString method but why would it do that? What reason has the ListBox got to call that method? It has no knowledge of the change you made so why would it think that it has to get new data?
The solution to your problem is to change your code in some way to notify the ListBox that data has changed so that it knows that it needs to get that new data and display it. There are multiple ways that you could do that.
The simplest option would be to bind your data to the ListBox via a BindingSource and then, when you modify an item, call the ResetItem method or similar of the BindingSource. That will raise an event that is handled by the ListBox and the ListBox then knows that it needs to refresh the data for that item. That is what will prompt the ListBox to call your ToString method and get the new data to display. You would add the BindingSource to the form in the designer and then do the binding where you are currently adding the items, e.g.
Dim destinations As New List(Of Destination)
For i = 1 To 10
Dim d As New Destination
d.strDestinationName = "Destination " & i
destinations.Add(d)
Next
destinationBindingSource.DataSource = destinations
destinationListBox.DataSource = destinationBindingSource
The modification would look something like this:
Dim selectedDestination = DirectCast(destinationBindingSource.Current, Destination)
selectedDestination.strDestinationName = "New Destination"
destinationBindingSource.ResetCurrentItem()
The Current property returns the item currently selected in the bound UI and the ResetCurrentItem method notifies the bound UI to refresh the display of that item.
This is really not the best way to go about it though, given that you have control over the item type. What you ought to do is implement the type using properties rather than fields, get rid of the ToString method that only returns the value of one property and then add a change event to that property:
Public Class Destination
Private _destinationName As String
Public Property DestinationName As String
Get
Return _destinationName
End Get
Set(value As String)
If _destinationName <> value Then
_destinationName = value
OnDestinationNameChanged(EventArgs.Empty)
End If
End Set
End Property
Public Property Address As String
Public Property Quality As Integer
Public Property Price As Integer
Public Event DestinationNameChanged As EventHandler
Protected Overridable Sub OnDestinationNameChanged(e As EventArgs)
RaiseEvent DestinationNameChanged(Me, e)
End Sub
End Class
You can now bind a list of Destination objects directly and specify any of those properties as the DisplayMember to have that property value displayed:
Dim destinations As New List(Of Destination)
For i = 1 To 10
Dim d As New Destination
d.strDestinationName = "Destination " & i
destinations.Add(d)
Next
destinationListBox.DisplayMember = "DestinationName"
destinationListBox.DataSource = destinations
You don't need the ToString method because the DisplayMember specifies that the value of the property with that name should be displayed. When you modify the value of the DestinationName property of an item, it will raise the DestinationNameChanged event and that will notify the ListBox that it needs to refresh the display for that item, so you don't need any additional code to make the ListBox update.
That's fine if you only plan to modify existing items. There's still a problem if you want to add and/or remove items after binding though. The List(Of T) class that is used to bind the items to the control in this example does not have any events to notify the control of changes to the list like that. In that case, you can use a BindingSource again if you want. If you add and remove items via the BindingSource then it will raise that appropriate events and the ListBox will update. If you wanted to add and remove via the underlying list then you'd have to call an appropriate method of the BindingSource when you made a change.
An alternative would be to use a BindingList(Of Destination) instead of a List(Of Destination). As the name suggests, the BindingList(Of T) class is made for binding, so it will automatically raise the appropriate events when the list changes to enable the UI to update without extra code from you. Using the combination of property change events in your item class and a BindingList(Of T), you can add, edit and remove items in the bound list and the UI will reflect those changes automatically.
I am learning vb.net and I'm having issues searching for what I need. I want to create a button that is "re-usable" throughout my application without needing to write code for each instance. So, what I would like to start with is take a variable in a form, example, public integer value and when this value changes I want to write to the text of a button. I know I can easily do this by writing code in the form btn_xxx.text = variable, but what if I have several buttons and each button looks at the same variable? Currently what I do is create a component which inherits a button and have a timer that on tick will look at the variable and write to the text. I'm sure there is a better way. Can anyone point me in the right direction? I know part of my problem is I don't know the nomenclature on what things are called, so hopefully I asked my question without too much confusion.
I saw this, https://www.daniweb.com/programming/software-development/threads/124842/detect-variable-change, but I don't see how to adapt that to my situation.
Here is what I have:
Private WithEvents Active_Alarm As New Nav_Active_Alarm
Then inside of a sub that calculates the count:
Active_Alarm.Count = CInt(dt_Active_Alarms.Rows.Count)
The user control:
Public Class Nav_Active_Alarm
Private mActive_Alarm_Count As Integer
Public Event Active_Alarm_Count_Changed(ByVal mvalue As Integer)
Public Property Count() As Integer
Get
Count = mActive_Alarm_Count
End Get
Set(ByVal value As Integer)
mActive_Alarm_Count = value
If Not Me.DesignMode Then
RaiseEvent Active_Alarm_Count_Changed(mActive_Alarm_Count)
test()
End If
End Set
End Property
Private Sub test()
If Not Me.DesignMode Then
If mActive_Alarm_Count = 0 Then
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
'console or msgbox will work but updating the image will not
Else
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
'console or msgbox will work but updating the image will not
End If
End If
End Sub
End Class
If I write to console or add a msgbox I will see the event working. But, the image will not change. If I call the test sub from a timer it will work. Why won't the button update (by the way, I did try refresh and update in the code)?
Observer pattern is what you probably looking for.
This is quick and dirty.
Create a class to hold the variable value. Add a method that adds a button instance to a list.
Then a button that needs to know about the variable calls the register method.
When the value of the variable changes, it iterates through the list of buttons and sets the Text property of each one.
You might have jumped in a bit too deep too quick here. Google Custom data binding in .net, there's loads of built in stuff you can use. Though do it yourself is a good exercise.
A simple method to do this might be:
Create a form level list to hold the buttons you are interested in
Add the buttons you are interested in, into the list (maybe in form load or some other place where you have initialization code)
Create a private property in your form with a backing variable to hold the value you want to have applied to the buttons. In the setter portion spin through the list and set each buttons text.
Dim lstButtons As New List(Of Button)
Sub SetupButtons()
'call from form load or other init code
lstButtons.Add(btnPopulate)
lstButtons.Add(btnPopulate)
End Sub
Private _buttonText As String
Private Property ButtonText As String
Get
Return _buttonText
End Get
Set(value As String)
_buttonText = value
For Each b As Button In lstButtons
b.Text = value
Next
End Set
End Property
When you set the property - which now acts as your variable - it will update all of your textboxes for you.
I realize you mentioned without having to write code - but something has to tie things together. Even if you used the observer pattern (which is an elegant solution for this - so props to those who suggested it) you'd probably end up creating a class to hold the property and have that class implement the INotifyPropertyChanged from System.ComponentModel, and then you'd also have to have each button have a databinding for its text property to the property in the object of your class. There isn't really a way (that I can think of) to get around having to write some code for each form you do this in (though the class part you'd only have to write once of course).
I made a custom DataGridViewColumn control together with its DataGridViewCell controls.
The idea is to dynamically create the contents of the cell, which consists of a series of clickable function buttons, upon databinding. The number and kinds of buttons depend on the data value passed.
For this, I override the Paint method of the DataGridViewCell and check the formattedValue on its contents and draw buttons accordingly. However, these buttons are "dead" and not clickable, so the question is how to make them clickable, i.e. how do I add a handler for the click event?
Do I have to override the cell's OnClick method and then try to pinpoint which button exactly is clicked? Is this even possible? Are there better ways?
This is what I've got so far:
Protected Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, cellState As DataGridViewElementStates, value As Object, formattedValue As Object, errorText As String, cellStyle As DataGridViewCellStyle, advancedBorderStyle As DataGridViewAdvancedBorderStyle, paintParts As DataGridViewPaintParts)
MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
Dim cellBackground As New SolidBrush(cellStyle.BackColor)
graphics.FillRectangle(cellBackground, cellBounds)
cellBackground.Dispose()
PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
Dim sValue As String = formattedValue.ToString()
If (sValue.Contains("ViewAsPDF")) Then
Dim buttonArea As Rectangle = cellBounds
Dim buttonAdjustment As Rectangle = Me.BorderWidths(advancedBorderStyle)
buttonArea.X += buttonAdjustment.X
buttonArea.Y += buttonAdjustment.Y
buttonArea.Height -= buttonAdjustment.Height
buttonArea.Width -= buttonAdjustment.Width
buttonArea.Width = buttonArea.Width / 4
ButtonRenderer.DrawButton(graphics, buttonArea, PushButtonState.Default)
TextRenderer.DrawText(graphics, "PDF", Me.DataGridView.Font, buttonArea, SystemColors.ControlText)
End If
'etcetera
End Sub
I think you may have wandered down the wrong road. Based on the code provided, you are simply drawing the cells to look like they contain buttons. Since they are not actually objects, they are incapable of raising events.
I don't understand ButtonRenderer, if you can't create actual Buttons with it
The ButtonRender does not create a new button object, it is meant to be used by Button objects for drawing. Often a subclassed a button, will not use it because it employs the existing theme and style which is may be what a you wants to do differently (even the DataGridViewButtonCell does not use it -- at least not directly).
From the code provided, it seems to work out each button on the fly each time rather than drawing from some collection or definition. What if the "action" list needs to vary based on the row (e.g. different actions for a DOC, XLS or Image row)? Doing so, would seem to take a great deal of code.
Your current course may not be impossible, but it is not trivial either. You might be able to create a collection of virtual buttons (basically the Rect from when it was drawn) and render them as you have done. Then in the cell-click event, translate/adjust the X position to see which rectangle contains thisPt.X to determine which related action to take.
There are "issues" still such as what happens when the user resizes the column? What about when the button list varies by some other cell value (DOC vs XLS vs IMG vs PDF)? This would require a collection of button sets...and a fair amount of code.
This is not to say it cant be done, but it seems like a great deal of code would be required to make it even a little flexible.
Are there better ways?
I think so.
A simpler, existing solution might be to use the existing DataGridViewComboBoxColumn to store "Actions" or "Activities". It seems a bit less cluttered and more user friendly:
It takes only a small amount of code to provide a different list for each animal:
' dogs like to go for walks
Private ActsCan() As String = {"Feed", "Pet", "Bathe", "Brush", "Take for Walk"}
' no walks or baths for cats
Private ActsFel() As String = {"Feed", "Pet", "Baby-Talk To", "Brush"}
' never bathe a Mugwai, comb rather than brush
Private ActsMug() As String = {"Feed", "Pet", "Baby-Talk To", "Comb"}
Private ActsGrem() As String = {"Hide From", "Strangle"}
...
Private Sub dgv_RowEnter(sender As Object,
e As DataGridViewCellEventArgs) Handles dgv.RowEnter
Dim dgvCBO As DataGridViewComboBoxCell
dgvCBO = CType(dgv.Rows(e.RowIndex).Cells("ColActs"), DataGridViewComboBoxCell)
dgvCBO.Items.Clear()
Select Case dgv.Rows(e.RowIndex).Cells("colSpecies").Value.ToString
Case "Canine"
dgvCBO.Items.AddRange(ActsCan)
Case "Feline"
dgvCBO.Items.AddRange(ActsFel)
Case "Mugwai"
dgvCBO.Items.AddRange(ActsMug)
Case "Gremlin"
dgvCBO.Items.AddRange(ActsGrem)
End Select
End Sub
A class to encapsulate most of that might be nice for unbound DGVs. It could be optimized not to rebuild the list when the trigger value for thisRow is the same as the last.
What about this approach. Only implementing the ui.
make my own UserControl and I can aggregate new TabPages to a TabControl and then, inside of then TabPage, I add my own UserControl using the following code.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim TabX As New Windows.Forms.TabPage("Tab " & TabCount.ToString) '(ConfiguracionTabPage)
Dim MyControl As New ClientesEmpresa
MyControl.Name = "Control" & TabCount.ToString
If ClientesTabControl.TabPages.Count = 10 Then
ClientesTabControl.TabPages.RemoveAt(9)
End If
TabX.Controls.Add(MyControl)
TabX.Name = "Tab" & TabCount.ToString
TabX.Text = "Tab" & TabCount.ToString
MyControl.TitularLbl.Text = "Coca Cola"
Me.ClientesTabControl.TabPages.Insert(0, TabX)
Me.ClientesTabControl.SelectedIndex = 0
TabCount += 1
End Sub
My user control have several Labels, TextBox and TabPages(inside of a TabControl).
Now I want to change some properties dynamically from the source code, but I don't know how to access them.
The most similar theme that I found is this How to Acces of an User control in c#, but, as the title says, is in C#, how I can do it in VB.NET?
Sorry, I just notice that the Enter key post the comment. :(
Thanks for your feedback, I understand what are you saying but I missing something in the middle.
When I create the control in running time in the above code I can access easily to the properties of the created object, in this case my UserControl, but I don't understand how to reach the properties of a particular instance of that control from outside of Button_Click; ie. another button_click event(second button)
I was thinking to use something like
Dim ControlList As Windows.Forms.Control() = Me.ClientesTabControl.TabPages(0).Controls.Find("ModeloLbl", True)
or
ClientesTabControl.TabPages(0).Controls.OfType(Of AlarmasVehiculo)()
But I'm stuck here.
------------------------------------- 3th post ---------------
Thanks Steve, I was resolved using "Control.Find" and a For Each but your solution is easier.
There's any way to get the name of the selected tab or I must to create an Array when I create the New TabPage?, the idea is to update the text of the controls inside of the selected tab only when is selected by the user or every 5 seconds but just the in selected one.
Thanks.
To borrow M4N's answer from the C# question, and translate it to VB:
Cleanest way is to expose the desired properties as properties of your usercontrol, e.g:
Public Class MyUserControl
' expose the Text of the richtext control (read-only)
Public ReadOnly Property TextOfRichTextBox As String
Get
Return richTextBox.Text
End Get
End Property
' expose the Checked Property of a checkbox (read/write)
Public Property CheckBoxProperty As Boolean
Get
Return checkBox.Checked
End Get
Set (value As Boolean)
checkBox.Checked = value
End Set
End Property
'...
End Class
In this way you can control which properties you want to expose and whether they should be read/write or read-only. (of course you should use better names for the properties, depending on their meaning).
Another advantage of this approach is that it hides the internal implementation of your user control. Should you ever want to exchange your richtext control with a different one, you won't break the callers/users of your control.
To answer your second question, if you need to access your dynamically created controls, you can do so easily using their names, for instance:
Dim c As ClientesEmpresa= CType(Me.ClientesTabControl.TabPages("Tab1").Controls("Control1"), ClientesEmpresa)
c.CheckBoxProperty = True
I have a custom control that does some work for me on async postbacks. Normally, I'd call the control directly from the presentation side using the following code on the ASPX page:
<mytag:CustomControl runat="server">
html (or other text) goes here
</mytag:CustomControl>
However, in my current application, I need to dymanically create the control from the codebehind, using code similar to the following:
Dim myControl As myClass.CustomControl = New myClass.CustomControl
myControl.ID = "someID"
myControl.?????? = "html (or other text) goes here"
Me.Controls.Add(myControl)
When adding the control to the page dynamically, how do I add info that would normally be between the start and end tags if the control were added the normal, non-dynamic way?
Thanks
Here's the actual control:
Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter)
Dim scriptmanagerPage As ScriptManager = ScriptManager.GetCurrent(Page)
If scriptmanagerPage Is Nothing Then
'Do nothing
Else
'See if we are in a postback
If scriptmanagerPage.IsInAsyncPostBack Then
'We are in a postback; register the script
Dim stringbuilderWorking As New StringBuilder()
MyBase.Render(New HtmlTextWriter(New StringWriter(stringbuilderWorking)))
Dim stringScript As String = stringbuilderWorking.ToString()
ScriptManager.RegisterStartupScript(Me, GetType(ScanWorkXAJAX), UniqueID, stringScript, False)
Else
'Not in a postback
MyBase.Render(writer)
End If 'In an async postback
End If 'Scriptmanager present
End Sub
What do you mean by data? More controls?
You can use
myControl.Controls.Add(childControlHere);
EDIT After question was clarified:
Add a literal control. i.e.
myControl.Controls.Add(new LiteralControl("<b>hello world</b><script type='text/javascript'>alert('hi');</script>"));
Do you mean FIND and append data to controls inside your dynamic control?
You can use the WebControl.FindControl method to find your control embedded in your custom control and then you can add data via its properties.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.webcontrol.findcontrol.aspx
That depends on the properties of the custom control you are using. You may have to bind to a template to display so that events will be fired, it really just depends on your specific control.
Was able to accomplish this using a literal control.
Code above plus:
Dim myLiteral As LiteralControl = New LiteralControl
myLiteral.ID = "myLiteral"
myLiteral.Text = "html (or some other text) goes here"
myControl.Controls.Add(myLiteral)