My setting:
I've got a C# application (.NET 3.5) in Visual Studio 2008. No chance to switch to WPF or whatsoever :).
My app contains a custom control (a button class derived from Windows.Forms.Button) that acts as a replacement for the Windows.Forms.TabControl. I can associate these buttons with one another and each button can be associated with one control that it is dealing with (usually some sort of Windows.Forms.Panel). It looks something like this:
public class TabButton : System.Windows.Forms.Button
{
// ...
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
this.myAssociatedControl.Visible = true;
this.tellMyBuddiesToHideTheirControls();
}
// ...
}
Basically it is just about clicking a button, showing its bound control and having the controls bound to the associated buttons disappear - just like the TabControl, but the approach is easily designable and I can place the buttons far from their content panels.
The problem:
This works pretty well at runtime, but the usage at design time is arguably odd: With the mouse, find a control that´s belonging to the group and run a series of <Send To Back>s until the desired control is visible.
The question:
Is there a way to tell the VS designer to evaluate the clicks on the buttons at design time like it does with the TabControl so that I can switch the tabs just by clicking them like I would at runtime?
I've been searching for quite a while now. There are some articles here at SO but they only seem to cover adding additional attributes to the properties designer.
Edith says:
By request, an answer to my own question ...
This is the solution that is suitable to my application. It is basically an example from the msdn with some twists to get the custom designer to use a callback on click. Hope it helps anyone :-).
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class TabButtonDesigner : System.Windows.Forms.Design.ControlDesigner
{
ShowTabGlyph myGlyph = null;
Adorner myAdorner;
public TabButtonDesigner()
{
}
public override void Initialize(IComponent component)
{
base.Initialize(component);
// Add the custom set of glyphs using the BehaviorService.
// Glyphs live on adornders.
myAdorner = new Adorner();
BehaviorService.Adorners.Add(myAdorner);
myGlyph = new ShowTabGlyph(BehaviorService, Control);
myGlyph.Callback = () =>
{
((MyCustomTabButton)this.Control).ShowMyTab();
};
myAdorner.Glyphs.Add(myGlyph);
}
class ShowTabGlyph : Glyph
{
Control control;
BehaviorService behaviorSvc;
public Action Callback
{
get;
set;
}
public ShowTabGlyph(BehaviorService behaviorSvc, Control control) :
base(new ShowTabBehavior())
{
this.behaviorSvc = behaviorSvc;
this.control = control;
}
public override Rectangle Bounds
{
get
{
// Create a glyph that is 10x10 and sitting
// in the middle of the control. Glyph coordinates
// are in adorner window coordinates, so we must map
// using the behavior service.
Point edge = behaviorSvc.ControlToAdornerWindow(control);
Size size = control.Size;
Point center = new Point(edge.X + (size.Width / 2),
edge.Y + (size.Height / 2));
Rectangle bounds = new Rectangle(
center.X - 5,
center.Y - 5,
10,
10);
return bounds;
}
}
public override Cursor GetHitTest(Point p)
{
// GetHitTest is called to see if the point is
// within this glyph. This gives us a chance to decide
// what cursor to show. Returning null from here means
// the mouse pointer is not currently inside of the glyph.
// Returning a valid cursor here indicates the pointer is
// inside the glyph, and also enables our Behavior property
// as the active behavior.
if (Bounds.Contains(p))
{
return Cursors.Hand;
}
return null;
}
public override void Paint(PaintEventArgs pe)
{
// Draw our glyph. It is simply a blue ellipse.
pe.Graphics.DrawEllipse(Pens.Blue, Bounds);
}
// By providing our own behavior we can do something interesting
// when the user clicks or manipulates our glyph.
class ShowTabBehavior : Behavior
{
public override bool OnMouseUp(Glyph g, MouseButtons button)
{
//MessageBox.Show("Hey, you clicked the mouse here");
//this.
ShowTabGlyph myG = (ShowTabGlyph)g;
if (myG.Callback != null)
{
myG.Callback();
}
return true; // indicating we processed this event.
}
}
}
}
[DesignerAttribute(typeof(TabButtonDesigner))]
public class MyCustomTabButton : System.Windows.Forms.Button
{
// The attribute will assign the custom designer to the TabButton
// and after a rebuild the button contains a centered blue circle
// that acts at design time like the button in runtime does ...
// ...
}
Related
This is probably a basic question, but I can't find answers because the terms are generic.
I am building a WinForm aplication. Its purpose is to set up memory in a certain chip. I think the best way to organize the application is to have a user control for each chip type, derived from a generic parent class. Think of the children as "iphone," "android" and "blackberry," derived from a parent class "phone".
VS2017 Designer has a Panel where I want the control to be. On startup, I generate an object of the base class and add it to the panel. When I press a button, the old object is deleted and replaced with a new one. Each class has just one control, a label with distinctive text.
The problem is, after I press the button, I see both texts. The panel's Controls collection has just one element, but I see the text from both objects. I have tried Refresh, Update and Invalidate withe the same results.
What do I have to do to make the old text "go away" so the only thing I see is the latest object?
private ChipMemBase ChipMemControl = new ChipMemBase();
public Form1()
{
InitializeComponent();
//tbFeedback.Text = string.Format(fmtString, 0, 1, 2, 3, 4, 5);
cbChipName.SelectedIndex = 0;
tbVersion.Text = Version;
OriginalWindowColor = tbFeedback.BackColor;
ShowChipMemControl();
PrintToFeedback(Version);
}
private void ShowChipMemControl()
{
var ctl = pnlChipMem.GetChildAtPoint(new Point(5,5));
if (null != ctl)
{
if (ctl != ChipMemControl)
{
pnlChipMem.Controls.Remove(ctl);
ctl.Dispose();
pnlChipMem.Update();
Refresh();
}
}
if (null != ChipMemControl)
{
pnlChipMem.Controls.Add(ChipMemControl);
}
}
private void btnMakeChipMemory_Click(object sender, EventArgs e)
{
ChipMemControl = new ChipMemGen2();
ShowChipMemControl();
}
Screenshots before and after clicking Create
Your ShowChipMemControl gets the control at point 5,5 and checks if it's a ChipMemControl then removes it.
I'm guessing that the reason it's not getting removed is that the control at point 5,5 is not a ChipMemControl.
You can use:
pnlChipMem.Controls.Clear()
to remove all the controls
Or:
ChipMemControl cmc = pnlChipMem.Controls.OfType<ChipMemBase>().FirstOrDefault();
if (cmc != null)
{
pnlChipMem.Controls.Remove(cmc);
cmc.Dispose();
}
To only remove the first instance of ChipMemBase on your pnlChipMem panel.
Got it. The problem was from inheritance, not window behavior. Control lblDefault in the base class, carrying the inconvenient text, was still present in the child class. I had to make it Public in the base class and remove it in the child class constructor:
InitializeComponent();
Controls.Remove(lblDefault);
lblDefault.Dispose();
lblDefault = null;
The clue was this article and project:
dynamically-and-remove-a-user-control
I wrote an Visual Studio 2013 extension that observes Windows.Forms designer windows. When a developer is changing controls in the designer window, the extension tries to verify that the result is consistent with our ui style guidelines. If possible violations are found they are listed in a tool window. This all works fine.
But now I would like to mark the inconsistent controls in the designer window, for example with a red frame or something like this.
Unfortunately, I did not find a way to draw adornments on controls in a designer window. I know that you can draw those adornments if you develop your own ControlDesigner, but I need to do it from "outside" the control's designer. I only have the IDesignerHost from the Dte2.ActiveWindow and can access the Controls and ControlDesigners via that host. I could not find any way to add adornments from "outside" the ControlDesigners.
My workaround for now is to catch the Paint-Events of the controls and try to draw my adornments from there. This doesn't work well for all controls (i.e. ComboBoxes etc), because not all controls let you draw on them. So I had to use their parent control's Paint event. And there are other drawbacks to this solution.
I hope someone can tell me if there is a better way. I'm pretty sure that there has to be one: If you use Menu->View->Tab Order (not sure about the correct english menu title, I'm using a german IDE), you can see that the IDE itself is able to adorn controls (no screenshot because it's my first post on SO), and I'm sure it is not using a work around like me. How does it do that?
I've been googling that for weeks now. Thanks for any help, advice, research starting points....
UPDATE:
Maybe it gets a little bit clearer with this screenshot:
Those blue numbered carets is what Visual Studio shows when selecting Tab order from the View menu. And my question is how this is done by the IDE.
As mentioned I tried to do it in the Paint event of the controls, but e.g. ComboBox doesn't actually support that event. And if I use the parent's Paint event I can only draw "around" the child controls because they are painted after the parent.
I also thought about using reflection on the controls or the ControlDesigners, but am not sure how to hook on the protected OnPaintAdornments method. And I don't think the IDE developers used those "dirty" tricks.
I believe you are seeking for BehaviorService architecture. The architecture with supporting parts like Behavior, Adorner and Glyph and some examples is explained here Behavior Service Overview. For instance
Extending the Design-Time User Interface
The BehaviorService model enables new functionality to be easily layered on an existing designer user interface. New UI remains independent of other previously defined Glyph and Behavior objects. For example, the smart tags on some controls are accessed by a Glyph in the upper-right-hand corner of the control (Smart Tag Glyph).
The smart tag code creates its own Adorner layer and adds Glyph objects to this layer. This keeps the smart tag Glyph objects separate from the selection Glyph objects. The necessary code for adding a new Adorner to the Adorners collection is straightforward.
etc.
Hope that helps.
I finally had the time to implement my solution and want to show it for completeness.
Of course I reduced the code to show only the relevant parts.
1. Obtaining the BehaviorService
This is one of the reasons why I don't like the service locator (anti) pattern. Though reading a lot of articles, I didn't came to my mind that I can obtain a BehaviorService from my IDesignerHost.
I now have something like this data class:
public class DesignerIssuesModel
{
private readonly BehaviorService m_BehaviorService;
private readonly Adorner m_Adorner = new Adorner();
private readonly Dictionary<Control, MyGlyph> m_Glyphs = new Dictionary<Control, MyGlyph>();
public IDesignerHost DesignerHost { get; private set; }
public DesignerIssuesModel(IDesignerHost designerHost)
{
DesignerHost = designerHost;
m_BehaviorService = (BehaviorService)DesignerHost.RootComponent.Site.GetService(typeof(BehaviorService));
m_BehaviorService.Adornders.Add(m_Adorner);
}
public void AddIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control))
{
MyGlyph g = new MyGlyph(m_BehaviorService, control);
m_Glyphs[control] = g;
m_Adorner.Glyphs.Add(g);
}
m_Glyphs[control].Issues += 1;
}
public void RemoveIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control)) return;
MyGlyph g = m_Glyphs[control];
g.Issues -= 1;
if (g.Issues > 0) return;
m_Glyphs.Remove(control);
m_Adorner.Glyphs.Remove(g);
}
}
So I obtain the BehaviorService from the RootComponent of the IDesignerHost and add a new System.Windows.Forms.Design.Behavior.Adorner to it. Then I can use my AddIssue and RemoveIssue methods to add and modify my glyphs to the Adorner.
2. My Glyph implementation
Here is the implementation of MyGlyph, a class inherited from System.Windows.Forms.Design.Behavior.Glyph:
public class MyGlyph : Glyph
{
private readonly BehaviorService m_BehaviorService;
private readonly Control m_Control;
public int Issues { get; set; }
public Control Control { get { return m_Control; } }
public VolkerIssueGlyph(BehaviorService behaviorService, Control control) : base(new MyBehavior())
{
m_Control = control;
m_BehaviorService = behaviorService;
}
public override Rectangle Bounds
{
get
{
Point p = m_BehaviorService.ControlToAdornerWindow(m_Control);
Graphics g = Graphics.FromHwnd(m_Control.Handle);
SizeF size = g.MeasureString(Issues.ToString(), m_Font);
return new Rectangle(p.X + 1, p.Y + m_Control.Height - (int)size.Height - 2, (int)size.Width + 1, (int)size.Height + 1);
}
}
public override Cursor GetHitTest(Point p)
{
return m_Control.Visible && Bounds.Contains(p) ? Cursors.Cross : null;
}
public override void Paint(PaintEventArgs pe)
{
if (!m_Control.Visible) return;
Point topLeft = m_BehaviorService.ControlToAdornerWindow(m_Control);
using (Pen pen = new Pen(Color.Red, 2))
pe.Graphics.DrawRectangle(pen, topLeft.X, topLeft.Y, m_Control.Width, m_Control.Height);
Rectangle bounds = Bounds;
pe.Graphics.FillRectangle(Brushes.Red, bounds);
pe.Graphics.DrawString(Issues.ToString(), m_Font, Brushes.Black, bounds);
}
}
The details of the overrides can be studied in the links posted in the accepted answer.
I draw a red border around (but inside) the control and add a little rectangle containing the number of found issues.
One thing to note is that I check if Control.Visible is true. So I can avoid to draw the adornment when the control is - for example - on a TabPage that is currently not selected.
3. My Behavior implementation
Since the constructor of the Glyph base class needs an instance of a class inherited from Behavior, I needed to create a new class. This can be left empty, but I used it to show a tooltip when the mouse enters the rectangle showing the number of issues:
public class MyBehavior : Behavior
{
private static readonly ToolTip ToolTip = new ToolTip
{
ToolTipTitle = "UI guide line issues found",
ToolTipIcon = ToolTipIcon.Warning
};
public override bool OnMouseEnter(Glyph g)
{
MyGlyph glyph = (MyGlyph)g;
if (!glyph.Control.Visible) return false;
lock(ToolTip)
ToolTip.Show(GetText(glyph), glyph.Control, glyph.Control.PointToClient(Control.MousePosition), 2000);
return true;
}
public override bool OnMouseLeave(Glyph g)
{
lock (ToolTip)
ToolTip.Hide(((MyGlyph)g).Control);
return true;
}
private static string GetText(MyGlyph glyph)
{
return string.Format("{0} has {1} conflicts!", glyph.Control.Name, glyph.Issues);
}
}
The overrides are called when the mouse enters/leaves the Bounds returned by the MyGlyph implementation.
4. Results
Finally I show screenshot of a example result. Since this was done by the real implementation, the tooltip is a little more advanced. The button is misaligned to all the comboboxes, because it's a little too left:
Thanks again to Ivan Stoev for pointing me to the right solution. I hope I could make clear how I implemented it.
Use the System.Drawing.Graphics.FromHwnd method, passing in the HWND for the designer window.
Get the HWND by drilling down into the window handles for visual studio, via pinvoke. Perhaps use tools like Inspect to find window classes and other information that might help you identify the correct (designer) window.
I've written a C# program to get you started here.
I wonder if it's possible to customize my C# application (winforms) to get a better design, I made a PSD (photoshop document) so I can generate png jpeg... pictures if I need them.
Example of a form like the one I want :
Indeed as it was pointed out in the comments, it is easy to use WPF (indows Presentation Foundation) to achieve that result, but if you really need that it must be made in windows forms I can help you with that...
ControlBox and Border
It seens that your form does not have a control box (minimize, maximize and close buttons)
to achieve that you can set
form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
I'm not sure if that galaxy behind your form is part of the application so i'll be considering that it is not
To achieve that irregular shape of the form we have to do a workaround here
Irregular Shape of the Form
we are going to set a Color to TransparentKey, so everything in the form in that specific color will be transparent, like it does not exists (if you click in that part it will go into de desktop or whatever application you have behind in your form)
So let's use a specific color which we will probably dont use in the form
form.TransparencyKey = Color.FromArgb(111, 111, 111); //You can do it by the editor
So in order to make that white part we are going to use an Panel and a PictureBox outsite of the Panel trying to copy the shape of your image
Stylized Inputs
To make it easier and reusable I'm going to make a userControl in this one
the usercontrol will have
a Panel called HighLightPanel, its dock property will be set to Fill
a Panel called BackColorPanel, it will be inside the HighLightPanel
a PictureBox called InputPicture, its dock property will be set to Left, it will be inside BackColorPanel and its acessor will be public
a TextBox called TextBox, its dock property wil be set to fill, it will be inside BackColorPanel, the BorderStyle Property set to None, you should set the size and font you most desize in this one, I'm going to use Segoe UI; 15,75pt and its acessor will be public
Now we have to make some properties in our UserControl to make it work without work in other controls
First in the SizeChanged event of the HighLightPanel we will make the BackColorPanel be exacly two points smaller in every direction and its position to 1;1 so we can see the HighLightPanel
private void HighlightPanel_SizeChanged(object sender, EventArgs e)
{
this.BackColorPanel.Size = new Size(
HighlightPanel.Width - 2,
HighlightPanel.Height - 2);
}
Now we will create two propertys to handle the Highlight Color
public Color HighlightBorderColor { get; set; }
public Color NonHighlightBorderColor { get; set; }
And in the Enter and Leave Property of our TextBox we are going to change the HighlightPanel
private void TextBox_Enter(object sender, EventArgs e)
{
HighlightPanel.BackColor = HighlightBorderColor;
}
private void TextBox_Leave(object sender, EventArgs e)
{
HighlightPanel.BackColor = NonHighlightBorderColor;
}
So now every time the user enter the Input it will appear that the Input has an Border in the specified Color
Now to enhance its usability to developers we will make some wrappers in its controls to be easier change property of child controls in the editor
public Image InputImage
{
get { return InputPicture.Image; }
set { InputPicture.Image = value; }
}
public PictureBoxSizeMode InputImageLayout
{
get { return InputPicture.SizeMode; }
set { InputPicture.SizeMode = value; }
}
public char PasswordCharacter
{
get { return TextBox.PasswordChar; }
set { TextBox.PasswordChar = value; }
}
public bool ShowInputImage
{
get { return InputPicture.Visible; }
set { InputPicture.Visible = value; }
}
In the InputImage set the picture you want for the User and the Key
Insert the two controls in the position you like
Position of the Form
if you want your form to be moveable without the border you will have to use this snippet, it is more easy in WPF
#region MoveForm
Point LastPoint;
bool ShouldMove;
private void form_MouseDown(object sender, MouseEventArgs e)
{
LastPoint = e.Location;
ShouldMove = true;
this.TransparencyKey = Color.FromArgb(111, 111, 111);
}
private void form_MouseUp(object sender, MouseEventArgs e)
{
ShouldMove = false;
}
private void form_MouseMove(object sender, MouseEventArgs e)
{
if (ShouldMove)
{
this.Location = new Point(
this.Location.X - LastPoint.X + e.X,
this.Location.Y - LastPoint.Y + e.Y);
}
}
#endregion
If you need a lot of special graphics effects learning WPF will indeed be a sound investement.
If all you want is that login screen, it is trivial in Winforms and doesn't take any horrible hacks as you've been told..
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.BackColor = System.Drawing.Color.LavenderBlush;
this.TransparencyKey = System.Drawing.Color.LavenderBlush;
this.ControlBox = false;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Text= "";
These seven lines are all it takes for a form to be transparent. I copied them from the Designer code; you can simply set the 7 Properties in the property grid.
Now add a panel, dock it to the bottom and give it the right color; add a picturebox and your other controls and you are set.
To create the two input groups you also need just a few regular controls and only a few simple lines of code:
You place one Panel, BorderStyle = FixedSingle; and add a Label and a TextBox to it. The Label has AutoSize = False; and both ImageAlign and TextAlign are set to MiddleLeft. You assign an image to the Label's Image and prefix the Text with enough blanks to not overlap. Obviously you should define a PasswordChar for the 2nd TextBox. Now all you need is to script the Enter and Leave events to change the BackColor of the respective Panels between, say SystemColors.Control and SystemColors.MenuHighlight. Size the Labels to almost fill the Panels and you are done. Less code than the WPF version, I'd bet.
If you need such input an controls again and again, simply create Usercontrols for each type you need!
Here is an example of the limits you will hit: Wouldn't it be nice to add a dropshadow effect to the image? It is doable in Winforms. But it would involve painting that effect; this would take at least 15 or 20 lines of involved code instead of simply turning the effect on with (estimated) 1-3 simple lines.
Do you need any nice hover effects? Not easy, to say the least..
These limits will be all over the place, so it really depends on how fancy your requirements will get.
Maybe you should use this example as a starter to compare the two techniques and to warm you up to WPF?
I've developed a custom UserControl. When I add it to a form in design view, there is no obvious border around it (unless I change the BorderStyle property to something other than None).
Some controls (such as PictureBoxes) have a dashed outline to indicate the area they are using. Is there a way to do this for a UserControl?
I'm using C#, .NET 3.5, Windows Forms.
You'll need to write a custom designer for your UserControl, which is the same thing that WinForms does for the Panel control. The code in the designer class overrides the OnPaintAdornments method in order to draw the dashed border around the control's client area.
The easiest way to get started is by inheriting from the ScrollableControlDesigner class, which will give you most of the necessary functionality for free. Then add the logic into these methods:
public class MyUserControlDesigner : ScrollableControlDesigner
{
public MyUserControlDesigner()
{
base.AutoResizeHandles = true;
}
protected override void OnPaintAdornments(PaintEventArgs p)
{
// Get the user control that we're designing.
UserControl component = (UserControl)base.Component;
// As you mentioned, no reason to draw this border unless the
// BorderStyle property is set to "None"
if (component.BorderStyle == BorderStyle.None)
{
this.DrawBorder(p.Graphics);
}
// Call the base class.
base.OnPaintAdornments(p);
}
protected virtual void DrawBorder(Graphics g)
{
// Get the user control that we're designing.
UserControl component = (UserControl)base.Component;
// Ensure that the user control we're designing exists and is visible.
if ((component != null) && component.Visible)
{
// Draw the dashed border around the perimeter of its client area.
using (Pen borderPen = this.BorderPen)
{
Rectangle clientRect = this.Control.ClientRectangle;
clientRect.Width--;
clientRect.Height--;
g.DrawRectangle(borderPen, clientRect);
}
}
}
protected Pen BorderPen
{
get
{
// Create a Pen object with a color that can be seen on top of
// the control's background.
return new Pen((this.Control.BackColor.GetBrightness() < 0.5) ?
ControlPaint.Light(this.Control.BackColor)
: ControlPaint.Dark(this.Control.BackColor))
{ DashStyle = DashStyle.Dash };
}
}
}
Once you've done that, you'll need to instruct your UserControl class to use the custom designer you've written. That's done by adding the DesignerAttribute to its class definition:
[Designer(typeof(MyUserControlDesigner)), DesignerCategory("UserControl")]
public class MyUserControl : UserControl
{
// insert your code here
}
And of course, this will require that you add a reference to System.Design.dll to your assembly, forcing you to target the full version of the .NET Framework (rather than the "Client Profile").
Is there a way to disable or better yet draw your own focus rectangle for a regular button control! (that dotted line seems so Windowss 95ish)
I've noticed that the control properties (FOR BUTTONS) does not have a ownerdrawfixed setting (which I don't know if that's even the route to use for the solution, although i've seen it used for customizing other controls).
Getting this right is trickier than it sounds. No doubt one of the reasons that custom button painting isn't overridable. This worked as expected:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
class MyButton : Button {
private VisualStyleRenderer renderer;
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (this.Focused && Application.RenderWithVisualStyles && this.FlatStyle == FlatStyle.Standard) {
if (renderer == null) {
VisualStyleElement elem = VisualStyleElement.Button.PushButton.Normal;
renderer = new VisualStyleRenderer(elem.ClassName, elem.Part, (int)PushButtonState.Normal);
}
Rectangle rc = renderer.GetBackgroundContentRectangle(e.Graphics, new Rectangle(0, 0, this.Width, this.Height));
rc.Height--;
rc.Width--;
using (Pen p = new Pen(Brushes.DarkGray)) {
e.Graphics.DrawRectangle(p, rc);
}
}
}
}
A quick and easy way to disable the focus rectangle all together is to subclass the control and include this code:
public class CustomButton : Button
{
protected override bool ShowFocusCues
{
get
{
return false;
}
}
}
Just simple way.
Set
button.FlatStyle = Flat;
button.FlatAppearance.BorderColor = Color.FromArgb(0, 255, 255, 255);
button.FlatAppearance.BorderSize = 0;
button.TabStop = false;
FlatAppearance.BorderColor
set on code cause could not transparent color set in design mode.
Subclass the Button class and override OnPaint. If your override does not call base.OnPaint, nothing will be drawn for the button and you will have complete control (including the focus rectangle).
One quick/dirty solution that I found (for removing the focus rectangle only) was explicitly defining the background color. For the default control color, for ex:
this._dropDownButton.BackColor = System.Drawing.ColorTranslator.FromHtml("#F0F0F0");
EDIT: Apparently this doesn't work. It was being fixed in my case for an unrelated reason. Apologies.
I had the same issue when using BackgroundImage to set an image on the button. When the user pressed 'Tab', my image button got a black rectangle.
The solution that worked for me is:
Call for NotifyDefault(false) for every button I used.
Set the TabStop property to false for every button I used.
Tested on .NET Framework 4.6.2.
Before:
After:
In my case, I have to use both solutions above to make it work.
public class ButtonNoFocus : Button
{
protected override bool ShowFocusCues
{
get
{
return false;
}
}
public override void NotifyDefault(bool value)
{
base.NotifyDefault(false);
}
}
A simple solution to hide the focus frame would be to switch the focus from buttons to a dummy control as soon as the button has been clicked:
public frmMain()
{
...
RemoveControlFocusFrame(this);
}
private void RemoveControlFocusFrame(Control c)
{
if (c.Controls.Count == 0)
{
if (c is Button || c is CheckBox)
c.GotFocus += (o, e) => lblFocusDump.Focus();
return;
}
foreach (Control sub in c.Controls)
RemoveControlFocusFrame(sub);
}
The dummy lblFocusDump label has its Visible set to true and can be hidden in Designer by pushing it to background behind any other control.