AB4D Forum

Full Version: Using ResourceDictionaries, NullReferenceException
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I am evaluating the most recent version of ReaderSvg and in most aspects it is working well. The resource dictionaries however do not appear to be working, or the documentation isn't sufficient.

I receive a NullReferenceException from within ReaderSvg when I run code similar to the following. Unfortunately I cannot post our SVG files on a public forum, but if someone contacts me directly I believe I can provide a sample (though so far the same error occurs for all SVG).

var shapeDictionary = new ResourceDictionaryWriter();
using (var stream = file.OpenRead())
shapeDictionary.AddStream(stream);

at Ab2d.Utility.ReaderSvg.ResourceDictionaryWriter.ഽ(Viewbox ാ, Boolean ി)
at Ab2d.Utility.ReaderSvg.ResourceDictionaryWriter.AddStream(Stream stream)

at SvgConverter.MainWindow.addAsShape(ResourceDictionaryWriter dictionary, FileInfo file) in C:\Source\Visual Studio 2008\Projects\SvgConverter\SvgConverter\MainWindow.xaml.cs:line 93
at SvgConverter.MainWindow.convertWithDecimals(ReaderSvg reader, DirectoryInfo inputDir, String formatString, String dirExtension) in C:\Source\Visual Studio 2008\Projects\SvgConverter\SvgConverter\MainWindow.xaml.cs:line 62
at SvgConverter.MainWindow.convert_Click(Object sender, RoutedEventArgs e) in C:\Source\Visual Studio 2008\Projects\SvgConverter\SvgConverter\MainWindow.xaml.cs:line 43
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
at System.Windows.Controls.Primitives.ButtonBase.OnClick()
at System.Windows.Controls.Button.OnClick()
at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at SvgConverter.App.Main() in C:\Source\Visual Studio 2008\Projects\SvgConverter\SvgConverter\obj\x86\Debug\App.g.cs:line 0
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
I have sent you private contact information.

You can also send feedback with attachments (max size is 2 MB) with our Contact Us form: http://www.wpf-graphics.com/Feedback.aspx.

The files send with Contact Us form will be kept private and will not be shared with others in any way.

If you need to send bigger attachment, please contact us and we will provide you with direct contact address.
I have checked the file you send and it is opened correctly in ViewerSvg.

After some additional investigation I have found a bug in ResourceDictionaryWriter.

It only works when you add svg files to ResourceDictionary with AddGeometryFile or AddGeometryStream methods (this means that svg file will be read as Geometry and than added to the ResourceDictionary). But ResourceDictionaryWriter does not work if you use AddFile or AddStream method (read svg file as Shapes and add them to resource dictionary). Actually the last two methods work if they are called after calling AddGeometryFile or AddGeometryStream methods.

I have marked this as P1 bug and will fix it in the next release. Currently I am finishing a new version of ReaderWmf. Than I will do some improvements on ReaderSvg and fix this also.

Until than you can use the following nasty workaround to make your code work - before first use of AddFile or AddStream method write the following (resourceDictionaryWriter is an instance of ResourceDictionaryWriter):

Code:
_resourceDictionaryWriter.AddGeometryStream(new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes("<svg></svg>")), new GeometrySettings()); ;

This will initialize a private filed that would otherwise throw NullReferenceException. After getting xaml you will also have an empty DrawingImage as the first element in ResourceDictionary - but it can be simply deleted by hand.


With the provided workaround it is possible to use AddFile or AddStream method. However I would like to advice you to use AddGeometryFile or AddGeometryStream methods instead.

This would give you the following advantages:
- your objects would be represented as Geometry elements - therefore would have better performance and use lower memory.
- ReaderSvg can additionally optimize the read geometry. Already with using basic optimization the ResourceDictionaryWriter would create separate ResourceDictionary entries for brushes and pens that are commonly used. For example black pen can than be used for all the objects included in ResourceDictionary (also from more than one svg file).

The following screenshow shows this:
[Image: http://www.wpf-graphics.com/images/SvgRe...Writer.png]

A drawback of using Geometry elements is that they provide less layout, focus and other features.


The code you used can be simply changed into code that will create ResourceDictionary with Geometry elements and with combined brushes and pens:
Code:
var geometryDictionary = new ResourceDictionaryWriter();
using (var stream = file.OpenRead())
    shapeDictionary.AddGeometryStream(stream, Ab2d.Common.ReaderSvg.GeometrySettings.BasicOptimization);

A good sample on how to create ResourceDictionary with Geometry elements is also part of ReaderSvg package - a ResourceDictionary writer application will full source code.
Thank you for the reply. I'll use that workaround. We've actually evaluated Geometry and Shape options, and in our case the file size of Shape was smaller, and for us that is a first concern we've been measuring. I'll have to do some deeper inspection to find alters Geometry/Shape runtime performance is affected, but as you can see the individual size of our graphics is quite small so I expect both to be near trivial. We however need to provide users with several hundred thousand such graphics so even with compression we are looking at maybe 500MB. It may also be that your metrics for the relative performance of Shape/Geometry is based upon more complex graphics?

I should also mention that almost all our graphics produce anomalies with the setting CombineElementWithPenAndBrush as true, so that optimization is disabled.

Funny thing by the way.. I was trying to do a comparison of ResourceDictionary for Geometry and Shape when I encountered this defect. Of course in my code the Shape call was first which unfortunately kept me from finding your workaround.

With your help however I've completed the comparison, and I'm disappointed to see that the ResourceDictionaries created are about twice the size as the individual shape files, and 40% larger than the geometry files. Part of this is due to the inability to control the number of decimal points from ResourceDictionaryWriter (GetXaml does not take BaseXamlWriterSettings). I could probably reduce it in my own code, but it appears as if it would still be larger, and not yet certain why. (For our graphics we've found one decimal point to be sufficient, whereas it defaults to 3.. however comparing against 3 still shows the dictionary to be larger).

I very much appreciate ReaderSvg. I evaluated it a year or more ago and it wasn't sufficient at the time, but it has come quite a ways since then. I wouldn't say the ResourceDictionaryWriter is complete, but I've gotten very good results out of the standard ReaderSvg.

One last comment issue we need resolved before I can recommend a purchase. In ViewerSvg the files we process come out with the Id's translated to Name, but no matter what I set for NamedObjectsSource, from code this does not seem to occur. Is this a limitation during trial or is there another cause? We definitely need that function.

Thank you,

Ryan
The NamedObjectsSource property works correctly (getting names from Id property). If you check the Name property of children of read Viewbox, you will see that the name is correctly set.

The problem is that the GetXaml method uses only NamedObjects dictionary for getting the object name. This has been already improved in the development version and will also come with the next release.

Until than you need to explicitly set the NamedObjects for the GetXaml method. For example:

Code:
Ab2d.ReaderSvg readerSvg = new ReaderSvg();
readerSvg.NamedObjectsSource = ReaderSvg.NamedObjectsSourceType.Id;
readerSvg.Read(fileName);

WpfXamlWriterSettings wpfXamlWriterSettings = new WpfXamlWriterSettings();
wpfXamlWriterSettings.NamedObjects = readerSvg.NamedObjects;

string xaml = readerSvg.GetXaml(wpfXamlWriterSettings);


If I understand you correctly you are sending clients ResourceDictionaries in xaml format. Why don't you make a project from a ResourceDictionaries, compile it and send the client a dll.

It will be much smaller and what is more data from it will be read much faster. Also there the number of decimals would not matter.

If dll is not ok for you than I have done some additional coding and prepared a source code for new ShapesResourceDictionaryWriter that works only with shapes. You can use the source for your projects:

Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
using Ab2d.Common.ReaderSvg;

namespace Ab2d.Utility.ReaderSvg
{
    /// <summary>
    /// ResourceDictionaryWriter class is a helper class that can be used to build ResourceDictionaries from multiple input files.
    /// </summary>
    public class ShapesResourceDictionaryWriter
    {
        private const string DefaultXamlNamespace = "xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:PresentationOptions=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation/options\"";

        private Ab2d.ReaderSvg _myReader;

        private List<string> _readXamls;

        private int _resourceIndex;

        private WpfXamlWriterSettings _wpfXamlWriterSettings;

        /// <summary>
        /// Constructor.
        /// </summary>
        public ShapesResourceDictionaryWriter()
        {
            _myReader = new Ab2d.ReaderSvg();

            Reset();

            _wpfXamlWriterSettings = new WpfXamlWriterSettings();

            _wpfXamlWriterSettings.NamedObjects = null;
            _wpfXamlWriterSettings.RootObject = "";
            _wpfXamlWriterSettings.XamlNamespace = "";
            _wpfXamlWriterSettings.StartComment = "";
            _wpfXamlWriterSettings.NumberFormatString = "0.0";
        }

        private void AddReadXamlToDictionary()
        {
            string xaml;
            int pos1, pos2;

            xaml = _myReader.GetXaml(_wpfXamlWriterSettings);
            
            pos1 = xaml.IndexOf("<Canvas") + "<Canvas".Length;
            pos2 = xaml.LastIndexOf("</Canvas>") + "</Canvas>".Length;

            // Strip off comment and Viewbox and add x:Key
            xaml = string.Format("    <Canvas x:Key=\"Canvas_{0}\"", _resourceIndex) + xaml.Substring(pos1, pos2 - pos1);

            _resourceIndex++;

            _readXamls.Add(xaml);
        }

        /// <summary>
        /// Resets the collected svg files so a new ResourceDictionary can be created.
        /// </summary>
        public void Reset()
        {
            _readXamls = new List<string>();
            _resourceIndex = 0;
        }

        /// <summary>
        /// Adds a svg or svgz file to the ResourceDictionary as WPF Shapes.
        /// </summary>
        /// <param name="fileName">fileName</param>
        /// <returns>read svg or svgz file as Viewbox</returns>
        public Viewbox AddFile(string fileName)
        {
            Viewbox viewbox;

            if (string.IsNullOrEmpty(fileName))
                return null;

            viewbox = _myReader.Read(fileName);
            AddReadXamlToDictionary();

            return viewbox;
        }

        /// <summary>
        /// Adds a svg or svgz from stream to the ResourceDictionary as WPF Shapes.
        /// </summary>
        /// <param name="stream">stream</param>
        /// <returns>read svg or svgz file as Viewbox</returns>
        public Viewbox AddStream(Stream stream)
        {
            Viewbox viewbox;

            viewbox = _myReader.Read(stream);
            AddReadXamlToDictionary();

            return viewbox;
        }

        /// <summary>
        /// Gets the xaml string of the ResourceDictionary with all added svg objects as resources.
        /// </summary>
        /// <returns>xaml</returns>
        public string GetXaml()
        {
            StringBuilder sb;

            sb = new StringBuilder();
            sb.Append("<!-- Converted from svg or svgz with Ab2d.ReaderSvg. See www.wpf-graphics.com for more WPF tools.-->").AppendLine();
            sb.Append("<ResourceDictionary ").Append(DefaultXamlNamespace).Append(">").AppendLine();

            foreach (string oneInnerXaml in _readXamls)
                sb.Append(oneInnerXaml).AppendLine();

            sb.Append("</ResourceDictionary>");

            return sb.ToString();
        }
    }
}
The suggested change for NamedObjects works well. That wasn't intuitive/obvious, but it makes sense now.

I've found one cause of the larger resource dictionaries. With the other methods I'm able to control the "ShowDemoTextInEvaluation", but not with the resource dictionaries. Guess that test will have to wait till after we get a license, which I think will be soon.
The problem is that the GetXaml method uses only NamedObjects dictionary for getting the object name. This has been already improved in the development version and will also come with the next release.