我在winforms数据绑定中使用了BindingSource
,我希望在用户尝试在对数据进行更改后关闭表单时提供某种提示。有点“你确定要退出而不保存更改吗?”
我知道我可以通过BindingSource
的{{3}}事件来执行此操作,只需翻转“已更改”布尔值即可。
但是,我想要一个更强大的功能。 我想知道当前数据何时与原始数据不同。该事件只会告诉我某些事情是否有所改变。用户仍然可以更改属性,点击撤消,我仍然认为要保存的数据发生了变化。
我想模仿记事本的类似功能
如果无法做到这一点,那么我应该使用上面列出的ItemChanged
事件处理程序,还是有更好的方法?
为了记录,我正在寻找符合
的内容bool HasChanged()
{
return this.currentState != this.initialState;
}
不是这个
bool HasChanged()
{
// this._hasChanged is set to true via event handlers
return this._hasChanged;
}
我只是不必自己管理当前状态和初始状态,我正在寻找一种从BindingSource
获取信息的方法如果我可以从{{1}获取此功能它的方式更理想,因为我可以在许多不同的数据源上使用这些功能,无论类型等等。
答案 0 :(得分:4)
您必须在对象类中实现INotifyPropertyChanged
接口,然后在DataSource
BindingSource
属性中通过适当的类型类事件处理程序捕获更改。
提供所需内容的一个对象是DataSet
,包含持久实体的原始状态和当前(已更改)状态。然后,当取消时,您需要调用的只是Rollback()
方法。如果接受更改,则会调用AcceptChanges()
方法。
除DataSet
之外,或许考虑像NHibernate这样的ORM将为您完成工作,并允许您使用自定义对象,而不是DataSet
。保持ISession
API在您的表单中保持活动状态将允许ISession跟踪您的更改,无论它是什么对象,只要它是由NHibernate知道的。
实现INotifyPropertyChanged
接口的另一个解决方案是在属性设置器中,您可以在私有字段或对象的每个属性中存储原始值。您可以简单地使用HasChanges
属性的抽象类返回每个属性是否为其原始状态,然后相应地返回true或false。
我对我们有趣的初步讨论有疑问。我只想确定一件事。如果我们愿意,我们称之为语言障碍。但是通过PropertyChanged
接口发布INotifyPropertyChanged
事件也会以某种方式将对象“回滚”到其原始状态。您必须注意的唯一细节是,如果用户说他不想保留更改,那么通过BackgroundWorker
类从底层数据库重新加载此CurrentItem并完成!没有滞后于您的GUI,您的用户已取消更改,您将对象重置为其默认/原始状态!
嗯,我想这里有足够的细节可以让自己成为一个想法,加上其他人提供的所有其他好的答案。我相信你会找到自己想要的方法。
最好的成功! =)
答案 1 :(得分:4)
是的,您应该实施INotifyPropertyChanged
,最好与IDataInfoError
一起为您的用户提供可见的信息。
要让您的对象获得状态和编辑通知,请尝试使用IEditableObject
界面。
默认情况下,WinForms使用所有三个接口,帮助程序员更轻松地生活。
答案 2 :(得分:1)
您可以根据初始状态的快照检查状态,而不是翻转一下。
答案 3 :(得分:1)
当您打开详细信息时,您可以创建要修改的实体的克隆。
然后,当用户尝试关闭表单时,您可以将克隆(处于其原始状态的实体)与已修改(或不是)实体进行比较。如果克隆和实体不相等,则可以提示用户。
答案 4 :(得分:0)
您可以滚动自己的绑定源并实现它,以便在每个表单上都不需要INotifyChange
处理您想要的方式 - 您只需让BindingSource
为您提供更改的元素 - 这在BindingSource
更新时有效 - 可绑定控件.UpdateSourceTrigger
设置为UpdateOnPropertyChanged
。是即时的(差不多)。
这是让你入门的东西 - 我几年前在网上发现它我不记得代码的创始人,我为了我的目的稍微修改了它。
Imports System.ComponentModel.Design
Imports System.Windows.Forms
Imports System.ComponentModel
Public Class BindingSourceExIsDirty
Inherits System.Windows.Forms.BindingSource
Implements INotifyPropertyChanged
#Region "DECLARATIONS AND PROPERTIES"
Private _displayMember As String
Private _dataTable As DataTable
Private _dataSet As DataSet
Private _parentBindingSource As BindingSource
Private _form As System.Windows.Forms.Form
Private _usercontrol As System.Windows.Forms.Control
Private _isCurrentDirtyFlag As Boolean = False
Public Property IsCurrentDirty() As Boolean
Get
Return _isCurrentDirtyFlag
End Get
Set(ByVal value As Boolean)
If _isCurrentDirtyFlag <> value Then
_isCurrentDirtyFlag = value
Me.OnPropertyChanged(value.ToString())
If value = True Then 'call the event when flag is set
OnCurrentIsDirty(New EventArgs)
End If
End If
End Set
End Property
Private _objectSource As String
Public Property ObjectSource() As String
Get
Return _objectSource
End Get
Set(ByVal value As String)
_objectSource = value
Me.OnPropertyChanged(value)
End Set
End Property
' Private _autoSaveFlag As Boolean
'
' Public Property AutoSave() As Boolean
' Get
' Return _autoSaveFlag
' End Get
' Set(ByVal value As Boolean)
' _autoSaveFlag = value
' Me.OnPropertyChanged(value.ToString())
' End Set
' End Property
#End Region
#Region "EVENTS"
'Current Is Dirty Event
Public Event CurrentIsDirty As CurrentIsDirtyEventHandler
' Delegate declaration.
Public Delegate Sub CurrentIsDirtyEventHandler(ByVal sender As Object, ByVal e As EventArgs)
Protected Overridable Sub OnCurrentIsDirty(ByVal e As EventArgs)
RaiseEvent CurrentIsDirty(Me, e)
End Sub
'PropertyChanged Event
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
#End Region
#Region "METHODS"
Private Sub _BindingComplete(ByVal sender As System.Object, ByVal e As System.Windows.Forms.BindingCompleteEventArgs) Handles Me.BindingComplete
If e.BindingCompleteContext = BindingCompleteContext.DataSourceUpdate Then
If e.BindingCompleteState = BindingCompleteState.Success And Not e.Binding.Control.BindingContext.IsReadOnly Then
'Make sure the data source value is refreshed (fixes problem mousing off control)
e.Binding.ReadValue()
'if not focused then not a user edit.
If Not e.Binding.Control.Focused Then Exit Sub
'check for the lookup type of combobox that changes position instead of value
If TryCast(e.Binding.Control, ComboBox) IsNot Nothing Then
'if the combo box has the same data member table as the binding source, ignore it
If CType(e.Binding.Control, ComboBox).DataSource IsNot Nothing Then
If TryCast(CType(e.Binding.Control, ComboBox).DataSource, BindingSource) IsNot Nothing Then
If CType(CType(e.Binding.Control, ComboBox).DataSource, BindingSource).DataMember = (Me.DataMember) Then
Exit Sub
End If
End If
End If
End If
IsCurrentDirty = True 'set the dirty flag because data was changed
End If
End If
End Sub
Private Sub _DataSourceChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.DataSourceChanged
_parentBindingSource = Nothing
If Me.DataSource Is Nothing Then
_dataSet = Nothing
Else
'get a reference to the dataset
Dim bsTest As BindingSource = Me
Dim dsType As Type = bsTest.DataSource.GetType
'try to cast the data source as a binding source
Do While Not TryCast(bsTest.DataSource, BindingSource) Is Nothing
'set the parent binding source reference
If _parentBindingSource Is Nothing Then _parentBindingSource = bsTest
'if cast was successful, walk up the chain until dataset is reached
bsTest = CType(bsTest.DataSource, BindingSource)
Loop
'since it is no longer a binding source, it must be a dataset or something else
If TryCast(bsTest.DataSource, DataSet) Is Nothing Then
'Cast as dataset did not work
If dsType.IsClass = False Then
Throw New ApplicationException("Invalid Binding Source ")
Else
_dataSet = Nothing
End If
Else
_dataSet = CType(bsTest.DataSource, DataSet)
End If
'is there a data member - find the datatable
If Me.DataMember <> "" Then
_DataMemberChanged(sender, e)
End If 'CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)
If _form Is Nothing Then GetFormInstance()
If _usercontrol Is Nothing Then GetUserControlInstance()
End If
End Sub
Private Sub _DataMemberChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.DataMemberChanged
If Me.DataMember = "" Or _dataSet Is Nothing Then
_dataTable = Nothing
Else
'check to see if the Data Member is the name of a table in the dataset
If _dataSet.Tables(Me.DataMember) Is Nothing Then
'it must be a relationship instead of a table
Dim rel As System.Data.DataRelation = _dataSet.Relations(Me.DataMember)
If Not rel Is Nothing Then
_dataTable = rel.ChildTable
Else
Throw New ApplicationException("Invalid Data Member")
End If
Else
_dataTable = _dataSet.Tables(Me.DataMember)
End If
End If
End Sub
Public Overrides Property Site() As System.ComponentModel.ISite
Get
Return MyBase.Site
End Get
Set(ByVal value As System.ComponentModel.ISite)
'runs at design time to initiate ContainerControl
MyBase.Site = value
If value Is Nothing Then Return
' Requests an IDesignerHost service using Component.Site.GetService()
Dim service As IDesignerHost = CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)
If service Is Nothing Then Return
If Not TryCast(service.RootComponent, Form) Is Nothing Then
_form = CType(service.RootComponent, Form)
ElseIf Not TryCast(service.RootComponent, UserControl) Is Nothing Then
_usercontrol = CType(service.RootComponent, UserControl)
End If
End Set
End Property
Public Function GetFormInstance() As System.Windows.Forms.Form
If _form Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then
_form = Me.CurrencyManager.Bindings(0).Control.FindForm()
End If
Return _form
End Function
''' <summary>
''' Returns the First Instance of the specified User Control
''' </summary>
''' <returns>System.Windows.Forms.Control</returns>
Public Function GetUserControlInstance() As System.Windows.Forms.Control
If _usercontrol Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then
Dim _uControls() As System.Windows.Forms.Control
_uControls = Me.CurrencyManager.Bindings(0).Control.FindForm.Controls.Find(Me.Site.Name.ToString(), True)
_usercontrol = _uControls(0)
End If
Return _usercontrol
End Function
'============================================================================
'Private Sub _PositionChanged(ByVal sender As Object, ByVal e As EventArgs) Handles Me.PositionChanged
' If IsCurrentDirty Then
' If AutoSave Then ' IsAutoSavingEvent
' Try
' 'cast table as ITableUpdate to get the Update method
' ' CType(_dataTable, ITableUpdate).Update()
' Catch ex As Exception
' ' - needs to raise an event
' End Try
' Else
' Me.CancelEdit()
' _dataTable.RejectChanges()
' End If
' IsCurrentDirty = False
' End If
'End Sub
#End Region
End Class
答案 5 :(得分:0)
是,但其中涉及一些工作。我知道,这是一个很晚的答案,但我最近又问了我自己同样的问题,并提出了以下解决方案,我将其包装到类UpdateManager
中。到目前为止,我只考虑了绑定到单个对象。
这适用于普通的POCO对象。不需要实施{1}};但是,仅当通过UI进行更改时它才有效。不会检测到通过业务对象中的代码进行的更改。但这在大多数情况下足以检测出对象是否脏了或处于保存状态。
INotifyPropertyChanged
在我这样使用的形式中:
public class UpdateManager
{
public event EventHandler DirtyChanged;
private readonly BindingSource _bindingSource;
// Stores original and current values of all bindings.
private readonly Dictionary<string, (object original, object current)> _values =
new Dictionary<string, (object original, object current)>();
public UpdateManager(BindingSource bindingSource)
{
_bindingSource = bindingSource;
bindingSource.CurrencyManager.Bindings.CollectionChanged += Bindings_CollectionChanged;
bindingSource.BindingComplete += BindingSource_BindingComplete;
}
private bool _dirty;
public bool Dirty
{
get {
return _dirty;
}
set {
if (value != _dirty) {
_dirty = value;
DirtyChanged?.Invoke(this, EventArgs.Empty);
}
}
}
private void Bindings_CollectionChanged(object sender, CollectionChangeEventArgs e)
{
// Initialize the values information for the binding.
if (e.Element is Binding binding && GetCurrentValue(binding, out object value)) {
_values[binding.BindingMemberInfo.BindingField] = (value, value);
}
}
private void BindingSource_BindingComplete(object sender, BindingCompleteEventArgs e)
{
if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate &&
e.BindingCompleteState == BindingCompleteState.Success) {
UpdateDirty(e.Binding);
}
}
private void UpdateDirty(Binding binding)
{
if (GetCurrentValue(binding, out object currentValue)) {
string propertyName = binding.BindingMemberInfo.BindingField;
var valueInfo = _values[propertyName];
_values[propertyName] = (valueInfo.original, currentValue);
if (Object.Equals(valueInfo.original, currentValue)) {
Dirty = _values.Any(kvp => !Object.Equals(kvp.Value.original, kvp.Value.current));
} else {
Dirty = true;
}
}
}
private bool GetCurrentValue(Binding binding, out object value)
{
object model = binding.BindingManagerBase?.Current;
if (model != null) {
// Get current value in business object (model) with Reflection.
Type modelType = model.GetType();
string propertyName = binding.BindingMemberInfo.BindingField;
PropertyInfo modelProp = modelType.GetProperty(propertyName);
value = modelProp.GetValue(model);
return true;
}
value = null;
return false;
}
}
只要private UpdateManager _updateManager;
private Person _person = new Person();
public frmBindingNotification()
{
InitializeComponent();
_updateManager = new UpdateManager(personBindingSource);
_updateManager.DirtyChanged += UpdateManager_DirtyChanged;
personBindingSource.DataSource = _person; // Assign the current business object.
}
private void UpdateManager_DirtyChanged(object sender, EventArgs e)
{
Console.WriteLine(_updateManager.Dirty ? "Dirty" : "Saved"); // Testing only.
}
状态发生变化,就会在“输出”窗口中打印“脏”或“已保存”。