User Control's Picture box looses image - vb.net

I have created a little user control which is just a label and a picturebox. (Its designed as a menu item control, many of which are added to a flow layout panel as a 'home manu')
The code for the user control is as follows:
Imports System.ComponentModel
Public Class HomeButton
Public Event HomeButton_Click()
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Description("HomeButtonProperties"),
Browsable(True)>
Public Property Image() As Image
Get
Return Me.Icon_PictureBox.Image
End Get
Set(ByVal image As Image)
Me.Icon_PictureBox.Image = image
End Set
End Property
Public Property Text_Menu As String
Get
Return MenuText_Label.Text
End Get
Set(value As String)
MenuText_Label.Text = value
End Set
End Property
Private Sub Icon_PictureBox_Click(sender As Object, e As EventArgs) Handles Icon_PictureBox.Click, MenuText_Label.Click, Me.Click
RaiseEvent HomeButton_Click()
End Sub
End Class
The problem i have is at random times (usualy a build) the controls loose the image in the controls Icon_picturebox.image. and it all needs to be reset.
The text labels are fine. The images are from the project resources.
What have I done wrong? Thanks.

Related

Void user check RadioButton without disabling it

I have four RadioButtons inside a GroupBox and I need to forbid user to change it status but without disabling the control because I need that they look NOT disabled(grayed).
I'm controlling them from code.
I tried to change AutoClick to false but I get more than one RadioButton checked when I change them by code.
Of course I can change all the RadioButtons status every time but that looks a bit messy.
You can use an inherited class for this.
Mark IsReadOnly to true to disable the click.
Class ReadOnlyRadioButton
Inherits System.Windows.Forms.RadioButton
Private mRo as Boolean
Public Property IsReadOnly As Boolean
Get
Return mRo
End Get
Set(ByVal value as String)
mRo = value
End Set
End Property
Protected Overrides Sub OnClick(ByVal e As EventArgs)
If Not Me.IsReadOnly Then
MyBase.OnClick(e)
End If
End Sub
End Class

Custom control collection - Adding items at design time?

I am trying to make a reusable control similar to an Outlook-style sidebar. I have a CustomPanel. I also have a CustomCollectionControl, that inherits from flow layout panel. At design time I would like to add (x) CustomPanels to my CustomCollectionControl, through the properties window.
When I try to add from the (Collection) list in the properties window, it will show up in the list, but it will not add it to the control that is on the form.
Here is my code so far.
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Public Class CustomCollectionControl
Inherits FlowLayoutPanel
''' <summary>
''' Required designer variable.
''' </summary>
Private _mComponents As Container = Nothing
Private _mCustompanels As CustomPanelCollection
Public Sub New()
' This call is required by the Windows.Forms Form Designer.
InitializeComponent()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
_mCustompanels = New CustomPanelCollection(Me)
Padding = New Padding(0)
End Sub
#Region "Component Designer generated code"
''' <summary>
''' Required method for Designer support - do not modify
''' the contents of this method with the code editor.
''' </summary>
Private Sub InitializeComponent()
_mComponents = New System.ComponentModel.Container()
End Sub
#End Region
<EditorBrowsable(EditorBrowsableState.Always)> _
<Browsable(True)> _
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
<Bindable(True)> _
Public Property CustomPanels() As CustomPanelCollection
Get
Return _mCustompanels
End Get
Set(value As CustomPanelCollection)
_mCustompanels = value
End Set
End Property
Protected Overrides Sub OnResize(e As EventArgs)
MyBase.OnResize(e)
End Sub
End Class
Public Class CustomPanelCollection
Inherits CollectionBase
Private _mControl As CustomCollectionControl
Private _mCustomCollectionControl As CustomCollectionControl
Friend Sub New(control As CustomCollectionControl)
_mCustomCollectionControl = control
End Sub
Default Public ReadOnly Property Item(index As Integer) As CustomPanel
Get
Return DirectCast(List(index), CustomPanel)
End Get
End Property
Public Function Contains(cPanel As CustomPanel) As Boolean
Return List.Contains(cPanel)
End Function
Public Function Add(cPanel As CustomPanel) As Integer
Dim i As Integer
i = List.Add(cPanel)
cPanel.Control = _mCustomCollectionControl
Return i
End Function
Public Sub Remove(cPanel As CustomPanel)
List.Remove(cPanel)
cPanel.Control = Nothing
End Sub
End Class
Public Class CustomPanel
Inherits Panel
Friend Control As CustomCollectionControl
Public Sub New()
' TODO Set Stuff!
Height = 100
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Dim cBtn As New Button
cBtn.Height = 30
Controls.Add(cBtn)
cBtn.Dock = DockStyle.Top
End Sub
End Class
I need to find out when a CustomPanel is added through the properties window during design time, how to update the control with the changes?
The basic problem is that in order for the flow-layout logic to work on your panels, they need to be in the base control's ControlCollection. If/When you expose this thru the properties IDE the standard collection editor allows any control to be added to it.
Your CustomPanels() property on the other hand, allows only CustomPanel controls but they get stored in a different collection, so they do not show up on the form.
The SmartTag action to only add CustomPanel is a very viable workaround if it adds to the Controls collection. I am not sure how many of the standard Panel properties you want them to be able to edit, and since there is no way to specify the child button properties, there doesnt seem much difference between the collection editor and the SmartTag. I assume this is because it is a work in progress and/or removed to post a minimal example.
Another way is to get rid if the extra collection and use a custom collection editor which will restrict the type of control to what you want. This is shown below.
Notes:
I changed the generic names to make it easier to read. CustomCollectionControl is now FlowLayoutPanelEx and CustomPanel is FlowPanel.
Your Buttons arent hooked up to anything, nor are they exposed, so I am not sure how you plan to use them.
Since all that the FlowPanel does is store that one button, why not omit it and just add buttons of a certain size?
There are several other issues with the code(e.g. CustomPanel/FlowPanel should implement IDisposable since it is creating stuff). These and other issues are ignored in order to focus on implementing a minimal custom collection editor.
FlowLayoutPanelEx and FlowPanel:
' collection editor will need this:
Imports System.ComponentModel.Design
Public Class FlowLayoutPanelEx
Inherits FlowLayoutPanel
Public Sub New()
' This call is required by the Windows.Forms Form Designer.
' {PL} - no, it is not
'InitializeComponent()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Padding = New Padding(0)
End Sub
<EditorBrowsable(EditorBrowsableState.Always),
Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Bindable(True),
Editor(GetType(FlowPanelCollectionEditor),
GetType(System.Drawing.Design.UITypeEditor))>
Public Overloads Property Controls() As ControlCollection
Get
Return MyBase.Controls
End Get
Set(value As ControlCollection)
End Set
End Property
End Class
Public Class FlowPanel
Inherits Panel
' ToDo: implememt IDisposable
Private myBtn As Button
' allow user to specify the text for the child button
Public Property ButtonText As String
Get
If myBtn IsNot Nothing Then
Return myBtn.Text
Else
Return String.Empty
End If
End Get
Set(value As String)
myBtn.Text = value
End Set
End Property
Public Sub New()
' TODO Set Stuff!
Height = 100
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Height = 40
myBtn = New Button
myBtn.Height = 30
Controls.Add(myBtn)
myBtn.Dock = DockStyle.Top
End Sub
End Class
The way you have it, the user can change any FlowPanel property in the Collection Editor including those you have explicitly set. I dont know enough about what you ultimately want to do to offer alternatives other than it seems like perhaps the Panel is cosmetic and maybe a Button alone would suffice.
Note the additional Editor attribute on the Controls property. This tells VS to use that collection editor:
Public Class FlowPanelCollectionEditor
Inherits CollectionEditor
Public Sub New(t As Type)
MyBase.New(t)
End Sub
' *** Magic happens here: ***
' override the base class to SPECIFY the Type allowed
' rather than letting it derive the Types from the collection type
' which would allow any control to be added
Protected Overrides Function CreateNewItemTypes() As Type()
Dim ValidTypes As Type() = {GetType(FlowPanel)}
Return ValidTypes
End Function
Public Overrides Function EditValue(context As ITypeDescriptorContext,
provider As IServiceProvider,
value As Object) As Object
Return MyBase.EditValue(context, provider, value)
End Function
End Class
Results:
The collection editor adds only FlowPanels:
As you can see, the new ButtonText property can be set from the collection editor. When the controls are added to the Controls collection for use on the form, ButtonText shows on the buttons:
Note that the user can still drag a TextBox or whatever to your FlowLayoutPanelEx and it will accept it. This is another of those "other issues" mentioned above.
An article on CodeProject, Enhanced CollectionEditor Framework provides a fairly comprehensive overview of collections and custom collection editors.
It includes a custom collection editor framework but it wont handle this situation as is. If you remove NotOverridable from the CreateNewItemTypes method and recompile, you should be able to inherit from EnhancedCollectionEditor and use some of the other features it provides.
It is not really needed; as the code above shows there is not much involved in restricting the Type allowed. The article might be of value though as you modify and refine FlowPanel and the button into their final form. (Disclaimer: I wrote the article).
I am adding this here because I cannot do it in the comments because there is too much text and images. Also, maybe someone coming here from a search engine will be able to get an idea of what to do.
This is what I wanted to achieve with the control:
Closed
Open
And here is the edited code to allow the (flat style) buttons to be clicked and open the parent panel. This is a very crude method of doing it, but I put it together to check if it worked before I tied up too much time in it:
' collection editor will need this:
Imports System.ComponentModel.Design
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing
Public Class FlowLayoutPanelEx
Inherits FlowLayoutPanel
Public Sub New()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Padding = New Padding(0)
BackColor = Color.FromKnownColor(KnownColor.ControlDark)
End Sub
<EditorBrowsable(EditorBrowsableState.Always),
Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Bindable(True),
Editor(GetType(FlowPanelCollectionEditor),
GetType(System.Drawing.Design.UITypeEditor))>
Public Overloads Property Controls() As ControlCollection
Get
Return MyBase.Controls
End Get
Set(value As ControlCollection)
End Set
End Property
End Class
Public Class HeaderButton
Inherits Button
Public Property BtnID As Integer
Public Property BtnColor As System.Drawing.Color
Public Event ButtonClicked(sender As HeaderButton, buttonID As Int32)
Private Sub clicked(sender As Object, e As EventArgs) Handles Me.Click
RaiseEvent ButtonClicked(Me, BtnID)
End Sub
End Class
Public Class FlowPanel
Inherits Panel
' ToDo: implememt IDisposable
Private myBtn As HeaderButton
' allow user to specify the text for the child button
Public Property ButtonText As String
Get
If myBtn IsNot Nothing Then
Return myBtn.Text
Else
Return String.Empty
End If
End Get
Set(value As String)
myBtn.Text = value
End Set
End Property
Public Sub New()
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Height = 32
BackColor = Color.FromKnownColor(KnownColor.Info)
myBtn = New HeaderButton
AddHandler myBtn.ButtonClicked, AddressOf Me.ItemButtonClicked
myBtn.Height = 30
myBtn.Margin = New Padding(0)
myBtn.Padding = New Padding(0)
myBtn.Dock = DockStyle.Top
myBtn.FlatStyle = FlatStyle.Flat
BackColor = Color.FromKnownColor(KnownColor.Control)
Controls.Add(myBtn)
End Sub
Public Sub ItemButtonClicked(ByVal btn As HeaderButton, ByVal buttonID As Int32)
If btn.Parent.Height = 32 Then
btn.Parent.Height = 200
Else : btn.Parent.Height = 32
End If
End Sub
End Class
Public Class FlowPanelCollectionEditor
Inherits CollectionEditor
Public Sub New(t As Type)
MyBase.New(t)
End Sub
' *** Magic happens here: ***
' override the base class to SPECIFY the Type allowed
' rather than letting it derive the Types from the collection type
' which would allow any control to be added
Protected Overrides Function CreateNewItemTypes() As Type()
Dim ValidTypes As Type() = {GetType(FlowPanel)}
Return ValidTypes
End Function
Public Overrides Function EditValue(context As ITypeDescriptorContext,
provider As IServiceProvider,
value As Object) As Object
Return MyBase.EditValue(context, provider, value)
End Function
End Class
There is so much more that I have to do, like displaying changes to the controls in the designer, implementing Idisposable, adding a collapsible button on the side, and passing the height value of the panel through the form so it will open the full height. I'm probably going to draw the buttons to get some effects that are not available with the standard button.

VB.Net - UserControl don't update Controls inside

I'm working on a VB.Net application. I need to create an UserControl I've named signalIO, with a TextBox and a Label, with some properties to change contents of TextBox and Label, change BackColor, etc.
When I add signalIO into the Form of my application, the signalIO control works fine. If I change the parameter StatusSignal from False to True, I can see the backcolor of textbox into control changed from Red to Green, if I change the name of the Signal, I can see text into Label changed correctly.
But if I run on debug the application, I cannot see the changes after the Set methods of the properties.
This is the code I've used:
Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel.Design
Imports System.Windows.Forms
<Designer("System.Windows.Forms.Design.ParentControlDesigner,System.Design", GetType(IDesigner))> _
Public Class controlIO
Inherits UserControl
Private status As Boolean
Private ID As Integer = 0
Private label As String
Public Property labelSignal As String
Get
Return labelIO.Text
End Get
Set(value As String)
label = value
labelIO.Text = label
updateControl()
Invalidate()
End Set
End Property
Private Sub updateControl()
Me.Refresh()
boxIO.Refresh()
labelIO.Refresh()
End Sub
Public Property statusSignal As Boolean
Get
Return status
End Get
Set(value As Boolean)
status = value
If status Then boxIO.BackColor = Color.Green Else boxIO.BackColor = Color.Red
updateControl()
Invalidate()
End Set
End Property
Public Property signalIO As Integer
Get
Return ID
End Get
Set(value As Integer)
ID = value
boxIO.Text = CStr(ID)
updateControl()
Invalidate()
End Set
End Property
End Class
I cannot understand why if I change the property statusSignal, for example, I can see the change of the color of boxIO into FormDesigner of the application, but this not changed during execution of the application.
I wish you can understand my question.

VB.net MVC understanding and binding

I am trying to seperate my code logic from my gui as in MVC principles, what I am trying to achieve is quite simple I believe
I have my Form1, which contains a textbox and button, once the button is clicked it loads a function in my controller class which adds a string to a database using entity and then should update the textbox with this name.
I thought what I would need to do is pass the original form through and then databind to the textbox object on the form, this is where I have come unstuck though, as my logic fails...
Public Class Form1
Private mf As New MainForm(Me)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
mf.buttonClick()
End Sub
End Class
Public Class MainForm
Private Property a As Form
Public Sub New(ByVal s As Form)
a = s
End Sub
Function buttonClick() As Boolean
Dim context As TestDBEntities2 = New TestDBEntities2
Dim newCategory As tTable = New tTable
newCategory.Name = "Test1 " & Today.DayOfWeek
context.tTables.Add(newCategory)
context.SaveChanges()
Dim current As String = newCategory.Name
a.DataBindings.Add("text", "TextBox1", current)
Return True
End Function
End Class
and my error:
Cannot bind to the property or column Test1 6 on the DataSource.
Am I looking at this the right way? Or am I so far off that there is an obvious reason this doesn't work?
Any input would be appreciated! Whats the best way to pass data back to a source without returning it in as a result of a function?
You should consider changing your code a bit, so that it reflects more the MVC structure:
use Events to exchange data and indicate action triggers, instead of using the form object
normally the controller has knowledge of the form and not the other way around, so swap this in your project. This reflects also the first point
So a possible solution for a Windows Forms application could look like this:
The form that has one button and one text field, one event to signal the button click and one WriteOnly property to fill the TextBox from outside the form:
Public Class MainForm
Public Event GenerateAndShowEvent()
' allow text box filling
Public WriteOnly Property SetTextBoxContent()
Set(ByVal value)
generatedInputTextBox.Text = value
End Set
End Property
Private Sub generateAndShowButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles generateAndShowButton.Click
' forward message: inform the subscriber that something happened
RaiseEvent GenerateAndShowEvent()
End Sub
End Class
The controller class has knowledge of the form, creates it, binds (listens) to the form's button click event and makes the form ReadOnly for the world (exposes the form):
Public Class MainFormController
' the form that the controller manages
Private dialog As MainForm = Nothing
Public Sub New()
dialog = New MainForm()
' bind to the form button click event in order to generate the text and response
AddHandler dialog.GenerateAndShowEvent, AddressOf Me.GenerateAndShowText
End Sub
' allow the world to access readonly the form - used to start the application
Public ReadOnly Property GetMainForm()
Get
Return dialog
End Get
End Property
Private Sub GenerateAndShowText()
' create the text
Dim text As String = "Test test test"
' access the Database ...
' give the generated text to the UI = MainForm dialog!
dialog.SetTextBoxContent = text
End Sub
End Class
Now what left is to create first the controller, that creates the form and use the form to show it. This can be done like this:
Create an AppStarter module with a Main method:
Module AppStarter
Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
' create the controller object
Dim controller As MainFormController = New MainFormController()
' use the public property to get the dialog
Application.Run(controller.GetMainForm)
End Sub
End Module
In your project settings uncheck the enable application framework setting and set the AppStarter module as the start point of your project:
Now you have a Windows Form Project using the MVC pattern.
If you still want to use DataBinding for the TextBox control, then create a Data Transfer Object or DTO that represents the fields you will transfer from your controller to the form:
Public Class DataContainer
Private t As String
Private i As Integer
Public Property Text() As String
Get
Return t
End Get
Set(ByVal value As String)
t = value
End Set
End Property
Public Property Id() As Integer
Get
Return i
End Get
Set(ByVal value As Integer)
i = value
End Set
End Property
End Class
Then add a BindingSource for your TextBox and configure it to use the DTObject:
Now bind the TextBox control to the DataBinding control:
What left is to add a public setter for the TextBox data binding control in the form:
Public Property TextBoxDataSource()
Get
Return TextBoxBindingSource.DataSource
End Get
Set(ByVal value)
TextBoxBindingSource.DataSource = value
End Set
End Property
and transfer the data from the controller:
Private Sub GenerateAndShowText()
' create the text
Dim text As String = "Test test test"
' access the Database ...
' give the generated text to the UI = MainForm dialog!
'dialog.SetTextBoxContent = text
Dim data As DataContainer = New DataContainer
data.Text = text
data.Id = 1 ' not used currently
dialog.TextBoxDataSource = data
End Sub
The binding can also be set programmatically - instead of doing this over the control property window, add the following code in the constructor of the form:
Public Sub New()
InitializeComponent()
' bind the TextBox control manually to the binding source
' first Text is the TextBox.Text property
' last Text is the DataContainer.Text property
generatedInputTextBox.DataBindings.Add(New Binding("Text", TextBoxBindingSource, "Text"))
End Sub
You seem to misunderstand the Add method.
The first argument is the name of the control's property to which you are binding. This should be "Text", not "text".
The second argument is an object that contains the data you want to bind. You have passed the name of the target control, rather than the source of the data. You are also binding to the form rather than the text box. So what you have said is that you want to bind the form's text property to data that can be extracted from the string "TextBox1".
The third argument says where to go to find the data. For example, if you passed a FileInfo object for the second argument, and you wanted to bind the file's path, you would pass the string "FullName", because that is the name of the property containing the data you want. So you have told the binding to look for a property on the string class called "Test1 6", which is why you have received the error message saying it can't be found.
I think what you want is
a.TextBox1.DataBindings.Add("Text", newCategory, "Name");

Highlighting around textboxes

I am trying to draw a highlighted border around a custom textbox control so that I can reuse the highlighting feature for each new program I create. My approach so far has been to override the paint event in the control library (dll) after the custom property I have created is set. The code for the control is below.
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing
Imports System.ComponentModel.Design
<ToolboxBitmap(GetType(Button))>
Public Class Textbox_Custom
Inherits System.Windows.Forms.TextBox
Public Event OnEnterKeyPress()
Public Event MissingInfo_Change As EventHandler
Dim iMissing_Info As Boolean
Dim iCharacterInput As Cinput
Public Property CharacterInput As Cinput
'<Browsable(True), DefaultValue("AllowAll")>
Get
Return Me.iCharacterInput
End Get
Set(ByVal value As Cinput)
Me.iCharacterInput = value
End Set
End Property
Public Property Missing_Info As Boolean
'<Browsable(True), DefaultValue(True)>
Get
Return iMissing_Info
End Get
Set(value As Boolean)
iMissing_Info = value
**MyBase.Refresh()**
End Set
End Property
Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs)
MyBase.OnKeyPress(e)
If Asc(e.KeyChar) = 13 Then
RaiseEvent OnEnterKeyPress()
End If
Select Case Me.iCharacterInput
Case Cinput.CharactersOnly
If IsNumeric(e.KeyChar) Then
e.Handled = True
End If
Case Cinput.NumericOnly
If Not IsNumeric(e.KeyChar) And Asc(e.KeyChar) <> 8 Then
e.Handled = True
End If
End Select
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
**If iMissing_Info = True Then**
Dim rect As New Rectangle(New Point(0, 0), New Size(Me.Size.Width + 2, Me.Size.Height + 2))
Dim pen As New Pen(Brushes.OrangeRed, 2)
e.Graphics.DrawRectangle(pen, rect)
e.Dispose()
End If
End Sub
End Class
Public Enum Cinput
AllowAll
NumericOnly
CharactersOnly
End Enum
While debugging I have set a breakpoint in the OnPaint override (lines **), but it never hits it. I then put a breakpoint in the Set section of the Missing_Info property where I am trying to invalidate the control to redraw. It does hit the MyBase.Refresh breakpoint so I don't understand what I've missed.
I realize there have been several other posts on this topic, but from what I can tell they seem to require putting panels behind the control. I feel like I should be able to include this action in a custom control and not have to code a new highlighting section for each new project. Thanks for any help in advance.
In the end I decided to just go with changing the control background to a semi-transparent red color which should be obvious enough for what I'm doing.