MVVM 清除绑定

晚安苏州 2018-07-01 12:38:28
老哥们 ,我遇到一个情况,就是当界面里一个图片绑定了BitmapImage 属性源的情况下,无法释放内存。是一个大项目中的一个问题,我整理了一下,下面用一个简单的示例演示:

界面绑定了一个图片:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
x:Name="main"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Window1" Height="300" Width="300">
<Grid>
<Image x:Name="image" Source="{Binding ImageSource1}"></Image>
</Grid>
</Window>


这个图片绑定的属性源:

private BitmapImage imageSource1;

public BitmapImage ImageSource1
{
get
{
return imageSource1;
}
set
{
imageSource1 = value;
if (null != PropertyChanged)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("ImageSource1"));
}
}
}


为了演示内存没被释放 ,用一个在MainWindow上的按钮来弹出上面的Window1界面:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Width="75" Click="button_Click"/>
</Grid>
</Window>



private void button_Click(object sender, RoutedEventArgs e)
{

BitmapImage imageSource1 = new BitmapImage();
imageSource1.BeginInit();
imageSource1.CacheOption = BitmapCacheOption.OnLoad;
imageSource1.StreamSource = new MemoryStream(File.ReadAllBytes("TestImage.png"));
imageSource1.EndInit();

Window1 w1 = new Window1();
w1.ImageSource1 = imageSource1;
w1.ShowDialog();

//BindingOperations.ClearBinding(w1.image, System.Windows.Controls.Image.SourceProperty);

}


在没关闭Window1界面的时候,内存里是有 Window1 这个实例的:


当我关闭Window1界面的时候,内存里是还是有 Window1 这个实例的,显然Window1 没被释放掉:


找了很久,发现原来是图片一直占用着绑定资源,直到我手动加上最后一句:


private void button_Click(object sender, RoutedEventArgs e)
{

BitmapImage imageSource1 = new BitmapImage();
imageSource1.BeginInit();
imageSource1.CacheOption = BitmapCacheOption.OnLoad;
imageSource1.StreamSource = new MemoryStream(File.ReadAllBytes("TestImage.png"));
imageSource1.EndInit();

Window1 w1 = new Window1();
w1.ImageSource1 = imageSource1;
w1.ShowDialog();

BindingOperations.ClearBinding(w1.image, System.Windows.Controls.Image.SourceProperty);

}


内存就被释放掉了:

但是用这句就不能释放,存留疑问:

BindingOperations.ClearAllBindings(w1);


有人说换图片数据源,不要这么赋值,我也试过了,从一个文件直接绑定图片,是可以释放掉的,但是项目里是从硬件里copy过来byte[]转换成的图片, 上面的只是我为了演示,暂时从图片文件里获取。

最后,我就是想知道有没有其它方法能够释放掉上面的图片绑定,我不想给界面控件赋上一个x:Name="xxx" ,然后再用下面的方式去解绑BindingOperations.ClearBinding(w1.xxx, System.Windows.Controls.Image.SourceProperty)

显然不符MVVM的风格,ViewModel里还有各种页面控件,看起来就很乱,有没有其它能从属性源头解绑的方式?或者其它更好的解绑做法?

谢谢 老哥们了~
...全文
381 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
Freeze() 本来就是断开跟图片文件的联系,把图片仅仅缓存为内存中的 byte[ ] 数据。这个先把图片文件读到内存 byte[ ] 再加载控件,然后关闭文件,是一个道理。
晚安苏州 2018-08-22
  • 打赏
  • 举报
回复
问题自己解决了 参考 https://bbs.csdn.net/topics/392407583
晚安苏州 2018-07-04
  • 打赏
  • 举报
回复
引用 8 楼 qq_25095899 的回复:
ICommand代码省略,MVVM模式你应该知道怎么调用CloseWindowCommand
然后在 CloseWindow方法里
ImageSource1 =null


这个试过了 也不行 在4楼:

public DelegateCommand<object> AppClosingCommand
{
get
{
return new DelegateCommand<object>((a) =>
{
//ImageSource1 = null;
ImageSource1.ClearValue(Image.SourceProperty);
GC.Collect();
GC.WaitForPendingFinalizers();
});
}
}
大然然 2018-07-04
  • 打赏
  • 举报
回复
ICommand代码省略,MVVM模式你应该知道怎么调用CloseWindowCommand
然后在 CloseWindow方法里
ImageSource1 =null
大然然 2018-07-04
  • 打赏
  • 举报
回复
window1关闭的时候把ImageSource1清空

<Window x:Class="WpfInfragisticsModal.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"
xmlns:ig="http://schemas.infragistics.com/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Name="myWindow">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding CloseWindowCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
</Grid>
</Window>


晚安苏州 2018-07-01
  • 打赏
  • 举报
回复
还试过:

<Image x:Name="image1" Grid.Row="1" Source="{Binding ImageSource1, Mode=OneWay}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource1, Converter={StaticResource NullToBoolenConverter}}" Value="True">
<Setter Property="Source">
<Setter.Value>
<MultiBinding Converter="{StaticResource ReleaseImageConverter}">
<Binding Path="ImageSourceNULL"/>
<Binding ElementName="image1" />
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>



public class ReleaseImageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((BitmapImage)values[0] == null)
{
System.Windows.Controls.Image image = (Image)values[1] as System.Windows.Controls.Image;
BindingOperations.ClearBinding(image, System.Windows.Controls.Image.SourceProperty);
return DependencyProperty.UnsetValue; ;
}
else
{
return DependencyProperty.UnsetValue; ;
}
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}

}


都不行
晚安苏州 2018-07-01
  • 打赏
  • 举报
回复
我也试过使用style trigger , 在给ImageSource1赋值为null时,自动解除绑定,不过好像不行,不知道是不是写法不对:

<Image x:Name="image1" Grid.Row="1" Source="{Binding ImageSource1, Mode=OneWay}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding ImageSource1, Converter={StaticResource NullToBoolenConverter}}" Value="True">
<Setter Property="Source" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>


public class NullToBoolenConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((BitmapImage)value == null)
{
return true;
}
else
{
return false;
}
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
晚安苏州 2018-07-01
  • 打赏
  • 举报
回复
引用 2 楼 sp1234 的回复:
或者换一个角度来说,MVVM 是双向绑定,那么当我们的界面组件“不需要存在了”的时候,我们需要自动化地解除(注销)绑定,这才是真正的 MVVM。而你使用的 MVVM 框架可能并没有这个能力,并不会自动捕获窗体关闭而中止所有的内部(包括 image)的绑定。


引用 1 楼 sp1234 的回复:
这应该是你的
Binding RelativeSource
声明造成的,并不是 Window1 以及 MVVM 本身有问题。


引用 3 楼 sp1234 的回复:
假设没有上述自动注销绑定的能力,那么就要求,VM 对象必须能确保及时被 GC 当作垃圾而回收。所以这个时候要小心 你设计的 VM 不能比 V 的生存期还长,因为我们都是直接关闭 V 而要求 VM 自动销毁。


非常感谢老哥的提醒,我重写了一下示例,用了一个VM作为Window1的DataContext:


public class Window1VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private BitmapImage imageSource1;

public BitmapImage ImageSource1
{
get
{
return imageSource1;
}
set
{
imageSource1 = value;
if (null != PropertyChanged)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("ImageSource1"));
}
}
}

public Window1VM()
{
BitmapImage imageSource1 = new BitmapImage();
imageSource1.BeginInit();
imageSource1.CacheOption = BitmapCacheOption.OnLoad;
imageSource1.StreamSource = new MemoryStream(File.ReadAllBytes("TestImage.png"));
imageSource1.EndInit();
ImageSource1 = imageSource1;
}

public DelegateCommand<object> AppClosingCommand
{
get
{
return new DelegateCommand<object>((a) =>
{
//ImageSource1 = null;
ImageSource1.ClearValue(Image.SourceProperty);
GC.Collect();
GC.WaitForPendingFinalizers();
});
}
}

}


前端就剩下:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
x:Name="main"
Title="Window1" Height="300" Width="300">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding AppClosingCommand}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Image x:Name="image" Source="{Binding ImageSource1}"></Image>
</Grid>
</Window>


原先的MainWindow button 按钮事件里代码就变成:

private void button_Click(object sender, RoutedEventArgs e)
{
Window1 w1 = new Window1();
Window1VM w1VM = new Window1VM();
w1.DataContext = w1VM;
w1.ShowDialog();

//BindingOperations.ClearBinding(w1.image, System.Windows.Controls.Image.SourceProperty);
}


如果注释掉下面这行代码,依然是无法回收 Window1VM 以及 Window1:

BindingOperations.ClearBinding(w1.image, System.Windows.Controls.Image.SourceProperty);


问题就在于我不知道 如何正确的回收这种ImageSource,下面的写法都不对,最终都无法释放:

//ImageSource1 = null;
ImageSource1.ClearValue(Image.SourceProperty);
GC.Collect();
GC.WaitForPendingFinalizers();




在不将前端页面控件传到VM的情况下,如何在窗体关闭时去释放这种图片资源。
  • 打赏
  • 举报
回复
假设没有上述自动注销绑定的能力,那么就要求,VM 对象必须能确保及时被 GC 当作垃圾而回收。所以这个时候要小心 你设计的 VM 不能比 V 的生存期还长,因为我们都是直接关闭 V 而要求 VM 自动销毁。
  • 打赏
  • 举报
回复
或者换一个角度来说,MVVM 是双向绑定,那么当我们的界面组件“不需要存在了”的时候,我们需要自动化地解除(注销)绑定,这才是真正的 MVVM。而你使用的 MVVM 框架可能并没有这个能力,并不会自动捕获窗体关闭而中止所有的内部(包括 image)的绑定。
  • 打赏
  • 举报
回复
这应该是你的
Binding RelativeSource
声明造成的,并不是 Window1 以及 MVVM 本身有问题。

111,092

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧