Word Document.Close does not show saving option dialog - c#

I am writting word automation functions. My app let users click on a word document file name to open it; when users click another doc, it closes the previous one first, then opens the new one.
My problem is about closing a previous doc.
I tried _Application.Quit(WdSaveOptions.wdPromptToSaveChanges) first. It shows the saving option dialog, but it doesn't return to codes, so, a new doc could open before users' response to the dialog. That is not I want.
Sendmessage can close the Word with a saving dialog, and wait users response to the dialog. This is better than the _Application.Quit.
I like Document.Close(). It close the Document, but the Word application is not closed, so it is more quick to open the next doc than the _Application.Quit and Sendmessage.
The problem with the Document.Close is that it just saves the doc with changes without showing a saving option dialog, although I pass WdSaveOptions.wdPromptToSaveChanges as a parameter in Document.Close().
Are there some bugs in my codes or the close() has bugs itself? Thanks.
using System;
using System.Windows.Forms;
using System.IO;
using Word = Microsoft.Office.Interop.Word;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Word;
namespace Test_IE_Doc
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
int FHwnd = 0;
public Word._Application oWord;
private void button1_Click(object sender, EventArgs e)
{
string fileName = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "1.docx");
CloseDoc();
Thread.Sleep(1000);
if (oWord == null)
oWord = new Word.Application();
oWord.Visible = true;
oWord.Documents.Open(fileName, true);
FHwnd = oWord.Documents[1].ActiveWindow.Hwnd;
oWord.WindowState = Word.WdWindowState.wdWindowStateNormal;
SetForegroundWindow(new IntPtr(FHwnd));
}
private void CloseDoc()
{
if (FHwnd != 0)
//oWord.Quit(WdSaveOptions.wdPromptToSaveChanges);
oWord.Documents[1].ActiveWindow.Close(WdSaveOptions.wdPromptToSaveChanges);
}
}
}

I use Documents.Saved to check if the document is changed, then ask user whether the user want to save it. then Document's ActiveWindow.Close() pass WdSaveOptions.wdSaveChanges for saving, pass WdSaveOptions.wdDoNotSaveChanges for not saving.

Related

Previewing a PDF in C#

I would like to create a PDF document with iTextSharp and preview it directly in the application.
And this not only once, but as often as I like, during runtime, when the user makes changes to the text input.
So far it works, but as I said before only once, when the program is started.
When I try to generate the PDF file again, I get an error message,
that the process cannot access the saved PDF document because it is currently being used by another process.
I have already tried to prevent access, but without success so far.
private void CreateDocument()
{
//my attempt to stop the browser from blocking the file acces
if (browser.IsBusy())
{
browser.Stop();
}
doc = new Document(PageSize.A4);
writer = PdfWriter.GetInstance(doc, new FileStream("document.pdf", FileMode.Create));
doc.Open();
cb = writer.DirectContent;
//here is the actual pdf generation
doc.Close();
//this is the part where I set the pdf document reference from the web browser
browser.Navigate(#"path\document.pdf");
}
The actually error occurs where I set the PDFwriter instance.
I've found a page preview component in the toolbox from iTextSharp, but sadly no reference on how to use it. Using that might work easier than trying it with the web browser.
If you don't mind a little bit of flickering just navigate to "about:blank" before you try to save.
If you have a probelm with that, just make a temporary copy of the file and open the copy with the browser. Probably not the best solutions, but should work
My problem was, that the web browser navigation is asynchronous.
As a workaround I used an event listener that keeps track, when the browser actually loaded the document.
For more information about that topic check this question out: https://stackoverflow.com/a/583909/12178103
Down here you can see my complete code
//gets called when the application starts
public Form1()
{
InitializeComponent();
//first time the web browser load operation gets called - make sure to set the event handler
webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(WebBrowserUnload);
WebBrowserLoad();
}
//this button regenerates the pdf
private void Button_Click(object sender, EventArgs e)
{
WebBrowserLoad();
}
//creates the actually pdf document
private void WebBrowserLoad()
{
browser.Hide();
browser.Navigate("about:blank");
}
private void WebBrowserUnload(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (e.Url.ToString() == "about:blank")
{
doc = new Document(PageSize.A4);
using (fileStream = new FileStream("document\pdf", FileMode.Create))
{
using (writer = PdfWriter.GetInstance(doc, fileStream))
{
PageEventHelper pageEventHelper = new PageEventHelper();
writer.PageEvent = pageEventHelper;
doc.Open();
cb = writer.DirectContent;
//create the pdf here
writer.Flush();
doc.Close();
doc.Dispose();
}
}
browser.Navigate(#"path\document.pdf");
}
else if (e.Url.ToString() == "file:///path/document.pdf")
{
browser.Show();
}
}

Microsoft.Office.Tools.Excel.ApplicationFactory.GetVstoObject causes VBA memory leak?

below is a minimum sample VSTO-Project that forces the Excel VBA-Environment to show an error "Not enough memory" and causes the VBA-Project to appear twice.
Question: Is this a bug in the "GetVstoObject"-Method. How can I get around???
Steps:
In Excel 2010: Create a new workbook and hit ALT + F11 to enter the VBA-Environment
In VBA.ThisWorkworkbook: Create an empty new Sub
Sub test()
End Sub
Save the workbook as Test.xlsm and exit Excel.
Create a new Excel 2010-Add-In in Visual Studio
Replace all code in ThisAddIn.cs by the code below.
Start debugging Excel with F5
Start debugging ExcelAddIn1 with F5
Open the saved workbook Test.xlsm.
ReOpen the saved workbook again (IMPORTANT: Must do it "externally" e.g. by using the Excel jumplist in the windows taskbar)
Results:
The CTP is doubled
If you hit ALT + F11 to open VBA, you will get an "No enough memory"-Error
Thanks for any explanation/solution!
Regards,
Jörg
C# Minimum Add-In Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Excel;
namespace ExcelAddIn1
{
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.WorkbookActivate += new Excel.AppEvents_WorkbookActivateEventHandler(Application_WorkbookActivate);
}
void Application_WorkbookActivate(Excel.Workbook Wb)
{
//This is the important line:
var activeVstoWorkbook = Globals.Factory.GetVstoObject(Wb);
}
#region VSTO generated code
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
}
#endregion
}
}

handle native external process windows events from c#

I'm starting a process (namely gnuplot.exe) from within a C# application.
The process can open some window and for that process i'd like to intercept the events of:
opened window
closed window
focused window
The basic idea is to handle if the user closes some windows or changes the active window and so on, referring only to the started process. In other word, i don't want to handle others focus changes or closed windows events which are not thrown by a gnuplot window.
Can you help me? Is it possible to avoid polling? Which api should i refer to? Can you paste/link an mwe or some example code?
Thank you in advance
Update
As suggested by Eric Brown I tried this way, but still not works. Can you help me to detect where am I wrong?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Automation;
namespace WinApiEvents
{
class Program
{
public static void Main()
{
Process gp = new Process();
gp.StartInfo.FileName = #"C:\Software\gp463-win32\gnuplot\bin\gnuplot.exe";
gp.StartInfo.UseShellExecute = false;
gp.StartInfo.RedirectStandardInput = true;
gp.StartInfo.CreateNoWindow = true;
gp.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
gp.Start();
AutomationElement targetElement =
AutomationElement.FromHandle(gp.Handle);
StructureChangedEventHandler structChangedHandler =
new StructureChangedEventHandler(OnGnuplotWindowStructureChanged);
Automation.AddStructureChangedEventHandler(
targetElement, TreeScope.Element, structChangedHandler);
AutomationEventHandler focusHandler =
new AutomationEventHandler(OnGnuplotWindowFocusGained);
Automation.AddAutomationEventHandler(
AutomationElement.AutomationFocusChangedEvent, targetElement, TreeScope.Element, focusHandler);
StringBuilder sb = new StringBuilder();
sb.AppendLine("set term wxt 1 enhanced");
sb.AppendLine("plot sin(x)");
gp.StandardInput.WriteLine(sb.ToString());
gp.StandardInput.Flush();
sb.Clear();
sb.AppendLine("set term wxt 2 enhanced");
sb.AppendLine("plot cos(x)");
gp.StandardInput.WriteLine(sb.ToString());
gp.StandardInput.Flush();
sb.Clear();
sb.AppendLine("set term wxt 3 enhanced");
sb.AppendLine("plot atan(x)");
gp.StandardInput.WriteLine(sb.ToString());
gp.StandardInput.Flush();
sb.Clear();
MessageBox.Show("Click to exit.");
}
private static void OnGnuplotWindowStructureChanged(object src, StructureChangedEventArgs e)
{
Console.WriteLine("structure changed window, id=" + e.EventId.ProgrammaticName);
}
private static void OnGnuplotWindowFocusGained(object src, AutomationEventArgs e)
{
Console.WriteLine("focused window, id=" + e.EventId.ProgrammaticName);
}
}
}
Thank you in advance
I would use the UI Automation framework for this.
I assume you have (or can get) the gnuplot window handle. Once you have that, you can get the UI Automation element for that window, and set up FocusChanged event handlers, and StructureChanged handlers to detect subwindow opening & closing. The WindowClosed event will tell you if the gnuplot window itself closes. (You can use this on subwindows as well, but you'll still need to listen for StructureChanged events to detect subwindow open events.)

C# - Accessing Javascript modified HTML via a Console app

I am attempting to access the HTML of a page after it has been modified by the JavaScripts on the page. This is what I have been currently attempting based on what I have found online.
using System;
using System.Windows.Forms;
using System.IO;
namespace WebBrowserDemo
{
class Program
{
public const string TestUrl = #"http://www.theverge.com/2012/7/2/3126604/android-jelly-bean-updates-htc-samsung-google-pdk";
[STAThread]
static void Main(string[] args)
{
WebBrowser wb = new WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
wb.Navigate(TestUrl);
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
Console.WriteLine("\nPress any key to continue...");
Console.ReadKey(true);
}
static void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = (WebBrowser)sender;
HtmlElement document = wb.Document.GetElementsByTagName("html")[0];
using (StreamWriter sw = new StreamWriter("OuterHTML.txt"))
{
sw.WriteLine(document.OuterHtml);
}
var abc = wb.Document.InvokeScript("eval", new object[] { "window.scrollTo(0, document.body.scrollHeight);" });
Console.WriteLine();
document = wb.Document.GetElementsByTagName("html")[0];
using (StreamWriter sw = new StreamWriter("OuterHTML2.txt"))
{
sw.WriteLine(document.OuterHtml);
}
}
}
}
The ultimate goal is to scroll to the bottom of the page activating any JS to load the comments on the article. Though currently the html I get back from before and after the script is ran is the same.
Any Suggestions?
Thanks
You should do it with a WebBrowser control.
This is basically a componentized version of IE. Load the page into the control. You probably do not even need to display the page. You can register an event handler that will be called when the page is fully loaded. There is no definite way to determine when the scripts have "completed" - scripts are open-ended and may run as long as they like. So you'd have to build in a heuristic "Wait period", then examine the HTML after that wait period passes.
Incidentally this is exactly what IECapt does.

Windows Forms\Console - Make Your KeyBord Type Words. Or Your Mouse to be clicked

Well,
I am not sure if someone already asked this qusiton before. I was trying to look arround but noting came up. (if there is, please show me and close this. I am very sorry!)
For a few days now I am looking for a way that when I click on a button in my windows form in C# it will copy paste something to somewhere else.
The best way to expline this:
Lets say I got a Ms Word open, and I want that when I will click on a button in my windows form, after 5 seconds, it will write something in my word office. Of course I will open the Ms Word by my self.
Another thing: is how to make your mouse click on hes key?
edit:
When i use this code --
int forhow = int.Parse(textBox1.Text);
for(int i = 0;i <forhow; i++)
{
i++;
SendKeys.Send("ספאמר על ידי פריזו - ספאמר על גירסא ראשונה");
//ספאמר על ידי פThread.Sleep(1200);
//Thread.Sleep(5000);
SendKeys.Send("{ENTER}");
}
well, its should do it only 1 time. is i write 1 in the text box. but, it is doing it about 50 times. and the stop. any one knows why? + . if you lick on the button, the program stops to work until she compltite all the "Send:".
I couldn't find a way to force a mouse click, but you cam mimic the keyboard using the SendKeys class. All code that is not in between "//{" and "//}" was generated by visual studio.
Hope this helps!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
//{
using System.Diagnostics;
//}
namespace ClickToWord
{
public partial class Form1 : Form
{
//{
Process imsWord = new Process();
Timer tempTime = new Timer();
int counter = 0;
//}
public Form1()
{
InitializeComponent();
//{
imsWord.StartInfo.FileName = #"";
//Inside the "" put the path to the file/application. No need to escape it, because of the "#"
tempTime.Interval = 1000;
//The interval in miliseconds
tempTime.Tick += new EventHandler(tempTime_Tick);
//}
}
void tempTime_Tick(object sender, EventArgs e)
{
//{
char send = 'a';
send += (char)(counter % 26);
SendKeys.Send(send.ToString());
counter++;
//An example of looping through the alphabet. Send any string via SendKeys, and it will act as if the keyboard ent it.
//This mimics keyboard strokes, and requires the document to have focus. That is why it is not the ideal way to do this.
//To programmatically communicate with Word, use the Microsoft Word Object Model library.
//tempTime.Enabled = false;
//}
}
private void button1_Click(object sender, EventArgs e)
{
//{
imsWord.Start();
//Starts the proccess
tempTime.Enabled = true;
//Starts the timer
//}
}
}
}

Categories