In WinForms with C# 4.0 / C# 2.0, I cannot bind to a control if the control\'s visible field is false:
this.checkBox_WorkDone.DataBindings.Add(\"Visible\", W
What you could do is make the control visible, and make it invisible again once the binding has changed.
this.checkBox_WorkDone.Visible = true;
this.checkBox_WorkDone.BindingContextChanged += (object sender, EventArgs e) => {
this.checkBox_WorkDone.Visible = false;
};
Not very pretty, but it works.
UPDATED CODE
That works for me with the code given in your Question.
private WorkStatus m_WorkStatus = new WorkStatus();
public Form1()
{
InitializeComponent();
this.checkBox_WorkDone.Visible = true;
this.checkBox_WorkDone.DataBindings.Add("Visible", m_WorkStatus, "Done");
}
private void btnToggle_Click(object sender, EventArgs e)
{
m_WorkStatus.Done = !m_WorkStatus.Done;
}
You can set the Control to visible = true before the binding.
If the control is invisible an we execute the following code it would work too:
this.checkBox_WorkDone.DataBindings.Add("Visible", m_WorkStatus, "Done");
// Binding does not work till Visible is set to true once.
this.checkBox_WorkDone.Visible = true;
DataSourceUpdateMode.OnPropertyChanged
is not needed! When the WorkStatus object has Done = false
it will not show the control but trigger the VisibleChanged
event.
I created a test harness (see below), and tried your code. I needed to use the overload of the Add method to set DataSourceUpdateMode.OnPropertyChanged
.
public partial class Form1 : Form
{
private readonly WorkStatus _status = new WorkStatus();
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
var t = new Timer();
t.Interval = 1000;
t.Tick += (s, ea) => { _status.Done = true; t.Enabled = false; };
t.Enabled = true;
checkBox_WorkDone.DataBindings.Add("Visible", _status, "Done", true, DataSourceUpdateMode.OnPropertyChanged);
base.OnLoad(e);
}
}
EDIT: If you remove the setter from the form's constructor this works fine. If you set visibility to false in the form's constructor, this binding will fail to update. There's no reason manually specify the initial visibility if your databinding works correctly. That really defeats the purpose of databinding in the first place.
I know this is a bit late in the day, but I have had the same problem - the control I want to bind to is set to visible = false when the form is displayed. I possibly want to do this on a lot of forms and I am always reluctant to write reems of code for each binding.
So I put together a little hack.
I have a form with a panel that I set to Visible = false in the constructor. I want to bind the view to a custom view model I wrote. In the form, I drop in a BindingSource from the Toolbox. I DataSource of the binding source to a Project Data source for my view model.
The idea is then to iterate through the controls on the form, and update the controls bound value from the data source (which is the view model).
To do this, I store the visible value of the control, set it to false, and read the bound value. Then restore the initial visible value. This is done in the aptly named method HackIt().
Here is the code:
ViewModel
public class SimpleViewModel // : DomainModelBase - add your notify property changed
{
public SimpleViewModel()
{
_visible = true;
}
private bool _visible;
public bool Visible
{
get
{
return _visible;
}
set
{
_visible = value;
OnPropertyChanged("Visible");
}
}
}
Form Designer Code
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.panel1 = new System.Windows.Forms.Panel();
this.bindingSource1 = new System.Windows.Forms.BindingSource(this.components);
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.bindingSource1)).BeginInit();
this.SuspendLayout();
//
// panel1
//
this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(128)))), ((int)(((byte)(0)))));
this.panel1.DataBindings.Add(new System.Windows.Forms.Binding("Visible", this.bindingSource1, "Visible", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.panel1.Location = new System.Drawing.Point(94, 85);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(200, 100);
this.panel1.TabIndex = 0;
//
// bindingSource1
//
this.bindingSource1.DataSource = typeof(WindowsFormsBindVisible.SimpleViewModel);
//
// button1
//
this.button1.Location = new System.Drawing.Point(74, 34);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// button2
//
this.button2.Location = new System.Drawing.Point(155, 34);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 2;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(500, 261);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.panel1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.bindingSource1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.BindingSource bindingSource1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
Form Code
public partial class Form1 : Form
{
public SimpleViewModel ViewModel = new SimpleViewModel();
public Form1()
{
InitializeComponent();
this.panel1.Visible = false;
this.bindingSource1.DataSource = this.ViewModel;
}
private void Form1_Load(object sender, EventArgs e)
{
HackIt();
}
void HackIt()
{
this.SuspendLayout();
foreach(Control control in this.Controls)
{
var v = control.Visible;
control.Visible = true;
foreach(Binding db in control.DataBindings)
{
db.ReadValue();
}
control.Visible = v;
}
this.ResumeLayout();
}
}
With the above code, the form starts up and shows my control. You can change the view model constructor and default to false to hide. It works either way.
In the form constructor - I want to explicitly hide the panel (this.panel1.Visible = false) - just to prove the binding when view model default visible = true, the control is displayed correctly on load.
We can then make the buttons change the visible on the view model which will toggle the state of the panel visible:
private void button1_Click(object sender, EventArgs e)
{
this.ViewModel.Visible = false;
}
private void button2_Click(object sender, EventArgs e)
{
this.ViewModel.Visible = true;
}
UPDATE
This got me over the first hurdle. However, I am using Telerik components so I decided to drop a Telerik control on the form. That completely broke everything.
Instead of the HackIt method above, call the following RefreshDataBindings() in the load event.
I decided to iterate through all controls on the form and manually update the binding the reflective way. This is crazy! But it works 100% - even with Telerik controls on my form. And the performance is ok in my main application. This is a down right dirty hack - but I put this once in either a base form or base control - and I am not worrying about my bindings.
protected void RefreshDataBindings()
{
foreach (Control control in this.Controls)
RefreshControlBindingsRecursive(control);
}
private void RefreshControlBindingsRecursive(Control control)
{
if (!control.Visible || !control.Created)
{
foreach (Binding db in control.DataBindings)
{
if (db.PropertyName == "Visible")
{
try
{
object dataSource = db.DataSource is BindingSource ?
(db.DataSource as BindingSource).DataSource : db.DataSource;
PropertyInfo pi =
dataSource.GetType().GetProperty(db.BindingMemberInfo.BindingMember); ;
PropertyInfo piC = db.Control.GetType().GetProperty(db.PropertyName);
piC.SetValue(db.Control, pi.GetValue(dataSource));
}
catch (Exception ex)
{
string s = ""; // not bothered its too late at night
}
}
}
}
foreach (Control child in control.Controls)
RefreshControlBindingsRecursive(child);
}
My workaround:
private void Form_Load(object sender, EventArgs e)
{
button.Visible = true;
button.DataBindings["Visible"].ReadValue();
}
button.Visible = true;
is needed to force creating control (in other word, associate Button class instance with actual Win32 window handle).
Because data binding won't work until control is created. So create control at first.
And then reload actual Visible
value from data source by calling Binding.ReadValue()
.
Trying using this Add overload:
this.checkBox_WorkDone.DataBindings.Add("Visible", WorkStatus, "Done",
true, DataSourceUpdateMode.OnPropertyChanged);