关于C# WinForms数据绑定(使用INotifyPropertyChanged)和数据刷新问题

问题描述 投票:0回答:2

我是初学者,正在使用C# WinForms开发网络程序。该程序不断尝试从远程服务器读取数据并将其同步显示在我的文本框中。另外,当用户通过输入修改TextBox并按Enter键时,应该发送一条消息来修改远程服务器上的相应数据。这是我的要求。

我已经使用 INotifyPropertyChanged 和 BindingList 实现了数据绑定。但是,此数据绑定阻止我修改用户界面上文本框的内容。当我尝试修改此值并发送修改消息时,输入与原始数据不同的数据,如果从连续读取的远程服务器接收到新数据,它将同步并刷新文本框。导致界面无法输入修改内容

这是我的 ValueBinding 类

    public class ValueBinding : INotifyPropertyChanged
    {
        public int _value;
        public int Value
        {
            set
            {
                if (value != _value)
                {
                    _value = value;
                    // change
                    OnPropertyChanged(nameof(Value));
                }
            }
            get { return _value; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Invoke
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

我使用此代码来创建我的 BindingList:

BindingList<ValueBinding> valueList = new BindingList<ValueBinding>();
ValueBinding valueBinding = new ValueBinding();
valueList.Add(valueBinding);
// Binding TextBox
textBox1.DataBindings.Add("Text", valueList[0], "Value", true, DataSourceUpdateMode.OnPropertyChanged);

下面的代码是当用户按 ENTER 键时将更改发送到服务器:

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    TextBox textBox = sender as TextBox;
    if (e.KeyChar == (char)Keys.Enter)
    {
        SendChangemessage(textbox.Text);
        e.Handled = true;
    }
}

有人请帮我解决这个难题。

我尝试在 BindingValue 类中添加一个布尔标志,以区分程序内接收消息引起的变化和用户输入引起的变化,但由于某种原因,没有成功。问题仍然存在;当我输入与当前值不同的数据时,由于后端数据的更新,它会被刷新。

c# .net winforms user-interface data-binding
2个回答
0
投票

这就是双向绑定的工作原理,控件绑定到单个源,并且没有知道为什么值更改或更改内容的概念。

对此有多种解决方案,但如果您单独使用绑定,情况会变得很复杂,因为有时您需要在修改另一个源时抑制来自一个源的更新。

复杂之处在于如何识别正在发生编辑以及如何通知您已结束编辑。

对此的一个解决方案是更改您的

ValueBinding
,以便可以将外部值更改与 UI 更改区分开来,并且我们可以公开状态:

/// <summary> 
/// Binding that defaults to a background value that changes rapidly
/// But allows for the User to edit itserver value 
public class ValueBinding : INotifyPropertyChanged
{
    public int? _displayValue;
    public int DisplayValue
    {
        set
        {
            if (value != _displayValue)
            {
                bool wasModified = IsModified;
                _displayValue = value;
                OnPropertyChanged();
                if (!wasModified)
                   OnPropertyChanged(nameof(IsModified)); 
            }
        }
        get 
        { 
            return _displayValue.GetValueOrDefault(_serverValue); 
        }
    }

    /// <summary> Helper to identify if the DisplayValue has changed </summary>
    public bool IsModified { get => _displayValue.HasValue; }

    public int _serverValue;
    /// <summary>
    /// Bind this to the external caller
    /// It will only update the DisplayValue
    /// if the DisplayValue has not been set.
    /// </summary>
    /// <remarks>
    /// When the next server value received matches the DisplayValue, 
    /// we will reset the Display value.
    /// </remarks>
    public int ServerValue
    {
        set
        {
            if (value != _serverValue)
            {
                int previousValue = value;
                _serverValue = value;
                OnPropertyChanged();

                // Check if we need to reset the user edit
                // AKA - the server has received the change
                if (_displayValue.HasValue 
                    && _displayValue.Value.Equals(value))
                {
                    _displayValue = null;
                    OnPropertyChanged(nameof(IsModified));
                }
                if (!_displayValue.HasValue)
                    OnPropertyChanged(nameof(DisplayValue));
            }
        }
        get => _serverValue;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Invoke
    /// </summary>
    /// <param name="propertyName"></param>
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

然而,即使这个简单的解决方案对于某些特定的值输入场景也会出现问题,因为更改检测仍然不知道用户正在积极编辑,但输入速度很慢。

  • 这不是典型的绑定场景,当您学习将这两个概念分成 UI 中的 2 个不同字段时可能会更好,也许使服务器控件只读。

这种类型的绑定场景在控件级别得到更好的管理,您创建一个自定义控件,可以绑定到 2 个与此具有类似逻辑的属性,但由于您位于控件上下文中,所以您可以访问其他属性,例如控件是否具有焦点或鼠标悬停,可以让您更好地控制行为,它还可以轻松为背景着色或设置工具提示,从而帮助用户识别服务器值和本地值。


0
投票

我认为您在模型中添加布尔值是正确的。我认为让这个成功的是给文本框一个方向的概念。您可以尝试的一种方法是仅当文本框为

ReadOnly
时才允许刷新传入值。然后(例如)通过双击文本框,可以有效地将方向更改为“传出”,直到
SendChangeMessage
提交完成。模型中添加了 textbox.ReadOnly 的附加绑定。

    public MainForm()
    {
        InitializeComponent();
        // Set bindings
        textbox.DoubleClick += (sender, e) =>
        {
            textbox.BeginInvoke(() =>
            {
                textbox.ReadOnly = false;
                textbox.SelectAll();
            });
        };
        textbox.KeyDown += (sender, e) =>
        {
            switch (e.KeyData) 
            {
                case Keys.Enter:
                    if (!textbox.ReadOnly)
                    {
                        e.Handled = e.SuppressKeyPress = true; 
                        SendChangemessage(textbox.Text);
                        textbox.ReadOnly = true;
                    }
                    break;
            }
        };
        textbox.LostFocus += (sender, e) =>
        {
            if(this == ActiveForm) textbox.ReadOnly = true;
        };
        textbox.DataBindings.Add(
            new Binding(
                propertyName: nameof(TextBox.Text),
                dataSource: Model,
                dataMember: nameof(Model.Value),
                formattingEnabled: false,
                dataSourceUpdateMode: DataSourceUpdateMode.OnPropertyChanged
        ));
        textbox.DataBindings.Add(
            new Binding(
                propertyName: nameof(TextBox.ReadOnly),
                dataSource: Model,
                dataMember: nameof(Model.IsReadOnly),
                formattingEnabled: false,
                dataSourceUpdateMode: DataSourceUpdateMode.OnPropertyChanged
        ));
    }
    MainFormViewModel Model { get; } = new MainFormViewModel();

发出更改消息

    private void SendChangemessage(string text) =>
        MessageBox.Show(text, "Change Message");

模拟来自服务器的传入数据

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        _ = ExecRefreshLoop();
    }

    private async Task ExecRefreshLoop()
    {
        while(!Disposing)
        {
            if (Model.IsReadOnly) 
            {
                Model.Value = Random.Next(1000, 10000);
            }
            await Task.Delay(TimeSpan.FromSeconds(1)); 
        }
    }
    private Random Random { get; } = new Random();
}

从服务器模拟更新

典型的视图模型,可以充当整个 MainForm 的绑定上下文

public class MainFormViewModel : INotifyPropertyChanged
{
    public int _value;
    public int Value
    {
        set
        {
            if (value != _value)
            {
                _value = value;
                OnPropertyChanged();
            }
        }
        get { return _value; }
    }
    public bool IsReadOnly
    {
        get => _IsReadOnly;
        set
        {
            if (!Equals(_IsReadOnly, value))
            {
                _IsReadOnly = value;
                OnPropertyChanged();
            }
        }
    }
    bool _IsReadOnly = true;

    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.