Использование VirtualBitmapControl из Win2D и немедленная загрузка изображения

Новое в XAML, UWP, MVVM и Win2D.

Мне нужно отображать очень большое изображение на моей странице. Я смотрю на Win2D, в частности на VirtualBitmapControl и VirtualBitmapExample.

Это близко к тому, что я хочу сделать, но мне не нужно выбирать свое изображение, у меня уже есть эта информация, когда я перехожу на страницу.

Я попытался продублировать элемент управления и удалить средства выбора файлов, но я не вижу, куда бы я загружал свое изображение из пути к файлу для отображения на странице.

Начиная с отладчика, VirtualBitmapControl инициализируется еще до того, как будет установлена ​​привязка для пути к файлу. Чтобы добавить еще один уровень, я также использую MVVM, поэтому я пытался инкапсулировать все это как UserControl и иметь возможность использовать его так же, как при использовании элемента управления Image.

Вот мой код XAML:

    <UserControl
    x:Class="PEERNET.UWPImageViewer.Views.VirtualBitmapControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PEERNET.UWPImageViewer.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Win2Dcanvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400"
    d:DataContext="{d:DesignInstance Type=local:VirtualBitmapControl, IsDesignTimeCreatable=true}"
    SizeChanged="Control_SizeChanged"
    Unloaded="Control_Unloaded"
    Loading="Control_Loading"
    Loaded="Control_Loaded">

    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="AdaptiveVisualStateGroup">
                <VisualState x:Name="VisualStateNarrow">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NarrowMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO: change properties for narrow view  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateNormal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource NormalMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO: change properties for normal view  -->
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="VisualStateWide">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="{StaticResource WideMinWidth}" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <!--  TODO: change properties for wide view  -->
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <ScrollViewer HorizontalScrollMode="Enabled"
                      VerticalScrollMode="Enabled"
                      ZoomMode="Disabled"
                      HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto"
                      x:Name="ImageScrollViewer">
            <Grid>
                <Win2Dcanvas:CanvasVirtualControl
                    x:Name="ImageVirtualControl"
                    CreateResources="ImageVirtualControl_CreateResources"
                    RegionsInvalidated="ImageVirtualControl_RegionsInvalidated"/>
            </Grid>
        </ScrollViewer>

    </Grid>
</UserControl>

А вот код программной части:

    public sealed partial class VirtualBitmapControl : UserControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public VirtualBitmapControl()
    {
        this.InitializeComponent();

        if (!DesignMode.DesignModeEnabled)
        {
            DataContext = this;
        }

        virtualBitmapOptions = CanvasVirtualBitmapOptions.None;
        //virtualBitmapOptions = CanvasVirtualBitmapOptions.CacheOnDemand;
        //virtualBitmapOptions = CanvasVirtualBitmapOptions.ReleaseSource;
    }

    public string LoadedImageInfo { get; private set; }

    public bool IsImageLoaded { get { return virtualBitmap != null; } }


    //StorageFile PhotoAsStorageFile;
    IRandomAccessStream imageStream;

    CanvasVirtualBitmap virtualBitmap;
    CanvasVirtualBitmapOptions virtualBitmapOptions;

    // This is the file we are displaying
    public string FilePath
    {
        get { return (string)GetValue(FilePathProperty); }
        set { SetValue(FilePathProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FilePath.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FilePathProperty =
        DependencyProperty.Register("FilePath", typeof(string), typeof(VirtualBitmapControl),
                                    new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
                                    //null);

     private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var instance = d as VirtualBitmapControl;
        if (d == null)
            return;

        if (instance.virtualBitmap != null)
        {
            //instance.virtualBitmap.Invalidate();
            //instance.virtualBitmap.InvalidateMeasure();
        }            
    }

    private void Control_SizeChanged(object sender, SizeChangedEventArgs e)
    {

        // TODO: What do I need to do here?
        ImageScrollViewer.MaxWidth = double.MaxValue;
        ImageScrollViewer.MaxHeight = double.MaxValue;

        /* WIN2d sample code
        if (smallView)
        {
            ImageScrollViewer.MaxWidth = ActualWidth / 4;
            ImageScrollViewer.MaxHeight = ActualHeight / 4;
        }
        else
        {
            ImageScrollViewer.MaxWidth = double.MaxValue;
            ImageScrollViewer.MaxHeight = double.MaxValue;
        }*/
    }

    private void Control_Loading(FrameworkElement sender, object args)
    {
        System.Diagnostics.Debug.WriteLine("VirtualBitmapControl::Control_Loading");

    }


    private void Control_Loaded(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("VirtualBitmapControl::Control_Loaded");
    }

    private void Control_Unloaded(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("VirtualBitmapControl::Control_Unloaded");

        if (ImageVirtualControl != null)
        {
            ImageVirtualControl.RemoveFromVisualTree();
            ImageVirtualControl = null;
        }
    }

    private void ImageVirtualControl_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasVirtualControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
    {
        System.Diagnostics.Debug.WriteLine("VirtualBitmapControl::ImageVirtualControl_CreateResources");
        if (imageStream != null)
        {
            args.TrackAsyncAction(LoadVirtualBitmap().AsAsyncAction());
        }
    }

    private void ImageVirtualControl_RegionsInvalidated(Microsoft.Graphics.Canvas.UI.Xaml.CanvasVirtualControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasRegionsInvalidatedEventArgs args)
    {
        foreach (var region in args.InvalidatedRegions)
        {
            using (var ds = ImageVirtualControl.CreateDrawingSession(region))
            {
                if (virtualBitmap != null)
                    ds.DrawImage(virtualBitmap, region, region);
            }
        }
    }


    private async Task LoadVirtualBitmap()
    {
        if (virtualBitmap != null)
        {
            virtualBitmap.Dispose();
            virtualBitmap = null;
        }

        LoadedImageInfo = "";

        if (imageStream != null)
        {
            imageStream.Dispose();
            imageStream = null;
        }
        NotifyPropertyChanged();


        if (imageStream == null)
        {
            imageStream = await GetBitmapStreamFromFilePathAsync(this.FilePath);
        }
        NotifyPropertyChanged();


        virtualBitmap = await CanvasVirtualBitmap.LoadAsync(ImageVirtualControl.Device, imageStream, virtualBitmapOptions);

        if (ImageVirtualControl == null)
        {
            // This can happen if the page is unloaded while LoadAsync is running
            return;
        }

        var size = virtualBitmap.Size;
        ImageVirtualControl.Width = size.Width;
        ImageVirtualControl.Height = size.Height;
        ImageVirtualControl.Invalidate();

        LoadedImageInfo = string.Format("{0}x{1} image, is {2}CachedOnDemand",
            size.Width, size.Height, virtualBitmap.IsCachedOnDemand ? "" : "not ");

        NotifyPropertyChanged();
    }

    private void NotifyPropertyChanged()
    {
        if (PropertyChanged == null)
            return;

        foreach (var property in new string[] { "LoadedImageInfo", "IsImageLoaded", "FilePath"})
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    internal async static Task<IRandomAccessStream> GetBitmapStreamFromFilePathAsync(String filePath)
    {
        IRandomAccessStream imageStream = null;
        <trimmed for space>

        return imageStream;
    }

}

И как я использую элемент управления со своей страницы XAML:

   <Grid x:Name="rootPhotoGrid" RelativePanel.Below="pageHeader"
            RelativePanel.AlignLeftWithPanel="True"
    RelativePanel.AlignRightWithPanel="True" Background="AntiqueWhite">
    <local:VirtualBitmapControl FilePath="{x:Bind ViewModel.LoadedImagePath}"/> </Grid>

Если бы кто-то мог указать мне правильное направление, или сказать, что я делаю неправильно или чего мне не хватает, это было бы очень признательно.

Шери


person SheriSteeves    schedule 21.06.2016    source источник


Ответы (1)


Подключите событие CreateResources к своему элементу управления и используйте TrackAsyncAction, чтобы позволить этому отслеживать ход выполнения вызова CanvasVirtualBitmap.LoadAsync.

Посмотрите пример кода по адресу http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasBitmap.htm

Более подробная справочная информация: https://blogs.msdn.microsoft.com/win2d/2014/12/05/async-resource-loading-in-win2d/

person Shawn Hargreaves    schedule 23.06.2016
comment
Это работает, но CreateResources вызывается только один раз, когда я впервые захожу на эту страницу. Я перехожу туда и обратно на эту страницу с другой страницы (используя MVVM и ViewModel Locater), и мне нужно новое изображение (выбранное на другой странице и установленное как свойство FilePath в пользовательском элементе управления), отображаемое каждый раз, когда я перехожу в страница. Мне также пришлось прокомментировать выгрузку ImageVirtualControl в Control_Unloaded, иначе я получал нулевые ссылки, когда возвращался на страницу. - person SheriSteeves; 23.06.2016
comment
В общем, я бы не советовал кэшировать элементы управления Win2D для повторного использования при повторном посещении страницы. Каждый элемент управления Win2D потребляет значительный объем ресурсов графического процессора, который вы действительно хотите освободить, когда страница больше не отображается. Вы можете отключить кеширование страниц, установив для NavigationCacheMode значение Disabled (подробнее см. mikaelkoskinen.net/post/) Альтернативный (более сложный) подход: blogs.msdn.microsoft.com/win2d/2015/01 / 29 / - person Shawn Hargreaves; 27.06.2016