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
Related
I have a mdi container form that when it opens a child inside I get this double title bar with another set of close/minimize/maximize buttons and the form icon. How can I get rid of it? I've tried setting Control to false but it does not work. FormBorderStyle to none also does not work and it deforms the child form.
Also, I have a toolstrip that contains some buttons and it has this annoying border looking line at the bottom (check out the blue arrow I drew). How can I disable that aswell?
First Part
For the first part of your question, the workaround posted here by Mr. #RezaAghaei could be the answer. Here's the VB.NET version:
Public Class YourParentForm
'Or drop one in the designer...
Private ReadOnly ContainerPanel As Panel
Sub New()
InitializeComponent()
IsMdiContainer = False
ContainerPanel = New Panel With {.Dock = DockStyle.Fill}
Controls.Add(ContainerPanel)
End Sub
End Class
And where you create the child Form:
Dim f As New ChildForm With
{
.FormBorderStyle = FormBorderStyle.None,
.TopLevel = False,
.ControlBox = False,
.Dock = DockStyle.Fill,
}
ContainerPanel.Controls.Add(f)
f.Show()
Since the last inserted control into the collection is docked first, you need to explicitly set the order of the controls to avoid any possible dock overlapping. So as you noticed and mentioned in your comment, the Dock.Fill Form is overlapping the Dock.Top Label. To fix that:
Dim f As New ChildForm With
{
.FormBorderStyle = FormBorderStyle.None,
.TopLevel = False,
.ControlBox = False,
.Dock = DockStyle.Fill
}
ContainerPanel.Controls.Add(f)
Controls.SetChildIndex(ContainerPanel, 0)
Controls.SetChildIndex(Label1, 1)
f.Show()
In fact you can omit Controls.SetChildIndex(Label1, 1), just added to clear up the idea.
If you'r using the designer to add the ContainerPanel, select it and right click, you will see in the context menu Bring to Front and Send to Back which are used to do the same thing. Also, you can use the Document Outline window (Ctrl + T to show it) to change the order of the controls using the up/down arrows and the left/right arrows to move controls to different containers.
Second Part
As for the second part, you need to create a new class that inherits the ProfessionalColorTable and override the ToolStripBorder property to prevent it from returning the default color when rendering the ToolStrip:
Public Class CustomColorTable
Inherits ProfessionalColorTable
Public Overrides ReadOnly Property ToolStripBorder As Color
Get
Return Color.Empty
End Get
End Property
End Class
Then, pass the custom color table to a new instance of the ToolStripProfessionalRenderer class and assign it to the ToolStrip.Renderer or the ToolStripManager.Renderer properties.
Revisiting the constructor of the parent Form:
Sub New()
InitializeComponent()
ContainerPanel = New Panel With {.Dock = DockStyle.Fill}
Controls.Add(ContainerPanel)
ToolStrip1.Renderer = New ToolStripProfessionalRenderer(New CustomColorTable) With {
.RoundedEdges = False
}
'Or
'ToolStripManager.Renderer = New ToolStripProfessionalRenderer(New CustomColorTable) With {
' .RoundedEdges = False
'}
End Sub
I'm trying to add theme options to my VB.NET program without doing it the dirty way of repeating the same code for each element... Is it possible to create some styling method similar to what we have in android? (styles.xml)
First thing I started making is a library for the themes I want to use in my app but I quickly realized that it still won't help to use a regular class/group of methods since I will still have to call them everytime I need a control to be themed since it is not passive like the following:
Public Class Theme
Public Shared Sub SetThemeLight(c As Control, Gray As Boolean)
Dim LBack As Color = Color.White
Dim LBackGray As Color = Color.WhiteSmoke
Dim LFore As Color = SystemColors.ControlText
Dim LForeGray As Color = Color.FromArgb(51, 51, 51)
If Gray Then
c.BackColor = LBackGray
c.ForeColor = LForeGray
Else
c.BackColor = LBack
c.ForeColor = LFore
End If
End Sub
'...
'Somewhere else in the program for example
Public Class Main
Theme.SetThemeLight(TextBox1, False)
'...
Issue here is the excessive simplicity which is so inefficient. I would love to find an efficient way of theming similar to android's styles.xml
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 looking for a way to design things differently in my project. Instead of using TabControls I wish to use Buttons (Instead of pressing the tabs on the top I would like to press the Buttons on the left-side). These buttons when pressed they have their own Panel where each has their own respective content.
Select Case tabAdmin.SelectedIndex
Case 0
If txtCode_Patient.Text = "" Then
txtCode_Patient.Focus()
Else
cmdAdminister.Focus()
End If
Case 1
If txtD_Patient.Text = "" Then
txtD_Patient.Focus()
Else
cmdRefresh.Focus()
End If
Case 2
If txtI_Patient.Text = "" Then
txtI_Patient.Focus()
Else
cmdI_CUser.Focus()
End If
Case 3
If txtStat_CS.Text = "" Then
txtStat_CS.Focus()
Else
cmdStat_Refresh.Focus()
End If
End Select
The code above is similar to what my project acts and it works with TabControls. I want to do a similar thing but this time, like I said before, pressing Buttons on the left-side. How can I do a similar thing ?
UPDATE:
I found a way for this one but now my concern is how do I make it look like one of its default button 3D look-alike?
Public Class Tab
Inherits TabControl
Private Property DoubledBuffered As Boolean
Sub New()
SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint, True)
DoubledBuffered = True
SizeMode = TabSizeMode.Fixed
ItemSize = New Size(30, 110)
End Sub
Protected Overrides Sub CreateHandle()
MyBase.CreateHandle()
Alignment = TabAlignment.Left
End Sub
Protected Overrides Sub OnPaint(e As System.Windows.Forms.PaintEventArgs)
Dim B As New Bitmap(Width, Height)
Dim G As Graphics = Graphics.FromImage(B)
G.Clear(Color.AliceBlue)
For i = 0 To TabCount - 1
Dim TabRectangle As Rectangle = GetTabRect(i)
If i = SelectedIndex Then
'//Selected
G.FillRectangle(Brushes.DarkSlateGray, TabRectangle)
Else
'//Not Selected
G.FillRectangle(Brushes.AntiqueWhite, TabRectangle)
End If
G.DrawString(TabPages(i).Text, Font, Brushes.Black, TabRectangle, New StringFormat With {.Alignment = StringAlignment.Center, .LineAlignment = StringAlignment.Center})
Next
e.Graphics.DrawImage(B.Clone, 0, 0)
G.Dispose() : B.Dispose()
MyBase.OnPaint(e)
End Sub
End Class
Use the CheckBox control instead of Button, but set Appearance = Button, that way it looks exactly like a button can remains in the "pressed" state when clicked.
To shift between content, put each of your sub-forms into their own UserControl instances, then host them within a Panel control, then switch the .Visibility property of each sub-form according to which CheckBox was clicked.
There is an Outlook-style side bar available on Code Project. It has a VB version as well as C# and although it's knocking on a bit now, you could always adapt this to look a bit nicer. I have used it in the past and it worked pretty well as I recall.
You can set the selected tab via tabAdmin.SelectedIndex = 0 (or 1, 2, etc, but remember it is 0 based)
You may also set the tab by the tab's name via tabAdmin.SelectedTab = TabName
Use a common click event handler for the buttons. Store the relevant tabindex in the Tag property of the buttons. Then tabAdmin.SelectedIndex equals the tag of the clicked button cast as integer.
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()