WinForms:响应应用BindingSource

4

有没有事件可以依附,以便在数据源应用于其绑定控件时通知我?

还是有另一个事件,在该事件中我可以确保数据源已被应用?


我正在使用WinForms(从WPF转移),并使用带有数据绑定值的标记来确定我正在使用的控件类型。许多控件可能具有相同的标记值,我必须检索具有所需标记的控件以执行业务逻辑。

问题在于我不知道何时执行搜索标记值。 我尝试在调用以下操作后立即搜索标记值:

myBindingSource.DataSource = OutputFunctions.Instance;
//Yes... I'm binding to a singleton with a list of properties.
//Its not the best method, but works.

在我的Form.Load事件处理程序中,我希望能够设置标签的值。但是,在搜索过程中,我发现标签的值并未设置。如果我刚刚设置了数据源,怎么会这样呢?

从我表单的内部管理代码中可以看出,我已经通过设计窗口的属性正确设置了值:

this.textBoxDTemp.DataBindings.Add(new System.Windows.Forms.Binding(
    "Tag",
    this.myBindingSource,
    "KNOB_DRIVER_TEMP",
    true));

我已经查看了 BindingComplete,它看起来非常有前途,但是在绑定初始化过程中,即使值从数据源传播到目标控件,它也不会触发。 编辑: 按要求,数据源首先在表单的内部代码后台中设置如下:
this.myBindingSource.DataSource = typeof(OutputFunctions);

如果有帮助的话,这里是单例模式。

public class OutputFunctions
{
    private static OutputFunctions instance;

    public static OutputFunctions Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new OutputFunctions();
            }
            return instance;
        }
    }

    private OutputFunctions() { }

    public string KNOB_DRIVER_TEMP { get { return "KNOB_DRIVER_TEMP"; } }
    public string KNOB_PASSENGER_TEMP { get { return "KNOB_PASSENGER_TEMP"; } }
    public string KNOB_FAN { get { return "KNOB_FAN"; } }
}

在设置之前,myBindingSource.DataSource 属性包含什么内容?我无法复制这种情况。如果我将 DataSource 留空,则会在加载事件之前抛出异常。如果我将其设置为像 typeof(MyDataObject) 这样的东西,那么加载事件就会触发,并且在将其设置为实际实例后,标记会立即更新。您确定是在加载事件或构造函数内设置数据源吗? - Ivan Stoev
您可能正在尝试解决错误的问题。您可以在 WinForms 中使用 MVVM- 将视图模型与视图保持独立,并使用绑定源和自定义绑定代码将其绑定到控件上。(视图模型逻辑应该永远不会与控件及其标记交互。视图模型应使用对象表示状态。与控件和标记交互是绑定源、事件处理程序以及偶尔用于绑定这些对象并表示其状态的自定义视图特定绑定代码的工作。) - jnm2
@IvanStoev 奇怪。代码后台将其设置为 typeof(OutputFunctions),如果我理解正确,这应该初始化一个空的 BindingList。在我设置单例实例之后,我搜索标签但找不到它们。 - Nicholas Miller
1
@NickMiller 我会的,但我正在尝试复制您的情况,但我无法做到。到目前为止,我所知道的唯一事情是,如果控件的Visible = false,则绑定不起作用,如果这有帮助。 - Ivan Stoev
1
@NickMiller 我已经在WinForms中享受了一段时间的MVVM,所以保持代码整洁是*可能的。我将视图模型编写为将在WPF中使用的方式,只是使用无参数方法而不是ICommands。然后,我使用BindingSource将所有内容绑定到UI,并使用事件处理程序调用视图模型的操作方法。有时,在拥有可工作、可测试的视图模型之后,您需要编写额外的视图代码来将视图绑定到视图模型,但重要的是没有业务逻辑知道视图的存在。 - jnm2
显示剩余7条评论
2个回答

4
在表单加载事件之前,数据绑定应该已经被激活。你遇到的问题是由于数据绑定基础设施优化导致的,绑定不会发生在不可见控件上,直到它们第一次变为可见状态。这可能是因为WF的设计者认为数据绑定仅用于绑定数据属性(如Text等),对于不可见控件进行绑定没有意义。
如果你不怕使用一些内部方法(或像用户HighCore所说的黑科技),那么以下辅助程序将有助于解决你的问题(我们多年来一直在使用类似的方法)。
public static class ControlUtils
{
    static readonly Action<Control, bool> CreateControlFunc = (Action<Control, bool>)Delegate.CreateDelegate(typeof(Action<Control, bool>),
        typeof(Control).GetMethod("CreateControl", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(bool) }, null));
    public static void CreateControls(this Control target)
    {
        if (!target.Created)
            CreateControlFunc(target, true);
        else
            for (int i = 0; i < target.Controls.Count; i++)
                target.Controls[i].CreateControls();
    }
}

只需将以下代码放置在表单的加载事件处理程序的开头即可:
this.CreateControls();

太棒了。我不知道你可以像那样利用内部功能。虽然HighCore有一个公正的观点,称它们为黑客技巧,因为你完全违反了该函数的预期范围。显然,可见性行为已经记录在文档中(请参阅Control.CreateControl Method ())。非常感谢! - Nicholas Miller
@NickMiller 不用谢。但我更愿意问微软为什么要让我使用黑科技——这个函数存在,所以它有用例,那为什么要将其设为内部函数呢?保重。 - Ivan Stoev
1
我遇到了这个问题。如果在设计器中将控件设置为初始可见,则不需要此解决方法,因为绑定会处理使其不可见的操作。只要在构造函数期间设置了绑定源的数据源,绑定就会在线程绘制任何内容之前立即发生,因此用户永远看不到该控件。 - jnm2
1
@jnm 正确,但请查看线程中的评论。OP 的情况正是需要解决的情况。还要注意,如果您在窗体中使用选项卡控件,则无论它们在设计时是否设置为可见,所有包含在非活动页中的控件都不会绑定。该方法确保创建所有控件(内部方法的第二个参数称为 ignoreVisible,公共的 CreateControl 只传递 false)。 - Ivan Stoev
@IvanStoev 我想说的是这种情况根本不存在。OP不需要在设计器中将控件设置为不可见。通过保持其可见性并依赖于绑定来隐藏它,可以获得相同的视觉效果。 - jnm2
@jnm2,我认为你是正确的,可以使用绑定来隐藏控件。由于控件最初是从设计师中可见的,因此绑定仍然会触发。尽管如此,我没有充分利用数据绑定的全部功能(应该这样做),而Ivan Stoev提到的解决方法解决了我的特定设置问题。关于选项卡控件,似乎只有在选项卡变为可见之后,绑定才会触发。在设计时只能看到一个选项卡,因此绑定只对第一个选项卡触发。这个技巧将允许所有选项卡在启动时进行数据绑定。谢谢大家! - Nicholas Miller

1
我认为您正在寻找控件的“BindingContextChanged”事件。您是否尝试在发生绑定时强制添加一些钩子?由于您是在整个表单准备好并建立绑定之后查找,因此您可能可以挂钩到“LOAD”事件。表单首先准备所有内容,然后将调用“Load”事件。如果有任何订阅它(监听)的内容,它们将得到通知。一旦调用了它,您可以运行并循环遍历表单上的所有控件,查找任何部分/组件/标记/控件类型等。
    public Form1()
    {
        InitializeComponent();

        this.VisibleChanged += Form1_VisibleChanged;
    }

    void Form1_VisibleChanged(object sender, EventArgs e)
    {
        if (!this.Visible)
            return;

        // Disable the event hook, we only need it once.
        this.VisibleChanged -= Form1_VisibleChanged;

        StringBuilder sb = new StringBuilder();
        foreach (Control c in this.Controls)
            sb.AppendLine(c.Name);
    }

根据评论进行编辑。我从LOAD事件更改为VISIBILITY事件。此时,表单现在正在显示,因此您的所有内容应该已经完成并可用。因此,初始检查是确保它正在变得可见。如果是这样,立即将其从事件处理程序中移除,您只需要执行一次而不是每次显示/隐藏/显示...


这看起来很有帮助。似乎如果一个控件的绑定上下文发生变化,那么所有绑定的标记应该立即可用。你如何正确设置/更改顶层表单的绑定上下文? - Nicholas Miller
@NickMiller,修正后的答案…这对你有帮助吗? - DRapp
是的,这确实有帮助。实际上,这正是我目前正在做的,但我发现控件在Form.Load处理程序被调用时是不可见的,所以它们没有绑定的标签值。 - Nicholas Miller
@NickMiller,请查看已更改VISIBILITY的修订版本。 - DRapp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接