RDLC issues on monitors with higher recommended scaling - c#

I'm on .net framework 4.8 in my WPF app and I have two usages on RDLC. 1st is a fully fetched ReportViewer that uses a DataTable from postgres, 2nd is just a LocalReport with small number of parameters rendered as EMF and printed directly with use of default printer.
They both have what would seem to be rendering issues, but just on monitors that have recommended scaling (RS) >100%. The outcome is squashing of letters vertically and adding some extra space in between (I can provide samples as soon as I get access to client machine again). If I just increase scaling on my 100% RS monitor, everything prints out just fine. If I replace the >100% RS monitor with a 1080p 100% RS one, again, everything prints out fine. Printouts on machines with monitors with >100% RS come out always messed up irrelevant of the scaling I set in Windows. Issues can be quickly reproduced with just 'Print Layout' view in ReportViewer, exporting to PDF produces same results.
Since I have ReportViewer and a direct printout of LocalReport I was able to try out several different approaches:
Making the app DPIAware / not aware / true/PM etc. (also included manifest, App.config and App.xaml changes)
Putting the ReportViewer in ViewBox
Using DpiX/Y and PrintDpiX/Y on DeviceInfo
ScaleTransform and DrawImageUnscaled on PrintPage callback with and without the DeviceInfo changes
countless printer options in Windows
Client machines run on either latest Windows 10 or close to latest and are rather empty otherwise.
Does it ring any bells? Any idea for potential fix?
I would love to use RDLC in my app, for the simplicity of development and usage, but those issues are really a no go for the technology.
Code
No preview printout
Used to print a single document directly without preview from parameters.
class CytologiaPrinter : IDisposable
{
private static readonly ILog log = LogManager.GetLogger(typeof(CytologiaPrinter));
private int m_currentPageIndex;
private IList<Stream> m_streams;
private int WizytaID;
private CytologiaAnkieta Cytologia;
public CytologiaPrinter(int wizytaID)
{
WizytaID = wizytaID;
}
public CytologiaPrinter(CytologiaAnkieta cytologia)
{
Cytologia = cytologia;
}
public void Print()
{
try
{
CytologiaAnkieta cytologia;
if (Cytologia == null)
{
cytologia = DBCommunication.fetchCytologia(WizytaID);
}
else
{
cytologia = Cytologia;
}
if (cytologia != null && cytologia.AnkietaNumer != null && cytologia.AnkietaNumer.Length > 0)
{
LocalReport report = new LocalReport();
var cytologie = new List<CytologiaAnkieta>();
cytologie.Add(cytologia);
ReportDataSource reportDataSource = new ReportDataSource("DataSet1", cytologie);
report.DataSources.Add(reportDataSource);
report.ReportEmbeddedResource = "Suplement.CytologiaAnkieta.rdlc";
var parameters = new List<ReportParameter>();
//parameters.Add(...); //setting all parameters omitted for demo
report.SetParameters(parameters);
m_currentPageIndex = 0;
Print(cytologia);
}
}
catch (Exception ex)
{
log.Error("Error (" + ex.Message + "), stack:" + ex.StackTrace);
}
}
private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
{
Stream stream = new MemoryStream();
m_streams.Add(stream);
return stream;
}
private void Export(LocalReport report)
{
string deviceInfo =
#"<DeviceInfo>
<OutputFormat>EMF</OutputFormat>
<PageWidth>29.7cm</PageWidth>
<PageHeight>21cm</PageHeight>
<MarginTop>1cm</MarginTop>
<MarginLeft>1cm</MarginLeft>
<MarginRight>1cm</MarginRight>
<MarginBottom>1cm</MarginBottom>
</DeviceInfo>"; //printing in landscape
Warning[] warnings;
m_streams = new List<Stream>();
report.Render("Image", deviceInfo, CreateStream,
out warnings);
if (warnings != null && warnings.Length > 0)
{
foreach (var warn in warnings)
{
log.Warn("Cytologia printing issues: " + warn.Message);
}
}
foreach (Stream stream in m_streams)
stream.Position = 0;
}
private void PrintPage(object sender, PrintPageEventArgs ev)
{
Metafile pageImage = new
Metafile(m_streams[m_currentPageIndex]);
Rectangle adjustedRect = new Rectangle(
ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,
ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,
ev.PageBounds.Width,
ev.PageBounds.Height);
ev.Graphics.FillRectangle(Brushes.White, adjustedRect);
ev.Graphics.DrawImage(pageImage, adjustedRect);
m_currentPageIndex++;
ev.HasMorePages = m_currentPageIndex < m_streams.Count;
}
private void Print(CytologiaAnkieta cytologia)
{
if (m_streams == null || m_streams.Count == 0)
throw new Exception("Error: no stream to print.");
PrintDocument printDoc = new PrintDocument();
printDoc.DefaultPageSettings.Landscape = true;
if (!printDoc.PrinterSettings.IsValid)
{
throw new Exception("Error: cannot find the default printer.");
}
else
{
printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
m_currentPageIndex = 0;
printDoc.Print();
}
}
public void Dispose()
{
if (m_streams != null)
{
foreach (Stream stream in m_streams)
stream.Close();
m_streams = null;
}
}
}
Preview WinForms
Xaml
xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"
...
<WindowsFormsHost DockPanel.Dock="Bottom" Margin="0 0 0 0" >
<rv:ReportViewer x:Name="RVDemo"/>
</WindowsFormsHost>
C# code part
private void RaportGenerate_Click(object sender, RoutedEventArgs e)
{
RVDemo.Reset();
ReportDataSource reportDataSource = new ReportDataSource("Ankiety", DBCommunication.fetchCytologiaAnkietyReport(...));
RVDemo.LocalReport.DataSources.Add(reportDataSource);
RVDemo.LocalReport.ReportEmbeddedResource = "Suplement.Cytologie.rdlc";
var parameters = new List<ReportParameter>();
//parameters.Add(...); // omitted for demo
RVDemo.LocalReport.SetParameters(parameters);
RVDemo.RefreshReport();
}

If there are no fixes available for RDLC scaling issue on WPF app.
One possible workaround would be migrating the file rendering part to Web version of RDLC, which would ignore screen DPI (as far as I know).
You'll need extra resource to develop this.
But a few generic functions would be enough, in most cases.
Then your reports should be able to rendered with consistence scaling.
(You may not need addtional project library for ReportViewer.WebForms if the library Microsoft.ReportViewer.Common version can be used by both WebForms and WinForms ReportViewer.)
Here's the possible solution:
1) Add a Library Project to your WPF solution
The solution should use .NET Framework 4+. It would look something like this.
2) Download the WebForm version of RDLC to the new library through NuGet
Look for Microsoft.ReportViewer.WebForms by Microsoft.
Correct version of Microsoft.ReportViewer.Common will be installed for you as dependence.
3) Create the code for Rendering through Web Version of RDLC
Create a static class for WDF project to use, this is a very simple sample for you to test if it works, before you continuing on.
Copy this class in the "RLDCRendering" Project:
using Microsoft.Reporting.WebForms;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RDLCRendering
{
public class RDLCRender
{
public static byte[] RenderReport(String reportPath, DataTable data)
{
Microsoft.Reporting.WebForms.ReportDataSource rdc1 =
new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1", data);
Microsoft.Reporting.WebForms.ReportViewer v1 = new Microsoft.Reporting.WebForms.ReportViewer();
v1.LocalReport.DataSources.Clear();
v1.LocalReport.ReportPath = reportPath;
v1.LocalReport.DataSources.Add(rdc1);
return v1.LocalReport.Render(format: "PDF", deviceInfo: "");
}
}
}
The project would looks like this:
4) Hide the WPF version's report print button
Hide the Print /Save button with this example code so users would not use the faulted rendering method
ReportViewer.ShowPrintButton = false;
ReportViewer.ShowExportButton = false;
Add a print button on your WDF page, how you do it is up to you.
The end result is something like this:
Add a callback when the button is clicked, then provide all the needed data source, report path, output path to the library we created.
The follow is a sample code for you:
string connString = "Server=someIP;Database=catalogName;User Id=uid;Password=pwd";
SqlConnection sqlConn = new SqlConnection(connString);
SqlDataAdapter sqlDA = new SqlDataAdapter("select top 100 * from samplesData", sqlConn);
DataTable dt= new DataTable();
sqlDA.Fill(dt);
//Important
Byte[] bytes = RDLCRendering.RDLCRender.RenderReport(#"the path to the report teamplate\Report1.rdlc", dt);
using (FileStream stream = new FileStream(#"C:\test\test.pdf", FileMode.Create))
{
stream.Write(bytes, 0, bytes.Length);
}
5) Validate
You may change the output path as you need. When the button is clicked, a PDF file should be rendered and saved under the location you specified. In my sample, it is in C:\test\test.pdf.
If this solution works for you, you may continue to add parameters and etc. to the/other rendering function byte[] RenderReport.
Then handle the returned byte file by sending it to printer or save to some local folder and open with other applications.

The problem is well known one, it affects performance as well. The reason is simple:
RDLC was created to deliver simple reports such as receipt or invoice. anything more then that is going to cause you alot of issues and alot of headache.
There are simple solution offered by Microsoft technical support across the web:
Change the system DPI settings
Change the font on report
Make the report viewer adapt to higher resolution automatically.
But all of them just ignore the simple fact, RDLC was never meant for big reports or higher resolutions.
Only for documents such as invo
ice or receipt which are modest and with small amount of details to address

Related

How to generate new PDF using PDFLib 9 in C#?

I am trying to create a new, blank PDF document using PDFLib 9 in my .Net project.
I've looked at some of the tutorials and documentation but could not get it working.
Here is the code I have in a unit test:
public void Test()
{
try
{
var outfile = "newPDF.pdf";
const string docOption = "searchpath={C:\\Users\\me\\Desktop\\Test_Pdfs}";
var p = new PDFlib();
p.set_option(docOption);
p.set_option("errorpolicy=return");
var x = p.begin_document(outfile, "");
if (x != -1)
{
p.begin_page_ext(595.0, 842.0, "topdown");
p.end_page_ext("");
p.end_document("");
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
}
I don't get any errors in the catch, and the test does not fail.
However, when I enable CLR exceptions I get a number of CommunicationAbortedException's and InvalidOperationExceptions that basically talk about how some connection was closed.
This all happens after the last bracket. Also, the PDF is simply not created.
Any insight is really appreciated!
looks like something wrong with searchpath.
I commented out your docOption and PDF is created. this should give you some clue.

Capture click events inside a WPF WebBrowser control showing a PDF document

I'm developing a Windows WPF application that uses the default WebBrowser control (System.Windows.Controls.WebBrowser) to embed web pages. Using the COM object that underlies the WPF control, I am able to manipulate as needed every HTML document loaded inside the control. Just as an example, here is a snippet of the code I use to get a handle of the COM object:
public void HookInputElementForKeyboard()
{
HTMLDocument htmlDocument = (HTMLDocument)webBrowserControl.Document;
var inputElements = htmlDocument.getElementsByTagName("input");
HTMLDocumentEvents_Event documentEvents = (HTMLDocumentEvents_Event) htmlDocument;
documentEvents.onclick += documentEvents_onclick;
DeregisterAll();
foreach (var item in inputElements)
{
DispHTMLInputElement inputElement = item as DispHTMLInputElement;
if (inputElement.type == "text" || inputElement.type == "password" || inputElement.type == "search")
{
HTMLButtonElementEvents_Event htmlButtonEvent = inputElement as HTMLButtonElementEvents_Event;
this.hookedElements.Add(htmlButtonEvent);
htmlButtonEvent.onclick += InputOnClickHandler;
htmlButtonEvent.onblur += InputOnBlurHandler;
}
}
}
Where I use dependencies from Microsoft.mshtml.dll.
Here I attach handlers to the events of the DOM elements to be able to manage them in .NET code (onclick and onblur events).
With that object (HTMLDocument) I can overcome almost every limitation of the WPF control.
My problem arises when the WebBrowser control navigates to a PDF document (i.e. the response MIME type is application/pdf). In this case I have to assume that the default Adobe Reader plugin is used to show the PDF (this is how my target machine has to behave). I can still get a handle of the underlying AcroRead ActiveX that is used with the following:
private void HookHtmlDocumentOnClick()
{
var document = (IAcroAXDocShim)WebBrowserControl.Document;
// Just a test
var obj = document as AcroPDF;
obj.OnMessage += obj_OnMessage;
}
After I added the proper dependencies to the project (Interop.AcroPDFLib.dll)
But at this point I do not know if there is a way to register for the mouse events happening on the document. All I have to do is to handle the click event on the PDF document.
of course, using the following, does not work. The event does not bubble up to the .NET code level.
WebBrowserControl.MouseDown += WebBrowserControl_MouseDown;
Does Anybody know if there is a way to hook the IAcroAXDocShim in order to do handle mouse-click events?
Any possible alternative? Should I rather go on a complete different path?
Does using directly the AcroRead ActiveX give me some advantages over the current scenario?
I ended up following a different approach. I decided to switch to a different UserControl, instead of the WPF WebBrowser, whenever a PDF content is recognized.
I knew that some classes and API for PDF rendering are available when you develop a Windows Store App, so I applied a solution in order to use those classes in my WPF application.
First I edited the csproj file adding the following tag as a child of <PropertyGroup>:
<TargetPlatformVersion>8.1</TargetPlatformVersion>
then a section named "Windows 8.1" should appear in the Add Reference dialog window. I added the only assembly of this section to the references. Some more assemblies may be necessary in order to make the solution work:
System.IO
System.Runtime
System.Runtime.InteropServices
System.Runtime.InteropServices.WindowsRuntime
System.Runtime.WindowsRuntime
System.Threading
System.Threading.Tasks
Once I had those, I wrote a render method that goes through the pages of the PDF file and creates a png file for each page. Then I added the pages to a StackPanel. At this point, you can manage the Click (or MouseDown) event on the StackPanel as you normally would on any UIElement.
Here is a brief example of the rendering methods:
async private void RenderPages(String pdfUrl)
{
try
{
HttpClient client = new HttpClient();
var responseStream = await client.GetInputStreamAsync(new Uri(pdfUrl));
InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream();
using (responseStream)
{
await responseStream.AsStreamForRead().CopyToAsync(inMemoryStream.AsStreamForWrite());
}
var pdf = await PdfDocument.LoadFromStreamAsync(inMemoryStream);
if (pdf != null && pdf.PageCount > 0)
{
AddPages(pdf);
}
}
catch (Exception e)
{
throw new CustomException("An exception has been thrown while rendering PDF document pages", e);
}
}
async public void AddPages(PdfDocument pdfDocument)
{
PagesStackPanel.Children.Clear();
if (pdfDocument == null || pdfDocument.PageCount == 0)
{
throw new ArgumentException("Invalid PdfDocument object");
}
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
StorageFolder rootFolder = await StorageFolder.GetFolderFromPathAsync(path);
var storageItemToDelete = await rootFolder.TryGetItemAsync(Constants.LOCAL_TEMP_PNG_FOLDER);
if (storageItemToDelete != null)
{
await storageItemToDelete.DeleteAsync();
}
StorageFolder tempFolder = await rootFolder.CreateFolderAsync(Constants.LOCAL_TEMP_PNG_FOLDER, CreationCollisionOption.OpenIfExists);
for (uint i = 0; i < pdfDocument.PageCount; i++)
{
logger.Info("Adding PDF page nr. " + i);
try
{
await AppendPage(pdfDocument.GetPage(i), tempFolder);
}
catch (Exception e)
{
logger.Error("Error while trying to append a page: ", e);
throw new CustomException("Error while trying to append a page: ", e);
}
}
}

How can I use google text to speech api in windows form?

I want to use google text to speech in my windows form application, it will read a label. I added System.Speech reference. How can it read a label with a button click event?
http://translate.google.com/translate_tts?q=testing+google+speech This is the google text to speech api, or how can I use microsoft's native text to speech?
UPDATE Google's TTS API is no longer publically available. The notes at the bottom about Microsoft's TTS are still relevant and provide equivalent functionality.
You can use Google's TTS API from your WinForm application by playing the response using a variation of this question's answer (it took me a while but I have a real solution):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.FormClosing += (sender, e) =>
{
if (waiting)
stop.Set();
};
}
private void ButtonClick(object sender, EventArgs e)
{
var clicked = sender as Button;
var relatedLabel = this.Controls.Find(clicked.Tag.ToString(), true).FirstOrDefault() as Label;
if (relatedLabel == null)
return;
var playThread = new Thread(() => PlayMp3FromUrl("http://translate.google.com/translate_tts?q=" + HttpUtility.UrlEncode(relatedLabel.Text)));
playThread.IsBackground = true;
playThread.Start();
}
bool waiting = false;
AutoResetEvent stop = new AutoResetEvent(false);
public void PlayMp3FromUrl(string url)
{
using (Stream ms = new MemoryStream())
{
using (Stream stream = WebRequest.Create(url)
.GetResponse().GetResponseStream())
{
byte[] buffer = new byte[32768];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
}
ms.Position = 0;
using (WaveStream blockAlignedStream =
new BlockAlignReductionStream(
WaveFormatConversionStream.CreatePcmStream(
new Mp3FileReader(ms))))
{
using (WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback()))
{
waveOut.Init(blockAlignedStream);
waveOut.PlaybackStopped += (sender, e) =>
{
waveOut.Stop();
};
waveOut.Play();
waiting = true;
stop.WaitOne(10000);
waiting = false;
}
}
}
}
}
NOTE: The above code requires NAudio to work (free/open source) and using statements for System.Web, System.Threading, and NAudio.Wave.
My Form1 has 2 controls on it:
A Label named label1
A Button named button1 with a Tag of label1 (used to bind the button to its label)
The above code can be simplified slightly if a you have different events for each button/label combination using something like (untested):
private void ButtonClick(object sender, EventArgs e)
{
var clicked = sender as Button;
var playThread = new Thread(() => PlayMp3FromUrl("http://translate.google.com/translate_tts?q=" + HttpUtility.UrlEncode(label1.Text)));
playThread.IsBackground = true;
playThread.Start();
}
There are problems with this solution though (this list is probably not complete; I'm sure comments and real world usage will find others):
Notice the stop.WaitOne(10000); in the first code snippet. The 10000 represents a maximum of 10 seconds of audio to be played so it will need to be tweaked if your label takes longer than that to read. This is necessary because the current version of NAudio (v1.5.4.0) seems to have a problem determining when the stream is done playing. It may be fixed in a later version or perhaps there is a workaround that I didn't take the time to find. One temporary workaround is to use a ParameterizedThreadStart that would take the timeout as a parameter to the thread. This would allow variable timeouts but would not technically fix the problem.
More importantly, the Google TTS API is unofficial (meaning not to be consumed by non-Google applications) it is subject to change without notification at any time. If you need something that will work in a commercial environment I'd suggest either the MS TTS solution (as your question suggests) or one of the many commercial alternatives. None of which tend to be even this simple though.
To answer the other side of your question:
The System.Speech.Synthesis.SpeechSynthesizer class is much easier to use and you can count on it being available reliably (where with the Google API, it could be gone tomorrow).
It is really as easy as including a reference to the System.Speech reference and:
public void SaySomething(string somethingToSay)
{
var synth = new System.Speech.Synthesis.SpeechSynthesizer();
synth.SpeakAsync(somethingToSay);
}
This just works.
Trying to use the Google TTS API was a fun experiment but I'd be hard pressed to suggest it for production use, and if you don't want to pay for a commercial alternative, Microsoft's solution is about as good as it gets.
I know this question is a bit out of date but recently Google published Google Cloud Text To Speech API.
.NET Client version of Google.Cloud.TextToSpeech can be found here:
https://github.com/jhabjan/Google.Cloud.TextToSpeech.V1
Here is short example how to use the client:
GoogleCredential credentials =
GoogleCredential.FromFile(Path.Combine(Program.AppPath, "jhabjan-test-47a56894d458.json"));
TextToSpeechClient client = TextToSpeechClient.Create(credentials);
SynthesizeSpeechResponse response = client.SynthesizeSpeech(
new SynthesisInput()
{
Text = "Google Cloud Text-to-Speech enables developers to synthesize natural-sounding speech with 32 voices"
},
new VoiceSelectionParams()
{
LanguageCode = "en-US",
Name = "en-US-Wavenet-C"
},
new AudioConfig()
{
AudioEncoding = AudioEncoding.Mp3
}
);
string speechFile = Path.Combine(Directory.GetCurrentDirectory(), "sample.mp3");
File.WriteAllBytes(speechFile, response.AudioContent);

.RDLC Report 2008 (in VS 2010) ReportViewer appears with no report or data

Working with .RDLC 2005 in VS 2008 this technique worked very well, now in .RDLC 2008 as implemented in VS 2010 I get a blank (or no?) report.
I have made a couple of changes to accommodate .RDLC 2008 and at this time I am getting no exceptions. The present (not desired) output looks like:
I have a custom ReportController class that has a public method to ShowReport (also one to manage the exporting of reports, but that is not (yet) in play.)
From the asp.net page I invoke the controller in the property set (of Type DataSet, invoked by the page controller) like: (ReportController implements IDisposable)
try
{
using (var reportController = new ReportController(true))
{
_ReportViewer = reportController.ShowReport("DemonstrationList", value, phReportHolder);
if (_ReportViewer != null)
{
_ReportViewer.ShowRefreshButton = false;
_ReportViewer.ShowPrintButton = false;
_ReportViewer.Width = Unit.Pixel(700);// Unit.Percentage(99);
_ReportViewer.Height = Unit.Pixel(700);// Unit.Percentage(90);
}
}
lblRecordCount.InnerText = value.Tables[0].Rows.Count.ToString();
}
catch (Exception ex)
{
phReportHolder.InnerHtml = string.Format("There was an error attempting to process this report <br/><br/><div style='color:White;'>{0}</div>", ex.Message);
}
and the ShowReport method is:
public ReportViewer ShowReport(string ReportName, DataSet ds, HtmlContainerControl ReportContainer)
{
ReportContainer.Controls.Clear();
ReportViewer reportViewer = BuildReport(ReportName, ds);
ReportContainer.Controls.Add(reportViewer);
return reportViewer;
}
This allows me to tell the controller to put any 'valid' report into any htmlcontainercontrol using any provided dataset.
BuildReport takes the data and the report name and builds the report as:
private ReportViewer BuildReport(string ReportName, DataSet ds)
{
try
{
_activeDS = ds;
string ReportFileName = ResolveRDLCName(ReportName);
// ResolveRDLCName is used along with path strings
// initialized from configuration settings in the
// constructor to make this portable.
var viewer = new ReportViewer();
viewer.ProcessingMode = ProcessingMode.Local;
viewer.LocalReport.ReportPath = ReportFileName;
viewer.LocalReport.DisplayName = ReportName;
viewer.LocalReport.EnableHyperlinks = true;
AssignReportData(ds, viewer.LocalReport);
return viewer;
}
//...Exception handlers below are not invoked at this time
And 'AssignReportData' attaches the data to the report.
private static void AssignReportData(DataSet ds, LocalReport Report)
{
var listOfDatasources = Report.GetDataSourceNames();
foreach (string dsn in listOfDatasources)
{
ReportDataSource rds = new ReportDataSource(dsn,ds.Tables[dsn]);
Report.DataSources.Add(rds);
}
}
Development techniques ensure that dataTable/dataSource names stay in agreement (and if they were not I would get a specific exception, which I do not.)
I was having a similar problem which this blog post answered. Short answer is I needed to install the report viewer redistributable, and add the handler.
It seems like the report content gets rendered but is simply not visible.
Try to look at the generated HTML (DOM) with
Chrome: right-click on the report area, "Inspect Element" to explore the DOM
Internet Explorer: install the IE Developer Toolbar to explore the DOM
Maybe some CSS that has worked in the past now hides your report area.

How can I serialize a DevExpress XtraReport report design

I need to serialize a report design. This is the scenario:
The app has base reports, let's say "Sales Report" with a set of pre-defined columns and design, like the corp. logo in the header. The users needs to have the ability to change that layout adding, for example, a footer with the office address, or page numbers. For doing that they need to edit the report, enter the designer and add/change what they need. This changed report layout needs to be serialized to be stored in the database for that user, so the next time, the user opens that report, using that design.
Makes sense?
Here's a simplified version of how I do this:
XtraReport customReport;
customReport = new MyXtraReport();
byte[] layout = LoadCustomLayoutFromDB();
if (layout != null) {
using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(layout)) {
customReport.LoadLayout(memoryStream);
}
}
using (XRDesignFormEx designer = new XRDesignFormEx()) {
MySaveCommandHandler customCommands = new MySaveCommandHandler(designer.DesignPanel);
designer.DesignPanel.AddCommandHandler(customCommands);
designer.OpenReport(customReport);
designer.ShowDialog(this);
if (customCommands.ChangesSaved)
SaveCustomLayoutToDB(customCommands.Layout);
}
Inside MySaveCommandHandler class:
public virtual void HandleCommand(ReportCommand command, object[] args, ref bool handled) {
if (command != ReportCommand.SaveFileAs && command != ReportCommand.SaveFileAs)
return;
using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream()) {
panel.Report.SaveLayout(memoryStream);
this.layout = memoryStream.ToArray();
changesSaved = true;
}
panel.ReportState = ReportState.Saved;
handled = true;
}
I think what you are looking for is the SaveLayout method:
Saving the report
YourReport report = new YourReport();
// Save the layout to a file.
report.SaveLayout(#"C:\YourReport.repx");
Loading the report
YourReport report = new YourReport();
// Load the layout
report.LoadLayout(#"C:\YourReport.repx");
Edit:
here a link to the devexpress support site explaining how to save the report definition.
You can Save/Load to and from a stream using Save and LoadLayout overrides. For the designer you can add a command handler to intercept the save command.
These articles should cover what you need:
How to: Save and Restore a Report Definition from a Stream
How to: Override Commands in the End-User Designer (Custom Saving)
And for completeness: List of all how-to's
Edit: fixed links

Categories