65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 未保存数据检测

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (16投票s)

2010年10月3日

Ms-PL

4分钟阅读

viewsIcon

88438

downloadIcon

454

检测用户是否未保存更改,并弹出窗口允许他们停止离开页面(使用 ViewModel / MVVM)

保护您的用户免于丢失未保存的更改

实时示例: http://silverlight.adefwebserver.com/UnsavedDataDetection

使用 Silverlight 构建业务应用程序的一个优点是,用户可以输入大量信息,而不必担心页面“超时”。但是,如果他们输入了大量信息,却意外地离开了页面,或者意外地关闭了 Web 浏览器,那么他们就会丢失所有未保存的更改。

本文介绍了一种弹出窗口的方法,该窗口可以让用户有机会保存任何未保存的更改。

示例应用程序

加载应用程序时,您会看到示例信息。保存按钮是禁用的,ISDirty 复选框是未选中的。

如果您进行更改并按下Tab键,保存按钮现在会启用ISDirty 复选框现在会选中

如果您试图在表单“”时离开页面,您将看到一个弹出窗口,指示未保存的更改数量,并询问您是想继续离开页面,还是想停留并修复所有未保存的更改。

如果您单击保存按钮,保存按钮将禁用ISDirty 复选框将取消选中

您现在可以离开页面,或者关闭 Web 浏览器,而不会看到任何警告。

LightSwitch 的实现方式

Microsoft 的 LightSwitch 程序内置了此功能。这就是它使用的 JavaScript

  function checkDirty(e) {
    var needConform = false;
    var message = 'You may lose all unsaved data in the application.'; // default message
    
    var silverlightControl = document.getElementById("SilverlightApplication").Content;
    if (silverlightControl) {
        var applicationState = silverlightControl.ApplicationState;
        if (applicationState) {
            if (applicationState.IsDirty) {
                needConform = true;
                message = applicationState.Message;
            }
        }
        else {
            needConform = true;
        }
    }
    
    if (needConform) {
        if (!e) e = window.event;
        e.returnValue = message;
        
        // IE
        e.cancelBubble = true;
        
        //e.stopPropagation works in Firefox.
        if (e.stopPropagation) {
            e.stopPropagation();
            e.preventDefault();
        }
        
        // Chrome
        return message;
    }
}
window.onbeforeunload = checkDirty;

我感到很惊讶,因为这就是全部了。其他所有内容都隐藏在 LightSwitch 程序中,而 Microsoft 没有共享任何代码。我决定让我的版本使用他们的 JavaScript,因为我认为他们花了很多钱聘请了最优秀、最聪明的人来编写它。

关于如何做到这一点的信息出乎意料地少。我只找到一个由Daniel Vaughan 提供的例子,当浏览器关闭时从 Silverlight 调用 Web 服务,该例子像 LightSwitch 那样弹出窗口。然而,他的例子还包含了更多内容,例如调用 Web 服务,这仍然需要我创建自己的实现。不过,他的例子确实向我展示了如何做到这一点。

ApplicationState 类

我需要实现的基本功能是

  • 检测属性何时更改(即
  • 检测属性何时恢复到原始值(即不再
  • 允许重置所有属性为非状态(例如,当按下保存按钮时)

这是执行此操作的类

namespace UnsavedDataDetection
{
    public class ApplicationState
    {
        // Properties

        #region IsDirty
        [ScriptableMember]
        public bool IsDirty
        {
            get
            {
                // Return bool if there are Dirty Elements
                return (Elements.Where(x => x.IsDirty == true).Count() > 0);
            }
        }
        #endregion

        #region Message
        [ScriptableMember]
        public string Message
        {
            get
            {
                // Return a message indicating how many Dirty Elements there are
                return string.Format("There are {0} unsaved changes",
                    Elements.Where(x => x.IsDirty == true).Count().ToString());
            }
        }
        #endregion

        // Methods

        #region AddElement
        public void AddElement(ApplicationElement paramElementName)
        {
            // Do we already have the Element?
            var CurrentElement = (from Element in Elements
                                  where Element.ElementKey == paramElementName.ElementKey
                                  select Element).FirstOrDefault();

            if (CurrentElement == null)
            {
                // Ensure that the Element has been marked not Dirty
                paramElementName.IsDirty = false;
                // Set the Initial Value
                paramElementName.ElementInitialValue = 
				paramElementName.ElementCurrentValue;
                // Add the element
                Elements.Add(paramElementName);
            }
            else
            {
                // Update the element
                CurrentElement.ElementCurrentValue = 
				paramElementName.ElementCurrentValue;
                // Set IsDirty
                CurrentElement.IsDirty = (CurrentElement.ElementCurrentValue 
					!= CurrentElement.ElementInitialValue);
            }
        } 
        #endregion

        #region ClearIsDirty
        public void ClearIsDirty()
        {
            // Clear all the ISDirty flags
            foreach (var item in Elements)
            {
                item.ElementInitialValue = item.ElementCurrentValue;
                item.IsDirty = false;
            }
        } 
        #endregion

        // Collections

        #region Elements
        private List<ApplicationElement> _Elements = new List<ApplicationElement>();
        public List<ApplicationElement> Elements
        {
            get { return _Elements; }
            set
            {
                if (Elements == value)
                {
                    return;
                }
                _Elements = value;
            }
        }
        #endregion
    }

    #region ApplicationElement
    public class ApplicationElement
    {
        public string ElementKey { get; set; }
        public string ElementName { get; set; }
        public string ElementCurrentValue { get; set; }
        public string ElementInitialValue { get; set; }
        public bool IsDirty { get; set; }
    }
    #endregion
}

请注意,一些属性被标记为 [ScriptableMember],以便 **JavaScript** 可以调用它们。

在应用程序中注册它

ApplicationState 类需要在应用程序级别实例化调用。我们打开 App.xaml.cs 文件,并添加以下代码

#region ApplicationState
private ApplicationState _objApplicationState = new ApplicationState();
public ApplicationState objApplicationState
{
    get { return _objApplicationState; }
    set
    {
        if (objApplicationState == value)
        {
            return;
        }
        _objApplicationState = value;
    }
}
#endregion

我们还将此添加到应用程序类的构造函数中

HtmlPage.RegisterScriptableObject("ApplicationState", objApplicationState);

这允许 **JavaScript** 访问 ApplicationState 类中的 IsDirtyMessage 属性。

实现

最后一步是在应用程序的每个页面中实现此功能。本质上,我们需要将任何更改的属性注册到 ApplicationState 类,它将完成其余的工作。

首先,我们从一个基本的 ViewModel 开始

public class HomeViewModel : INotifyPropertyChanged
{
    public HomeViewModel()
    {
        // Set default values
        FullName = "John Doe";
        Email = "JohnDoe@Whitehouse.gov";
    }
    
    // Properties
    
    #region IsDirty
    private bool _IsDirty;
    public bool IsDirty
    {
        get { return _IsDirty; }
        set
        {
            if (IsDirty == value)
            {
                return;
            }
            _IsDirty = value;
            this.NotifyPropertyChanged("IsDirty");
        }
    }
    #endregion
    
    #region FullName
    private string _FullName;
    public string FullName
    {
        get { return _FullName; }
        set
        {
            if (FullName == value)
            {
                return;
            }
            _FullName = value;
            this.NotifyPropertyChanged("FullName");
        }
    }
    #endregion
    
    #region Email
    private string _Email;
    public string Email
    {
        get { return _Email; }
        set
        {
            if (Email == value)
            {
                return;
            }
            _Email = value;
            this.NotifyPropertyChanged("Email");
        }
    }
    #endregion
    
    // Utility
    
    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion
}

我们在构造函数中添加一个 PropertyChanged 处理程序,当任何属性更改时都会触发该处理程序

// Wire-up property changed event handler
PropertyChanged += new PropertyChangedEventHandler(HomeViewModel_PropertyChanged);

方法的实现如下

#region HomeViewModel_PropertyChanged
void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // Run this method for any property other than the IsDirty property
    // otherwise you will be in in infinite loop
    if (e.PropertyName != "IsDirty")
    {
        // Create a new ApplicationElement
        ApplicationElement objApplicationElement = new ApplicationElement();
        objApplicationElement.ElementKey = 
		string.Format("HomeViewModel_{0}", e.PropertyName);
        objApplicationElement.ElementName = e.PropertyName;
        
        // Set ElementCurrentValue
        PropertyInfo pi = this.GetType().GetProperty(e.PropertyName);
        objApplicationElement.ElementCurrentValue = 
			Convert.ToString(pi.GetValue(this, null));
        
        // Get an instance of the App class
        App AppObj = (App)App.Current;
        // Add the ApplicationElement to the objApplicationState object
        AppObj.objApplicationState.AddElement(objApplicationElement);
        
        // Set IsDirty
        IsDirty = (AppObj.objApplicationState.Elements.Where
			(x => x.IsDirty == true).Count() > 0);
    }
}
#endregion

请注意,ElementKey 使用的是 “HomeViewModel_{0}”。您可以将 “HomeViewModel” 替换为当前页面的名称,以便轻松跟踪多个页面。

我们还添加了这个 Save 命令,它将清除所有 IsDirty 标志

#region SaveCommand
public ICommand SaveCommand { get; set; }
public void Save(object param)
{
    // Clear IsDirty Flag 
    // (normally you would actually perform a save first)
    
    // Get an instance of the App class
    App AppObj = (App)App.Current;
    
    // Clear all the ISDirty flags
    AppObj.objApplicationState.ClearIsDirty();
    
    // Set IsDirty on this class
    IsDirty = false;
}
private bool CanSave(object param)
{
    // Only enable if form is Dirty
    return (IsDirty);
} 
#endregion

用户界面 (视图)

上图显示了 UI 如何绑定到 ViewModel

集合 (DataGrid)

这不处理集合。在使用 DataGrid 等控件时,它会自动跟踪 DataGrid 是否。我建议挂钩到该属性,而不是尝试使用 ApplicationState 类来跟踪 DataGrid 中的更改。

延伸阅读

© . All rights reserved.