11

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", WorkStatus, "Done");

I can confirm the binding is successfully added to the control's databindings list but if I change my bound object (WorkStatus), nothing happens.

This is what WorkStatus looks like:

public class WorkStatus : INotifyPropertyChanged
{
    private Boolean _done;
    public Boolean Done
    {
        get { return _done; }

        set
        {
            if (_done == value) return;

            _done = value;

            // fire event
            RaisePropertyChanged("Done");
        }
    }

    private Int32 _time;
    public Int32 Time
    {
        get { return _time; }

        set
        {
            if (_time == value) return;

            _time = value;

            // fire event
            RaisePropertyChanged("Time");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged(String propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null) { PropertyChanged(this, e); }
    }
}

Edit
To reproduce, just set the Visible=false in the designer, or in the constructor before the databinding.
Using one overload of the Add() method fails too:

this.checkBox_WorkDone.DataBindings.Add("Visible", WorkStatus, "Done",
   true, DataSourceUpdateMode.OnPropertyChanged);

The reason I want to hide the control is that I don't want user to see the control when the form is shown the very first time.

Solution
Thanks guys, I think I find a solution for this:

just set the Control.Visible = false in the Form.Load() event. In that case the control is not visible when the form is shown.

Although, why MS design the data binding in this way is still unknown.

AZ.
  • 7,333
  • 6
  • 44
  • 62
  • See here for the answer, why DataBinding doesn't work if the visibility is set to false: http://stackoverflow.com/a/2570153/2455604 – Marwie Apr 11 '14 at 15:51

7 Answers7

12

I ran in to this exact situation before. Until the control is viable for the first time some back-end initialization never happens, part of that initialization is enabling the data binding. You must call CreateControl(true) before data binding works. However, that method it is a protected method so you must do it though reflection or by extending the control.

Via reflection:

private static void CreateControl( Control control )
{
    var method = control.GetType().GetMethod( "CreateControl", BindingFlags.Instance | BindingFlags.NonPublic );
    var parameters = method.GetParameters();
    Debug.Assert( parameters.Length == 1, "Looking only for the method with a single parameter" );
    Debug.Assert( parameters[0].ParameterType == typeof ( bool ), "Single parameter is not of type boolean" );

    method.Invoke( control, new object[] { true } );
}

All events will be deferred until the control has Created set to true.

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I dunno if this is necessary. I can replicate his failure and then fix it by specifying the `DataSourceUpdateMode` to be 'OnPropertyChanged'. – Ritch Melton Mar 22 '12 at 18:37
  • 4
    `DataSourceUpdateMode.OnPropertyChanged` do not work for me if the control ist initialy set to Visible = false in the designer. – Tarion Mar 22 '12 at 18:43
  • ..or just use the databinding properly.' – Ritch Melton Mar 22 '12 at 19:07
  • 3
    So @RitchMelton what would be the "proper way" to do databinding when you have a control on another TabPage and you do not want the event to be deferred? (see the my old SO question I linked to in my answer for example code) – Scott Chamberlain Mar 22 '12 at 19:38
  • @Scott - I don't know about your situation, but letting the model represent the state of the controls is fairly 'proper' in my book. Sometimes you can't do it that way, but answering every problem of this type by farting into reflection isn't. – Ritch Melton Mar 22 '12 at 20:53
2

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.

Bas
  • 3,016
  • 3
  • 22
  • 18
1

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);
}
Andez
  • 5,588
  • 20
  • 75
  • 116
1

Trying using this Add overload:

this.checkBox_WorkDone.DataBindings.Add("Visible", WorkStatus, "Done",
   true, DataSourceUpdateMode.OnPropertyChanged);
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
  • 2
    It doesn't work for me. Try to set the Visible to false in constructor before the databinding. – AZ. Mar 22 '12 at 18:48
  • @AZ. - According to this page (http://msdn.microsoft.com/en-us/library/83fhsxwc.aspx) using uninitialized variables is not allowed. Maybe this has something to do with `_done` not having a default value. Try setting it (`private Boolean _done = false`) and see if that works – SwDevMan81 Mar 22 '12 at 18:53
  • @AZ - If you are binding, why set it false in the constructor, just let the binding do its job. – Ritch Melton Mar 22 '12 at 18:56
  • 1
    @AZ. - Found the answer here: (http://stackoverflow.com/a/943483/95573) Basically `data-bound control are not updated until the control is made visible` – SwDevMan81 Mar 22 '12 at 18:58
  • @RitchMelton because I don't want user to see this control when the form is shown the very first time. – AZ. Mar 22 '12 at 18:58
  • As I mentioned in the other comments, this is because Control.Created is false until visible is set to true at least once, you can also call the internal method [that I mention in my answer](http://stackoverflow.com/a/9828423/80274) to set Created to true. – Scott Chamberlain Mar 22 '12 at 18:59
  • @AZ - But if your data object is returning false it won't show. That's kind of the point of databinding. – Ritch Melton Mar 22 '12 at 19:02
  • @RitchMelton yes you are right. But I simplified my problem: the real world case is I'm doing the databinding **after** the form is shown so I want to hide the control first. – AZ. Mar 22 '12 at 19:06
1

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.

Tarion
  • 16,283
  • 13
  • 71
  • 107
  • I did the exact same thing, try to set Visible to false in the designer, or set this.checkBox_WorkDone.Visible = false in the constructor before the binding, do you still get the correct result? – AZ. Mar 22 '12 at 18:29
  • @Tarion - It fails for me also. – Ritch Melton Mar 22 '12 at 18:35
  • 1
    When I set the Control to visible = false before the binding it will fail - I will take a closer look on it. – Tarion Mar 22 '12 at 18:37
  • If you check [`Created`](http://msdn.microsoft.com/en-us/library/system.windows.forms.control.created.aspx) it will be false until the control is viable at least once, or [until the internal method `CreateControl` with the parameter `ignoreVisable` set to true](http://stackoverflow.com/a/9828423/80274). Until Control.Created == true binding does not work. – Scott Chamberlain Mar 22 '12 at 18:56
  • You need to set visible to true, then do the databinding, then set visible to false. Stupid but it works. – Rob Jul 06 '17 at 13:15
1

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.

Ritch Melton
  • 11,498
  • 4
  • 41
  • 54
  • Please see my reply in SwDevMan81's answer. – AZ. Mar 22 '12 at 19:08
  • @RitchMelton: Please consider the case where the checkbox is placed on page 2 of a tabcontrol. In that case, the behavior is just as described by the OP **without** the visibility being set explicitly. In that case, you have to resort to a solution such as the one proposed by Scott. – Andreas Nov 22 '17 at 15:11
0

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().

kenjiuno
  • 341
  • 5
  • 11