I'm using the WebBrowser class to open document, change values, save and print. The problem is, that it prints the document including the header("Page 1 of 1") and footer(root of the document + date)
I looked at the documentation and didn't find a way to remove them. Is it even possible using WebBrowser or should I look for alternatives?
There is a solution, probably not as cleaner as it could have been. Since WebBrowser inhertis it's settings from Internet Explorer it is possible to change the values in the registry. Luckily the values are under HKCU so no administration permissions are needed.
Take a look at https://stackoverflow.com/a/1321314/1630928
The trick to doing this is to pass a Variant containing a ByRef SafeArray of Variants to the WebBrowser control. I haven't figured out how to do it from C#. Here's someone else who was working on the same problem who resorted to using managed C++
http://www.limilabs.com/blog/printing-in-webbrowser-control-custom-header-and-footer
Not a C#, but here's C++ code that I came up with based on a now defunct KB267240. It will remove the header and the footer while printing:
BOOL bRes = FALSE;
//Get IWebBrowser2 from your IE control
CComPtr<IWebBrowser2> pWebBrowser = this->GetIWebBrowser2();
if(pWebBrowser)
{
HRESULT hr;
COleVariant varNull;
SAFEARRAYBOUND psabBounds[1];
SAFEARRAY *psaHeadFoot;
hr = S_OK;
VARIANT vArg;
BOOL bGot_vArg = FALSE;
VARIANT vHeadStr, vFootStr;
long rgIndices;
VariantInit(&vHeadStr);
VariantInit(&vFootStr);
// Initialize header and footer parameters to send to ExecWB().
psabBounds[0].lLbound = 0;
psabBounds[0].cElements = 3;
psaHeadFoot = SafeArrayCreate(VT_VARIANT, 1, psabBounds);
if(psaHeadFoot)
{
// Argument 1: Header
vHeadStr.vt = VT_BSTR;
vHeadStr.bstrVal = SysAllocString(L" "); //Must be at least one space
if (vHeadStr.bstrVal)
{
// Argument 2: Footer
vFootStr.vt = VT_BSTR;
vFootStr.bstrVal = SysAllocString(L" "); //Must be at least one space
if(vFootStr.bstrVal)
{
rgIndices = 0;
SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vHeadStr));
rgIndices = 1;
SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vFootStr));
rgIndices = 2;
SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&varNull)); //Set stream to NULL as we don't need it
//NOTE: Currently, the SAFEARRAY variant must be passed by using
// the VT_BYREF vartype when you call the ExecWeb method.
VariantInit(&vArg);
vArg.vt = VT_ARRAY | VT_BYREF;
vArg.parray = psaHeadFoot;
//Got it
bGot_vArg = TRUE;
}
}
}
//Did we get all the vars?
if(bGot_vArg)
{
if(SUCCEEDED(hr = pWebBrowser->ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, &vArg, NULL)))
{
//All good
bRes = TRUE;
}
}
else
{
//Use fallback (that will keep the footer & header)
if(SUCCEEDED(hr = pWebBrowser->ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, varNull, varNull)))
{
//Printed via fallback
bRes = TRUE;
}
}
//Clean up
VariantClear(&vHeadStr);
VariantClear(&vFootStr);
if(psaHeadFoot)
{
SafeArrayDestroy(psaHeadFoot);
psaHeadFoot = NULL;
}
}
Related
Background: I'm trying to write a program to insert an image into a cell of a spreadsheet. LibreOffice recently changed how this is done, and all the samples I could find use the old method which no longer works.
Technically I know that you can't "insert" an image into a cell and that such an image is an overlay on a DrawPage that sits on top of the spreadsheet to "decorate" it.
One of the first steps in doing this (the new way) is to create an XGraphic object which contains the image. The process is to create an XGraphicProvider and call it with MediaProperties that specify the image file URL to be loaded. I have a program that is supposed to do this but the resulting XGraphic is null. The LO SDK gives pretty much no information when you do something wrong; it just doesn't work.
Here is the code I have, with all the headers removed:
// addpic
// add picture to spreadsheet - debug version
class OpenOfficeApp {
[STAThread]
static void Main(string[] args) {
bool lreadonly;
string pqfile;
string pqURL;
string pqpic;
pqfile = "file:///D:/Documents/NSexeye/ODS%20File%20Access/"+
"addpix/addpic.ods";
pqpic = "addpic2";
pqURL = pqpic+".jpg";
lreadonly = false;
Console.WriteLine("Using: "+pqfile);
// get the desktop
XComponentContext XCC = uno.util.Bootstrap.bootstrap();
XMultiComponentFactory XMCF =
(XMultiComponentFactory)XCC.getServiceManager();
XMultiServiceFactory XMSF = (XMultiServiceFactory)XCC.getServiceManager();
XComponentLoader XCL =
(XComponentLoader)XMSF.createInstance("com.sun.star.frame.Desktop");
// open the spreadsheet
PropertyValue[] pPV = new PropertyValue[2];
pPV[0] = new PropertyValue();
pPV[0].Name = "Hidden";
pPV[0].Value = new uno.Any(true);
pPV[1] = new PropertyValue();
pPV[1].Name = "ReadOnly";
if (lreadonly) pPV[1].Value = new uno.Any(true);
else pPV[1].Value = new uno.Any(false);
XComponent XCo = XCL.loadComponentFromURL(pqfile,"_blank",0,pPV);
// create graphic object containing image
object oGP = XMCF.createInstanceWithContext(
"com.sun.star.graphic.GraphicProvider",XCC);
if (oGP == null) {
Console.WriteLine("oGP is null. Aborting.");
return;
}
XGraphicProvider XGP = (XGraphicProvider)oGP;
if (XGP == null) {
Console.WriteLine("XGP is null. Aborting.");
return;
}
pPV = new PropertyValue[1];
pPV[0] = new PropertyValue();
pPV[0].Name = "URL";
pPV[0].Value = new uno.Any(pqURL);
Console.WriteLine("Creating XGraphic containing "+pqURL);
XGraphic XG = XGP.queryGraphic(pPV);
// *** XG is null here
if (XG == null) {
Console.WriteLine("XG is null. Aborting.");
return;
}
// ... lots of stuff to be added here
// save and close the spreadsheet
XModifiable XM = (XModifiable)XCo;
XM.setModified(true);
XStorable XSt = (XStorable)XCo;
XSt.store();
XCloseable XCl = (XCloseable)XCo;
XCl.close(true);
// terminate LibreOffice
// *** I want this to not terminate it if something else is open
XDesktop XD = (XDesktop)XCL;
if (XD != null) XD.terminate();
}
}
I get a null for the XGraphic, in the place indicated in the comments. I don't know if the call to create it is failing, or if one of the earlier steps of the process are incorrect.
My goal here, in addition to getting my program working, is to create a sample program showing how to add an image to a Calc spreadsheet cell, and to manipulate such images. There are a fair number of people asking questions about this and none of the examples I've found will work. I think a good working sample will be of value.
I've spent a lot of time searching for information and code samples for this, with nothing that helps. I've tried to find ways to verify the validity of the XGraphicProvider interface with no luck. I've run out of things to try.
I'm hoping someone who knows about the LibreOffice SDK can take a look and maybe see what I'm doing wrong.
Update: I figured out what I was doing wrong: I was passing a bare filename in the "URL" property to XGraphicProvider. It has to be the same format (starting with "file:///") as the spreadsheet's file name specification.
Now I'm stuck with another property problem. The XGraphic has to be specified as a parameter to the GraphicObjectShape's Graphic property, but the setPropertyValue() function requires that it be a uno.Any type. I can't figure out how to specify an interface name like XGraphic as a uno.Any.
Here is the piece of code that won't compile, complaining that it can't convert an XGraphic to a uno.Any, in the first setPropertyValue call:
// set image XGraphic
XPropertySet XPS = (XPropertySet)XS;
XPS.setPropertyValue("Graphic",XG);
XPS.setPropertyValue("Name",new uno.Any(pqpic));
XG is an XGraphic type. Using "new uno.Any(XG)" doesn't work either, giving a similar compiler error.
After trying unsuccessfully for a few hours to get the latest LO SDK up and running, let me offer some untested ideas.
First of all, here is some working Basic code, no doubt similar to what you're translating from. The important line is oShape.Graphic = oProvider.queryGraphic(Props()).
oDoc = ThisComponent
oSheet = oDoc.CurrentController.ActiveSheet
pqURL = "file:///C:/Users/JimK/Desktop/addpic.jpg"
oProvider = createUnoService("com.sun.star.graphic.GraphicProvider")
oShape = oDoc.createInstance("com.sun.star.drawing.GraphicObjectShape")
Dim Props(0) as new com.sun.star.beans.PropertyValue
Props(0).Name= "URL"
Props(0).Value = pqURL
oShape.Graphic = oProvider.queryGraphic(Props())
oCell = oSheet.getCellByPosition(5,5)
oShape.Name = oCell.AbsoluteName + "##" + Props(0).Value
oShape.Anchor = oCell
oSheet.DrawPage.add(oShape)
'Resize
w = oShape.Graphic.Size.Width
h = oShape.Graphic.Size.Height
wcl = oCell.Size.Width
hcl = oCell.Size.Height
If w<>0 and h<>0 then
oCell.String=""
Dim Size as new com.sun.star.awt.Size
Size.Width = wcl
Size.Height = h*wcl/w
If Size.Height > hcl then
Size.Width = hcl*w/h
Size.Height = hcl
Endif
oShape.setSize(Size)
oShape.setPosition(oCell.Position)
erase oShape
Else
oShape.dispose()
Endif
Now, how to translate this to C#? It looks like you may need to explicitly specify the type. In the SDK example, there are calls like this.
xFieldProp.setPropertyValue(
"Orientation",
new uno.Any(
typeof (unoidl.com.sun.star.sheet.DataPilotFieldOrientation),
unoidl.com.sun.star.sheet.DataPilotFieldOrientation.DATA ) );
So in your case, something like this:
XPS.setPropertyValue(
"Graphic"
new uno.Any(
typeof(unoidl.com.sun.star.graphic.XGraphic),
XG));
Alternatively, follow the suggestion here: set GraphicURL, which should load the image and set Graphic for you.
I'm making a MS-Word Addin with some features. One of them is to remove excessive blank lines (the current rule says that the Document can't have more than two sequential blank lines and that it can't have blank lines after the last line of text).
I've made a code to try to achieve this:
private void formatText() {
Microsoft.Office.Interop.Word.Paragraphs paragraphs = Globals.ThisAddIn.Application.ActiveDocument.Paragraphs;
Boolean isPreviousLineEmpty = false;
Boolean isLastLine = true;
for (int i = paragraphs.Count - 1; i > 0; i--) {
Microsoft.Office.Interop.Word.Paragraph paragraph = paragraphs[i];
if (paragraph.Range.Text.Trim().Equals("")) {
if (isLastLine) {
paragraph.Range.Delete();
continue;
}
if (isPreviousLineEmpty) {
paragraph.Range.Delete(); //This is the line where the error happens
}
isPreviousLineEmpty = true;
continue;
}
if (isLastLine) {
paragraph.Range.Text = paragraph.Range.Text.TrimEnd();
isLastLine = false;
}
isPreviousLineEmpty = false;
}
}
It was working untill I've added a "Table of Contents" (TOC) to the document. Now I get an error:
System.Runtime.InteropServices.COMException: 'Cannot edit Range.'
The reason is: there is a white line on the TOC that my code is trying to remove, and it can't. I've searched the documentation/internet and tried everything I could think of to be able to prevent my code from running on TOC lines, but nothing worked.
I need a way to know that I can skip that line, because I don't need to delete blank lines inside TOCs.
For the moment, what I can do is wrap the specific line who executes the deletion with a Try/Catch block, but I don't think this is the best solution (for I may be letting other errors go unnoticed, this is just a silencer).
Does anyone know the correct approach to this case?
UPDATE:
Following Freeflow comment I replaced all my method code with this:
private void formatText() {
Microsoft.Office.Interop.Word.Find find = Globals.ThisAddIn.Application.ActiveDocument.Range().Find;
Microsoft.Office.Interop.Word.Paragraphs paragraphs = Globals.ThisAddIn.Application.ActiveDocument.Paragraphs;
Boolean operationResult = true;
//Remove blank lines at the end of the document
for (int i = paragraphs.Count - 1; i > 0; i--) {
Microsoft.Office.Interop.Word.Paragraph paragraph = paragraphs[i];
if (paragraph.Range.Text.Trim().Equals("")) {
paragraph.Range.Delete();
continue;
}
paragraph.Range.Text = paragraph.Range.Text.TrimEnd();
break;
}
//Remove blank lines between paragraphs
while (operationResult) {
operationResult = find.Execute("^p^p^p", false, false, false, false, false, false, null, null, "^p^p",
Microsoft.Office.Interop.Word.WdReplace.wdReplaceAll);
}
}
It has been working very well untill this moment. If any problem comes up, I'll post here.
Thanks for your comment. If you transform it in an answer I'll mark as the accepted one.
The Word object model has a useful method: InRange, which allows checking whether one range of text is part of another. Logically, then, it's possible to compare whether a paragraph's location is within a TOC.
Below is a test example, originally written in VBA. I'm converting it on-the-fly to C#, so there may be some minor syntax errors...
public void TestRangeInToc()
{
Word.Document doc =Globals.ThisAddIn.Application.ActiveDocument;
bool HasToc = false;
if(doc.TablesOfContents.Count > 0)
{
HasToc = true;
}
foreach(Word.Paragraph para In doc.Paragraphs)
{
if(HasToc)
{
if(IsRangeInTOC(para.Range, doc))
{
Debug.Print("in range");
//skip this one
}
}
}
}
public bool IsRangeInTOC(Word.Range rng, Word.Document doc)
{
Word.TableOfContents toc
foreach(toc in doc.TablesOfContents)
{
if(rng.InRange(toc.Range))
{
return true;
break;
}
}
}
I need to delete a text from a PDF document. I am using Aspose for the purpose
am currently using TextFragmentAbsorber.
FYI, I cannot use any other 3rd party library.
Below is the code I am using :
private string DeleteMachineReadableCode(string inputFilePath)
{
var outputFilePath = Path.Combine(Path.GetTempPath(), string.Format(#"{0}.pdf", Guid.NewGuid()));
try
{
// Open document
Document pdfDocument = new Document(inputFilePath);
// Create TextAbsorber object to find all the phrases matching the regular expression
TextFragmentAbsorber textFragmentAbsorber = new TextFragmentAbsorber("#START#((.|\r\n)*?)#END#");
// Set text search option to specify regular expression usage
TextSearchOptions textSearchOptions = new TextSearchOptions(true);
textFragmentAbsorber.TextSearchOptions = textSearchOptions;
// Accept the absorber for all pages
pdfDocument.Pages.Accept(textFragmentAbsorber);
// Get the extracted text fragments
TextFragmentCollection textFragmentCollection = textFragmentAbsorber.TextFragments;
// Loop through the fragments
foreach (TextFragment textFragment in textFragmentCollection)
{
// Update text and other properties
textFragment.Text = string.Empty;
// Set to an instance of an object.
textFragment.TextState.Font = FontRepository.FindFont("Verdana");
textFragment.TextState.FontSize = 1;
textFragment.TextState.ForegroundColor = Aspose.Pdf.Color.FromRgb(System.Drawing.Color.White);
textFragment.TextState.BackgroundColor = Aspose.Pdf.Color.FromRgb(System.Drawing.Color.White);
}
pdfDocument.Save(outputFilePath);
}
finally
{
if (File.Exists(inputFilePath))
File.Delete(inputFilePath);
}
return outputFilePath;
}
I am able to replace the content if the content to be deleted is on a single page.
My problem is that if the text spans over multiple pages the TextFragmentAbsorber does not recognize the text with the mentioned regex pattern ("#START#((.|\r\n)*?)#END#").
Please suggest if anything can be done on the regex or the some setting in Aspose can fix my issue.
As shared earlier, we can not promise earlier resolution of the issue reported by you, because of architecture limitation. However, we have modified the code snippet to meet your requirements.
The idea is to find text starting from '#START#' on the one of the document pages. Then to find text ending with '#END#' on the one of subsequent pages. And also to process all text fragments that placed on the pages between those two pages (if it exists).
private string DeleteMachineReadableCodeUpdated(string inputFilePath)
{
string outputFilePath = Path.Combine(Path.GetTempPath(), string.Format(#"{0}.pdf", Guid.NewGuid()));
try
{
// Open document
Document pdfDocument = new Document(inputFilePath);
// Create TextAbsorber object to find all the phrases matching the regular expression
TextFragmentAbsorber absorber = new TextFragmentAbsorber("#START#((.|\r\n)*?)#END#");
// Set text search option to specify regular expression usage
TextSearchOptions textSearchOptions = new TextSearchOptions(true);
absorber.TextSearchOptions = textSearchOptions;
// Accept the absorber for all pages
pdfDocument.Pages.Accept(absorber);
// Get the extracted text fragments
TextFragmentCollection textFragmentCollection = absorber.TextFragments;
// If pattern found on one of the pages
if (textFragmentCollection.Count > 0)
{
RemoveTextFromFragmentCollection(textFragmentCollection);
}
else
{
// In case nothing was found tries to find by parts
string startingPattern = "#START#((.|\r\n)*?)\\z";
string endingPattern = "\\A((.|\r\n)*?)#END#";
bool isStartingPatternFound = false;
bool isEndingPatternFound = false;
ArrayList fragmentsToRemove = new ArrayList();
foreach (Page page in pdfDocument.Pages)
{
// If ending pattern was already found - do nothing
if (isEndingPatternFound)
continue;
// If starting pattern was already found - activate textFragmentAbsorber with ending pattern
absorber.Phrase = !isStartingPatternFound ? startingPattern : endingPattern;
page.Accept(absorber);
if (absorber.TextFragments.Count > 0)
{
// In case something is found - add it to list
fragmentsToRemove.AddRange(absorber.TextFragments);
if (isStartingPatternFound)
{
// Both starting and ending patterns found - the document processing
isEndingPatternFound = true;
RemoveTextFromFragmentCollection(fragmentsToRemove);
}
else
{
// Only starting pattern found yet - continue
isStartingPatternFound = true;
}
}
else
{
// In case neither starting nor ending pattern are found on current page
// If starting pattern was found previously - get all fragments from the page
if (isStartingPatternFound)
{
absorber.Phrase = String.Empty;
page.Accept(absorber);
fragmentsToRemove.AddRange(absorber.TextFragments);
}
// Otherwise do nothing (continue)
}
}
}
pdfDocument.Save(outputFilePath);
}
finally
{
if (File.Exists(inputFilePath))
File.Delete(inputFilePath);
}
return outputFilePath;
}
private void RemoveTextFromFragmentCollection(ICollection fragmentCollection)
{
// Loop through the fragments
foreach (TextFragment textFragment in fragmentCollection)
{
textFragment.Text = string.Empty;
}
}
Note:
This code assumed that the only one text block starting from '#START#' and ending with '#END#' is in the document. However the above code can be easly modified to process several those blocks.
Instead of processing text on intermediate page(s) you may store page number(s) and than delete using pdfDocument.Pages.Delete(pageNumber) before the saving document. It lets to avoid 'blank' pages if them undesirable.
Backstory,
So I am working on a personal assistant program and all my voice commands are translated into strings for parsing.
I have set up the ability to search Google and display the results in a text block as hyperlinks.
Now I want to be able to set up the ability to open these links with speech(string commands). So far I have the following.
This bit allows me to search using the Google Custom Search API with a custom "GoogleSearch" class.
public void search_google(string query) //Google Searching
{
#region link strings
string result_1 = "";
string result_2 = "";
string result_3 = "";
string result_4 = "";
string result_5 = "";
string result_6 = "";
string result_7 = "";
string result_8 = "";
string result_9 = "";
string result_10 = "";
#endregion
GoogleSearch search = new GoogleSearch()
{
Key = "{apikey}",
CX = "{cxkey}"
};
search.SearchCompleted += (a, b) =>
{
tab_control.SelectedIndex = 2;
int p = 1;
search_results.Text = String.Empty;
foreach (Item i in b.Response.Items)
{
Hyperlink hyperLink = new Hyperlink()
{
NavigateUri = new Uri(i.Link)
};
hyperLink.Inlines.Add(i.Title);
hyperLink.RequestNavigate += Hyperlink_RequestNavigate;
hyperLink.Name = "result_" + p;
//search_results.Inlines.Add(hyperLink.Name);
search_results.Inlines.Add(Environment.NewLine);
search_results.Inlines.Add(hyperLink);
search_results.Inlines.Add(Environment.NewLine);
search_results.Inlines.Add(i.Snippet);
search_results.Inlines.Add(Environment.NewLine);
search_results.Inlines.Add(Environment.NewLine);
p++;
};
};
search.Search(query);
}
It outputs my results in a series of hyperlinks and text snippets into a text block that I set up on the main window. The search process is triggered by my input parser which looks for the keywords "search" or "Google".
The next step would be the input parser checking for keyword "result" to look for the hyperlink to open. Here is the unfinished code for that.
if ((Input.Contains("result") || Input.Contains("Result")) && tab_control.TabIndex == 2)
{
int result_number = 0;
switch(result_number)
{
case 1:
if (Input.Contains("first") || Input.Contains("1st"))
{
// open hyperlink with name property result_1
}
break;
case 2:
// additional cases added up to 10 with similar syntax for parsing.
}
}
You can open a hyperlink in the default browser using:
Process.Start(myHyperlink);
EDIT
Based on your comments, it seems you are having trouble accessing result_1 (etc.).
You define result_1 as a variable local to the method search_google()
public void search_google(string query) //Google Searching
{
#region link strings
string result_1 = "";
That means result_1 is only visible within that method.
Your if and switch statements do not appear to be part of search_google(), so they can never see result_1. If those statements are in a different method, you can work around that issue by moving result_1 to the class level (outside of search_google()).
ON a site note, rather than defining ten individual result strings, you probably want to use an array of strings or a list of strings.
I'm using WIA to acquire images from a scanner with C#. I can scan the papers, but I can't set up the page size correctly, it always defaults to A4 and I need to use Letter or Legal sometimes.
I tried with the WIA_DPS_PAGE_SIZE property, but when I try to set a value, I always get an error, that the value is out of the interval (tried a lot of possible values).
I wan't to be able to use WIA_DPS_PAGE_SIZE = WIA_PAGE_AUTO (for automatic page size), but I can't find anything on the web related to this.
Does anyone know a solution? thanks!
I know this is probably too late to actually help you with that, but it may become handy for future reference. To change scanned items properties use such code:
WIA.CommonDialog wiaDlg;
WIA.Device wiaDevice;
WIA.DeviceManager wiaManager = new DeviceManager();
wiaDlg = new WIA.CommonDialog();
wiaDevice = wiaDlg.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, false, false);
foreach (WIA.Item item in wiaDevice.Items)
{
StringBuilder propsbuilder = new StringBuilder();
foreach (WIA.Property itemProperty in item.Properties)
{
IProperty tempProperty;
Object tempNewProperty;
if (itemProperty.Name.Equals("Horizontal Resolution"))
{
tempNewProperty = 75;
((IProperty)itemProperty).set_Value(ref tempNewProperty);
}
else if (itemProperty.Name.Equals("Vertical Resolution"))
{
tempNewProperty = 75;
((IProperty)itemProperty).set_Value(ref tempNewProperty);
}
else if (itemProperty.Name.Equals("Horizontal Extent"))
{
tempNewProperty = 619;
((IProperty)itemProperty).set_Value(ref tempNewProperty);
}
else if (itemProperty.Name.Equals("Vertical Extent"))
{
tempNewProperty = 876;
((IProperty)itemProperty).set_Value(ref tempNewProperty);
}
}
image = (ImageFile)item.Transfer(WIA.FormatID.wiaFormatPNG);
}
This means that scanned document will be size A4 with dimensions 619 x 876.