Find all the objects mapped to a template field

Knowledge exchange related to the VPE Report Engine and PDF Library

Moderator: IDEAL Software Support

Find all the objects mapped to a template field

Postby Aleksei » Fri Mar 31, 2017 9:26 pm

Dear Thorsten,

I would like to write the following extension method in C#:
Code: Select all
public TVPEObject[] FindVpeObjects(this TVPETemplate tpl, string dataSource, string vpeFieldName)
{
     //TODO:???
}


When I create a template in DCD, I usually add some fields and map them to the template objects. This mapping is stored in the template. What I want is to be able to get all the objects which are mapped to a particular field. It should be possible. Maybe using a .net reflection? Could you please help me with this?

Thank you for any tip.
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Mon Apr 03, 2017 2:03 pm

We think we have a very complete API to cover every possible use-case, but this is not possible. The VPE API does not provide access to this information.

For what reason do you wish to retrieve the objects that hold references to a field?
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Mon Apr 03, 2017 9:57 pm

Well, our app uses a lot of tpl files which use fileds to provide data to template objects. Let's say that some of those objects don't have names. Since they are mapped to fields it is not a problem to write to those objects - just use tpl.SetFieldAsString() method. The template knows the mapping and displays the provided string in all objects mapped to this field. So far, so good.

However, recently we received a new requirement - support different languages, let's say Russian, Arabic, Greek, Latvian, etc. It is quite tricky if you still want to use fields. We have to convert let's say Russian text from UTF-16 to non-Unicode by calling unmanaged piece of code:
Code: Select all
              [DllImport("Kernel32.dll")]
              public static extern int WideCharToMultiByte(uint CodePage, uint dwFlags,
                     [In, MarshalAs(UnmanagedType.LPWStr)]string lpWideCharStr,
                     int cchWideChar,
                     [Out, MarshalAs(UnmanagedType.LPStr)]StringBuilder lpMultiByteStr,
                     int cbMultiByte,
                     IntPtr lpDefaultChar,
                     IntPtr lpUsedDefaultChar
                     );

The first parameter (CodePage) is code page to use in performing the conversion. So for example, for Russian language it will be 1251.
After the Russian string is converted to ANSI, it still cannot be used with tpl.SetFieldAsString("SomeDataSource", "SomeField", convertedString) method until we find all the objects mapped to that field and set CharSet.WinCyrillic to those objects. Now we came to the point why I need the method I asked for in the beginning.

If I have that method, I can implement my task in 2 ways:
The first solution:
Code: Select all
            string text = "Some Russian text";
            StringBuilder sb = new StringBuilder(text.Length + 1);
            uint codepage = 1251;
            WideCharToMultiByte(codepage, 0, text, -1, sb, text.Length + 1, IntPtr.Zero, IntPtr.Zero);
            var convertedString = sb.ToString();

            TVPEObject[] TVPEObjects = tpl.FindVpeObjects("SomeDataSource", "SomeField");
            foreach (TVPEObject obj in TVPEObjects)
            {
                obj.CharSet = CharSet.WinCyrillic;
            }
            tpl.SetFieldAsString("SomeDataSource", "SomeField", convertedString);


The second solution:
Code: Select all
            string text = "Some Russian text";
            TVPEObject[] TVPEObjects = tpl.FindVpeObjects("SomeDataSource", "SomeField");
            foreach (TVPEObject obj in TVPEObjects)
            {
                WriteStringToObject(vpe, obj, text, CharSet.WinCyrillic);
            }

        private void WriteStringToObject(VpeControl vpe, TVPEObject obj, string text, CharSet ch)
        {
            vpe.TextColor = obj.TextColor;
            vpe.TextBold = obj.TextBold;
            vpe.TextItalic = obj.TextItalic;
            vpe.TextStrikeOut = obj.TextStrikeOut;
            vpe.TextUnderline = obj.TextUnderline;
            vpe.TextAlignment = obj.TextAlignment;
            vpe.SetFont(obj.FontName, obj.FontSize);
            vpe.CharSet = ch;

            vpe.Write(obj.nLeft, obj.nTop, obj.nRight, obj.nBottom, text);
        }
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Tue Apr 04, 2017 7:29 am

Do you use mixed charsets within one document? E.g. Cyrillic and Greek in the same document?

Otherwise the simple solution would be to set for example Cyrillic for all objects.
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Thu Apr 06, 2017 10:57 pm

Do you mean this code:
Code: Select all
        private void SetCharSetForTemplate(TVPETemplate tpl, CharSet сharSet)
        {
            for (int p = 0; p < tpl.PageCount; p++)
            {
                var page = tpl.PageObject(p);
                for (int o = 0; o < page.VpeObjectCount; o++)
                {
                    var obj = page.VpeObject(o);
                    obj.CharSet = сharSet;
                }
            }
        }

It has some limitations:
1. As you correctly noted, it will not work if mixed charsets within one document used, e.g. Cyrillic and Greek.
2. Even if one language is used within one document, this approach will help only for the first solution I mentioned above. However, I would prefer to use the second solution for several reasons: a) I would like to avoid calling unmanaged piece of code (WideCharToMultiByte); b) I don't want to keep mapping between CharSet and CodePage (e.g. CharSet.WinCyrillic and 1251).

Any ideas?
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Mon Apr 10, 2017 7:41 am

a) I would like to avoid calling unmanaged piece of code (WideCharToMultiByte);


When you call Write() or Print() from .NET, VPE internally calls WideCharToMultiByte(). There is nothing wrong with this, and you can call it safely from your own code, too. The reason VPE does not call it is, because the template methods have no handle to the VPE document, where the currently selected Charset is stored.

b) I don't want to keep mapping between CharSet and CodePage (e.g. CharSet.WinCyrillic and 1251)


VPE internally uses the CharSet value, when calling WideCharToMultiByte(). There is no mapping between CharSet.WinCyrillic and 1251.
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Thu Apr 13, 2017 8:47 am

VPE internally uses the CharSet value, when calling WideCharToMultiByte(). There is no mapping between CharSet.WinCyrillic and 1251.

I guess I see what you mean.

Initially, I thought that you have a function similar to this:
Code: Select all
      private uint ConvertCharSetToCodepage(CharSet сharSet)
      {
         switch (сharSet)
         {
            case CharSet.WinEastEurope:
               return 1250;
            case CharSet.WinCyrillic:
               return 1251;
            case CharSet.WinAnsi:
               return 1252;
            case CharSet.WinGreek:
               return 1253;
            case CharSet.WinTurkish:
               return 1254;
            case CharSet.WinHebrew:
               return 1255;
            case CharSet.WinArabic:
               return 1256;
            case CharSet.WinBaltic:
               return 1257;
            case CharSet.WinVietnamese:
               return 1258;
            ...
         }
      }

But, now I realize that you are using TranslateCharsetInfo function. With this function, indeed you don't need any mapping and the first solution can be modified the following way:
Code: Select all
      private void FirstSolution(TVPETemplate tpl)
      {
         string text = "Some Russian text";
         StringBuilder sb = new StringBuilder(text.Length + 1);
         CharSet сharSet = CharSet.WinCyrillic;
         WideCharToMultiByte(ConvertCharSet(сharSet), 0, text, -1, sb, text.Length + 1, IntPtr.Zero, IntPtr.Zero);
         var convertedString = sb.ToString();

         TVPEObject[] TVPEObjects = tpl.FindVpeObjects("SomeDataSource", "SomeField");
         foreach (TVPEObject obj in TVPEObjects)
         {
            obj.CharSet = сharSet;
         }
         tpl.SetFieldAsString("SomeDataSource", "SomeField", convertedString);
      }

      /// <summary>
      /// Converts the specified CharSet to the corresponding code page.
      /// </summary>
      /// <param name="сharSet">CharSet to convert.</param>
      /// <returns>Code page.</returns>
      private uint ConvertCharSet(CharSet сharSet)
      {
         var csi = new CharsetInfo();
         TranslateCharsetInfo((uint)сharSet, ref csi, 1/*TCI_SRCCHARSET*/);
         return csi.ciACP;
      }

      [DllImport("gdi32.dll")]
      private static extern int TranslateCharsetInfo(uint charSet, ref CharsetInfo csi, uint flags);
      
      [DllImport("kernel32.dll")]
      private static extern int WideCharToMultiByte(uint codePage, uint dwFlags, [In, MarshalAs(UnmanagedType.LPWStr)] string lpWideCharStr, int cchWideChar, StringBuilder lpMultiByteStr, int cchMultiByte, IntPtr lpDefaultChar, IntPtr lpUsedDefaultChar);

      [StructLayout(LayoutKind.Sequential)]
      internal struct FontSignature
      {
         private uint fsUsb0;
         private uint fsUsb1;
         private uint fsUsb2;
         private uint fsUsb3;
         private uint fsCsb0;
         private uint fsCsb1;
      }

      [StructLayout(LayoutKind.Sequential)]
      internal struct CharsetInfo
      {
         /// <summary>
         /// Character set value.
         /// </summary>
         public uint ciCharset;

         /// <summary>
         /// Windows ANSI code page identifier.
         /// </summary>
         public uint ciACP;

         /// <summary>
         /// A FONTSIGNATURE structure that identifies the Unicode subrange and the specific Windows ANSI character set/code page.
         /// </summary>
         public FontSignature fs;
      }
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Thu Apr 13, 2017 10:28 am

Yes, that's correct. I have missed that. We are using TranslateCharset() when a value is assigned to the property Document.CharSet. In this moment an internal member Document.Codepage is set, which is used later each time when calling WideCharToMultiByte().
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Thu Apr 13, 2017 10:51 am

Coming back to my original question - "Find all the objects mapped to a template field",
this is not possible. The VPE API does not provide access to this information.

I am thinking about the following solution:
Code: Select all
   public static class VpeExtensions
   {
      /// <summary>
      /// In the specified template find all the objects which hold references to the specified TEXT field.
      /// </summary>
      /// <param name="objTemplate">The Template Object.</param>
      /// <param name="dataSourcePrefix">Prefix of the Data Source, this is the unique name of a Data Source as defined in dycodoc.</param>
      /// <param name="textFieldName">Name of the Data Source Field which is of type text.</param>
      /// <returns>The list of VPE Objects.</returns>
      public static List<TVPEObject> FindVpeObjects(this TVPETemplate objTemplate, string dataSourcePrefix, string textFieldName)
      {
         var objs = new List<TVPEObject>();
         TVPEField field = objTemplate.FindFieldObject(dataSourcePrefix, textFieldName);
         string origFieldValue = field.AsString;

         string key = Guid.NewGuid().ToString();
         field.AsString = key;

         for (int p = 0; p < objTemplate.PageCount; p++)
         {
            var page = objTemplate.PageObject(p);
            for (int o = 0; o < page.VpeObjectCount; o++)
            {
               TVPEObject obj = page.VpeObject(o);
               if (obj.ResolvedText == key)
                  objs.Add(obj);
            }
         }
         field.AsString = origFieldValue;
         return objs;
      }
   }

This solution is not exactly what I wanted, but it is probably the best I can do. The main limitation of this method is that it works only with fields of type TEXT. I am wondering if you see any pitfalls to this code.
Another question - how can I identify programmatically the type of the field? In dycodoc I can see a combobox with the following values for the type: Text, Integer, Number, Boolean, Date Time. However, in my version of VPE Enterprise (6.10) I don't see Type property in TVPEField object. As a result, I cannot write the following code in my method above:
Code: Select all
         TVPEField field = objTemplate.FindFieldObject(dataSourcePrefix, textFieldName);
         if (field.Type != FieldTypes.Text)
            throw new Exception(string.Format("The {0} field is not of type text", textFieldName));

Thank you for your valuable input.
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Thu Apr 13, 2017 12:07 pm

There is no API to retrieve the type of a field. But it is now on the to-do list and will be available with the next version.

Setting a numeric or date field will not cause any problems, except that the value is set to zero for numeric fields. But this should not hurt, if you call FindVpeObjects() before setting up the fields with real values.

Your code in FindVpeObjects() should work fine. Nice idea.
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Thu Apr 13, 2017 4:37 pm

Setting a numeric or date field will not cause any problems, except that the value is set to zero for numeric fields.

That's actually a problem for my method, because if the type of the field is Integer, for example and the key let's say equal '927fdb00-7c87-438f-bf78-ab1f15428440', then the following line of code:
Code: Select all
field.AsString = key;

will cause the corresponding objects to show 927 (obj.ResolvedText will have value 927). This means that the following condition will not be true:
Code: Select all
if (obj.ResolvedText == key)

As a result, my method will return empty list.

So, for Integer fields I would prefer to use integer key, e.g. int.MinValue and use AsInteger property. Here is the modification of the code for integer fields:
Code: Select all
      /// <summary>
      /// In the specified template find all the objects which hold references to the specified INTEGER field.
      /// </summary>
      public static List<TVPEObject> FindVpeObjects(this TVPETemplate tpl, string dataSourcePrefix, string integerFieldName)
      {
         var objs = new List<TVPEObject>();
         TVPEField field = tpl.FindFieldObject(dataSourcePrefix, integerFieldName);
         int origFiledValue = field.AsInteger;
         int key = int.MinValue;

         field.AsInteger = key;

         for (int p = 0; p < tpl.PageCount; p++)
         {
            var page = tpl.PageObject(p);
            for (int o = 0; o < page.VpeObjectCount; o++)
            {
               TVPEObject obj = page.VpeObject(o);
               if (obj.ResolvedText == key.ToString())
                  objs.Add(obj);
            }
         }
         field.AsInteger = origFiledValue;
         return objs;
      }

For the Number fields, I would use double.MinValue as a key, but as soon as I assign this value to Number field using field.AsNumber property and try to get ResolvedText, I get AccessViolationException error:
Code: Select all
      /// <summary>
      /// In dycodoc, create a template with a filed name NewField1 and type Number. Add a text object Text1 mapped to this field.
      /// </summary>
      /// <param name="tpl">Provide the loaded template.</param>
      private void ReproduceError(TVPETemplate tpl)
      {
         TVPEField field = tpl.FindFieldObject("NewTable1", "NewField1");
         field.AsNumber = double.MinValue;
         TVPEObject obj = tpl.FindVpeObject("Text1");
         string s = obj.ResolvedText; //AccessViolationException {"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."}
      }


This error does not block me actually. This is because I realized that I need my method (FindVpeObjects) only for Text fields. The reason why I need this method is to support different languages, but for Integer and Number fields, there is nothing to translate. (For Boolean fields this method will not work because there are only 2 possible values. For Date Time fields it is not possible to find all the objects mapped to a particular filed using my approach because I don't see how to get and set Date Time Format of a field programmatically.)
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby Aleksei » Thu Apr 13, 2017 6:06 pm

To sum up, here is the complete solution:
Code: Select all
   public static class VpeExtensions
   {
      /// <summary>
      /// In the specified template find all the objects which hold references to the specified TEXT field.
      /// </summary>
      /// <param name="objTemplate">The Template Object.</param>
      /// <param name="dataSourcePrefix">Prefix of the Data Source, this is the unique name of a Data Source as defined in dycodoc.</param>
      /// <param name="textFieldName">Name of the Data Source Field which is of type text.</param>
      /// <returns>The list of VPE Objects.</returns>
      private static IEnumerable<TVPEObject> FindVpeObjects(this TVPETemplate objTemplate, string dataSourcePrefix, string textFieldName)
      {
         var objs = new List<TVPEObject>();
         TVPEField field = objTemplate.FindFieldObject(dataSourcePrefix, textFieldName);
         //if (field.Type != FieldTypes.Text)
         //    throw new Exception(string.Format("The {0} field is not of type text", textFieldName));

         string origFieldValue = field.AsString;

         string key = Guid.NewGuid().ToString();
         field.AsString = key;

         for (int p = 0; p < objTemplate.PageCount; p++)
         {
            var page = objTemplate.PageObject(p);
            for (int o = 0; o < page.VpeObjectCount; o++)
            {
               TVPEObject obj = page.VpeObject(o);
               if (obj.ResolvedText == key)
                  objs.Add(obj);
            }
         }
         field.AsString = origFieldValue;
         return objs;
      }

      /// <summary>
      /// Write specified text with the specified CharSet based on the provided template.
      /// </summary>
      /// <param name="vpe">The VPE Control.</param>
      /// <param name="objTemplate">The Template Object.</param>
      /// <param name="dataSourcePrefix">Prefix of the Data Source, this is the unique name of a Data Source as defined in dycodoc.</param>
      /// <param name="textFieldName">Name of the Data Source Field which is of type text.</param>
      /// <param name="charSet">CharSet corresponding to the specified text.</param>
      /// <param name="text">The text to write.</param>
      public static void WriteStringField(this VpeControl vpe, TVPETemplate objTemplate, string dataSourcePrefix, string textFieldName, CharSet charSet, string text)
      {
         IEnumerable<TVPEObject> objs = objTemplate.FindVpeObjects(dataSourcePrefix, textFieldName);
         foreach (TVPEObject obj in objs)
         {
            vpe.TextColor = obj.TextColor;
            vpe.TextBold = obj.TextBold;
            vpe.TextItalic = obj.TextItalic;
            vpe.TextStrikeOut = obj.TextStrikeOut;
            vpe.TextUnderline = obj.TextUnderline;
            vpe.TextAlignment = obj.TextAlignment;
            vpe.SetFont(obj.FontName, obj.FontSize);
            vpe.CharSet = charSet;

            vpe.Write(obj.nLeft, obj.nTop, obj.nRight, obj.nBottom, text);
         }
      }
   }

And here is an example of the code which uses the extensions:
Code: Select all
         string dataSourcePrefix = "MyDataSource";
         string tplFileName = @"C:\Doc1.tpl";

         var vpe = new VpeControl();
         vpe.OpenDoc();
         TVPETemplate tpl = vpe.LoadTemplate(tplFileName); //The template should have 2 text fields named NewField1 and NewField2 and 2 text objects mapped to those fields.
         vpe.WriteStringField(tpl, dataSourcePrefix, "NewField1", CharSet.WinCyrillic, @"Некоторый текст"/*Russian*/);
         vpe.WriteStringField(tpl, dataSourcePrefix, "NewField2", CharSet.WinGreek, @"κείμενο"/*Greek*/);
         vpe.DumpTemplate(tpl);
         vpe.Preview();

         vpe = new VpeControl();
         vpe.OpenDoc();
         tpl = vpe.LoadTemplate(tplFileName);
         vpe.WriteStringField(tpl, dataSourcePrefix, "NewField1", CharSet.WinTurkish, @"alışveriş"/*Turkish*/);
         vpe.WriteStringField(tpl, dataSourcePrefix, "NewField2", CharSet.WinVietnamese, @"đồng hồ"/*Vietnamese*/);
         vpe.DumpTemplate(tpl);
         vpe.Preview();
Aleksei
 
Posts: 7
Joined: Fri Mar 31, 2017 8:56 pm

Re: Find all the objects mapped to a template field

Postby IDEAL Software Support » Thu Jan 04, 2018 4:49 pm

As a note, the Access Violation Exception has been fixed. The fix will be made available with the next release.

The bug occurs when you don't use scientific number formatting. In this case the number before the decimal point has 309 digits, which caused an internal buffer overflow. As a workaround, use scientific number formatting.
IDEAL Software Support
 
Posts: 1621
Joined: Thu Nov 18, 2004 4:03 pm


Return to VPE Open Forum

Who is online

Users browsing this forum: Google [Bot] and 12 guests