Hello, I am building windows C# WPF application which communicates with Microsoft Office word document (.docx). The app should update .docx template file with user input and this step is achived successfuly using OpenXML. The other part of the app is to show the edited word document to the user inside of the applicaion window or using MS Word and allow him to add some more information if he wants to do it. The problem I am facing is:
I should disable my application controls while word document is opened and I should enable them once the word is closed, also I want to know if the word app was saved (if a user made changes). The next code is button click event for openning word document:
using System.Windows;
using Microsoft.Office.Interop.Word;
using Application = Microsoft.Office.Interop.Word.Application;
public class MainWindowViewModel : BaseViewModel
{
...
... some view model initialization
...
public bool AreControlsEnabled { get; set; } = true;
private void OpenWord ()
{
AreControlsEnabled = false;
var app = new Application()
{
Visible = true
};
var doc = app.Documents.Open("pathtofile.docx");
var docClass = app.ActiveDocument as DocumentClass;
docClass.DocumentEvents2_Event_Close += DocClass_DocumentEvents2_Event_Close;
docClass.DocumentEvents_Event_Close += DocClass_DocumentEvents_Event_Close;
app.DocumentBeforeClose += new ApplicationEvents4_DocumentBeforeCloseEventHandler(DocBeforeClose);
app.DocumentBeforeSave += new ApplicationEvents4_DocumentBeforeSaveEventHandler(DocBeforeSave);
}
private void DocClass_DocumentEvents2_Event_Close ()
{
MessageBox.Show("DocClass_DocumentEvents2_Event_Close");
AreControlsEnabled = true;
}
private void DocClass_DocumentEvents_Event_Close ()
{
MessageBox.Show("DocClass_DocumentEvents_Event_Close");
AreControlsEnabled = true;
}
private void DocBeforeClose (Document doc, ref bool cancel)
{
MessageBox.Show("DocBeforeClose");
AreControlsEnabled = true;
}
private void DocBeforeSave (Document doc, ref bool SaveAsUI, ref bool cancel)
{
MessageBox.Show("DocBeforeSave");
AreControlsEnabled = true;
}
}
When I run the code - I see opened MS Word document as expected, but when I close it or save - no one of events fired and I can't understand why. Also, I can use System.Diagnostics.Process to launch the Word and add exit event to it, but in this way I can't know if the user applied some changes. So, if someone solved this problem, help me please. Thank you for reading and answers
You can:
Get the Current Changed Date of the File
Use System.Diagnostics.Process to start Word.
After The process ends you check the Changed Date again
If the user saved the file the Changed Date is updated
I don't know if the process is still running if the user just closes the document but not word. For this you could observe the Folder of the Dokument for thos ~... Temp Files Word creates while a document is open...
Related
I am trying to write an app that reads from an Excel file and displays information on a Windows form app. This is my code (this is taken straight from this video: https://www.youtube.com/watch?v=lsv7rAsvYuA)
Main form:
public partial class Form1 : Form
{
int i = 1;
public Form1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
Reader excel = new Reader("C:/Users/marti/Desktop/minput.xlsm", 1);
lbMain.Items.Add(excel.ReadCell(i, 1));
i++;
}
}
Reader class:
class Reader
{
string path = "";
_Application excel = new _Excel.Application();
Workbook wb;
Worksheet ws;
public Reader(string path, int sheet)
{
this.path = path;
wb = excel.Workbooks.Open(path);
ws = wb.Worksheets[sheet];
}
public string ReadCell(int row, int col)
{
if (ws.Cells[row, col].Value2 != null)
{
return ws.Cells[row, col].Value2;
}
else
{
return "";
}
}
}
Pressing the button freezes the app for about 5 seconds, before finally unfreezing and displaying the information in the listbox. I first thought that it would only happen on the first button click, as that's when the file is being loaded. However, it happens on every consecutive click too.
The freezing seems to be worse with larger Excel sheets.
Advice?
I once used this kind of source code to read out data from Excel.
I had the same issue, that the program was really slow.
I only needed the values from Excel (no background color and so on) so i decided to read in all data, when opening the Windows form and saving it to an array or list. It was faster.
I recommend to check if the file has been changed before, when you press an "update" button.
I have never figured out the problem but I think that the Workbook object don't contain the data, the object only contains the path and each time you want to read out data, the wb object accesses the original document once.
Best regards!
The Excel spreadsheet format is messy, and parsing it is complex. Your program freezes for 5 seconds because it's taking 5 seconds to read the file. You need to either find a way to read the file more quickly or do it in a way that doesn't freeze your application.
For the latter, that is what the BackgroundWorker class is designed for. You'd create an instance of the BackgroundWorker class, assign an event handler that does the slow process, and then set it running in the background while you wait.
BackgroundWorker loadingWorker = new BackgroundWorker();
int i = 1;
public Form1()
{
InitializeComponent();
loadingWorker.DoWork += loadingWorker_DoWork;
}
private void Button1_Click(object sender, EventArgs e)
{
loadingWorker.RunWorkerAsync();
}
private void loadingWorker_DoWork(object sender, DoWorkEventArgs e)
{
Reader excel = new Reader("C:/Users/marti/Desktop/minput.xlsm", 1);
lbMain.Items.Add(excel.ReadCell(i, 1));
i++;
}
The BackgroundWorker's DoWork event handler will run asynchronously, which will prevent the application from freezing while the loading is in progress.
Background:
I am creating application to capture the screen and paste the screenshot in the word document one below other.
Technical Part:
When I run the application, a word document opens along with a form having 'Capture' button. The button when clicked captures the screen and an window is opened asking for the caption and then screenshot with the caption is pasted in currently open word doc.
My Question
When user clicks Capture button, I want to check whether the document opened previously is still open or not. If document is not open, I will prompt user to open a new blank document.
I searched many forums and internet, but mostly all suggested to get the filename from the currently running processes. Note that my word document is unsaved, so it will have names like 'Document 1' etc. which will be a bad way to check.
I have pasted my code below for reference.
Any inputs will be appreciated. Thanks in advance.
WordProcessing.cs
namespace WordProcessing
{
class MSWord
{
Word.Application wordApp = new Word.Application(); //Creates new Word Instance
public MSWord()
{
wordApp.Visible = true;
Word.Document oDoc = wordApp.Documents.Add(ref useDefaultValue, ref useDefaultValue, ref useDefaultValue, ref useDefaultValue);
}
}
}
Screencapture.cs
namespace Screencapture
{
public partial class form_capture : Form
{
MSWord word = new MSWord();
public void button1_Click(object sender, EventArgs e)
{
/* Here I want to check whether document opened by 'word' object is still open */
ScreenCapture screen = new ScreenCapture();
screen.CaptureScreenToFile("D:/image.png", System.Drawing.Imaging.ImageFormat.Png);
//Form to ask for caption
DataForm textarea = new DataForm();
textarea.ShowDialog();
textarea.Focus();
word.InsertText(DataForm.textarea_text);
word.InsertImage(#"D:\image.png");
}
}
}
PS: Ignore any syntax errors or missing function definitions. I have pasted shorter version for better understanding.
How about a try/catch on focusing the document?
try
{
oDoc.Activate();
//or on the application
wordApp.Activate();
}
catch
{
//Open a new document or ask
Messagebox.Show("Please open a new Word Document");
}
You could try using the File.Open() method. If the document is open, it should provide you with an exception which can print your custom prompt:
FileStream stream = null;
bool isOpen = false;
try
{
stream = File.Open(#"DFilePath&Name",FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch(IOException)
{
isOpen = true;
//Show your prompt here.
}
finally
{
if (stream != null)
stream.Close();
}
if(!isOpen)
Process.Start(#"FilePath&Name");
Below is my code (simplified version for readability) from a VSTO-based Word addin.
The problem is that if I have two documents open, such as documentation and a template, my addin helps develop the template and works fine until the template is closed and re-opened in the same Word instance (the documentation file kept Word alive). Once that happens the SelectionChange event is not received, even though the listener is attached (confirmed with debugger).
Is there anything wrong with this code? Any other ways to attach a selection changed event?
void Application_DocumentOpen(Word.Document Doc)
{
// this method gets called as intended
Document vstoDoc = Globals.Factory.GetVstoObject(doc);
vstoDoc.SelectionChange += new Microsoft.Office.Tools.Word.SelectionEventHandler(ThisDocument_SelectionChange);
}
private void Application_DocumentBeforeClose(Word.Document doc, ref bool Cancel)
{
// this one also gets called as intended
Document vstoDoc = Globals.Factory.GetVstoObject(doc);
vstoDoc.SelectionChange -= new Microsoft.Office.Tools.Word.SelectionEventHandler(ThisDocument_SelectionChange);
}
void ThisDocument_SelectionChange(object sender, SelectionEventArgs e)
{
// this doesn't get called if the document is closed and open again within the same Word instance
Log("Selection changed");
}
UPDATE: This seems like VSTO bug.
Attaching to other events works fine, I can use ContentControlOnEnter/Exit:
vstoDoc.SelectionChange += ThisDocument_SelectionChange; // doesn't work
vstoDoc.ContentControlOnEnter += vstoDoc_ContentControlOnEnter; // works
vstoDoc.ContentControlOnExit += vstoDoc_ContentControlOnExit; // works
Why dont you use
Globals.ThisAddIn.Application.WindowSelectionChange +=
new ApplicationEvents4_WindowSelectionChangeEventHandler(Application_WindowSelectionChange);
instead of converting your Microsoft.Office.Interop.Word.Document object to Microsoft.Office.Tools.Word.Document
I have an MS Office 2010 application level add-in, when it's loaded all ribbon controls in my custom tab are disabled.Then based on certain conditions I run this method to enable all the ribbon controls in my custom tab:
public void EnableRibbonControls()
{
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
RibbonTab customTab = container.Resolve<RibbonTab>();
for (int i = 0; i < customTab.Groups.Count; i++)
{
IList<RibbonControl> controls = customTab.Groups[i].Items;
foreach (var control in controls)
{
control.Enabled = true;
}
}
}
The problem is that this code enables the ribbon controls in the ribbon of every open Word document and not the specific one that I'm working on.
I would like to know whether the only way to fix this is by implementing a document level add-in or does someone know a work around for this in the application level add-in?
I currently approach the same problem (in Excel) by setting a GUID as a custom document property and then add an event handler on the Document.Activate event in my application level VSTO add-in. Whenever a document is activated, I check for the GUID and then hide or show the buttons accordingly.
Condensed Code Example:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
var app = Globals.ThisAddIn.Application;
app.WorkbookActivate += new Excel.AppEvents_WorkbookActivateEventHandler(Application_WorkbookActivate);
app.WorkbookDeactivate += new Excel.AppEvents_WorkbookDeactivateEventHandler(Application_WorkbookDeactivate);
}
private Guid _GetIdentity(Excel.Workbook Wb)
{
try
{
// check for GUID
Microsoft.Office.Core.DocumentProperties properties = Wb.CustomDocumentProperties;
Microsoft.Office.Core.DocumentProperty version = properties["_CustomIdentifier"];
// parse the version for decide what features to activate
Guid guidVersion;
return Guid.TryParse(Convert.ToString(version.Value), out guidVersion) ? guidVersion : Guid.Empty;
}
catch
{
return Guid.Empty;
}
}
void Application_WorkbookDeactivate(Excel.Workbook Wb)
{
Globals.Ribbons.MyRibbon.btnButtonName.Visible = false;
}
void Application_WorkbookActivate(Excel.Workbook Wb)
{
if(_GetIdentity(Wb) == {PRE-DEFINED-GUID})
{
Globals.Ribbons.MyRibbon.btnButtonName.Visible = true;
}
}
My code is specific to Excel, you will need to check the docs what Word equivalent of the Activate/Deactivate events.
Disclaimer: This is only an extract of my actual code, may contain errors.
I am using a windows service and i want to print a .html page when the service will start. I am using this code and it's printing well. But a print dialog box come, how do i print without the print dialog box?
public void printdoc(string document)
{
Process printjob = new Process();
printjob.StartInfo.FileName = document;
printjob.StartInfo.UseShellExecute = true;
printjob.StartInfo.Verb = "print";
printjob.StartInfo.CreateNoWindow = true;
printjob.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
printjob.Start();
}
Have there any other way to print this without showing the print dialog box.
Update: in response to this:
But i have already used this class but when i am calling the
axW.ExecWB(SHDocVw.OLECMDID.OLECMDID_PRINT,SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_PROMPTUSER , ref em, ref em);
My program getting block here when i am using from window service but it is working fine from windows application.
First off, here's the code:
using System.Reflection;
using System.Threading;
using SHDocVw;
namespace HTMLPrinting
{
public class HTMLPrinter
{
private bool documentLoaded;
private bool documentPrinted;
private void ie_DocumentComplete(object pDisp, ref object URL)
{
documentLoaded = true;
}
private void ie_PrintTemplateTeardown(object pDisp)
{
documentPrinted = true;
}
public void Print(string htmlFilename)
{
documentLoaded = false;
documentPrinted = false;
InternetExplorer ie = new InternetExplorerClass();
ie.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(ie_DocumentComplete);
ie.PrintTemplateTeardown += new DWebBrowserEvents2_PrintTemplateTeardownEventHandler(ie_PrintTemplateTeardown);
object missing = Missing.Value;
ie.Navigate(htmlFilename, ref missing, ref missing, ref missing, ref missing);
while (!documentLoaded && ie.QueryStatusWB(OLECMDID.OLECMDID_PRINT) != OLECMDF.OLECMDF_ENABLED)
Thread.Sleep(100);
ie.ExecWB(OLECMDID.OLECMDID_PRINT, OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, ref missing, ref missing);
while (!documentPrinted)
Thread.Sleep(100);
ie.DocumentComplete -= ie_DocumentComplete;
ie.PrintTemplateTeardown -= ie_PrintTemplateTeardown;
ie.Quit();
}
}
}
You can access the SHDocVw namespace by adding a reference to 'Microsoft Internet Controls', found on the COM tab of the Add Reference dialog.
More information on the InternetExplorer object can be found on MSDN.
The Navigate() method will load the HTML file. The other parameters allow you to specify optional parameters, such as flags and headers.
We can't print until the document is loaded. Here, I enter a loop waiting until the DocumentComplete event is called, upon which a flag is set notifying us that navigation has completed. Note that DocumentComplete is called whenever navigation is finished - upon success or failure.
Once the documentLoaded flag is set, the printing status is queried via QueryStatusWB() until printing is enabled.
Printing is started with the ExecWB() call. The OLECMDID_PRINT command is specified, along with the option OLECMDEXECOPT_DONTPROMPTUSER to automatically print without user interaction. An important note is that this will print to the default printer. To specify a printer, you will have to set the default printer (in code, you could call SetDefaultPrinter()). The two final parameters allow optional input and output parameters.
We don't want to quit until printing is complete, so once again a loop is entered. After the PrintTemplateTeardown event is fired, the documentPrinted flag is set. The objects can then be cleaned up.
From this site http://www.ussbd.com/printhtm.html
using HtmlPrinter;
hpObj=new HtmlPrinter.HtmlPrinter();
hpObj.PrintUrlFromMemory(txtUrl.Text);
Now you add the code in your project to print html page from its source text:
HtmlPrinter.HtmlPrinter hpObj=new HtmlPrinter.HtmlPrinter();
hpObj.PrintHtml(txtString.Text, true);
If you want to print without the print dialog then use the following line:
hpObj.PrintHtml(txtString.Text, false);
Here's another way to print without a print dialog. You create a PrintDialog object, initialize it and then call the Print() method.
The function below is used to print a small 2"x0.75" barcode label. You'll need to figure out a way to get an Document object from the html file.
public void PrintToPrinter(string printerName)
{
PrintDialog pd = new PrintDialog();
pd.Document = userControl11.PrintDoc; // <--- Update this line with your doc
pd.PrinterSettings.PrinterName = printerName;
try
{
pd.Document.DocumentName = "My Label";
pd.Document.DefaultPageSettings.PaperSize = new System.Drawing.Printing.PaperSize("2-.75", 200, 75);
pd.Document.DefaultPageSettings.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);
//pd.PrinterSettings.Copies = (short)mNumCopies;
pd.Document.PrinterSettings.Copies = (short) mNumCopies;
pd.Document.Print();
}
catch
{
MessageBox.Show("INVALID PRINTER SPECIFIED");
}
}
You can use the PrintDocument class in the System.Drawing.Printing namespace to give you more control over the printing, see here for more info.
For example you can do the following:
using (PrintDocument doc = new PrintDocument())
{
doc.PrintPage += this.Doc_PrintPage;
doc.DefaultPageSettings.Landscape = true;
doc.DocumentName = fileNameOfYourDocument;
doc.Print();
}
Then a function is raised for each page to be printed and you are given a Graphics area to draw to
private void Doc_PrintPage(object sender, PrintPageEventArgs ev)
{
....
ev.Graphics.DrawImage(image, x, y, newWidth, newHeight);
}
This does require you handle the actual drawing on the text/image to the page, see here for more info.
OLECMDEXECOPT_PROMPTUSER seems to force a prompt to the user to select printer and all associated stuff, which I am pretty sure is not allowed from a service. Can someone verify this?