老哥们 ,我遇到一个情况,就是当界面里一个图片绑定了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/up-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/up-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里还有各种页面控件,看起来就很乱,有没有其它能从属性源头解绑的方式?或者其它更好的解绑做法?谢谢 老哥们了~

解决方案 »

  1.   

    这应该是你的 Binding RelativeSource 声明造成的,并不是 Window1 以及 MVVM 本身有问题。
      

  2.   

    或者换一个角度来说,MVVM 是双向绑定,那么当我们的界面组件“不需要存在了”的时候,我们需要自动化地解除(注销)绑定,这才是真正的 MVVM。而你使用的 MVVM 框架可能并没有这个能力,并不会自动捕获窗体关闭而中止所有的内部(包括 image)的绑定。
      

  3.   

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

  4.   


    非常感谢老哥的提醒,我重写了一下示例,用了一个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/up-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的情况下,如何在窗体关闭时去释放这种图片资源。
      

  5.   

    我也试过使用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();
            }
        }
      

  6.   

    还试过: <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();
    } }
    都不行
      

  7.   

    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>
      

  8.   

    ICommand代码省略,MVVM模式你应该知道怎么调用CloseWindowCommand
    然后在 CloseWindow方法里
    ImageSource1 =null
      

  9.   


    这个试过了 也不行 在4楼:    public DelegateCommand<object> AppClosingCommand
        {
            get
            {
                return new DelegateCommand<object>((a) =>
                {
                    //ImageSource1 = null;
                    ImageSource1.ClearValue(Image.SourceProperty);
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                });
            }
        }
      

  10.   

    问题自己解决了 参考 https://bbs.csdn.net/topics/392407583
      

  11.   

    Freeze() 本来就是断开跟图片文件的联系,把图片仅仅缓存为内存中的 byte[ ] 数据。这个先把图片文件读到内存 byte[ ] 再加载控件,然后关闭文件,是一个道理。