.NET Designer issues shadowing properties as readonly in inherited controls - vb.net

I am trying to create custom controls to provide some consistency to the design of an application. However when I shadow a property with a readonly alternatvie, I get designer errors at build time. Now I can delete the offending lines of code in the designer file and continue to build and run my application but firstly this is irritating, and secondly it tells me that I must be doing something fundamentally wrong!
Here's an example of a control that overrides datagridview
Class standardDataGridView
Inherits DataGridView
Public Sub New()
MyBase.New()
Me.RowHeadersVisible = False
MyBase.SelectionMode = DataGridViewSelectionMode.FullRowSelect
MyBase.MultiSelect = False
Me.ReadOnly = True
Me.BackgroundColor = Color.White
Me.AllowUserToDeleteRows = False
Me.AllowUserToResizeRows = False
Me.AllowUserToAddRows = False
End Sub
Public Shadows ReadOnly Property SelectionMode As DataGridViewSelectionMode
Get
Return MyBase.SelectionMode
End Get
End Property
Public Shadows ReadOnly Property MultiSelect As Boolean
Get
Return MyBase.MultiSelect
End Get
End Property
End Class
In the first build after adding one of these controls to a form, or after changing any properties, the following lines get added to the designer file by Visual Studio:
Me.standardDataGridView1.MultiSelect = False
Me.standardDataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect
Which results in the following build errors
Property 'MultiSelect' is 'ReadOnly'.
Property 'SelectionMode' is 'ReadOnly'
As I say... I can remove the lines that Visual Stuido added, and continue, but where have I gone wrong to be getting this problem?

Try telling the forms designer to not serialize those properties:
Imports System.ComponentModel
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public Shadows ReadOnly Property SelectionMode As DataGridViewSelectionMode
Get
Return MyBase.SelectionMode
End Get
End Property
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
Public Shadows ReadOnly Property MultiSelect As Boolean
Get
Return MyBase.MultiSelect
End Get
End Property
Make sure to rebuild your solution.

Related

Change the defaultValue of A custom control property

I created a number of custom controls but still struggling mastering the interface.
For uniformity besides creating custom proerties I also want to change some base properties of the custom control I tried following code
Protected Overrides Sub OnControlAdded(e As ControlEventArgs)
Me.AutoCompleteMode = AutoCompleteMode.Suggest
Me.AutoCompleteSource = AutoCompleteSource.ListItems
MyBase.OnControlAdded(e)
End Sub
That however does not work when I drop the custom control on a form, I suppose the solution lies with adding the attribute and overriding the property.
I found an answer to this for C# but did not succeed to understand/translate it for vb.net
Since those properties are not overridable, try using the Shadows modifier instead:
Public Class MyComboBox
Inherits ComboBox
Public Sub New()
Me.AutoCompleteMode = AutoCompleteMode.Suggest
Me.AutoCompleteSource = AutoCompleteSource.ListItems
End Sub
<DefaultValue(AutoCompleteMode.Suggest)> _
Public Shadows Property AutoCompleteMode As AutoCompleteMode
Get
Return MyBase.AutoCompleteMode
End Get
Set(value As AutoCompleteMode)
MyBase.AutoCompleteMode = value
End Set
End Property
<DefaultValue(AutoCompleteSource.ListItems)> _
Public Shadows Property AutoCompleteSource As AutoCompleteSource
Get
Return MyBase.AutoCompleteSource
End Get
Set(value As AutoCompleteSource)
MyBase.AutoCompleteSource = value
End Set
End Property
End Class
Please note though, the DefaultValue attribute probably doesn't do what you think it does: it doesn't actually set a default value of that property. All it is used for is to tell the PropertyGrid what the default value for the property is, and if it matches it, it won't make it bold in the PropertyGrid view and it won't serialize the value in the designer file.

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 Forms ListBox doesn't display DataSource

I'm trying to link my ListBox to an ObservableCollection.
Here's my class for defining mods:
Public Class TroveMod
Private m_FileName As String
Private m_Enabled As Boolean
Public Property FileName() As String
Get
Return m_FileName
End Get
Set(value As String)
m_FileName = value
End Set
End Property
Public Property Enabled() As Boolean
Get
Return m_Enabled
End Get
Set(value As Boolean)
m_Enabled = value
End Set
End Property
Public ReadOnly Property ModName()
Get
Return Path.GetFileNameWithoutExtension(FileName)
End Get
End Property
End Class
And this is the actual Property ModList:
Public Property ModList() As ObservableCollection(Of TroveMod)
Get
Return m_ModList
End Get
Set(value As ObservableCollection(Of TroveMod))
m_ModList = value
End Set
End Property
I add items using:
Private Sub AddMod(file__1 As String, enabled As Boolean)
If File.Exists(file__1) Then
ModList.Add(New TroveMod() With { _
.FileName = file__1, _
.Enabled = enabled _
})
End If
End Sub
Everytime I want to add something to this Collection using AddMod, it won't show off in my listbox :/ I added an ModListBindingSource to the ListBox and set the DisplayMember and ValueMember to ModName but it still won't work. I also got a status label, which says, that it successful added the mods to the collection but it simply won't show them in the ListBox. Did I miss something?
An ObservableCollection is not quite what you want - this would allow your code to see changes to the collection via events. For instance, if there were several actors adding items to the collection and you wanted to know that. In this case, since 'local' code is adding items, it is not needed.
It is also not clear how the collection is mapped to the ListBox as the DataSource. Try this:
Public Class Form...
Private myList As New BindingList(Of TroveMod)
Sub Form_Load(....
theListBox.DataSource = myList
theListBox.DisplayMember = "ModName"
Now, as you add things to the BindingList they will appear in theListBox. If an item changes though (e.g. the Name), that change will not show up without a little more work, but as these are file names that seems unlikely.
The form property is not needed unless an external class also needs access to the collection/BindingList; in that case, you probably do not want need a setter.

Custom CollectionEditor never triggers a property's "set" method

I am trying to implement a way of persisting a collection in a custom settings class. I have successfully created the settings class (inheriting ApplicationSettingsBase) and can save properties using the built-in editors on a PropertyGrid, but my custom implementation of a property grid for collections doesn't persist any of the values I add. Here's my code:
Imports System.Configuration
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.ComponentModel.Design
Public Class CustomSettings
Inherits ApplicationSettingsBase
<UserScopedSetting()> _
<DefaultSettingValue("White")> _
Public Property BackgroundColor() As Color
Get
BackgroundColor = Me("BackgroundColor")
End Get
Set(ByVal value As Color)
Me("BackgroundColor") = value
End Set
End Property
<UserScopedSetting()> _
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
<Editor(GetType(CustomStringCollectionEditor), GetType(UITypeEditor))> _
Public Property EmailAddresses() As Collection
Get
EmailAddresses = Me("EmailAddresses")
End Get
Set(ByVal value As Collection)
Me("EmailAddresses") = value
End Set
End Property
End Class
Public Class CustomStringCollectionEditor
Inherits CollectionEditor
Public Sub New()
MyBase.New(GetType(Collection))
End Sub
Protected Overrides Function CreateInstance(ByVal itemType As System.Type) As Object
Return String.Empty
End Function
Protected Overrides Function CreateCollectionItemType() As System.Type
Return GetType(String)
End Function
End Class
I set a breakpoint on the Set methods for both the BackgroundColor property and the EmailAddresses property. The BackgroundColor property works as it should - it breaks on the Set statement and stores the property correctly. But when I close the custom CollectionEditor dialog, the EmailAddresses "Set" method is never called. How can I get my custom editor to actually save the property once it's done being edited?
I think I fixed it (with help from this question). I added an override to the EditValue function in my custom editor. Here is the code:
Public Overrides Function EditValue(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal provider As System.IServiceProvider, ByVal value As Object) As Object
Dim result As Object = MyBase.EditValue(context, provider, value)
DirectCast(context.Instance, CustomSettings).EmailAddresses = DirectCast(result, List(Of String))
Return result
End Function
I also moved from a collection to a list - I read somewhere that was a safer way to go. I also added a constructor to my CustomSettings class that set the EmailAddresses property to a new List(Of String) if it was unset to begin with. I found that the first time it ran, I could edit the list and add items, but they wouldn't be persisted:
Public Sub New()
If Me("EmailAddresses") Is Nothing Then
Me("EmailAddresses") = New List(Of String)
End If
End Sub
And now it's all working like it should. But if this isn't the best way or there's an easier way to do it, please chime in.