I'm having a problem where every time I build my solution, the compile succeeds but when I run my program it will error as the forms designer.cs file has had the data source for my custom comboboxes added to it automatically; resulting in an exception stating
Items collection cannot be modified when the DataSource property is set.
Any ideas on what might be the problem? I've tried setting the data source after the initialize component method but this results in a different error as the unit type is null..
The type of data source is set in a property for the control and below is the relevant code
form.Designer.cs (this is generated for you not a custom cs file called designer)
//
// cmbWheelUnitCR
//
this.cmbWheelUnitCR.DataSource = ((object)(resources.GetObject("cmbWheelUnitCR.DataSource")));
this.cmbWheelUnitCR.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbWheelUnitCR.FormattingEnabled = true;
this.cmbWheelUnitCR.Items.AddRange(new object[] {
"mm",
"yd"});
My custom combobox
public string UnitType
{
get { return m_unitType; }
set { m_unitType = value;
this.DataSource = Units.Instance.UnitTypes(m_unitType);}
}
public UnitComboBox()
{
InitializeComponent();
}
I was able to solve this accidentally by setting the data source within an override onLoad event..
Related
I am working on some data collection forms in WinForms/C#. When the form loads, I am looping through a configuration and adding a new Binding to each of the TextBox controls; mapping the Text property of each TextBox control to specific string property on my POCO object.
public void BindTextBoxControls(dynamic entity, List<TextBoxConfig> textBoxConfig)
{
foreach (var config in textBoxConfig)
config.Control.DataBindings.Add(new Binding("Text", entity, config.PropertyName));
}
Everything has been working as expected, new records properly saving new values entered into the corresponding TextBox controls, TextBoxes populating with the correct values when reopened a previously entered records with the form, and updates to values in TextBoxes of previously entered records are getting the updated values set on the underlying POCO.
However, I started to layer in some business rules onto the form specifically to gray out/disable and clear out previously entered values in the TextBox based on other user input/activity on the form - things are not working as expected.
In a contrived example; a rule like if a Checkbox_1 is checked then TextBox #5 should not be valued (clear out any previously entered value and disable it from input). On my Checkbox_1 event handler for CheckedChanged, I specifically check if the Checkbox_1 is checked and if so, set TextBox_1.Text == null and TextBox_1.Enabled = false. This works as expected and on the form, I see any previously entered value cleared from the TextBox_1 and it becomes enabled.
private void chkCheckBox1_CheckedChanged(object sender, EventArgs e)
{
if(!chkCheckBox1.Checked)
{
txtBox5.Text = string.Empty;
}
}
However, when I debug and break on the save and inspect the underlying POCO's property that the underlying control is bound to after the method is called; the old value still persists on the object's property which the text box is bound to, despite the textbox having not value appearing on the form. When I reopen the form for that record, the old cleared out value is re-populated in the disabled TextBox. However, manually clearing out the value in the same TextBox or updating a value and inspecting the object shows the updated value after those operations are performed.
It seems like changing the Text value of a TextBox control (e.g. the Text property of a TextBox) in code maybe somehow be "bypassing" the DataBinding? I'm actually seeing the same/similar behavior when applying similar rules to "uncheck" TextBoxes programmatically within event handler methods - the CheckBox controls are also using DataBinding to boolean properties on the POCO.
When you setup databinding by this overload: Binding(String, Object, String), then the value of DataSourceUpdateMode will be OnValidation, which means when you modify the value of control's property using code or through UI, the binding will push the new value to data source only after Validating event happens for the control.
To fix the problem, use either of the following options:
Use another overload and set the DataSourceUpdateMode to OnProperetyChanged
OR, after setting the Value of the TextBox.Text call ValidateChildren method of the form.
Example - Set the DataSourceUpdateMode to OnProperetyChanged
public class Person
{
public string Name { get; set; }
public string LegalCode { get; set; }
public bool IsRealPerson { get; set; }
}
Person person;
private void Form1_Load(object sender, EventArgs e)
{
person = new Person() {
Name = "My Company", LegalCode = "1234567890", IsRealPerson = false };
NameTextBox.DataBindings.Add(nameof(TextBox.Text), person,
nameof(Person.Name), true, DataSourceUpdateMode.OnPropertyChanged);
LegalCodeTextBox.DataBindings.Add(nameof(TextBox.Text), person,
nameof(Person.LegalCode), true, DataSourceUpdateMode.OnPropertyChanged);
IsRealPersonCheckBox.DataBindings.Add(nameof(CheckBox.Checked), person,
nameof(Person.IsRealPerson), true, DataSourceUpdateMode.OnPropertyChanged);
IsRealPersonCheckBox.CheckedChanged += (obj, args) =>
{
if (IsRealPersonCheckBox.Checked)
{
LegalCodeTextBox.Text = null;
LegalCodeTextBox.Enabled = false;
}
};
}
Note - You can put the logic inside the model
Another solution (Which needs more effort and more changes in your code) is implementing INotifyPropertyChanged in your model class. Then when PropertyChanged event raises for your boolean property, you can check if it's false then you can set the string property to null.
In this approach you don't need to handle UI events. Also right after updating the model property, the UI will be updated; in fact implementing INotifyPropertyChanged enables two-way databinding for your model class.
I have a user control that contains a textbox and a combobox. I have exposed the combobox's Item property to clients of the user control, thus:
public System.Windows.Forms.ComboBox.ObjectCollection Item
{
get { return baseComboBox.Items; }
}
I added the user control to a windows form, and set the Items list using the property values editor in the form designer. I then ran the application, and the combobox's drop down list was empty. To confirm that the items added at design time were not in the list, I added the following two lines of code to the client form:
textBox1.Text = userControl1.Items.Count.ToString();
userControl1.Items.Add("Test item");
When I re-ran the application the test box showed a count of 0 (zero), and the drop-down list of the user control contained only "Test item".
Thinking that maybe the instance of the user control being referenced at design time is a different instance from that being referenced at run time, I set the user control's BackColor property at design time. When I re-ran the app, the user control's BackColor was what I had set it to in the designer.
Any ideas on why the design time setting of the Items does not carry over into the run time?
You need an attribute:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ComboBox.ObjectCollection Item {
get { return baseComboBox.Items; }
}
Maybe you need the setter defined also, something like:
public System.Windows.Forms.ComboBox.ObjectCollection Item
{
get { return baseComboBox.Items; }
set { baseComboBox.Items = value; }
}
Or, maybe it's because you are exposing Item but setting Items
Defining Set as a simple baseComboBox.Items = value; is impossible because baseComboBox.Items is defined as ReadOnly.
The problem is also the lack of a defined editor for such a collection.
Therefore, you should add the editor definition and instead of trying to replace the collection as one object - use AddRange:
[Editor("System.Windows.Forms.Design.StringCollectionEditor, " +
"System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
typeof(UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ComboBox.ObjectCollection Items
{
get
{
return baseComboBox.Items;
}
set
{
baseComboBox.Items.Clear();
baseComboBox.Items.AddRange(value.Cast<object>().ToArray());
}
}
I'm new in C# but not new to coding --being doing it for almost two decades--, and have a problem with properties in a custom control I'm building, which inherits from a Panel. When I put my properties, I can see them in the Designer properties list and can even set them, but when running my little application, it seems these properties values are not used. The same if I change a property programatically: no error but my control does nothing, it is like they are not properly set. However, if I do it programatically whithin the class, they do work. My guess is that something in my properties set/get stuff is not right. Please see the following code chunk of how I'm doing it:
public class ColorStrip : Panel
{
// properties
// ------------------------------------------
// size of color clusters (boxes)
private int _clusterSize = 20;
// controls if show the buttons panel
private Boolean _showButtons;
// property setters/getters
// ------------------------------------------
// clusterSize...
public int clusterSize
{
get { return _clusterSize; }
set { _clusterSize = value; }
}
// showButtons...
public Boolean showButtons
{
get { return _showButtons; }
set { Console.Write(_showButtons); _showButtons = value; }
}
....
So in my form, for instance in the load or even in a click event somewhere, if I put colorStrip1.showButtons = false; or colorStrip1.showButtons = true; whatever (colorStrip1 would be the instance name after placing the control in the form in design mode)... console.write says always 'false'; Even if I set it in the design properties list as 'true' it will not reflect the settled value, even if I default it to true, it will never change externally. Any ideas? Non of the methods get the new and externally settled property value neither, obviously the getter/setter thing is not working. Seems to me I'm not doing right the way I set or get my properties outside the class. It works only inside it, as a charm...Any help...very appreciate!
Cheers
lithium
p.s. TO CLARIFY SOLUTION:
Setting the property in this case didn't work because I was trying to use a new set value within the constructor, which seems can't get the new values since it is, well, building the thing. If I change the property value in Design mode > Property editor or in code externally to the object, say in it's parent form's load event, it will change it but readable for all methods except the constructor, of course :)
It's likely an issue of the order of execution. Your property setter just sets a variable, but doesn't actually trigger anything on the control to update the state related to this variable (e.g. adding or showing the buttons I assume).
When you set the property befre the rest of the initialization is done, the value is being used, otherwise it isn't because during the initial go the default value is still the property value.
You need to act on the setter, here's some pseudocode to illustrate:
set {
_showButtons = value;
if (alreadyInitialized) {
UpdateButtons();
}
}
Note: make sure to first set the value, then act - otherwise you end up using the old value (just like your Console.Write() is doing).
The quoted code doesn't look problematic. Are you sure you're referencing the same instance of ColorStrip? Also, check your .Designer.cs file to ensure that the code setting the property is there.
In fact, try simplifying your code by using auto-implementing properties:
public int clusterSize { get;set;}
public Boolean showButtons {get;set;}
public ColorStrip() { ... clusterSize = 20; ... }
I have an custom control that represents a grid; and implements another custom control.
When opening this control in de designer, I am able to use the collection editor to set my collection. When saving; the designer successfully saves my collection.
However, when dropping this control on a form; it still (and should) expose(s) the collection property allowing me to modify the default values as i have defined in the other control.
However; when saving this designer; it also tries to store the predefined items in the collection; adding the default ones with every save.
What is the best way to solve this problem? I have attached a code sample.
Code sample where i have defined my collection:
GridPicture.cs
[Category("Layout")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public GridPictureColumnDefinitionCollection ColumnDefinitions
{
// The DesignerSerializationVisibility attribute instructs the design editor to serialize the contents of the collection to source code.
// This will place all the code required to add the items to a collection variable of GridPictureColumnDefinitionCollection.
get
{
return m_ColumnDefinitions;
}
}
Generated designer code of my first 'implementation' of this grid; Picture1.cs
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
gridPictureColumnDefinition1.Auto = true;
gridPictureColumnDefinition1.Value = 0F;
gridPictureColumnDefinition2.Auto = true;
gridPictureColumnDefinition2.Value = 0F;
this.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.ColumnDefinitions.Add(gridPictureColumnDefinition2);
gridPictureRowDefinition1.Auto = true;
gridPictureRowDefinition1.Value = 0F;
this.RowDefinitions.Add(gridPictureRowDefinition1);
Code sample when i place this picture1 on another picture; picture2.cs: (Note that picture11 is the picture1, as it is the 1st of picture1 ;)
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition1 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition2 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureColumnDefinition gridPictureColumnDefinition3 = new VGTest.GridPictureColumnDefinition();
VGTest.GridPictureRowDefinition gridPictureRowDefinition1 = new VGTest.GridPictureRowDefinition();
// Some code removed that does the Auto and Value settings as above
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition1);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition2);
this.picture11.ColumnDefinitions.Add(gridPictureColumnDefinition3);
The picture2 control; when it regenerates the InitializeComponent() method; now adds the columndefinitions which i have added in picture1.
I have written this temporary fix for the problem:
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}
I was unable to find a better method. I have decided to stick with the solution below.
{
// This makes sure column definitions are only serialized when configured at a implementation of this GridPicture.
// This is a Quick/Dirty fix for the following problem:
// When MyPanel (:GridPicture) is put on PanelContainer(:Picture); the picture designer (re)serializes this each save.
return this.GetType().BaseType.Name == typeof(Picture).Name;
}
I'm trying to bind a collection to a DataGridView. As it turns out it's impossible for the user to edit anything in this DataGridView although EditMode is set to EditOnKeystrokeOrF2.
Here is the simplified code:
public Supplies()
{
InitializeComponent();
List<string> l = new <string>();
l.Add("hello");
this.SuppliesDataGridView.DataSource = l;
}
It also doesn't work when I change the collection type to SortableBindingList, Dictionary or even use a BindingSource.
What can be wrong here?
For me the following method works as expected:
Open your form (usercontrol, etc.) with the designer
Add a BindingSource to your form
Select the BindingSource in your form and open the properties page
Select the DataSource property and click on the down arrow
Click on Add project data source
Select Object
Select the object type you wish to handle
This should be the type that will be handled by your collection, not the CustomCollection itself!
Show the available data sources by selecting from the MenuBar Data - Show Data Sources
Drag and Drop your ItemType from the DatasSources on your form
Go into the code of your form and bind your CustomCollection to the BindingSource
var cc = new CustomCollection();
bindingSource1.DataSource = cc;
Remarks:
The DataGridView is just the last part in your chain to (dis)allow changing, adding and removing objects from your list (or CustomCollection). There is also a property AllowNew within the BindingSource and the ICollection interface has a property IsReadOnly which must be set to false to allow editing. Last but not least, the properties of your class within the collection must have a public setter method to allow changing of a value.
Try this:
public class CustomCollection { public string Value { get; set; } }
public Supplies()
{
InitializeComponent();
List<CustomCollection> l = new List<CustomCollection> { new CustomCollection { Value = "hello" } };
this.SuppliesDataGridView.DataSource = l;
}
Once you've set the DataSource property you'll then want to fire off the DataBind() method.
this.SuppliesDataGridView.DataSource = l;
this.SuppliesDataGridView.DataBind();
UPDATE:
As you rightly pointed out in the comments, the DataBind() method doesn't exist for this control.
This link might provide some helpful information: http://msdn.microsoft.com/en-us/library/fbk67b6z%28v=VS.90%29.aspx