删除对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方法。
当我编写和测试代码以动态地从窗口的可视树中添加和删除UserControl
对象时,我发现Unloaded
事件正如预期的那样被引发。
在您自己的代码示例中,至少存在一个严重的问题和两个不一致的地方:
- 严重的问题是你如何移除孩子。您的
for
循环通过索引迭代pnlMain
对象的子对象(Grid
)。但是删除任何子索引将使索引序列无效!也就是说,循环首先移除索引0处的子元素;这导致索引1处的子节点现在变成了索引0处的子节点。但是循环在继续之前将索引加1,然后将在索引1处删除子元素。子节点最初位于索引2。该代码跳过所有其他子节点(即最初位于奇数索引的子节点),将其中一半附加为Grid
的子节点。 - 不协调#1:我希望在名称中使用短语"OpenUC1"来添加
UC1
的实例。但是,您的OpenUC1_MouseDown()
方法似乎添加了UC2
的实例(OpenUC2_MouseDown()
反之亦然)。至少,代码中应该有注释,解释为什么代码与给定方法名的代码不同。 - 不协调#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();