事件循环
Event circularity
我发现自己经常遇到以下情况:
我有一个绑定到某些数据的用户控件。每当更新控件时,基础数据也会更新。每当基础数据更新时,控件也会更新。所以很容易陷入永无止境的更新循环(控制更新数据,数据更新控制,控制更新数据等)。
通常我通过使用布尔值(例如 updatedByUser
)来解决这个问题,所以我知道控件是否已通过编程方式或由用户更新,然后我可以决定是否触发事件更新基础数据。这看起来不太整洁。
是否有一些处理此类情况的最佳做法?
编辑:我添加了以下代码示例,但我想我已经回答了我自己的问题...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
一种选择是停止更新,以防数据自上次以来没有发生变化。例如,如果数据是 class 的形式,您可以检查数据是否与上次触发事件时的实例相同,如果是,则停止传播。
这是许多 MVVM 框架所做的,以防止引发 PropertyChanged
事件,以防 属性 实际上没有改变:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
您可以为 Windows 表单类似地实施此概念。
您应该研究 MVP - 它是 Winforms 的首选设计模式 UI。
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
除了让您避免循环事件之外,使用该设计模式还可以让您的代码更具可读性。
为了真正避免循环事件,您的视图应该只导出一个 属性,一旦它被设置,它将确保 txtChanged_Event 不会被调用。
像这样:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
或者您可以使用 答案和私人 属性
您要找的是 Data Binding。它允许您连接两个或多个属性,这样当一个 属性 更改时,其他属性将自动神奇地更新。
在 WinForms 中,它有点难看,但在像您这样的情况下,它就像一个魅力。首先,您需要一个 class 代表您的数据并实现 INotifyPropertyChanged
以在数据更改时通知控件。
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
比起在 Form/Control
中,您将 ViewModel.TextFieldValue
的值绑定到 textBox.Text
。这意味着只要 TextFieldValue
的值发生变化,Text
属性 就会更新,每当 Text
属性 发生变化时,TextFieldValue
就会更新。换句话说,这两个属性的值将相同。这解决了您遇到的循环问题。
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
如果您需要从 Form/Control 之外修改值,只需设置 ViewModel
的值
form.ViewModel.TextFieldValue = "new value";
控件将自动更新。
我发现自己经常遇到以下情况:
我有一个绑定到某些数据的用户控件。每当更新控件时,基础数据也会更新。每当基础数据更新时,控件也会更新。所以很容易陷入永无止境的更新循环(控制更新数据,数据更新控制,控制更新数据等)。
通常我通过使用布尔值(例如 updatedByUser
)来解决这个问题,所以我知道控件是否已通过编程方式或由用户更新,然后我可以决定是否触发事件更新基础数据。这看起来不太整洁。
是否有一些处理此类情况的最佳做法?
编辑:我添加了以下代码示例,但我想我已经回答了我自己的问题...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
一种选择是停止更新,以防数据自上次以来没有发生变化。例如,如果数据是 class 的形式,您可以检查数据是否与上次触发事件时的实例相同,如果是,则停止传播。
这是许多 MVVM 框架所做的,以防止引发 PropertyChanged
事件,以防 属性 实际上没有改变:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
您可以为 Windows 表单类似地实施此概念。
您应该研究 MVP - 它是 Winforms 的首选设计模式 UI。 http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
除了让您避免循环事件之外,使用该设计模式还可以让您的代码更具可读性。
为了真正避免循环事件,您的视图应该只导出一个 属性,一旦它被设置,它将确保 txtChanged_Event 不会被调用。
像这样:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
或者您可以使用
您要找的是 Data Binding。它允许您连接两个或多个属性,这样当一个 属性 更改时,其他属性将自动神奇地更新。
在 WinForms 中,它有点难看,但在像您这样的情况下,它就像一个魅力。首先,您需要一个 class 代表您的数据并实现 INotifyPropertyChanged
以在数据更改时通知控件。
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
比起在 Form/Control
中,您将 ViewModel.TextFieldValue
的值绑定到 textBox.Text
。这意味着只要 TextFieldValue
的值发生变化,Text
属性 就会更新,每当 Text
属性 发生变化时,TextFieldValue
就会更新。换句话说,这两个属性的值将相同。这解决了您遇到的循环问题。
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
如果您需要从 Form/Control 之外修改值,只需设置 ViewModel
form.ViewModel.TextFieldValue = "new value";
控件将自动更新。