Look this class:
public class ControlContainer : Control
{
public ControlContainer() { DoubleBuffered = true; SetStyle(ControlStyles.SupportsTransparentBackColor, true); }
Control _Control;
public Control Control { get { return _Control; } set { _Control = value; SetHandlers(); } }
public void SetHandlers()
{
if (_Control == null) return;
Region = _Control.Region;
Size = _Control.Size;
_Control.Invalidated += new InvalidateEventHandler(_Control_Invalidated);
}
void _Control_Invalidated(object sender, InvalidateEventArgs e)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
if (_Control == null) return;
Bitmap p = new Bitmap(_Control.Width, _Control.Height);
_Control.DrawToBitmap(p, new Rectangle(0, 0, _Control.Width, _Control.Height));
e.Graphics.DrawImage((Image)p, 0, 0);
base.OnPaint(e);
}
}
As you see the mission of this control is to get an image of another control and draws it.
But if we handled a TextBox control named 'textBox1' as the following:
Suppose we have added a new instance of the type ControlContainer above and its name is controlContainer1 and we assign the value of the property 'Control' of controlContainer1 to textBox1.
Why if I write something in textBox1 the event 'Invalidated' doesn't fire? and why the pointer of the writing "|" doesn't appear by the method 'DrawToBitmap"?
Short answer: Because the TextBox control doesn't support the event Invalidated. Perhaps it will help if you call Invalidate() on event TextChanged of the textbox.
Longs answer: It's beacuse how windows is internal handling a Win32 native textbox. If you start editing, a control without borders, the same color at the same location as the textbox will be created. Now all events go to this Win32 internal editbox. You can observe this behaviour if you override the OnPaint for a textbox and fill the whole textbox with FillRectangle. As soon as the textbox is edited, you see the new editbox in the (original) color of the proprty BackColor.
Related
I have a button which I use all the time as a little pick button next to a combobox. When I click the button I open a larger full list. This side of things work well and I do not have a problem with this..
My problem lies when someone said to me can you change that ugly icon you picked to my nice icon.
I went crap, I have hundreds of these buttons on many forms. So I thought I will create a custom control called PickButton (which is a standard button and heap of default proeprties set) and drop these on the form everywhere instead. In the code of the PickButton custom control I set some properties and the image to the customers nice icon.
So I drop the PickButton from my toolbox onto the form, so far things are looking pretty good and I am feeling a bit clever. Now I think to myself I will change back to my nice icon not the crappy one the customer picked and change the code in the PickButton custom control. But I cannot get rid of that customers icon, because the code when the PickButton run happens before the code in the designer file which has the customers icon.
So my aim was to have a PickButton control and be able to change the icon and other properties in one place and all the properties would be set when an instance of the control is created and displayed on the form.
Was I not so clever and went about achieving the task the wrong way???
This is my PickButton custom control class
public class PickButton : Button
{
public PickButton()
{
InitialiseButton();
}
internal void InitialiseButton()
{
this.ImageAlign = ContentAlignment.MiddleCenter;
this.Image = WindowsFormsApplication1.Properties.Resources.Cancel.ToBitmap();
this.Size = new Size( 28, 28 );
this.Dock = DockStyle.Fill;
this.Margin = new Padding( 0, 2, 2, 0 );
this.Text = string.Empty;
}
}
Now I drop one onto my form and the code in the designer is as follows
//
// pickButton1
//
this.pickButton1.Dock = System.Windows.Forms.DockStyle.Fill;
this.pickButton1.Image = ((System.Drawing.Image)(resources.GetObject("pickButton1.Image")));
this.pickButton1.Location = new System.Drawing.Point(0, 0);
this.pickButton1.Margin = new System.Windows.Forms.Padding(0, 2, 2, 0);
this.pickButton1.Name = "pickButton1";
this.pickButton1.Size = new System.Drawing.Size(284, 262);
this.pickButton1.TabIndex = 0;
this.pickButton1.Text = "pickButton1";
this.pickButton1.UseVisualStyleBackColor = true;
Now I want to change the image so I change my PickButton code to use a different icon
this.Image = WindowsFormsApplication1.Properties.Resources.Browse.ToBitmap();
Run the application andd the first icon is still the one being displayed because of this line of code in the designer file
this.pickButton1.Image = ((System.Drawing.Image)(resources.GetObject("pickButton1.Image")));
The concept of setting all the properties in one place was a good idea, it just wasn't implemented quite right. I would make this class inherit from UserControl instead of from Button. By making it a UserControl, you can use the designer to set all the properties you want, like the default Image for the button. Set that in the designer, then just drag and drop your UserControl from the toolbox onto your forms. If you are only using your "PickButton" control with comboboxes, I would put the combobox on the UserControl as well. If you ever want to change your button image in the future (or any other property for that matter), you will be able to change it in ctlPickButton and that will propogate the changes to all the instances used throughout your project(s).
ctlPickButton:
public partial class ctlPickButton : UserControl
{
public event EventHandler pickButtonClicked;
public ctlPickButton()
{
InitializeComponent();
}
//Allows buttons image to be set in code if necessary
public Image Image
{
get
{
return button1.Image;
}
set
{
if (Image != null)
{
button1.Image = value;
}
}
}
private void button1_Click(object sender, EventArgs e)
{
if (pickButtonClicked != null)
{
pickButtonClicked(sender, e);
}
}
}
Demo Form:
public Form1()
{
InitializeComponent();
ctlPickButton1.pickButtonClicked += new EventHandler(ctlPickButton1_pickButtonClicked);
ctlPickButton2.pickButtonClicked += new EventHandler(ctlPickButton2_pickButtonClicked);
}
void ctlPickButton2_pickButtonClicked(object sender, EventArgs e)
{
if (comboBox2.SelectedItem != null)
{
MessageBox.Show(comboBox2.SelectedItem.ToString());
}
}
void ctlPickButton1_pickButtonClicked(object sender, EventArgs e)
{
if (comboBox1.SelectedItem != null)
{
MessageBox.Show(comboBox1.SelectedItem.ToString());
}
}
private void Form1_Load(object sender, EventArgs e)
{
comboBox1.Items.Add("French");
comboBox1.Items.Add("Spanish");
comboBox1.Items.Add("English");
comboBox1.Items.Add("German");
comboBox2.Items.Add("Pizza");
comboBox2.Items.Add("Hamburger");
comboBox2.Items.Add("Potato");
comboBox2.Items.Add("Chicken");
//Shows how the default image set in the designer can be overwritten for a
//specific instance using the "Image" property
ctlPickButton2.Image = Testbed.Properties.Resources.searchIcon2;
}
}
Image of ctlPickButton in designer
I think I've found a simple, clean solution:
In the CustomButton class (which inherits from System.Windows.Forms.Button), override the Refresh() method, and set the image of the button to the one you want to see:
public class CustomButton : Button
{
public override void Refresh()
{
Image = MyResources.HappyFace;
}
}
In the form that will hold an instance of your CustomButton, simply call customButton.Refresh() in the constructor, after InitializeComponent():
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
customButton.Refresh();
}
}
I've put a demo application up on Github.
I notice all the other controls I am using such as tabs, textboxes, rich textboxes with text already inside it and everything else resize automatically when I change the main Form font. But labels are an exception to this rule is there a property I can turn on or off in the Label that will make it resize font with the form? It is a pretty straightforward yes or no question I know how to work around it I just like how all my controls are automatically resizing without any unnecessary code.
I've a solution here:
If your controls have the same font with your Form's Font, you shouldn't change the particular Font of them (via the Properties window or via code), instead you just change your Form's Font. If that has already been done, you can find the code lines (normally in Form.Designer.cs) which specify Font for your controls (something like yourControl.Font = ...) and remove those lines.
If your controls need different Font from your Form's. You can register the FontChanged event handler of your Form and change its children controls' Fonts accordingly (just the font size):
private void Form1_FontChanged(object sender, EventArgs e){
UpdateChildrenFont(this);
}
private void UpdateChildrenFont(Control parent){
foreach(Control c in parent.Controls){
c.Font = new Font(c.Font.FontFamily, parent.Font.Size, c.Font.Style);
UpdateChildrenFont(c);
}
}
The recursive method UpdateChildrenFont works in most cases but if you have some TabControl on your form, that doesn't work, because TabControl has another kind of collection to hold its tab pages called TabPages... I've encountered such a case. Another solution is to create your own Controls and override the OnParentChanged to register the Parent.FontChanged event handler accordingly, like this:
public class YourControl : TheBaseControl {
protected override void OnParentChanged(EventArgs e){
if(Parent != null){
Parent.FontChanged -= ParentFontChanged;
Parent.FontChanged += ParentFontChanged;
}
}
private void ParentFontChanged(object sender, EventArgs e){
Font = new Font(Font.FontFamily, Parent.Font.Size, Font.Style);
}
}
You should apply that model on all your controls on your form. That's very clean although requires you to have custom classes for your controls.
The last solution I can think of is playing with ControllAdded event, this is applied only on your Containers such as Form, GroupBox, Panel... and every control which has some child control on your form. Here is the code:
public class YourContainerClass: YourContainerBaseClass {
protected override void OnControlAdded(ControlEventArgs e){
Binding bind = new Binding("Font", this, "Font");
bind.Format += (s, ev) =>
{
Font inFont = (Font)ev.Value;
Binding bin = (Binding)s;
ev.Value = new Font(bin.Control.Font.FontFamily, inFont.Size, bin.Control.Font.Style);
};
e.Control.DataBindings.Clear();
e.Control.DataBindings.Add(bind);
base.OnControlAdded(e);
}
}
Or simply if that's your Form:
public class Form1 : Form {
public Form1(){
ControlAdded += FormControlAdded;
InitializeComponent();
}
private void FormControlAdded(object sender, ControlEventArgs e){
Binding bind = new Binding("Font", this, "Font");
bind.Format += (s, ev) =>
{
Font inFont = (Font)ev.Value;
Binding bin = (Binding)s;
ev.Value = new Font(bin.Control.Font.FontFamily, inFont.Size, bin.Control.Font.Style);
};
e.Control.DataBindings.Clear();
e.Control.DataBindings.Add(bind);
}
}
We have a painting issue that appears only in Win7. It works perfectly in Win Xp. When the user clicks on an item or any portion of the custom control (the user control is marked in red rectangle in the picture below), the tree view and all controls in the screen starts flickering. The flickering stops only when the mouse pointer is moved out of the user control.
How can the issue be solved?
The user control code is given below
namespace NeST.ICE.Screens.ICEJobHistory
{
public partial class CustomControlPropertGrid : System.Windows.Forms.PropertyGrid
{
public CustomControlPropertGrid()
{
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here
// Calling the base class OnPaint
base.OnPaintBackground(pe);
base.OnPaint(pe);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
}
The data filling code is given below. this._waferGrid is an object of CustomControlPropertGrid
if (dataReader != null && dataReader.Read())
{
//FIX_OCT_08: BUG2891
WaferProperty wafer = new WaferProperty();
//FIX_JUN_09:Changed Name to SubstrateID
wafer.SubstrateID = dataReader[Constants.NAME_PARAMETER_INDEX].ToString();
wafer.InCarrier = dataReader[Constants.SOURCE_INCARRIER_PARAMETER_INDEX].ToString();
wafer.OutCarrier = dataReader[Constants.DESTINATION_OUTCARRIER_PARAMETER_INDEX].ToString();
wafer.InLoadPort = dataReader[Constants.DESTINATIONSLOT_INLP_PARAMETER_INDEX].ToString();
wafer.OutLoadPort = dataReader[Constants.STARTTIME_OUTLP_PARAMETER_INDEX].ToString();
wafer.StartTime = dataReader[Constants.WAFER_TIME_PARAMETER_INDEX].ToString();
wafer.EndTime = dataReader[Constants.ENDTIME_OPERATION_PARAMETER_INDEX].ToString();
//<<EHN_AUG_11_WaferLotId
//Added the waferLotId parameter to the grid
wafer.LotID = dataReader[Constants.LOT_ID_PARAMETER_INDEX].ToString();
//EHN_AUG_11_WaferLotId >>
//FIX_JUN_09:Changed WaferID to ActualWaferID
wafer.ActualWaferID = dataReader[Constants.WAFER_STATUS_PARAMETER_INDEX].ToString();
wafer.RouteRecipe = dataReader[Constants.WAFER_ROUTE_RECIPE_PARAMETER_INDEX].ToString();
this._waferGrid.SelectedObject =wafer;
}
I need to reinvent/recreate the Label Control from scratch to add my own mojoness to it. Yes, I know what you're thinking (and if you're not thinking that, shouldn't you be?).
Can somebody point me in the right direction?
Thank you.
The whole purpose for recreating the label is that I want full control over how it is drawn onto screen, and so that I can have KeyDown Event Handlers for it, too. For example, the user can edit the contents of a label the same way they would edit the contents of a TextBox control.
Also, I cannot just simply use a TextBox control, as it would require almost, if not more work to get my desired result.
Why not just extend the current one?
class MyMojoLabel : Label // Kind of thing
A good starting point would maybe to understand how Microsoft implemented the label.
To get a better in-depth look you should take a look with Reflector into the class or debugging the source code of the Label control.
public class MyLabel : System.Windows.Forms.Label
{
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
// or leave base out
// you would determine these values by the length of the text
e.Graphics.DrawEllipse(new System.Drawing.Pen(System.Drawing.Color.Red),
0, 0, 50, 12);
}
protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
{
base.OnKeyDown(e);
// although a label has a KeyDown event I don't know how it would
// receive focus, maybe you should create a text box that looks
// like a label
}
}
How about this?
Create a class that inherits from Control. Use the SetStyle() call to enable user painting and double-buffering, and override the OnPaint(), and any other methods that you need.
class MyLabel : System.Windows.Forms.Control
{
public MyLabel()
{
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
ControlPaint.DrawBorder3D( e.Graphics, this.ClientRectangle, Border3DStyle.Etched, Border3DSide.All);
e.Graphics.DrawString(this.Text, this.Font, Brushes.Red, 0, 0);
}
}
Creating your own label control is simple enough, you just need to start with Control and override OnPaint(). What is going to byte is to turn it into a control that also has focusing behavior. And allows the user to edit the text. By the time you're done, you'll have re-invented the TextBox control. Which is a lot harder than it looks.
Focus on the focusing first, that's the trickiest problem. It isn't very likely that the user will want to focus the control frequently. Maybe some kind of secret handshake, like a double-click. When you detect one, you could create a TextBox control and put it in front of the label. And dispose it when it loses focus, updating the label's Text property. Or a simple context menu that displays a small editing dialog.
An example that uses the double-click to edit approach:
using System;
using System.Windows.Forms;
class MyLabel : Label {
private TextBox mEditor;
protected override void OnDoubleClick(EventArgs e) {
if (mEditor == null) {
mEditor = new TextBox();
mEditor.Location = this.Location;
mEditor.Width = this.Width;
mEditor.Font = this.Font;
mEditor.Text = this.Text;
mEditor.SelectionLength = this.Text.Length;
mEditor.Leave += new EventHandler(mEditor_Leave);
this.Parent.Controls.Add(mEditor);
this.Parent.Controls.SetChildIndex(mEditor, 0);
mEditor.Focus();
}
base.OnDoubleClick(e);
}
void mEditor_Leave(object sender, EventArgs e) {
this.Text = mEditor.Text;
mEditor.Dispose();
mEditor = null;
}
protected override void Dispose(bool disposing) {
if (disposing && mEditor != null) mEditor.Dispose();
base.Dispose(disposing);
}
}
I have created a custom control that gets highlighted when the mouse hovers over it. The custom control also has a checkbox. When the mouse goes over the checkbox, the highlighting of the custom control does not occur. I've tried using WS_EX_TRANSPARENT on the checkbox but it isn't working for me.
int cbStyle = GetWindowLong(CompletedCheckBox.Handle, GWL_EXSTYLE);
SetWindowLong(CompletedCheckBox.Handle, GWL_EXSTYLE, cbStyle | WS_EX_TRANSPARENT);
How can I do this?
Thanks
Transparent only affects drawing, not mouse events. The check box is getting the mouse events, this in turn means that when you mouse over the checkbox, your control receives a MouseLeave event. To ensure that the background color changes, even when a child control ( at any level) gets a MouseEnter event, you need to track that a control of interest -- or any child, grand-child ..etc-- has the mouse over it. To do this, recurse through all descendant controls and intercept the appropriate events for them. To do this, try something similar to the class below.
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
AttachMouseEnterToChildControls(this);
}
void AttachMouseEnterToChildControls(Control con)
{
foreach (Control c in con.Controls)
{
c.MouseEnter += new EventHandler(control_MouseEnter);
c.MouseLeave += new EventHandler(control_MouseLeave);
AttachMouseEnterToChildControls(c);
}
}
private void control_MouseEnter(object sender, EventArgs e)
{
this.BackColor = Color.AliceBlue;
}
private void control_MouseLeave(object sender, EventArgs e)
{
this.BackColor = SystemColors.Control;
}
}