Display Image from Byte Array in WPF - Memory Issu

2019-08-02 14:28发布

I've developed an application to capture and save images to a database, but I'm having an issue with memory usage. On my domain object I have 3 properties:

Image - Byte array, contents are a jpg

RealImageThumb - The byte array converted to a BitmapImage and shrunk, displayed to the user in a gridview with other thumbnails

RealImage - Has no setter, the byte array converted to a bitmap source, this is shown in a tooltip when the user hovers over it.

The issue I have is that if a user hovers over each image in turn the memory usage spirals. I realise that as a user hovers over bitmap sources are generated and the memory isn't freed up, I've tried giving RealImage a backing property and assigning this to null after but again the memory isn't freed up (waiting for the garbage collector?).

edit:

Is this what you meant Ray? I'm not getting anything shown in the tooltip as below, but if I try and define a WeakReference<BitmapImage>, I get the System.WeakReference does not have type parameters error.

  private WeakReference _realImage;
        public virtual BitmapImage RealImage
        {
            get
            {
                if (_realImage == null || _realImage.Target == null)
                {

                    if (Image == null) return null;
                    var newBitmapImage = new BitmapImage();
                    newBitmapImage.BeginInit();
                    newBitmapImage.CacheOption = BitmapCacheOption.None;
                    newBitmapImage.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    newBitmapImage.StreamSource = new MemoryStream(Image);
                    newBitmapImage.EndInit();
                    _realImage = new WeakReference(newBitmapImage);
                }

                return (BitmapImage)_realImage.Target;
            }
        }

1条回答
干净又极端
2楼-- · 2019-08-02 15:10

You will need to do three things:

  1. When constructing your BitmapImage use StreamSource to supply the data. Do not use UriSource or pass a Uri into the constructor, which would cause the image to be added to the image cache.

  2. In your domain object's RealImage implementation, store a WeakReference to your BitmapImage not the BitmapImage itself. When RealImage is fetched, if either WeakReference or WeakReference.Target is null, create a new BitmapImage and a new WeakReference to it.

  3. Use a DataTrigger with template switching to only include your Image control in the visual tree when it is visible

Here are the templates needed for step 3, including the one with the DataTrigger:

<DataTemplate x:Key="EmptyTemplate">
</DataTemplate>

<DataTemplate x:Key="RealImageTemplate">
  <Image Source="{Binding RealImage.Target}" Width="300" Height="300" />
</DataTemplate>

<DataTemplate x:Key="RealImageWhenVisible">

  <!-- Use EmptyTemplate when I am not visible -->
  <ContentPresenter x:Name="Presenter"
                    Content="{Binding}"
                    ContentTemplate="{StaticResource EmptyTemplate}"/>

  <!-- Switch to RealImageTemplate when I am visible -->
  <DataTemplate.Triggers>
    <DataTrigger Binding="{Binding IsVisible, RelativeSource={RelativeSource Self}}"
                 Value="True">
      <Setter TargetName="Presenter"
              Property="ContentPresenter.ContentTemplate"
              Value="{StaticResource RealImageTemplate}" />
    </DataTrigger>
  </DataTemplate.Triggers>
</DataTemplate>

Now you can define your ToolTip like this:

<Rectangle Width="40" Height="40" Fill="Blue">
  <Rectangle.ToolTip>
    <ContentPresenter Content="{Binding}"
                      ContentTemplate="{StaticResource RealImageWhenVisible}" />
  </Rectangle.ToolTip>
</Rectangle>

How it works: There are two ContentPresenters inside one another:

  • When the outer ContentPresenter is invisible, the inner ContentPresenter will have EmptyTemplate so no image will be loaded.
  • When the outer ContentPresenter is visible, the inner ContentPresenter will have RealImageTemplate so the image will be loaded and displayed.

The reason you need to do this is that a ToolTip may attempt to optimize performance by not disposing of the Popup once it has been shown.

Update

The code you posted for RealImage ought to work, and is almost exactly what I was thinking of. I realized this morning there is really no need to set BitmapCacheOption or BitmapCreateOption as long as no SourceUri is specified. I've updated my answer to reflect this and also to clarify the WeakReference thing. I also corrected a bug in the template: I was binding to "RealImage" when I should have been binding to "RealImage.Target".

查看更多
登录 后发表回答