I have a ToolStripButton with CheckOnClick property set to true. When it is clicked the Checked property becomes true and a blue border appears surrounding the button and it remains there as long as in the checked state. I want to remove this border, because I change the background color myself to indicate that the button is selected. How could I do that? I tried with ToolStripRenderer but I could not found the way.
Implementing your own ToolStripRenderer should give you what you're looking for. I'm not real familiar with using these, but I was able able to get the functionality I believe you are looking for with the following implementation.
Public Class BorderlessToolStripRenderer
Inherits Windows.Forms.ToolStripProfessionalRenderer
Protected Overrides Sub OnRenderButtonBackground(e As System.Windows.Forms.ToolStripItemRenderEventArgs)
Dim button = TryCast(e.Item, ToolStripButton)
If (button IsNot Nothing AndAlso button.Checked) Then
e.Graphics.Clear(Color.Yellow)
Else
MyBase.OnRenderButtonBackground(e)
End If
End Sub
End Class
I'm assuming you're using ToolStripProfessionalRenderer, so I inherited that one and just omit the standard background rendering when the button is in the checked state. This keeps the mouse over indicator, but omits the border when checked and instead renders a yellow background.
And of course, to use this class, you just need to set the Renderer property on your ToolStrip like this
Me.ToolStrip1.Renderer = New BorderlessToolStripRenderer()
Related
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.
I have many buttons in my app, and some of them are disabled in various circumstances. The problem is, buttons "look wrong" when .Enabled = False
What follows is an example of a list of properties which may be similarly applied to all buttons.
.BackColor = Color.Goldenrod
.Flatstyle = FlatStyle.Flat
.FlatAppearance.MouseOverBackColor = Color.White
.FlatAppearance.BorderSize = 0
.BackgroundImageLayout = ImageLayout.Stretch
.BackGroundImage = My.Resources.Resources.ButtonFade 'This image is translucent, giving the rounded 3D look as shown below.
.ForeColor = Color.Black
.Image = My.Resources.Resources.refresh 'May be other images.
.Text = "RELOAD"
The .BackColor property may be all kinds of colors, as set by the user via a "theme".
To illustrate my concern, below is a screenshot of three buttons. "NEW" is enabled. "SAVE" is disabled. Although "NEW" AND "SAVE" look similar, "SAVE" is washed out with low contrast colors for the text and image.
I'd like all disabled buttons to look more like "RELOAD". That is, I would like the text and image to remain solid black, for better legibility, but I can set BackgroundImage = Nothing so it won't look 3D. (To the user, the model is "If it isn't 3D, it's not clickable.") I will probably also modify the background color of disabled buttons, but that part is easy. I just need the system to stop "greying out" the text and image when I set Enabled = False.
To get this screenshot, "RELOAD" is actually enabled, but I've removed its background image. Problem is, it can still be clicked.
How can I get the look I'm looking for?
You cannot achieve what you want by using the Enabled property, the Button class implements the Windows GUI style guide that disabled controls should look disabled by graying out their appearance. A further restriction is that the button renderers cannot be tinkered with, they are not overridable.
You need to achieve your goal by making the control act disabled. Add a new class to your project and paste the code shown below. Compile. Drag the new control from the top of the toolbox to your form, replacing the existing button controls. Set the Disabled property to True in your code when you want to disable the button. You probably want to tinker with the code that changes the appearance.
Imports System.ComponentModel
Public Class MyButton
Inherits Button
<DefaultValue(False)> _
Public Property Disabled As Boolean
Get
Return IsDisabled
End Get
Set(value As Boolean)
If Value = IsDisabled Then Return
IsDisabled = Value
MyBase.SetStyle(ControlStyles.Selectable, Not IsDisabled)
If IsDisabled And Me.Focused Then Me.Parent.SelectNextControl(Me, True, True, True, True)
'' Change appearance...
If IsDisabled Then
Me.FlatStyle = Windows.Forms.FlatStyle.Flat
Else
Me.FlatStyle = Windows.Forms.FlatStyle.Standard
End If
End Set
End Property
Protected Overrides Sub OnMouseEnter(e As EventArgs)
If Not IsDisabled Then MyBase.OnMouseEnter(e)
End Sub
Protected Overrides Sub OnMouseDown(mevent As MouseEventArgs)
If Not IsDisabled Then MyBase.OnMouseDown(mevent)
End Sub
Protected Overrides Sub OnKeyDown(kevent As KeyEventArgs)
If Not IsDisabled Then MyBase.OnKeyDown(kevent)
End Sub
Private IsDisabled As Boolean
End Class
The way i do it in c (is way more powerfull for extreme gui stuff. This example is trivial!) to override the disabled state and draw my image (in c):
NMHDR *nmr;
NMCUSTOMDRAW *nmcd;
case WM_NOTIFY:
nmr = (NMHDR *)lParam;
nmcd = (NMCUSTOMDRAW *)lParam;
if(nmr->idFrom == IDC_BUTTON && nmr->code == NM_CUSTOMDRAW){
if(nmcd->dwDrawStage == CDDS_PREERASE){
if(nmcd->uItemState & 0x1) {StretchBlt(nmcd->hdc,...);} //Down
else if(nmcd->uItemState & 0x40){StretchBlt(nmcd->hdc,...);} //Enter
else if(nmcd->uItemState & 0x4) {StretchBlt(nmcd->hdc,...);} //Disable
else {StretchBlt(nmcd->hdc,...);} //Leave
return CDRF_SKIPDEFAULT;
}
}
break;
The WM_NOTIFY is sent to your main form so you can catch it. The nmcd->hdc
is your button hdc, and you draw on it your image depending on the state (Down, Enter,
Disable or Leave). I know it is difficult to write vb from c but you have a start point if you are patient enough.
Valter
I defined a custom button class, which sets background color when button is enabled/disabled.
Enabled appearance at runtime (A):
Disabled appearance at runtime (B):
Design time appearance is always (A), regardless of the value of Enabled property.
I would like my control to appear in designer exactly the way it would appear at run time. Is it possible and, if so, how can do it?
Here is what I tried (only relevant parts of the code):
Public Class StyledButton : Inherits Button
Private p_fEnabled As Boolean
<DefaultValue(True)>
Public Shadows Property Enabled As Boolean
Get
Return p_fEnabled
End Get
Set(value As Boolean)
p_fEnabled = value
MyBase.Enabled = value
UpdateVisualStyle()
End Set
End Property
Private Sub UpdateVisualStyle()
If Me.Enabled Then
'set enabled appearance
Else
'set disabled appearance
End If
End Sub
End Class
I'll explain why it behaves this way. A control behaves a lot at design time as it does at runtime. It provides the strong WYSIWYG support in the Winforms designer. But certain properties are very awkward at design time, you would not actually want the Visible property to take effect for example. Pretty important that the control remains visible even though you set Visible to False in the Properties Window.
This is a core role of the designer for a control. It intercepts these kind of difficult properties and emulates them. Showing the intended value in the property grid but not actually passing them on to the control's property setter.
The Enabled property fits this category. If it weren't intercepted then the control couldn't be selected anymore. Other ones are ContextMenu, AllowDrop, Location for UserControl and Form, etcetera. Your Shadows replacement doesn't fool the designer, it uses Reflection to find properties by name. So your property doesn't have any effect, your property setter simply never gets called.
You can only truly get this by overriding OnPaint() for the control so you can display a different color at design time. And a custom designer to poke it. A significant hang-up however is that it isn't simple to replace the renderer for the button, the one that implements the OnPaint() method. Microsoft decided to make the renderers internal, you can't override them.
Way too much trouble, I recommend you pass this up.
The shadowed property does work as designed at runtime, just not in the IDE. You would not want to loose controls which are Visible = False, and you would want to drill into Button events even when Enabled = False. Since the IDE has no intention of drawing a disabled control, there is no reason for it to invoke Invalidate when you change the property.
Since it works at runtime, trick it in the designer to use another property which looks like the original:
<Browsable(False), DebuggerBrowsable(DebuggerBrowsableState.Never),
EditorBrowsable(False)>
Public Shadows Property Enabled As Boolean
Get
Return neoEnabled
End Get
Set(value As Boolean)
neoEnabled = value
End Set
End Property
A new property, with the right name for the IDE.
<DisplayName("Enabled")>
Public Property neoEnabled As Boolean
Get
Return p_fEnabled
End Get
Set(value As Boolean)
p_fEnabled = value
UpdateVisualStyle()
MyBase.Enabled = p_fEnabled
End Set
End Property
Sadly, both Enabled and neoEnabled will be offered by Intellisense in code, but since they both do the same thing, its not a big deal. test code:
Private Sub UpdateVisualStyle()
If p_fEnabled Then
' other interesting stuff
MyBase.BackColor = Color.Lime
Else
MyBase.BackColor = Color.LightGray
End If
MyBase.Invalidate()
End Sub
You have probably wrestled with it more that I, and come up with a cleaner implementation.
This persists the BackColor associated with neoEnabled state:
'
'StyledButton1
'
Me.StyledButton1.BackColor = System.Drawing.Color.LightGray
Me.StyledButton1.Enabled = False
Me.StyledButton1.neoEnabled = False
versus
Me.StyledButton1.BackColor = System.Drawing.Color.Lime
Me.StyledButton1.Enabled = False
Me.StyledButton1.neoEnabled = True
I am using a DrawItem and MeasureItem events to paint a combobox with a DrawMode of OwnerDrawVariable.
Basically, I'm trying to have the user highlight a selection with the mouse, and then press the space bar to toggle the Save status of a song list. Then I call the Me.Refresh() event for the form in an attempt to redraw the form and the ComboBox.
The problem that I am running into is that only the Combobox itself (not the drop-down area) that is a control on the main form is redrawing, and the text that is behind the mouse-highlighted selection of the drop-down list is not changing from Red to Black as I believe it should. If I move the mouse to another selection, then the color does in fact update.
Here is a snippet of the code.
If (e.KeyCode = Keys.Space) Then
If cmbList.SelectedItem IsNot Nothing Then
With DirectCast(cmbList.SelectedItem, SongTitle)
.bSave = Not .bSave
End With
End If
End If
e.Handled = True
Me.Refresh()
Thanks for any help you can provide.
You need to use .RefreshItem/.RefreshItems instead of .Refresh.
See this question: Dynamically changing the text of items in a Winforms ComboBox