1

マスター/詳細ベースに画像を含む項目のリスト (単純な ListBox) があります (ユーザーがリスト項目をクリックすると、詳細ページが開きます)。ここここここ、およびここで説明されている、画像のメモリリークに関する非常に有名な問題に直面しました。

考えられる方法の 1 つは、NavigatingFrom 時にすべての画像を実行してそれらを消去することです。

スレッドの 1 つで、より興味深い解決策を見つけました。画像は自動的に消去されますが、仮想化では機能しません (ImageSource を格納するためのプライベート フィールドを追加すると、画像が失われるか混合されます)。提案された修正は、依存関係プロパティを追加することでした。

しかし、私はまだ同じ問題に直面しています。下にスクロールして上に戻った後、画像が混同されます。依存関係プロパティがランダムに変更されているように見えますが、変更されている瞬間を捉えることができません。

public class SafePicture : ContentControl
{
    public static readonly DependencyProperty SafePathProperty =
        DependencyProperty.RegisterAttached(
            "SafePath",
            typeof(string),
            typeof(SafePicture),
            new PropertyMetadata(OnSourceWithCustomRefererChanged));

    public string SafePath
    {
        get { return (string)GetValue(SafePathProperty); }
        set { SetValue(SafePathProperty, value); }
    }

    private static void OnSourceWithCustomRefererChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue == null) // New value here
            return;
    }


    public SafePicture()
    {
        Content = new Image();
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    private void OnLoaded(object _sender, RoutedEventArgs _routedEventArgs)
    {
        var image = Content as Image;

        if (image == null)
            return;

        var path = (string)GetValue(SafePathProperty); // Also, tried SafePath (debugger cant catch setter and getter calls), but same result.

        image.Source = null;
        {
            var request = WebRequest.Create(path) as HttpWebRequest;
            request.AllowReadStreamBuffering = true;
            request.BeginGetResponse(result =>
            {
                try
                {
                    Stream imageStream = request.EndGetResponse(result).GetResponseStream();
                    DispatcherHelper.CheckBeginInvokeOnUI(() =>
                    {
                        if (imageStream == null)
                        {
                            image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) };
                            return;
                        }

                        var bitmapImage = new BitmapImage();
                        bitmapImage.CreateOptions = BitmapCreateOptions.BackgroundCreation;
                        bitmapImage.SetSource(imageStream);
                        image.Source = bitmapImage;
                    });
                }
                catch (WebException)
                {
                }
            }, null);
        }
    }


    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        var image = Content as Image;

        if (image == null)
            return;

        var bitmapImage = image.Source as BitmapImage;
        if (bitmapImage != null)
            bitmapImage.UriSource = null;
        image.Source = null;
    }
}

使用法:

<wpExtensions:SafePicture SafePath="{Binding ImageUrl}"/>

そのため、一見問題なく動作しますが、下にスクロールして上に戻ると、画像がランダムに変更されます。

編集:この場合、今のところ、仮想化なしで純粋なListBoxのみを使用しています(ただし、他の場合は期待しています)。

EDIT2: この問題を再現するためのサンプル プロジェクト。しばらくすると解決策が含まれると思います: https://simca.codeplex.com/

4

1 に答える 1

1

問題は、仮想化を使用する場合、各アイテムに使用される ui 要素がリサイクルされ、他のオブジェクト (画像オブジェクトを含む) に再利用されることです。また、十分に速くスクロールすると非同期に画像を読み込むため、ビットマップをオンに設定することになります。すでに別のアイテムに再利用されている画像。
の簡単な修正は、パスの値がまだ同じであることを確認することです。同じでない場合は、画像が別のオブジェクトで既に再利用されているため、単に戻ります。

...
var request = WebRequest.Create(path) as HttpWebRequest;
        request.AllowReadStreamBuffering = true;
        request.BeginGetResponse(result =>
        {

            try
            {

                Stream imageStream = request.EndGetResponse(result).GetResponseStream();
                DispatcherHelper.CheckBeginInvokeOnUI(() =>
                {
                if (path!=SafePath){
                  //Item has been recycled
                  return;
                }
                 ....

編集: コードにいくつかの問題がありました: -RegisterAttached を Register に切り替えます。RegisterAttached は、通常の依存関係プロパティではなく、添付プロパティ用です -OnSourceWithCustomRefererChanged で OnLoaded を呼び出します。変更された SafePath プロパティは、要素が読み込まれた後に実際に発生する可能性があるためです -明確な URI とソースを追加しますパスが空のときに画像をクリアするように onLoaded の開始時に

完全な作業コードは次のとおりです。

public class SafeImage : ContentControl
{
    private SynchronizationContext uiThread;

    public static readonly DependencyProperty SafePathProperty =
        DependencyProperty.Register("SafePath", typeof (string), typeof (SafeImage),
        new PropertyMetadata(default(string), OnSourceWithCustomRefererChanged));

    public string SafePath
    {
        get { return (string) GetValue(SafePathProperty); }
        set { SetValue(SafePathProperty, value); }
    }


    private static void OnSourceWithCustomRefererChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        SafeImage safeImage = o as SafeImage;
        safeImage.OnLoaded(null, null);
        //OnLoaded(null, null);
        if (e.NewValue == null)
            return;
    }





    public SafeImage()
    {
        Content = new Image();
        uiThread = SynchronizationContext.Current;

        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    private void OnLoaded(object _sender, RoutedEventArgs _routedEventArgs)
    {
        var image = Content as Image;

        if (image == null)
            return;

        var path = SafePath; //(string)GetValue(SafePathProperty);
        //image.Source = new BitmapImage(new Uri(SafePath));
        Debug.WriteLine(path);

        var bitmapImage = image.Source as BitmapImage;
        if (bitmapImage != null)
            bitmapImage.UriSource = null;
        image.Source = null;

        if (String.IsNullOrEmpty(path))
        {
            //image.Source = new BitmapImage { UriSource = new Uri(Constants.RESOURCE_IMAGE_EMPTY_PRODUCT, UriKind.Relative) };
            return;
        }

        // If local image, just load it (non-local images paths starts with "http")
        if (path.StartsWith("/"))
        {
            image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) };
            return;
        }



        {
            var request = WebRequest.Create(path) as HttpWebRequest;
            request.AllowReadStreamBuffering = true;
            request.BeginGetResponse(result =>
            {
                try
                {
                    Stream imageStream = request.EndGetResponse(result).GetResponseStream();
                    uiThread.Post(_ =>
                    {

                        if (path != this.SafePath)
                        {
                            return;
                        }
                        if (imageStream == null)
                        {
                            image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) };
                            return;
                        }

                        bitmapImage = new BitmapImage();
                        bitmapImage.CreateOptions = BitmapCreateOptions.BackgroundCreation;
                        bitmapImage.SetSource(imageStream);
                        image.Source = bitmapImage;
                        //imageCache.Add(path, bitmapImage);
                    }, null);
                }
                catch (WebException)
                {
                    //uiThread.Post(_ =>
                    //{
                    //    image.Source = new BitmapImage { UriSource = new Uri(Constants.RESOURCE_IMAGE_EMPTY_PRODUCT, UriKind.Relative) };
                    //}, null);
                }
            }, null);
        }
    }


    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        var image = Content as Image;

        if (image == null)
            return;

        var bitmapImage = image.Source as BitmapImage;
        if (bitmapImage != null)
            bitmapImage.UriSource = null;
        image.Source = null;
    }
}

最後に、Windows phone の ListBox はデフォルトで仮想化とリサイクルを使用しています (使用される ItemPanel は VirtualizedStackPanel です)。

于 2013-10-02T13:10:34.323 に答える