【问题标题】:Destroy the instance of ViewModel and Page as soon as frame Navigates to another Page一旦 frame 导航到另一个 Page 就销毁 ViewModel 和 Page 的实例
【发布时间】:2014-09-09 04:17:45
【问题描述】:

假设我有 3 个页面,即 Page1.xaml、Page2.xaml、Page3.xaml。

我有一个窗口和一个框架。 Frame 的声明如下:

<Frame Source="{Binding SourcePage}" NavigationUIVisibility="Hidden" />

我正在做一些工作,比如在每个页面的构造函数中增加全局计数器变量并在每个页面的析构函数中减少全局计数器。

让我们将此全局计数器命名为 x。所以代码应该如下所示:

public int x = 0;

Page1.xaml (与Page1关联的ViewModel是ViewModel1)

public ViewModel1()
{
    x++;
}
~ViewModel1()
{
    x--;
}

同样:

Page2.xaml (与Page2关联的ViewModel是ViewModel2)

public ViewModel2()
{
    x++;
}
~ViewModel2()
{
    x--;
}

Page3.xaml (与Page3关联的ViewModel是ViewModel3)

public ViewModel3()
{
    x++;
}
~ViewModel3()
{
    x--;
}

现在,我在运行时更改框架的来源。

最初,框架的来源是 Page1.xaml。因此,ViewModel1 的构造函数被调用。所以 x 的值变成了 1。

然后我在运行时借助按钮将框架的源更改为 Page2.xaml。因此,调用了 ViewModel2 的构造函数。因此,x 的值变为 2。现在,我希望立即调用 ViewModel1 的析构函数。这样 x 应该再次变为 1。但是 ViewModel1 的析构函数永远不会被调用。

然后我再次在运行时借助按钮将框架源更改为 Page3.xaml。因此,调用了 ViewModel3 的构造函数。因此,x 的值变为 3。现在,我希望立即调用 ViewModel2 的析构函数。这样 x 应该再次变为 2。但是 ViewModel2 的析构函数永远不会被调用。

然后我再次在运行时借助按钮将框架源更改为 Page1.xaml。因此,ViewModel1 的构造函数被调用。因此,x 的值变为 4。现在,我希望立即调用 ViewModel3 的析构函数。这样 x 应该再次变为 3。但是 ViewModel3 的析构函数永远不会被调用。

当我关闭程序时,这些 ViewModel 的析构函数被调用。我不想要这种行为。一旦框架导航到另一个页面,我总是想销毁 ViewModel 和 Page 的实例。

任何帮助将不胜感激。

更新:

让我在这里澄清一下这个问题。

这里的重点是我想从另一个 ViewModel 获取一个 ViewModel 中的属性的当前值。

我有一个 SessionViewModel 如下:

public sealed class SessionViewModel : ViewModelBase, IModule
{
    private static readonly SessionViewModel instance = new SessionViewModel();
    public static SessionViewModel Instance
    {
        get
        {
            return instance;
        }
    }

    private List<IModule> modulesOpen;
    public List<IModule> ModulesOpen
    {
        get
        {
            return modulesOpen;
        }
        set
        {
            modulesOpen = value;
            NotifyPropertyChanged("ModulesOpen");
        }
    }

    public static IModule GetModuleInstance(string moduleName, string finalName)
    {
        IModule moduleToOpen = null;
        if (Instance.ModulesOpen != null)
        {
            moduleToOpen = Instance.ModulesOpen.SingleOrDefault(mod => mod.ModuleName == moduleName);
        }
        else
        {
            Instance.ModulesOpen = new List<IModule>();
        }
        if (moduleToOpen != null) return moduleToOpen;
        Type module = Type.GetType(finalName);
        moduleToOpen = (IModule)Activator.CreateInstance(module);
        Instance.ModulesOpen.Add(moduleToOpen);
        return moduleToOpen;
    }

    public string ModuleFriendlyName
    {
        get { return "SessionViewModel"; }
    }

    public string ModuleName
    {
        get { return "Session"; }
    }
}

上面代码中我使用的接口是IModule,其声明如下:

public interface IModule
{
    string ModuleFriendlyName { get; }
    string ModuleName { get; }
}

当我的程序运行时,我想收集列表中所有页面的当前实例,然后我可以从另一个 ViewModel 访问任何 ViewModel 的当前实例。从中我可以得到另一个 ViewModel 中属性的当前值。但是实例不会自动添加到List中,所以我需要在ViewModel的构造函数中将它们添加到List中,并在ViewModel的Destructor中将它们从List中移除。

让我们看看 MainWindowViewModel 的代码:

public class MainWindowViewModel : IModule, ViewModelBase
{
    public MainWindowViewModel() //Constructor
    {
        SessionViewModel.Instance.ModulesOpen = new List<IModule>();
        SessionViewModel.Instance.ModulesOpen.Add((IModule)this);
    }

    ~MainWindowViewModel()
    {
        SessionViewModel.Instance.ModulesOpen.Remove((IModule)this);
    }
    ..
    ..
    ..
}

在 ViewModel1.cs 中

public class ViewModel1 : IModule, ViewModelBase
{
    public ViewModel1()
    {
        SessionViewModel.Instance.ModulesOpen.Add((IModule)this);
    }

    ~ViewModel1()
    {
        SessionViewModel.Instance.ModulesOpen.Remove((IModule)this);
    }
    ......
    ......
    ......
}

在 ViewModel2.cs 中

public class ViewModel2 : IModule, ViewModelBase
{
    public ViewModel2()
    {
        SessionViewModel.Instance.ModulesOpen.Add((IModule)this);
    }

    ~ViewModel2()
    {
        SessionViewModel.Instance.ModulesOpen.Remove((IModule)this);
    }
    ......
    ......
    ......
}

在 ViewModel3.cs 中

public class ViewModel3 : IModule, ViewModelBase
{
    public ViewModel3()
    {
        SessionViewModel.Instance.ModulesOpen.Add((IModule)this);
    }

    ~ViewModel3()
    {
        SessionViewModel.Instance.ModulesOpen.Remove((IModule)this);
    }
    ......
    ......
    ......
}

【问题讨论】:

  • 终结器最终运行,每当 GC 感觉像是在进行垃圾回收时;他们不适合这里。如果您有需要立即清理的资源,请使用 IDisposable。当然,使用 IDisposable,其他人必须明确处置您,而 WPF 没有内置的。如果您愿意使用框架,您可以尝试查看Caliburn.Micro - 它具有您可以在 ViewModel 上实现的接口,以便在页面显示/隐藏时收到通知,这听起来就像他们会做您所做的一样'正在寻找。
  • @JoeWhite 请看一下我的问题中的更新部分。

标签: c# wpf xaml mvvm


【解决方案1】:

在 .Net 托管应用程序中,内存释放由垃圾收集器 (GC) 管理。 GC 根据一些复杂的规则决定何时调用终结器。所以这就是它确实做到了你所期望的原因。这些文章将有助于理解 .Net 中的垃圾收集

http://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx http://joeduffyblog.com/2005/12/27/never-write-a-finalizer-again-well-almost-never/

我不太确定为什么你需要在终结器中这样做,但这篇文章应该对你有所帮助:

Dispose Pattern

更新:

  1. GC 永远不会调用终结器,因为视图模型被添加到静态列表中,除非整个应用程序已关闭。
  2. 再一次,在这里使用终结器来做你想做的事情是错误的。你无法控制它何时被调用。事实上,当你实现终结器时,它实际上与垃圾收集不同。您需要自己管理静态列表。

【讨论】:

  • 您在最后第二行的问题的答案是:请参阅有问题的更新部分。
  • 这里有什么解决办法?我应该使用中介模式吗?
  • 您能详细介绍一下吗?为什么要将它们添加到静态列表中?您想何时将其从列表中删除?
  • 好的,我想从任何其他 ViewModel 中的 MainWindowViewModel 访问属性,同样我想从 MainWindowViewModel 中的某个 ViewModel 访问属性的值。简而言之,我想在另一个视图模型中访问一个视图模型中包含的属性的值。
猜你喜欢
  • 2020-09-05
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
相关资源
最近更新 更多