I'm working on a feature which is to create a backup when a open word saved each times.
I'm using the blow code to hooking into word process and bind events to it, the word is opened by process.
officeApplication = (Application)Marshal.GetActiveObject("Word.Application").
officeApplication.DocumentBeforeSave += new ApplicationEvents4_DocumentBeforeSaveEventHandler(App_BeforeSaveDocument);
And in App_BeforeSaveDocument I did my work.
I get officeApplication right, and bind events were fine, when I click save in word, the events triggered perfectly.
The problem is, a few seconds(may be 30s) after, the events will not fire anymore, no matter click save or save us or close document.
Is there any suggestions?
After a lot of researching, I still can't find the reason. And I decide to use a trick to approach it.
First, open a thread in the binding event:
static void App_BeforeSaveDocument(Microsoft.Office.Interop.Word.Document document, ref bool saveAsUI, ref bool cancel)
{
if (th != null)
th.Abort();
th = new Thread(backupOnSave);
th.IsBackground = true;
th.Start(document);
}
Then do an infinity loop in the thread:
internal static void backupOnSave(object obj)
{
try
{
Application app = obj as Application;
if (app == null || app.ActiveDocument == null)
{
return;
}
Microsoft.Office.Interop.Word.Document document = app.ActiveDocument;
if (!tempData.ContainsKey(document.FullName))
return;
var loopTicks = 2000;
while (true)
{
Thread.Sleep(loopTicks);
if (document.Saved)
{
if (!tempData.ContainsKey(document.FullName))
break;
var p = tempData[document.FullName];
var f = new FileInfo(p.FileFullName);
if (f.LastWriteTime != p.LastWriteTime)//changed, should create new backup
{
BackupFile(p, f);
p.LastWriteTime = f.LastWriteTime;
}
}
}
}
catch (Exception ex)
{
log.write(ex);
}
}
And it works fine. Don't remember to abort the thread when the document closed or exception happen.
Related
I'm starting with SAP B1 UI API (9.0) and I'm trying to handle a button click without any luck so far. This is how I'm doing it (removing unnecessary to make it shorter):
static void Main(string[] args)
{
SetApplication(args);
var cParams = (FormCreationParams)App.CreateObject(BoCreatableObjectType.cot_FormCreationParams);
cParams.UniqueID = "MainForm_";
cParams.BorderStyle = BoFormBorderStyle.fbs_Sizable;
_form = App.Forms.AddEx(cParams);
/*Setting form's title, left, top, width and height*/
// Button
var item = _form.Items.Add("BtnClickMe", BoFormItemTypes.it_BUTTON);
/*Setting button's left, top, width and height*/
var btn = (Button)item.Specific;
btn.Caption = "Click Me";
_form.VisibleEx = true;
App.ItemEvent += new _IApplicationEvents_ItemEventEventHandler(App_ItemEvent);
}
private static void SetApplication(string[] args)
{
string connectionString = args[0];
int appId = -1;
try
{
var guiApi = new SboGuiApi();
guiApi.Connect(connectionString);
App = guiApi.GetApplication(appId);
}
catch (Exception e)
{ /*Notify error and exit*/ }
}
private static void App_ItemEvent(string FormUID, ref ItemEvent pVal, out bool BubbleEvent)
{
BubbleEvent = true;
if (FormUID == "MainForm_" && pVal.EventType == BoEventTypes.et_CLICK &&
pVal.BeforeAction && pVal.ItemUID == "BtnClickMe")
{
App.MessageBox("You just click on me!");
}
}
When I click the button nothing happens, is this the way to go? I've made so many variations in the handler method but nothing yet. Another detail is that the visual studio's debugger terminates as soon as the addon is started (maybe this has something to do with my problem).
I hope you can help me. Thanks in advance.
David.
Since the application stops running there are two possible answers to this question depending on what you prefer to use.
If you are using the SAPbouiCOM library you need a way to keep the application running, the way I use is the System.Windows.Forms.Application.Run(); from the windows forms assembly.
If you are using the SAPBusinessOneSDK and SAPbouiCOM.Framework as a reference you can use the App.Run();.
Both of these need to be invoked as soon as your setup code has run.
I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.
I'm using a System.Timers.Timer to get a PrintQueueCollection every N-seconds so I'm always up to date if something changed. The timer sends a RefreshEvent with the PrintQueue so I can handle changes in my Gui.
Here's the Refresh function, the timer calls every n-seconds
private void Refresh()
{
lock (_locker)
{
try
{
// _server == ServerName if isNullOrEmppty it's localhost
PrintServer printServer = new PrintServer(_server);
PrintQueueCollection printQueueCollection = printServer.GetPrintQueues();
foreach (PrintQueue pq in printQueueCollection)
{
if (_firstRun) break;
// List of Unique Printernames so not all printers get 'refreshed'
if (_printersToMonitor.Contains(pq.Name))
{
var currentPrinter = new Printer(pq);
// Event catched in Gui
Refreshed?.Invoke(currentPrinter);
}
}
}
catch (Exception)
{
//...
}
}
}
The Event is catched in a Control this way
// Printer is a Wrapper class that contains the PrintQueue and several other information I need e.g. results of SNMP walks
private void RefreshPrinter(Printer printer)
{
if (_localPrinters.Count == 0)
Dispatcher.Invoke(() => _localPrinters.Add(printer));
else
{
// _localPrinters is a ObservableCollection<Printer> Bound to gui
foreach (Printer p in _localPrinters.ToList())
{
if (p.FullName == printer.FullName)
{
p.NumberOfJobs = printer.NumberOfJobs;
p.Status = printer.Status;
return;
}
}
Dispatcher.Invoke(() => _localPrinters.Add(printer));
}
}
So far so good now comes the point I don't know how to handle. The ObservableCollection<Printer> is bound to a DataGrid were all general information is Displayed. Now if a user double clicks a Row, I want to show some 'deeper' information in a userControl. But I can't find a way to access the PrintQueue here because it's the wrong Thread.
private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
{
// Printer is fine, just the PrintQueue inside is full of System.InvalidOperationException
Printer p = (sender as DataGridRow)?.DataContext as Printer;
UcPrinterDetails.InitializeDetails(p.PrintQueue);
UcPrinterDetails.Visibility = UcPrinterDetails.Visibility != Visibility.Visible ? Visibility.Visible : Visibility.Collapsed;
}
So my question here: What is the best way to access the PrintQueue object and which Thread is the owner of the object?
You shouldn't use object (related to Windows functionality) in different thread then it was created in.
In the Row_DoubleClick function just call one more time GetPrintQueues method and find the one that you interested in.
I am experiencing a leak of some sort using webbrowser object; I am still surfing all over the place for answers -- I've seen some similar questions on this forum as well, but I cant see how to apply those findings in my case.
After a page loads the DocumentCompleted action fires and I parse the HTML on the page,
void PageScrollTimerTick(object sender, EventArgs e)
{
String pageSrc = webBrowser1.Document.Body.InnerHtml;
// Check if we need to stop scrolling..
if (m_iLastFramePageLength == pageSrc.Length)
{
m_iLastFramePageLength = 0;
m_scrollTimer.Tick -= PageScrollTimerTick
m_scrollTimer.Enabled = false;
parsePage();
nextPage();
}
else
{
m_iLastFramePageLength = pageSrc.Length;
webBrowser1.Document.Window.ScrollTo(0, webBrowser1.Document.Body.ScrollRectangle.Height);
}
}
The Leak:
As I type this, I wonder why these functions? I have 6 different functions that do very similar tasks. I think these have problems because they are executed from a TIMER which probably uses a different thread. I'm I close? How can I resolve this. Perhaps Invoke() on the web browser control?
doParse():
List<String> doSomeExtractions()
{
List<String> retVal = new List<String>();
foreach (HtmlElement div in webBrowser1.Document.GetElementsByTagName("div"))
{
String szClassName = div.GetAttribute("classname");
switch (szClassName)
{
case "someDivClass":
{
if (div.InnerHtml.Contains("<b>"))
{
retVal.Add(div.InnerHtml);
}
break;
}
default:
{
break;
}
}
}
return retVal;
}
moveNext():
// Store data, navigate to next page.
webBrowser1.DocumentCompleted += this.scrapeData;
webBrowser1.Navigate("about:blank");
I'm using a HtmlEditor control inside a Windows Form.
I got the control from this page:
http://windowsclient.net/articles/htmleditor.aspx
I want to extend the controls functionality by allowing the user to paste images from the clipboard. Right now you can paste plain and formatted text, but when trying to paste an image it does nothing.
Basically what I thought was to detect when the user presses Ctrl+V on the editor, check the clipboard for images and if there's an image, insert it manually to the editor.
The problem with this approach is that I cannot get the OnKeyDown or OnKeyPress events of the form to be raised.
I have the KeyPreview property set to true on the form, but still the events aren't raised.
I also tried to Subclass the form and the editor (as explained here) to intercept the WM_PASTE message, but it isn't raised either.
Any ideas on how to achieve this?
Thanks a lot
I spent all day on this problem and finally have a solution. Trying to listen for the WM_PASTE message doesn't work because Ctrl-V is being PreProcessed by the underlying mshtml Control. You can listen for OnKeyDown/Up etc to catch a Ctrl-V but this won't stop the underlying Control from proceeding with its default Paste behavior. My solution is to prevent the PreProcessing of the Ctrl-V message and then implementing my own Paste behavior. To stop the control from PreProcessing the CtrlV message I had to subclass my Control which is AxWebBrowser,
public class DisabledPasteWebBrowser : AxWebBrowser
{
const int WM_KEYDOWN = 0x100;
const int CTRL_WPARAM = 0x11;
const int VKEY_WPARAM = 0x56;
Message prevMsg;
public override bool PreProcessMessage(ref Message msg)
{
if (prevMsg.Msg == WM_KEYDOWN && prevMsg.WParam == new IntPtr(CTRL_WPARAM) && msg.Msg == WM_KEYDOWN && msg.WParam == new IntPtr(VKEY_WPARAM))
{
// Do not let this Control process Ctrl-V, we'll do it manually.
HtmlEditorControl parentControl = this.Parent as HtmlEditorControl;
if (parentControl != null)
{
parentControl.ExecuteCommandDocument("Paste");
}
return true;
}
prevMsg = msg;
return base.PreProcessMessage(ref msg);
}
}
Here is my custom method to handle Paste commands, yours might do something similar with the Image data from the Clipboard.
internal void ExecuteCommandDocument(string command, bool prompt)
{
try
{
// ensure command is a valid command and then enabled for the selection
if (document.queryCommandSupported(command))
{
if (command == HTML_COMMAND_TEXT_PASTE && Clipboard.ContainsImage())
{
// Save image to user temp dir
String imagePath = Path.GetTempPath() + "\\" + Path.GetRandomFileName() + ".jpg";
Clipboard.GetImage().Save(imagePath, System.Drawing.Imaging.ImageFormat.Jpeg);
// Insert image href in to html with temp path
Uri uri = null;
Uri.TryCreate(imagePath, UriKind.Absolute, out uri);
document.execCommand(HTML_COMMAND_INSERT_IMAGE, false, uri.ToString());
// Update pasted id
Guid elementId = Guid.NewGuid();
GetFirstControl().id = elementId.ToString();
// Fire event that image saved to any interested listeners who might want to save it elsewhere as well
if (OnImageInserted != null)
{
OnImageInserted(this, new ImageInsertEventArgs { HrefUrl = uri.ToString(), TempPath = imagePath, HtmlElementId = elementId.ToString() });
}
}
else
{
// execute the given command
document.execCommand(command, prompt, null);
}
}
}
catch (Exception ex)
{
// Unknown error so inform user
throw new HtmlEditorException("Unknown MSHTML Error.", command, ex);
}
}
Hope someone finds this helpful and doesn't waste a day on it like me today.