【问题标题】:Cannot data bind to a control when Control.Visible == false当 Control.Visible == false 时无法将数据绑定到控件
【发布时间】:2012-03-22 18:17:19
【问题描述】:

在 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为什么要这样设计数据绑定还是个未知数。

【问题讨论】:

标签: c# winforms data-binding


【解决方案1】:

我跑到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。

【讨论】:

  • 我不知道这是否有必要。我可以复制他的失败,然后通过将DataSourceUpdateMode 指定为“OnPropertyChanged”来修复它。
  • DataSourceUpdateMode.OnPropertyChanged 如果控件在设计器中初始设置为 Visible = false,则对我不起作用。
  • ..或者只是正确使用数据绑定。'
  • 那么@RitchMelton 当您在另一个 TabPage 上有一个控件并且您不希望事件被推迟时,执行数据绑定的“正确方法”是什么? (请参阅我在答案中链接到的旧 SO 问题以获取示例代码)
  • @Scott - 我不知道你的情况,但让模型代表控件的状态在我的书中是相当“正确的”。有时你不能那样做,但通过放屁反思来回答这类问题并不是。
【解决方案2】:

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

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

不是很漂亮,但很管用。

【讨论】:

    【解决方案3】:

    尝试使用这个Add重载:

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

    【讨论】:

    • 它对我不起作用。尝试在数据绑定之前在构造函数中将 Visible 设置为 false。
    • @AZ。 - 根据此页面 (msdn.microsoft.com/en-us/library/83fhsxwc.aspx),不允许使用未初始化的变量。也许这与_done 没有默认值有关。尝试设置它 (private Boolean _done = false) 看看是否可行
    • @AZ - 如果你正在绑定,为什么要在构造函数中设置它为 false,让绑定完成它的工作。
    • @AZ。 - 在这里找到答案:(stackoverflow.com/a/943483/95573) 基本上是data-bound control are not updated until the control is made visible
    • @RitchMelton 因为我不希望用户在第一次显示表单时看到此控件。
    【解决方案4】:

    更新代码

    这适用于您问题中给出的代码。

        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 事件。

    【讨论】:

    • 我也做了一模一样的事情,尝试在设计器中设置Visible为false,或者在绑定前在构造函数中设置this.checkBox_WorkDone.Visible = false,还是得到正确的结果吗?
    • @Tarion - 我也失败了。
    • 当我在绑定之前将 Control 设置为 visible = false 时,它​​会失败 - 我会仔细研究一下。
    • 如果您选中Created,它将为假,直到控件至少一次可行,或者until the internal method CreateControl with the parameter ignoreVisable set to true。直到 Control.Created == true 绑定不起作用。
    • 您需要将可见设置为true,然后进行数据绑定,然后将可见设置为false。愚蠢,但它有效。
    【解决方案5】:

    我创建了一个测试工具(见下文),并尝试了您的代码。我需要使用 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,则此绑定将无法更新。如果您的数据绑定工作正常,则没有理由手动指定初始可见性。这确实违背了数据绑定的初衷。

    【讨论】:

    • 请看我在 SwDevMan81 的回答中的回复。
    • @RitchMelton:请考虑将复选框放置在选项卡控件的第 2 页上的情况。在这种情况下,行为就像 OP 所描述的那样 没有明确设置可见性。在这种情况下,您必须求助于 Scott 提出的解决方案。
    【解决方案6】:

    我知道这有点晚了,但我遇到了同样的问题 - 当显示表单时,我要绑定的控件设置为 visible = false。我可能想在很多表单上执行此操作,但我总是不愿意为每个绑定编写大量代码。

    所以我整理了一个小技巧。

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

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

    为此,我存储了控件的可见值,将其设置为 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)——只是为了证明当视图模型默认可见=真时的绑定,控件在加载时正确显示。

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

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

    【讨论】:

      【解决方案7】:

      我的解决方法:

      private void Form_Load(object sender, EventArgs e)
      {
          button.Visible = true;
          button.DataBindings["Visible"].ReadValue();
      }
      

      需要button.Visible = true; 来强制创建控件(换句话说,将Button 类实例与实际的Win32 窗口句柄相关联)。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多