Custom WinForms button does not change image? - c#

I created a custom Button, called AcceptButton, inheriting from System.Windows.Forms.Button
On the constructor I set a few properties, but most important, an image (A green checkmark), like this:
this.Image = Proyecto.Controls.Bases.Properties.Resources.ok_16;
When I add this control using VS2013 form designer, in another project that references the DLL I just created, the image is displayed correctly. But if I go into my control, and change the image in code, for example, to:
this.Image = Proyecto.Controls.Bases.Properties.Resources.ok_32;
The image is not changed in the projects that use this control (even if the solution is cleaned and regenerated). I followed the code generated by VS2013 and I found that the designer adds this line:
this.botonAceptar1.Image = ((System.Drawing.Image)(resources.GetObject("botonAceptar1.Image")));
For some reason, this resource is "hardcoded" in a resource file generated by VS, but it's not updated when I regenerate the solution.
Removing this line makes it work as expected (I can change the image in the "upstream" class and it'll be updated when the solution is regenerated).
Why is this happening?
How can I avoid this?

This happens due to the DesignerSerializationVisibility (MSDN) attribute. Try adding this property and these methods (MSDN) to your class:
public class MyButton : System.Windows.Forms.Button
{
public bool ShouldSerializeImage()
{
return !object.ReferenceEquals(this.Image, _BaseImage);
}
public void ResetImage()
{
this.Image = _BaseImage;
}
[System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Visible)]
public new Image Image
{
get { return base.Image; }
set { base.Image = value; }
}
private Bitmap _BaseImage;
public MyButton()
{
_BaseImage = Proyecto.Controls.Bases.Properties.Resources.ok_16;
this.Image = _BaseImage;
}
}
This replaces the default Image property and prevents the serialization you encountered. Furthermore it allows the designer to check if the property has it's default value and if it needs to be serialized. The default value is stored in a private field in the button class. This should correctly serialize (or not serialize) the properties.
Remove all buttons you have, recompile, readd the buttons to make sure.

Related

Why does OnDrawItem event for a ListView not affect the Design-time environment?

If I create a class and make it derive from a ListView like this...
class MyListView : ListView
{
public MyListView() : base()
{
DoubleBuffered = true;
OwnerDraw = true;
Cursor = Cursors.Hand;
Scrollable = false;
}
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
//base.OnDrawItem(e);
}
}
Then I open the design view of my windows form and add a new MyListView object then add a single item and link it to a image list. I can see that there is one item in the mylistview object. It has no effect on the object I have on my form called lv of type MyListView. When I run my app on the other hand I see exactly what I expected and there is no items listed.
Why would this effect run-time and not design-time painting?
The answer
ListViewDesigner shadows OwnerDraw property like Visible or Enabled property of control. So it just works at run-time and changing it doesn't affect design-time.
Side Note
If you take a look at source code of ListViewDesigner, you will see this property:
private bool OwnerDraw
{
get { return (bool) base.ShadowProperties["OwnerDraw"]; }
set { base.ShadowProperties["OwnerDraw"] = value; }
}
And in PreFilterProperties you will see the designer replaced the original property with this one:
PropertyDescriptor oldPropertyDescriptor = (PropertyDescriptor) properties["OwnerDraw"];
if (oldPropertyDescriptor != null)
{
properties["OwnerDraw"] = TypeDescriptor.CreateProperty(typeof(ListViewDesigner),
oldPropertyDescriptor, new Attribute[0]);
}
So it doesn't matter what View you use, it performs the default painting regardless of what you have in OnDrawItem. It's because it doesn't use OwnerDraw property at design-time. The designer shadows it. This is the same behavior which you see for Enabled or Visible property.
Workaround to enable owner-draw at run-time
As a workaround, you can register a different Designer for your derived control. This way the OwnerDraw property will work as a normal property:
[Designer(typeof(ControlDesigner))]
public class MyListView : ListView
Warning: Keep in mind, by registering a new designer for the control, you will lose the current ListViewDesigner features like its designer verbs or its smart tag (actions list) window or Column Sizing options. If you need those features, you can implement those features in a custom designer by looking into ListViewDesigner source code.

How to set text to a control from resource file in design time?

I want to know if there exists a way to set a control's Text property from a resource file in design time:
Or this process can only be performed programatically?
The designer only serializes string for Text property. You can not set the Text property to a resource value directly using designer.
Even if you open the Form1.Designer.cs file and add a line to initialization to set the Text property to a resource value like Resource1.Key1, after first change in designer, the designer replace your code by setting the string value of that resource for Text property.
In general I recommend using standard localization mechanisms of windows forms, using Localizable and Language property of Form.
But if in some reason you want to use your resource file and want to use a designer-based solution, as good option you can create an extender component to set the resource key for your control at design-time and then use it at run-time.
Code for the extender component is at the end of the post.
Usage
Make sure you have a resource file. For example Resources.resx in the properties folder. Also make sure you have some resource key/value in the resource file. For example Key1 with value = "Value1", Key2 with value = "Value2". Then:
Put a ControlTextExtender component on your form.
Using property grid set the ResourceClassName property of it to the full name of your resource file for example WindowsApplication1.Properties.Resources`
Select each control you want to set its Text and using property grid set the value of ResourceKey on controlTextExtender1 property to the resource key that you want.
Then run the application and see the result.
Result
and here is an screenshot of result, and as you see, I even localized Text property of the form this way.
Switch between Cultures at Run-Time
You can switch between cultures at run-time, without need to close and reopen the form simply using:
System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fa");
this.controlTextExtender1.EndInit();
Implementation
Here is a basic implementation of the idea:
[ProvideProperty("ResourceKey", typeof(Control))]
public class ControlTextExtender
: Component, System.ComponentModel.IExtenderProvider, ISupportInitialize
{
private Hashtable Controls;
public ControlTextExtender() : base() { Controls = new Hashtable(); }
[Description("Full name of resource class, like YourAppNamespace.Resource1")]
public string ResourceClassName { get; set; }
public bool CanExtend(object extendee)
{
if (extendee is Control)
return true;
return false;
}
public string GetResourceKey(Control control)
{
return Controls[control] as string;
}
public void SetResourceKey(Control control, string key)
{
if (string.IsNullOrEmpty(key))
Controls.Remove(control);
else
Controls[control] = key;
}
public void BeginInit() { }
public void EndInit()
{
if (DesignMode)
return;
var resourceManage = new ResourceManager(this.ResourceClassName,
this.GetType().Assembly);
foreach (Control control in Controls.Keys)
{
string value = resourceManage.GetString(Controls[control] as string);
control.Text = value;
}
}
}

How to avoid text set of button in designer

I have a library. In the library, I have a button with a Green background color and Text as Go Green.
Now I made a winform project and dragged my Go green button in the form. On running the application, I noticed that the button color is changing to green but text is displayed as button1 (name of the class library).
My library looks like:
public class button : Button
{
public Seats()
{
button.BackColor = Color.Green;
button.Text = "Go Green";
}
}
I discovered that it is happening because InitializeComponent() method is called in the constructor of the form. And in designer.cs,
button.Text = "button1";
is called. How can I avoid that to happen. I want my text to be visible from my class library.
Note: When I manually removed the above code from the designer.cs, everything was working fine.
Easiest way - override button's Text property and make it hidden to designer serialization:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override string Text
{
get { return base.Text; }
set { base.Text = value; }
}
Designer will add default button name, but when you build application, your text will be shown.
UPDATE: Another (harder) way - provide to designer default property value for your button. In this case you need reference System.Design.dll which is available only for full version of .net framework (not client profile version).
First: create control designer for your button
public class GoGreenButtonDesigner : System.Windows.Forms.Design.ControlDesigner
{
public override void OnSetComponentDefaults()
{
base.OnSetComponentDefaults();
Control.Text = "Go Green";
}
}
Last: add Designer attribute to your custom button class
[Designer(typeof(GoGreenButtonDesigner))]
public class GoGreenButton : Button
{
//...
}
That's it. Now when you drag button to form, it will have default text "Go Green" without any additional compilations.

.NET Custom Control (ToolStripControlHost) Wreaks Havoc on the Designer

I need to have a MaskedTextBox in a ToolStrip, which isn't included by default, so I followed some advice I found online, and created custom control that inherits from ToolStripControlHost. What I've created works great when I'm running the application, but it really messes up the designer. By "messes up", I mean the custom control (Along with some others) disappear from the ToolStrip. Also I can no longer add new controls to the ToolStrip, and I can't select the existing controls on the ToolStrip to edit them.
Here's my class.
[DesignerCategory("code")]
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
public partial class ToolStripMaskedTextBox : ToolStripControlHost
{
public MaskedTextBox MaskedTextBox
{
get { return Control as MaskedTextBox; }
}
public ToolStripMaskedTextBox()
: base(CreateControlInstance()) { }
private static Control CreateControlInstance()
{
MaskedTextBox mtb = new MaskedTextBox();
mtb.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
mtb.MinimumSize = new System.Drawing.Size(100, 16);
mtb.PasswordChar = '*';
return mtb;
}
}
Any help on what I might be doing wrong that's giving the designer a hard time would be appreciated.
Addition Info
Now when I open my class file in Visual Studio, I get a warning page with the following error:
Constructor on type 'System.Windows.Forms.ToolStripControlHost' not found.
Addition Info 2
The problem only occurs after building the solution. I can get the designer working correctly by modifying the Form.Designer.cs file in even the smallest way. Like adding a single space. From there on out the designer will work fine. That is until I build the solution. Then the designer freezes up again. None of the controls on the form can be edited.
According to the exception
Constructor on type 'System.Windows.Forms.ToolStripControlHost' not found.
I found some information on the MSDN Forum.
This happends because the ToolStripControlHost class does not have a constructor with no parameter.
To solve this problem, you can create your own ToolStripControlHost with a none-parameter constructor and make the ToolStripMaskedTextBox inherited from your ToolStripControlHost. Try something like the following
//Declare a class that inherits from ToolStripControlHost.
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
public class ToolStripMaskedTextBox : MyCustomToolStripControlHost
{
// Call the base constructor passing in a MaskedTextBox instance.
public ToolStripMaskedTextBox() : base(CreateControlInstance()) { }
public MaskedTextBox MaskedTextBox
{
get
{
return Control as MaskedTextBox;
}
}
private static Control CreateControlInstance()
{
MaskedTextBox mtb = new MaskedTextBox();
mtb.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
mtb.MinimumSize = new System.Drawing.Size(100, 16);
mtb.PasswordChar = '*';
return mtb;
}
}
public class MyCustomToolStripControlHost : ToolStripControlHost
{
public MyCustomToolStripControlHost()
: base(new Control())
{
}
public MyCustomToolStripControlHost(Control c)
: base(c)
{
}
}
This will fix the problem with your exception.
The Problem with the Forms Designer (ToolStripMaskedTextBox is not visible after running the app) is not solved but you can close the designer and open the file again.
Then you can go on without any problems.
Hope this helps
I've used dknaack's solution, but placed MyCustomToolStripControlHost class in a separate file in System.Windows.Forms namespace. And...
First: it works - no exception.
Then: my control is visible in designer as well, so it's a jackpot.
In this link, the answer was that the objects that implement "blah" interface must have a parameter-less constructor. Give it a try.
FWIW: I also succeeded with dknaack's solution above, but only after I realized I was looking for the custom ToolStrip Control in the wrong place. The custom control doesn't show up in the Toolbox itself. Rather it shows up in the dropdown list of components under the "Add ToolStripButton" that appears on the ToolStrip when it is selected (in the designer).
I found a solution of designer's problem.
https://dobon.net/vb/dotnet/control/tschdesigneravail.html#tips_comment (Japanese)
All you need is just make a class derived from ToolStrip and substitute it.
class DesignerFriendlyToolStrip : ToolStrip { }
var ts = new DesignerFriendlyToolStrip();
ts.Items.Add(toolStripMaskedTextBox);
form.Controls.Add(ts);
I don't know why this is effective. Anyone knows...?

How to force a new property to be serialized in designer.cs

I added a new property to a component to uniquely identify every gridcontrol in my project, called GridIdentifier:
public class MyCustomGridControl : GridControl
{
private string gridIdentifier = "empty";
[Browsable(true)]
[DefaultValue("empty")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string GridIdentifier
{
get { return gridIdentifier; }
set { gridIdentifier = value; }
}
public MyCustomGridControl()
{
if (this.gridIdentifier == "empty")
this.gridIdentifier = Guid.NewGuid().ToString();
}
}
The problem is that for existing controls in my forms, the form only serializes the new property after I change something (read: anything) within the form. It might be the caption of the form, the size, etc.
But what I would like to see is that it detects that the form has changed when I open it, so I can save it and the new property gets serialized.
Does anyone have a clue why the new property doesn't get saved after opening the form and how to fix it? Any other ideas that help are of course also appreciated.
I would guess it is doing basic sanity checking (i.e. should anything have changed) - to prevent unexpected source code changes (I hate it when opening a file can cause side-effects - I'm looking at you, DBML!).
On a side note, to force serialization generally (I don't think it will apply due to the above):
public bool ShouldSerializeGridIdentifier() { return true; }
the bool ShouldSerialize*() and void Reset*() are a convention used by the framework.

Categories