{"id":55,"date":"2011-11-25T06:02:00","date_gmt":"2011-11-25T14:02:00","guid":{"rendered":"\/lisa\/post\/2011\/11\/25\/YAPS-on-TOCs-for-RDLCs.aspx"},"modified":"2021-11-24T11:24:59","modified_gmt":"2021-11-24T19:24:59","slug":"yaps-on-tocs-for-rdlcs","status":"publish","type":"post","link":"https:\/\/spacefold.com\/lisa\/2011\/11\/25\/yaps-on-tocs-for-rdlcs\/","title":{"rendered":"YAPS on TOCs for RDLCs"},"content":{"rendered":"<p>This isn&#8217;t really a very happy Thanksgiving for C &amp; me, even though we know we have a lot to be thankful for.\u00a0\u00a0\u00a0C is currently packing for a sad trip home.\u00a0 I&#8217;m not going to be able to be with him this time and am writing this blog entry partly to keep my mind off things.<\/p>\n<p>I&#8217;m also writing it because somebody has badgered me into it:\u00a0 A gentleman named Mansoor Ikbal feels I did not give sufficient aid to his work in my previously-published entries on Table of Contents production.<\/p>\n<p>I expect he was working from <a title=\"blog post on TOCs in RDLCs\" href=\"\/lisa\/2009\/09\/19\/Walkthrough-TOC2bLocal-Reports-or-The-RDLC-with-the-Fringe-on-Top\/\">this entry<\/a>, but am not sure which, of several, he tried.\u00a0 (When I say &#8220;last time&#8221; in reference to previous entries on this topic, in the instructions below, <a title=\"blog post on TOCs in RDLCs\" href=\"\/lisa\/2009\/09\/19\/Walkthrough-TOC2bLocal-Reports-or-The-RDLC-with-the-Fringe-on-Top\/\">this entry<\/a>\u00a0is the one I mean.)<\/p>\n<p>I&#8217;m also not sure exactly where his problems are, although he indicated in one message that he didn&#8217;t have much luck dealing with the permissions problems.\u00a0 I do know that he is working on RDLCs\u00a0in RS 2008, and that entry was written for 2005.\u00a0 As indicated in that entry, much can change, especially with localmode use of the ReportViewer control, in the sequence of evaluation between versions, as well as between renditions.<\/p>\n<p>The fact that Mansoor Ikbal wants his TOC at the end, rather than at the beginning, of the report might also contribute to differences between what he experiences and what I laid out in the earlier walkthrough(s).<\/p>\n<p>Whatever his problem is, I will try to explain again, with a new walkthrough written using BI Studio 2008.\u00a0 This time, I will simplify the method as much as possible to leave out anything that might be contributing to his problems following my instructions.\u00a0 This time, as last time, I will also not present a method that necessarily shows a correct TOC in the interactive display; it&#8217;s designed to be correct for the PDF output.\u00a0 As usual with this stuff, you have to do them differently, and the interactive version is actually quite different, and much easier (given that he wants the TOC at the end), in this case.<\/p>\n<p>Because I used an ASPX page last time, I&#8217;ll use a winform host for the report viewer control this time, just to change things up. (I&#8217;m not sure if it makes any difference to the localmode PDF renderer; let me know if you have ever observed this.)<\/p>\n<p class=\"NB\">You might also have to make the winform app fully trusted or otherwise be careful about where you put files you create during the report run, but that&#8217;s always the case.<\/p>\n<p>The general strategy should be pretty much the same as it was before:<\/p>\n<ol>\n<li>We&#8217;ll use a textbox in the page header to send information to custom code about the current page number and the current group header, as we did earlier.\u00a0 The actual code is slightly different, and simpler, partly because there&#8217;s only one group level in this example.<br \/>\n<span style=\"color: #ffffff;\">\u00a0 .<\/span><\/li>\n<li>Because we want to adjust the PDF content, we&#8217;re going to persist the information about page numbers in an external file, where we can pick it up again on a second run.\n<p>You&#8217;d think we wouldn&#8217;t need to do this, considering that the TOC appears at the end, but we do, at least in my tests with 2008.\u00a0 Remember that you can&#8217;t control in what order a renderer handles the page headers and footers versus the body, and page headers and footers are where page numbers can be got.<\/p>\n<p>In this particular walkthrough, I put the TOC in the body section, to make sure it would have as much room to stretch as it wanted. So this element in the body is picking up on information it got after the page headers and footers were done.\u00a0In my tests, this did indeed require two runs to work reliably.<br \/>\n<span style=\"color: #ffffff;\">\u00a0 .<\/span><\/li>\n<li>Because we want two runs before the TOC can reliably appear, I&#8217;m once again suppressing display of the export button from the standard report viewer interface, and providing a button on the form where the user can request a PDF.\u00a0 My code under the button can therefore do the two runs.<br \/>\n<span style=\"color: #ffffff;\">\u00a0 .<\/span><\/li>\n<li>Last time I did this, I used an XML format in the external file, to allow for additional information (such as multiple group levels). Here, I&#8217;ve removed that complexity. I&#8217;ll just write exactly what I want to appear in the TOC into that file (you could gussy it up with HTML markup, I suppose).\u00a0 Of course I still have to assure access to file IO.\u00a0 We&#8217;ll assure this access with almost the same code as in the last walkthrough, only simpler, because we won&#8217;t have to assure access to the System.XML libraries.<\/li>\n<\/ol>\n<p><span style=\"color: #ffffff;\">\u00a0 .<\/span><br \/>\nReady?<\/p>\n<h2>OK, then.\u00a0 Here&#8217;s all the code.<\/h2>\n<p>Let&#8217;s start with the RDLC.\u00a0 It looks like the screen shot below\u00a0in the Designer.\u00a0 As you can see, I&#8217;ve actually put the TOC into table footer rows here.\u00a0 There&#8217;s a simple expression (<strong><span style=\"color: #ff0000;\">= Code.GetTOC()<\/span><\/strong>), that retrieves the full TOC using custom code, which you&#8217;ll see in a minute:<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" style=\"margin-right: auto; margin-left: auto; display: block;\" src=\"\/lisa\/wp-non\/migrated\/2011\/11\/TOC1.png\" alt=\"\" \/><\/p>\n<p>But in this case, what you <strong>can&#8217;t<\/strong> see is more important to what you get.<\/p>\n<p>There are two textboxes that are white-on-white in the screenshot.<\/p>\n<p>One, over which I&#8217;m hovering in the screen shot, is\u00a0in the detail row of the table.\u00a0 It provides the grouping information we&#8217;ll need to determine whether a new group has started on this page (we want our TOC to give a page number for the beginning of every group, in my example). I&#8217;ve put this in the detail band because, if it isn&#8217;t the first page of the group, the actual group\u00a0 header\u00a0row with this information might not appear on the page.<\/p>\n<p>The second white-on-white textbox is in the Page Header.\u00a0 It contains the following expression (see, I told you I was simplifying it from the last example):<\/p>\n<p class=\"code\">= Code.SetGroupPage(ReportItems , Globals!PageNumber, Globals!TotalPages)<\/p>\n<p>Turning to the custom code, here&#8217;s what we&#8217;re doing:<\/p>\n<p class=\"code\"><span style=\"color: #0000ff;\">Shared<\/span> LastGroup <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">String<\/span> = <span style=\"color: #a31515;\">&#8220;&#8221;<\/span><\/p>\n<p><span style=\"color: #0000ff;\">Public <\/span><span style=\"color: #0000ff;\">Function<\/span> SetGroupPage(<span style=\"color: #0000ff;\">ByVal<\/span> r <span style=\"color: #0000ff;\">As<\/span> ReportItems, _<br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 ByVal<\/span> pagenumber <span style=\"color: #0000ff;\">As<\/span><span style=\"color: #0000ff;\"> Integer<\/span>, <span style=\"color: #0000ff;\">ByVal<\/span> totalpages <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">Integer<\/span>) <span style=\"color: #0000ff;\">As<\/span><span style=\"color: #0000ff;\"> String<\/span><\/p>\n<p><span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 Dim<\/span> group <span style=\"color: #0000ff;\">As<\/span><span style=\"color: #0000ff;\"> String<\/span>, theFile <span style=\"color: #0000ff;\">As<\/span><span style=\"color: #0000ff;\"> String<\/span> = GetFileName()<br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; see note below; you may be passing in one or more values here and<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; sending them on to the function that derives the filename<\/span><\/p>\n<p><span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 If<\/span> pagenumber = 1 <span style=\"color: #0000ff;\">AndAlso<\/span> System.IO.<span style=\"color: #2b91af;\">File<\/span>.Exists(theFile) <span style=\"color: #0000ff;\">Then<\/span><br \/>\nSystem.IO.<span style=\"color: #2b91af;\">File<\/span>.Delete(theFile)<br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 End <\/span><span style=\"color: #0000ff;\">If<\/span><br \/>\n<span style=\"color: #0000ff;\"><br \/>\nIf<\/span> r(<span style=\"color: #a31515;\">&#8220;txtGroupItem&#8221;<\/span>) <span style=\"color: #0000ff;\">Is <\/span><span style=\"color: #0000ff;\">Nothing<\/span><span style=\"color: #0000ff;\"> Then<\/span><br \/>\ngroup = <span style=\"color: #a31515;\">&#8220;&#8221;<\/span><br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 Else<\/span><br \/>\ngroup = r(<span style=\"color: #a31515;\">&#8220;txtGroupItem&#8221;<\/span>).Value.ToString()<br \/>\n<span style=\"color: #0000ff;\">If<\/span> group &lt;&gt; LastGroup <span style=\"color: #0000ff;\">Then<\/span><br \/>\nSystem.IO.<span style=\"color: #2b91af;\">File<\/span>.AppendAllText(theFile, <span style=\"color: #a31515;\">_<br \/>\n&#8220;Group &#8220;<\/span> &amp; group &amp; <span style=\"color: #a31515;\">&#8221; starts on page &#8220;<\/span> &amp; pagenumber.ToString() &amp; vbCRLF)<br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0 \u00a0\u00a0\u00a0 End <\/span><span style=\"color: #0000ff;\">If<\/span><br \/>\nLastGroup = group<br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 End <\/span><span style=\"color: #0000ff;\">If<br \/>\n<\/span><br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 Return <\/span><span style=\"color: #a31515;\">&#8220;&#8221;<br \/>\n<\/span><br \/>\n<span style=\"color: #0000ff;\">End<\/span><span style=\"color: #0000ff;\"> Function<\/span><\/p>\n<p><span style=\"color: #0000ff;\">Function<\/span> GetFileName() <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">String<\/span><br \/>\n<span style=\"color: #0000ff;\">Return<\/span> System.IO.<span style=\"color: #2b91af;\">Path<\/span>.GetTempPath() &amp; <span style=\"color: #a31515;\">&#8220;MyTextFile.txt&#8221;<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; you could use some method to distinguish this report <\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; and this instance from others, instead of a literal file name<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; as soon here. However, make sure you will be able to determine<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; it the same way for both the reads and the writes, no matter what<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; sequence of execution is used by any renderer. <\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; If this RDLC is being used by an ASP.NET application, for example,<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; you could pass the user name and\/or userID in from the report,<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; or use a session ID that you add to the report as a parameter<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; and use that to derive the filename. You must pass it both<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; at the time you want to read and the time you want to write.<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; Another way to do it is to derive the full filename from the <\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; calling application and pass *that* into the report as a parameter.<\/span><br \/>\n<span style=\"color: #0000ff;\">End <\/span><span style=\"color: #0000ff;\">Function<\/span><\/p>\n<p><span style=\"color: #0000ff;\">Public <\/span><span style=\"color: #0000ff;\">Function<\/span> GetTOC() <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">String<\/span><br \/>\n<span style=\"color: #008000;\">&#8216; see note above; you may be passing in one or more values here and<\/span><br \/>\n<span style=\"color: #008000;\">\u00a0\u00a0\u00a0 &#8216; sending them on to the function that derives the filename<\/span><br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 Return<\/span> System.IO.<span style=\"color: #2b91af;\">File<\/span>.ReadAllText(GetFileName())<br \/>\n<span style=\"color: #0000ff;\">End <\/span><span style=\"color: #0000ff;\">Function<\/span><\/p>\n<p>That&#8217;s really all the magic.\u00a0\u00a0Now comes the code <em>outside<\/em> the report, which as you&#8217;ll see is trivial.<\/p>\n<p>Here&#8217;s what I&#8217;ve put in the load event for the form:<\/p>\n<p class=\"code\"><span style=\"color: #008000;\">\u00a0 &#8216; the following two lines are what I&#8217;ve added<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; the first one is because we can&#8217;t rely on the internal Export to give<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; us what we want, so we&#8217;ll suppress its ability to do so, and<br \/>\n&#8216; add a button to the form to explicitly Export<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; to PDF, running code twice, instead<br \/>\n<\/span><span style=\"color: #0000ff;\">\u00a0 Me<\/span>.ReportViewer1.ShowExportButton = <span style=\"color: #0000ff;\">False<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; The second line is take directly from a gotreportviewer.com sample, and<br \/>\n&#8216; looks exactly like what I did last time except I&#8217;m omitting\u00a0an additional<br \/>\n&#8216; line that dealt with System.Xml last time.<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216;\u00a0This allows the RDLC to write out and read a text file:<\/span><br \/>\n<span style=\"color: #0000ff;\">\u00a0 Me<\/span>.ReportViewer1.LocalReport.ExecuteReportInCurrentAppDomain( _<br \/>\n<span style=\"color: #2b91af;\">Assembly<\/span>.GetExecutingAssembly().Evidence)<br \/>\n<span style=\"color: #008000;\">\u00a0 &#8216; end of what I added to the form load.<\/span><\/p>\n<p>&#8230; and, as explained earlier, I have added an &#8220;Export to PDF&#8221; button to the form, so I obviously have some code under that button.\u00a0 Here it is:<\/p>\n<p class=\"code\"><span style=\"color: #0000ff;\">Private <\/span><span style=\"color: #0000ff;\">Sub<\/span> Button1_Click(sender <span style=\"color: #0000ff;\">As<\/span> System.<span style=\"color: #2b91af;\">Object<\/span>, e <span style=\"color: #0000ff;\">As<\/span> System.<span style=\"color: #2b91af;\">EventArgs<\/span>) _<br \/>\n<span style=\"color: #0000ff;\">Handles<\/span> Button1.Click<br \/>\n<span style=\"color: #0000ff;\">\u00a0 Dim<\/span> bytes <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">Byte<\/span>()<br \/>\n<span style=\"color: #008000;\">\u00a0 &#8216; the print layout renderer *should* (caveats about layout and margins belong here)<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; give us the same layout and same TOC values as the PDF will need. I don&#8217;t really<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; know if this is required, but put it here as a reminder that if the text file exists<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; because of the interactive display, from a previous refresh, it&#8217;s likely to be wrong<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; for the PDF:<br \/>\n<\/span><span style=\"color: #0000ff;\">\u00a0 Me<\/span>.ReportViewer1.SetDisplayMode( _<br \/>\nMicrosoft.Reporting.WinForms.<span style=\"color: #2b91af;\">DisplayMode<\/span>.PrintLayout)<br \/>\n<span style=\"color: #0000ff;\">\u00a0 Me<\/span>.ReportViewer1.RefreshReport()<\/p>\n<p>bytes =\u00a0<span style=\"color: #0000ff;\">Me<\/span>.ReportViewer1.LocalReport.Render(<span style=\"color: #a31515;\">&#8220;PDF&#8221;<\/span>, _<br \/>\n<span style=\"color: #0000ff;\">\u00a0 \u00a0 Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>)<\/p>\n<p><span style=\"color: #008000;\">\u00a0 &#8216; now do it again to make sure we have the right values,<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; and not the values belonging to the interactive renderer<br \/>\n<\/span><span style=\"color: #008000;\">\u00a0 &#8216; or some other renderer<br \/>\n<\/span>\u00a0 bytes =\u00a0<span style=\"color: #0000ff;\">Nothing<br \/>\n<\/span><span style=\"color: #0000ff;\">\u00a0 Me<\/span>.ReportViewer1.RefreshReport()<br \/>\nbytes =\u00a0<span style=\"color: #0000ff;\">Me<\/span>.ReportViewer1.LocalReport.Render(<span style=\"color: #a31515;\">&#8220;PDF&#8221;<\/span>, _<br \/>\n<span style=\"color: #0000ff;\">\u00a0\u00a0\u00a0 Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>, <span style=\"color: #0000ff;\">Nothing<\/span>)<\/p>\n<p><span style=\"color: #0000ff;\">\u00a0 Dim<\/span> fs <span style=\"color: #0000ff;\">As <\/span><span style=\"color: #0000ff;\">New <\/span><span style=\"color: #2b91af;\">FileStream<\/span>(<span style=\"color: #a31515;\">&#8220;c:\\temp\\test.pdf&#8221;<\/span>, <span style=\"color: #2b91af;\">FileMode<\/span>.Create)<br \/>\nfs.Write(bytes, 0, bytes.Length)<br \/>\nfs.Close()<br \/>\nfs.Dispose()<br \/>\nfs =\u00a0<span style=\"color: #0000ff;\">Nothing<br \/>\n<\/span>\u00a0 bytes = <span style=\"color: #0000ff;\">Nothing<br \/>\n<\/span><span style=\"color: #0000ff;\">End<\/span><span style=\"color: #0000ff;\">Sub<\/span><\/p>\n<p>&#8230; that&#8217;s the rest of what you need.\u00a0 Honest.\u00a0 When you push the Export button, you see the Print Layout in the report viewer, with the last page including a TOC with the right values, and the PDF last page will match this display:<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" style=\"margin-right: auto; margin-left: auto; display: block;\" src=\"\/lisa\/wp-non\/migrated\/2011\/11\/TOC2.png\" alt=\"\" \/><\/p>\n<p>If you only need\u00a0to do this for the interactive report, you&#8217;ll find it much easier: you can actually use a StringBuilder to accrue the TOC information, and then you can put the contents of the StringBuilder into the last-page pagefooter. No need to worry about external files and getting permission to read and write them, or even two runs.<\/p>\n<p>Why doesn&#8217;t it work this way for PDFs?\u00a0 Repeat after me: the renderers do things in their own way.\u00a0 Get over it.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This isn&#8217;t really a very happy Thanksgiving for C &amp; me, even though we know we have a lot to be thankful for.\u00a0\u00a0\u00a0C is currently packing for a sad trip home.\u00a0 I&#8217;m not going to be able to be with him this time and am writing this blog entry partly to keep my mind off<a class=\"more-link\" href=\"https:\/\/spacefold.com\/lisa\/2011\/11\/25\/yaps-on-tocs-for-rdlcs\/\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-55","post","type-post","status-publish","format-standard","hentry","category-reporting"],"_links":{"self":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/55","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/comments?post=55"}],"version-history":[{"count":1,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/55\/revisions"}],"predecessor-version":[{"id":493,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/posts\/55\/revisions\/493"}],"wp:attachment":[{"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/media?parent=55"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/categories?post=55"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/spacefold.com\/lisa\/wp-json\/wp\/v2\/tags?post=55"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}