0

我们正在使用 c# MVVM 模式使用 Silverlight 开发一个项目。我们在页面内使用自定义控件。自定义控件根据其依赖属性更改引发一些事件,当我为同一页面使用新的 ViewModel 实例时,这些事件可以正常工作。

由于我们的业务需要,我们必须维护一个页面的 ViewModel 实例,同时我们不维护页面实例,所以每次都会重新创建页面实例。

现在,如果在特定页面打开超过一次(关闭然后重新打开)时特定视图模型中的任何属性发生更改,则视图模型命令(自定义控件的事件)每次单个属性更改都会引发多次。

我可以理解视图或自定义控件的先前实例保留在某处并响应相应视图模型的属性更改。解决此问题的最佳方法是什么?

// my custom control
public class CustomControl:Control
{
    public event EventHandler SearchCompletedEvent;
    public bool IsSearch
    {
        get{return (bool)GetValue(IsSearchProperty)};
        set{SetValue(IsSearchProperty,value)};
    }
    // Using a DependencyProperty as the backing store for IsSearch.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsSearchProperty=
    DependencyProperty.Register("IsSearch", typeof(bool), typeof(CustomControl), new PropertyMetadata(null, new PropertyChangedCallback(OnIsSearchPropertyChanged)));

    private static void OnIsSearchPropertyChanged(
        DependencyObject dp, DependencyPropertyChangedEventArgs de)
    {
        if (dp != null && dp is CustomControl&& de.NewValue != null)
        ((CustomControl)dp).OnIsSearchPropertyChanged((bool)de.NewValue);
    }
    private void OnIsSearchPropertyChanged(bool newValue)
    {
        if( newValue)
        {
            // some searching statements here
            if(SearchCompletedEvent!=null)
            SearchCompletedEvent(this,new EventArgs());      
        }
    }
}

public class RelayCommand : ICommand
{
    private Func<bool> canExecute;
    private Action executeAction;
    public event EventHandler CanExecuteChanged;  
    public RelayCommand(Action executeAction, Func<bool> canExecute)
    {
        this.executeAction = executeAction;
        this.canExecute = canExecute;
    } 

    public RelayCommand(Action executeAction)
    {
        this.executeAction = executeAction;
        this.canExecute = () => true;
    } 

    public void RaiseCanExecuteChanged()
    {
        if (CanExecuteChanged != null)
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
    } 

    public bool CanExecute(object parameter)
    {
        return canExecute == null ? true : canExecute();
    }
}        

//
//My ViewModel is like 
public class MainPageViewModel:INotifyPropertyChanged
{
    public MainPageViewModel()
    {
        SearchCommand=new RelayCommand(SearchEventMethod);
        FindCommand=new RelayCommand(FindMethod);
    }

    protected void RaisePropertyChanged(string property)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(property));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public ICommand SearchCommand{get;set;}
    public ICommand FindCommand{get;set;}
    private bool isSearch;
    public bool IsSearch
    {
        get { return isSearch;}
        set { isSearch=value;
            RaisePropertyChanged("IsSearch");
        }
    }

    private void SearchEventMethod()
    {
        IsSearch=false;
        // some codes for execute after search
    }

    private void FindMethod()
    {
        IsSearch=true;
    }
}

//
// My ViewModel Insatance Maintainer 
public class InstanceMaintainer
{
    public string InstanceKey{get;}
    public object ViewModelInstance{get;}

    public InstanceMaintainer(string instanceKey,object Instance)
    {
        this.InstanceKey=instanceKey;
        this.ViewModelInstance=Instance;
    }
}

//
// my App.xaml.cs file
public class App:Application
{
    private static List<InstanceMaintainer> instanceList=new  List<InstanceMaintainer>();
    public static object GetInstance(string programKey)
    {
        if(programKey=="PGM001")
        {
            MyXamlPage page=new MyXamlPage();
            if(insatnceList.select(x=>x.InstanceKey).Containes(programKey))
            {
                page.DataContext=insatnce.where(x=>x.InstanceKey==programKey).FirstOrDefault().Instance;
            }
            else
            {
                MainPageViewModel vm=new MainPageViewModel();
                instanceList.Add(new InstanceMaintainer(programKey,vm);
                page.DataContext=vm;
            }
        }
    }
}

// My xaml page is like
<Grid x:Name="LayoutRoot">
    <local:CustomControl IsSearch="{Binding IsSearch,Mode=TwoWay}" 
        SearchCompletedEvent="{Binding SearchCommand,Mode=TwoWay}">
    </local:CustomControl>

    <Button Command="{Binding FindCommand,Mode=TwoWay}" />
</Grid>

在这种情况下,我们第一次打开特定程序

App.GetInstance("PGM001");

页面并单击按钮 My CustomControl 响应一次。我再次关闭页面,用同样的方法打开同一个程序,现在页面是新的,ViewModelInstance 是旧的,对吗?现在我单击按钮 CustomCONtrol 响应二、三等。因为创建的 Page 实例仍然存在于某处并且对 ViewModel 属性更改的响应。

4

1 回答 1

0

如果没有大量信息(或代码)继续下去,我将不得不猜测问题所在。话虽如此,您很可能有一个事件处理程序导致您的一个视图模型停留的时间比您预期的要长。

考虑一下 XAML 绑定到这样的视图模型A

XAML -> VM-A

现在考虑一个场景,其中视图模型A订阅了视图模型中的一个事件BB必须持有一个引用才能A调用事件处理程序。在这种情况下A,被称为委托目标并且B是源。

VM-B -> Delegate -> VM-A

因此,每当 Silverlight 中发生导致 XAML 绑定到实例的操作时A,XAML 通常会像预期的那样将自身与旧实例解除绑定。但是,B仍然会持有对 的引用A,这就是为什么它仍然在接收来自B. 更进一步,的实例A也可能订阅同一个事件,B因此B现在在其多播委托链中有两个实例。A这可以解释为什么您会看到事件处理程序多次执行。顺便说一下,B不一定必须是另一个视图模型类。真的可以是任何东西。自定义用户控件也可以发挥作用B

我必须诚实。在进行 WPF 和 Silverlight 工作时,我仍然偶尔会遇到这种情况,我一直对此保持警惕。所以,是的,这是一个容易被忽视的问题。

您需要做的是告诉A它不再被使用,以便它可以取消订阅它正在侦听的任何事件。我建议您阅读弱事件模式以向自己介绍以这样一种方式订阅事件的想法,这样您就不必在完成后取消订阅。一些 MVVM 框架具有执行此操作的机制。

于 2013-11-06T03:44:00.463 回答