SvgForXaml icon indicating copy to clipboard operation
SvgForXaml copied to clipboard

Issues with rendering when using ItemsSource

Open ghost opened this issue 6 years ago • 0 comments

I've been trying to use ItemsSource property of a ListView to render a bunch of SVG images and had no luck. After debugging it a bit, I think there is an issue with SvgImage class.

Consider this xaml:

<Grid Background="Purple">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <ListView x:Name="SvgForXamlListView">
        <ListView.ItemTemplate>
            <DataTemplate x:Key="SvgForXamlDataTemplate">
                <svg:SvgImage Margin="10" Width="100" Height="100" Content="{Binding Document}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <StackPanel Orientation="Horizontal" Grid.Row="1">
        <Button Content="Draw" Click="OnDrawClick" />
        <Button Content="Navigate" Click="OnNavigateClick" />
    </StackPanel>
</Grid>

And this code-behind:

public sealed partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnDrawClick(object sender, RoutedEventArgs e)
    {
        var svgFiles = new []
        {
            "ms-appx:///Assets/test1.svg",
            "ms-appx:///Assets/test2.svg",
            "ms-appx:///Assets/test3.svg",
            "ms-appx:///Assets/test4.svg"
        };

        var viewModels = new List<SvgImageViewModel>();
        foreach (var svgFile in svgFiles)
        {
            var viewModel = new SvgImageViewModel();
            await viewModel.InitializeAsync(svgFile);
            viewModels.Add(viewModel);
        }

        SvgForXamlListView.ItemsSource = viewModels;
    }

    private void OnNavigateClick(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(Page2));
    }
}

public class SvgImageViewModel
{
    public Uri SvgUri { get; private set; }

    public SvgDocument Document { get; private set; }

    public async Task InitializeAsync(string fileUri)
    {
        this.SvgUri = new Uri(fileUri);

        var file = await StorageFile.GetFileFromApplicationUriAsync(SvgUri);

        using (var stream = await file.OpenStreamForReadAsync())
        using (var reader = new StreamReader(stream))
        {
            var xml = new XmlDocument();
            xml.LoadXml(reader.ReadToEnd(), new XmlLoadSettings { ProhibitDtd = false });
            var svgDocument = SvgDocument.Parse(xml);
            this.Document = svgDocument;
        }
    }
}

The above does not render anything on the screen. Even though it does create 4 instances of SvgImage controls and places them in the UI tree, the CanvasControl does not render anything. Debugging through this I see that when using ItemsSource, SvgImage.OnContentChanged is called before SvgImage.OnApplyTemplate. Therefore at the time that content is being applied, _canvasControl instance is NULL and so _renderer is not created.

Making the following change to the constructor of SvgImage resolves this problem because it ensures that the control will render its content after it is loaded, and then also whenever the content changes thereafter. It also takes care of safely unloading the underlying CanvasControl whenever SvgImage is unloaded:

public SvgImage()
    : base()
{
    this.DefaultStyleKey = typeof(SvgImage);
    Loaded += (sender, args) => OnContentChanged(this.Content);
    Unloaded += (sender, args) => SafeUnload();
}

If this sounds like a correct fix to your SVG viewer then I can make a pull request against your repository with my suggested fixes.

ghost avatar Aug 29 '17 15:08 ghost