当Control.Visible == false时,无法将数据绑定到控件

时间:2012-03-22 18:17:19

标签: c# winforms data-binding

在使用C#4.0 / C#2.0的WinForms中,如果控件的可见字段为false,则无法绑定到控件:

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

我可以确认绑定已成功添加到控件的数据绑定列表中,但如果我更改绑定对象(WorkStatus),则不会发生任何事情。

这就是WorkStatus的样子:

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); }
    }
}

修改
要重现,只需在设计器中设置Visible = false,或在数据绑定之前在构造函数中设置 使用Add()方法的一个重载也会失败:

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

我想隐藏控件的原因是我不希望用户在第一次显示表单时看到控件。

解决方案
谢谢大家,我想我找到了解决方案:

在Form.Load()事件中设置Control.Visible = false。在这种情况下,当显示表单时,控件不可见。

虽然,为什么MS以这种方式设计数据绑定仍然是未知的。

7 个答案:

答案 0 :(得分:10)

我跑到this exact situation before。在控件第一次可行之前,一些后端初始化永远不会发生,初始化的一部分是启用数据绑定。在数据绑定工作之前,您必须调用CreateControl(true)。但是,该方法是受保护的方法,因此您必须通过反射或扩展控件来执行此操作。

通过反思:

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 } );
}

所有事件都将推迟到控件的Created设置为true。

答案 1 :(得分:1)

尝试使用此Add重载:

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

答案 2 :(得分:1)

更新代码

根据您的问题中给出的代码,这对我有用。

    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;
    }

您可以在绑定之前将Control设置为visible = true。

如果控件不可见,我们执行以下代码也会起作用:

        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不需要!当WorkStatus对象具有Done = false时,它不会显示控件但会触发VisibleChanged事件。

答案 3 :(得分:1)

我创建了一个测试工具(见下文),并尝试了您的代码。我需要使用Add方法的重载来设置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);
    }
}

编辑: 如果从窗体的构造函数中删除setter,这可以正常工作。如果在窗体的构造函数中将可见性设置为false,则此绑定将无法更新。如果您的数据绑定正常工作,则没有理由手动指定初始可见性。这首先真的打败了数据绑定的目的。

答案 4 :(得分:1)

您可以做的是使控件可见,并在绑定更改后再次使其不可见。

this.checkBox_WorkDone.Visible = true; 
this.checkBox_WorkDone.BindingContextChanged += (object sender, EventArgs e) => {
    this.checkBox_WorkDone.Visible = false; 
};

不是很漂亮,但它确实有效。

答案 5 :(得分:1)

我知道这有点晚了,但是我遇到了同样的问题 - 我想要绑定的控件在显示表单时设置为visible = false。我可能想在很多形式上做这个,而且我总是不愿意为每个绑定写代码。

所以我把一个小黑客放在一起。

我有一个带有面板的表单,我在构造函数中将其设置为Visible = false。我想将视图绑定到我写的自定义视图模型。在表单中,我从工具箱中放入一个BindingSource。我为我的视图模型的Project Data源绑定源的DataSource。

然后想法迭代表单上的控件,并从数据源(即视图模型)更新控件绑定值。

为此,我存储控件的可见值,将其设置为false,并读取绑定值。然后恢复初始可见值。这是在适当命名的方法HackIt()中完成的。

以下是代码:

<强>视图模型

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");
        }
    }
}

表单设计器代码

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;
}

表单代码

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();
    }
}

使用上面的代码,表单启动并显示我的控件。您可以更改视图模型构造函数,默认为false以隐藏。无论哪种方式都可以。

在表单构造函数中 - 我想显式隐藏面板(this.panel1.Visible = false) - 只是为了在视图模型默认visible = true时证明绑定,控件在加载时正确显示。

然后我们可以让按钮更改视图模型上的可见项,这将切换面板的状态:

    private void button1_Click(object sender, EventArgs e)
    {
        this.ViewModel.Visible = false;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        this.ViewModel.Visible = true;
    }

<强>更新

这让我超越了第一道障碍。但是,我正在使用Telerik组件,因此我决定在表单上删除Telerik控件。这完全打破了一切。

而不是上面的HackIt方法,在load事件中调用以下RefreshDataBindings()。

我决定遍历表单上的所有控件,并以反射方式手动更新绑定。这太疯狂了!但它100%工作 - 即使我的表格上有Telerik控件。我的主要应用程序的性能还可以。这是一个右下角的黑客 - 但我把它放在基本形式或基本控制中 - 我并不担心我的绑定。

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);
}

答案 6 :(得分:0)

我的解决方法: private void Form_Load(object sender, EventArgs e) { button.Visible = true; button.DataBindings["Visible"].ReadValue(); }

需要

button.Visible = true;来强制创建控件(换句话说,它是创建窗口句柄)。

因为在创建控件之前数据绑定将不起作用。因此,首先创建控件。

然后通过调用Visible从数据源重新加载实际的Binding.ReadValue()值。