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.
Related
I'm trying to create a simple listbox with ObjectListView (WinForm, C#). The goal is to have a single value (a double) and a check box.
I want to be able to edit the double value by Single Click, so here are the relevant lines of code from my MyWindow.Designer.cs file (i've left out the default values for efficiency):
this.olvDepths = new BrightIdeasSoftware.ObjectListView();
this.olvColumn1 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
...
this.olvDepths.CellEditActivation = BrightIdeasSoftware.ObjectListView.CellEditActivateMode.SingleClick;
this.olvDepths.CheckBoxes = true;
this.olvDepths.CheckedAspectName = "IsDefault";
this.olvDepths.FullRowSelect = true;
//
// olvColumn1
//
this.olvColumn1.AspectName = "Depth";
this.olvColumn1.Text = "";
this.olvColumn1.IsEditable = true;
I then create a list of my class (ShieldingEntry) and use the olvDepths.SetObjects() with the list. My ShieldingEntry class looks like this:
public class ShieldingEntry
{
public double Depth { get; set; }
public bool IsDefault { get; set; }
}
However, when I click the field, it doesn't go into edit mode. I've also tried the DoubleClick, SingleClickAlways, and F2Only modes and they don't work either.
The Checkbox works fine.
************** I have additional information *********************
I've pulled and build the ObjectListView source, so I could step through it.
I put a breakpoint in the OLV StartCellEdit method and it gets called and appears to setup and select the control appropriately. It just never appears...
As I noted in the comments on the answer below, I've got this control on a tabbed dialog, and if I switch to another tab, then back, the control works fine.
What am I missing?
I've used ObjectListView before, and here is what I had to do:
Handle the CellEditStarting event. This event is raised when the cell goes into edit mode. Since OLV doesn't really have built-in editors, you have to make your own. Then handle the CellEditFinishing event to validate the data before putting it back into your model.
So first, handling the CellEditStarting event:
private void objlv_CellEditStarting(object sender, CellEditEventArgs e)
{
//e.Column.AspectName gives the model column name of the editing column
if (e.Column.AspectName == "DoubleValue")
{
NumericUpDown nud = new NumericUpDown();
nud.MinValue = 0.0;
nud.MaxValue = 1000.0;
nud.Value = (double)e.Value;
e.Control = nud;
}
}
This creates your editing control. If you want to make sure the size is right, you can set the size of the control (in this case a NumericUpDown) to the cell bounds using e.CellBounds from the event object.
This will show the editor when you click in the cell. Then you can handle the editor finished event to validate the data:
private void objlv_CellEditFinishing(object sender, CellEditEventArgs e)
{
if (e.Column.AspectName == "DoubleValue")
{
//Here you can verify data, if the data is wrong, call
if ((double)e.NewValue > 10000.0)
e.Cancel = true;
}
}
I don't think handling it is required, but its good practice to validate data from the user.
The editing control in the CellEditStarting event can be any control, even a user defined one. I've used a lot of user defined controls (like textboxes with browse buttons) in the cell editor.
[Edit]
I uploaded an example here dropbox link that seems to work. Might not be in the exact view as needed, but seems to do the job.
For anyone else with this problem. I had it specifically when trying to edit a 'null' value in a decimal? on the OLV on a tab page. Solution for me was to set UseCustomSelectionColors to 'False'. I didn't look elsewhere to see if it was reported as a bug. Seems like a bug.
Here I'm talking about Windows Forms Application written in C#. Consider a simple model
class Labelled
{
private string label;
public string Label
{
get { return label; }
set
{
if (label != value)
{
string message = String.Format(
"Label changed from {0} to {1}",
label, value
);
MessageBox.Show(message);
label = value;
}
}
}
public Labelled(string label)
{
this.label = label;
}
}
class Model
{
public Labelled SingularLabelled { get; set; }
public List<Labelled> ListedLabelled { get; set; }
public Model()
{
SingularLabelled = new Labelled("Singular");
ListedLabelled = new List<Labelled>();
for (int i = 1; i <= 10; ++i)
ListedLabelled.Add(new Labelled("Listed " + i.ToString()));
}
}
We have a simple class Labelled with string property Label and class Model with member SingularLabelled of type Labelled and ListedLabelled which is a list of Labelled's.
Now I want to display the data to the user. Here is my setup:
The main window has a TextBox for displaying SingularLabelled.Label and a DataRepeater from the Visual Basic PowerPacks to display labels of the elements of ListedLabelled. The ItemTemplate of the DataRepeater consists of just a single TextBox.
Let's focus on one way binding, namely I want the underlying data to be updated when the User changes the contents of a text box. The Label property of the Labelled raises a notification in form of a message box, so I can get to know exactly when the data is being updated. Now the arrows represent bindings. Blue arrows stand for data source and the red ones for data members. An instance of Model is created and bound to the modelBindingSource in the constructor of the main window form.
And here we come to a very important thing. I want the data to be updated immediately in sync with what the User is typing, so I made sure that the data source update modes of the data bindings are set to OnPropertyChanged. The generated code that might be of interest here is
this.singularTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.modelBindingSource, "SingularLabelled.Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.listedTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.listedLabelledBindingSource, "Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
And this is working as expected when typing into the text box of SingularLabelled but the text boxes within DataRepeater trigger the update only when they loose focus. I want them to behave like the singular one. Ideal solution would be to do it using the designer. Does anyone know how to do this?
Above is a sample of the program working. Notice how SingularLabelled's label is updated every character put in and the members of ListedLabelled get the whole edited chunk updated after the corresponding text box looses focus.
We were able to overcome this limitation of DataRepeater by simulating the Tab key.
private void listedTextBox_TextChanged(object sender, EventArgs e)
{
//simulate tab key to force databinding
SendKeys.Send("{TAB}");
SendKeys.Send("+{TAB}");
}
As a course project i'm building a form in c# which contains two user controls.
The first user control has a checkedlistbox and the second control has also a checkedlistbox when the first control checkedlistbox will contain list of people (male/female) and the second user control the checkedlistbox will have two options: male, female and when I click a button on the first control which says: "update friends" it's suppose to go to the second control and check if we selected male or female and according to that to update the checkedlistbox in the first user control with friends by gender type by what was selected on the second control.
Basically I want to raise an event every time the button on the first control selected then to get the data from the second control to the first control.
Is it possible to do so between two controls who are inside a form and are different controls?
Any help will be appriciated.
Thanks.
To do this "correctly," you would want to use something like the MVC architecture. It's definitely a lot more work initially to understand and implement but is very useful to know if you plan on doing any serious UI application development. Even if you don't go all the way with it, the concepts are useful to help design even "quick and dirty" applications.
Define your data model without thinking in terms of the UI, e.g.:
internal enum Gender
{
Male,
Female
}
internal class Person
{
public Gender Gender { get; set; }
public string Name { get; set; }
}
// . . .
// Populate the list of people
List<Person> allPeople = new List<Person>();
allPeople.Add(new Person() { Gender = Gender.Male, Name = "Xxx Yyy" });
allPeople.Add(new Person() { Gender = Gender.Female, Name = "Www Zzz" });
// . . .
For the view portion, you would typically use data binding on the UI controls so that the controls will automically reflect changes to the underlying data. However, this can get difficult especially if you are not using a database-like model (e.g. System.Data.DataSet). You may opt to "manually" update the data in the controls which might be fine in a small app.
The controller is the portion that uses the UI events and makes changes to the model, which may then be reflected as changes in the view.
internal class Controller
{
private Gender selectedGender;
private List<Person> allPeople;
private List<Person> friends;
public Controller(IEnumerable<Person> allPeople)
{
this.allPeople = new List<Person>(allPeople);
this.friends = new List<Person>();
}
public void BindData(/* control here */)
{
// Code would go here to set up the data binding between
// the friends list and the list box control
}
// Event subscriber for CheckedListBox.SelectedIndexChanged
public void OnGenderSelected(object sender, EventArgs e)
{
CheckedListBox listBox = (CheckedListBox)sender;
this.selectedGender = /* get selected gender from list box here */;
}
// Event subscriber for Button.Click
public void OnUpdateFriends(object sender, EventArgs e)
{
this.friends.AddRange(
from p in this.allPeople
where p.Gender == this.selectedGender
select p);
// If you use data binding, you would need to ensure a
// data update event is raised to inform the control
// that it needs to update its view.
}
}
// . . .
// On initialization, you'll need to set up the event handlers, etc.
updateFriendsButton.Click += controller.OnUpdateFriends;
genderCheckedListBox.SelectedIndexChanged += controller.OnGenderSelected;
controller.BindData(friendsListBox);
// . . .
Basically, I recommend not having controls talk directly, but rather through a controller-like class as above which has knowledge of the data model and the other controls in the view.
Of course it's possible: you need to make the link between the 2 controls in the form.
Just declare an event 'ButtonClicked' in control #1
Then make a public method 'PerformsClick' on the control #2
And in the form, in the constructor, after the call to InitializeComponent, link the event from the control #1 to the method to the control #2:
control1.ButtonClicked += delegate(sender, e) {
control2.PerformsClick();
};
(I type on the fly to give you an idea, it'll surely not compile)
If you want to pass any data, just add parameters in the PerformsClick method.
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
I have a grid that is binded to a collection. For some reason that I do not know, now when I do some action in the grid, the grid doesn't update.
Situation : When I click a button in the grid, it increase a value that is in the same line. When I click, I can debug and see the value increment but the value doesn't change in the grid. BUT when I click the button, minimize and restore the windows, the value are updated... what do I have to do to have the value updated like it was before?
UPDATE
This is NOT SOLVED but I accepted the best answer around here.
It's not solved because it works as usuall when the data is from the database but not from the cache. Objects are serialized and threw the process the event are lost. This is why I build them back and it works for what I know because I can interact with them BUT it seem that it doesn't work for the update of the grid for an unkown reason.
In order for the binding to be bidirectional, from control to datasource and from datasource to control the datasource must implement property changing notification events, in one of the 2 possible ways:
Implement the INotifyPropertyChanged interface, and raise the event when the properties change :
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
NotifyPropertyChanged("Name");
}
}
}
Inplement a changed event for every property that must notify the controls when it changes. The event name must be in the form PropertyNameChanged :
public event EventHandler NameChanged;
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
if (NameChanged != null) NameChanged(this, EventArgs.Empty);
}
}
}
*as a note your property values are the correct ones after window maximize, because the control rereads the values from the datasource.
It sounds like you need to call DataBind in your update code.
I am using the BindingSource object between my Collection and my Grid. Usually I do not have to call anything.