删除对wpf中用户控件的引用

本文关键字:控件 引用 用户 wpf 删除 | 更新日期: 2023-09-27 18:18:49

我有一个wpf应用程序,它有一个主窗口和菜单。这个主窗口有一个面板,在单击菜单项时,我创建了一个用户控件的实例,并用控件加载该面板。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
        Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
        <Grid Width="Auto">
            <Grid.RowDefinitions>
                <RowDefinition Height="38"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
            </Grid.RowDefinitions>
            <StackPanel Height="38" Width="Auto" Background="#09527B">
                <Grid Margin="0,0,0,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition Width="70"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                </Grid>
            </StackPanel>
            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="189"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid Grid.Column="0">
                    <StackPanel>
                        <Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
                            <StackPanel  Margin="20,0,0,0">
                                <Expander Header="Data" Foreground="White">
                                    <StackPanel>
                                        <TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
                                        <TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
                                    </StackPanel>
                                </Expander>                               
                    </StackPanel>
                </Grid>
                <StackPanel Grid.Column="1">
                    <Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
                    </Grid>
                </StackPanel>
            </Grid>
        </Grid>
    </ScrollViewer>
</Window>

MainWindow.cs

private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
            {
                pnlMain.Children.Remove(pnlMain.Children[i]);
            }
using (UC2 _uc2= new UC2())
            {
                pnlMain.Children.Add(_uc2);
            }
}
private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
            {
                pnlMain.Children.Remove(pnlMain.Children[i]);
            }
using (UC1 _uc1= new UC1())
            {
                pnlMain.Children.Add(_uc1);
            }
}

我的问题是,当我从主面板中删除控件(UC1)时,该控件何时被处置?用户控件(UC1和UC2)都将相同的视图模型附加到其数据上下文。所以我发现在删除的用户控件(UC1)的一些方法被调用,即使这是从面板上删除。原因是,当创建UC2的新实例时,数据模型中有一些更改,这些更改实际上调用了UC1中的依赖方法。但如果UC1被处理掉,这就不会发生了。如何确保在创建UC2实例之前处置UC1 ?

public UC1()
        {
            InitializeComponent();            
            this.DataContext = App.ViewModel.TestViewModel;
        }
 private void UC1_Unloaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = null;
        }

public UC2()
        {
            InitializeComponent();            
            this.DataContext = App.ViewModel.TestViewModel;
        }
 private void UC2_Unloaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = null;
        }

当控件从面板中移除时,不会立即调用unloaded方法。

删除对wpf中用户控件的引用

当我编写和测试代码以动态地从窗口的可视树中添加和删除UserControl对象时,我发现Unloaded事件正如预期的那样被引发。

在您自己的代码示例中,至少存在一个严重的问题和两个不一致的地方:

  1. 严重的问题是你如何移除孩子。您的for循环通过索引迭代pnlMain对象的子对象(Grid)。但是删除任何子索引将使索引序列无效!也就是说,循环首先移除索引0处的子元素;这导致索引1处的子节点现在变成了索引0处的子节点。但是循环在继续之前将索引加1,然后将在索引1处删除子元素。子节点最初位于索引2。该代码跳过所有其他子节点(即最初位于奇数索引的子节点),将其中一半附加为Grid的子节点。
  2. 不协调#1:我希望在名称中使用短语"OpenUC1"来添加UC1的实例。但是,您的OpenUC1_MouseDown()方法似乎添加了UC2的实例(OpenUC2_MouseDown()反之亦然)。至少,代码中应该有注释,解释为什么代码与给定方法名的代码不同。
  3. 不协调#2:在添加UserControl对象时,在调用Add()时有一个using语句。首先,UserControl本身不实现IDisposable,所以除非你的类型实现了该接口,否则代码甚至是不合法的。其次,即使你的UserControl子类实现该接口,它似乎不是一个很好的主意,我处置一个对象,你刚刚创建,你保留在可视化树(即通过将其添加到Grid的孩子)。

不幸的是,正如我在评论中提到的,如果没有一个好的、最小的完整的代码示例来可靠地再现您的问题,就不可能说为什么您的代码不像人们希望和/或期望的那样运行。以上任何一点(尤其是第一条)都有可能是你所看到的行为的原因,但我没有办法确定。

如果在解决了这些问题(或者以某种方式确定它们不是问题& help;尽管如果你可以合法地这样做,我认为代码仍然有缺陷,从设计糟糕的意义上说),你发现你的问题仍然存在,请编辑你的问题,以便它包含一个好的,最小的完整的代码示例,可靠地再现问题。


同时,这里有一个简单的代码示例,说明了当对象从可视化树中删除时,Unloaded事件的基本行为会像预期的那样被引发。请注意,虽然从Grid对象的Children集合中删除所有子对象的正确方法是简单地调用Clear()方法(例如pnlMain.Children.Clear()),但我已经包含了一个显式基于循环的方法的示例,该方法确实有效。

XAML:

UserControl1.xaml

<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Unloaded="UserControl_Unloaded"
             d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
    <TextBlock Text="UserControl" FontSize="36"/>
  </Grid>
</UserControl>

MainWindow.xaml

<Window x:Class="TestSO33289488UserControlUnloaded.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <Button x:Name="button1" Content="Add UserControl"
            HorizontalAlignment="Left" Click="Button_Click"/>
    <Grid x:Name="grid1"/>
  </StackPanel>
</Window>
c#

:

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;
namespace TestSO33289488UserControlUnloaded
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("UserControl.Unloaded was raised");
        }
    }
}

MainWindow.xaml.cs

using System.Windows;
namespace TestSO33289488UserControlUnloaded
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private bool _removeUserControl;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (_removeUserControl)
            {
                //grid1.Children.Clear();
                // Calling Clear() is better, but if you really want to loop,
                // it is possible to do correctly. For example:
                while (grid1.Children.Count > 0)
                {
                    grid1.Children.RemoveAt(grid1.Children.Count - 1);
                }
                button1.Content = "Add UserControl";
            }
            else
            {
                grid1.Children.Add(new UserControl1());
                button1.Content = "Remove UserControl";
            }
            _removeUserControl = !_removeUserControl;
        }
    }
}

引用自MSDN论坛关于Loaded/Unloaded事件的条目:

事件是异步引发的,因此可能会有一些延迟在导致事件的动作和事件本身之间。的事件被有效地放入列表中,任务被添加到调度程序的队列。当该任务运行时,它会引发事件列表。

所以答案是你不能准确地预测这些事件何时会引发,你不应该期望它们会在你从它的父控件中删除控件后立即被调用。

如果没有看到完整的项目,很难给你一个适当的解决方案,但这里有一个快速而肮脏的解决方案:而不是确保给定的用户控件的事件被及时触发,让我们在运行方法之前检查UC1/UC2对象的Parent属性。如果属性为null,则UC1/UC2对象被删除,您不应该执行该方法。

但是让我指出这段代码的一些问题:

  • 在MouseDown事件处理程序中的using块的意义是什么?你创建了一个用户控制对象,将它添加到面板上,然后在那之后你立即对它调用Dispose方法?(这就是using块在c#中的作用)
  • 你不需要一个for循环从面板控件中删除所有的子元素,比如Grid。一行就能搞定。pnlMain.Children.Clear();