I'm trying to use Visio Application events. When instantiating a new Application object, and setting any event (i.e. BeforeDocumentClose), this appears to result in unable to restore the Visio window after minimizing it.
I'm using VS/C# 2013, Windows Forms, Visio 2013 (on Windows 7). Though my main code project is huge implementing exchange between various office applications using Add-Ins, the following simple code reproduces the same issue. It is a Windows Forms project (with added Reference to Microsoft.Office.Interop.Visio).
using Visio = Microsoft.Office.Interop.Visio;
Visio.Application app;
bool initialised = false;
private void visioButton_Click(object sender, EventArgs e)
{
init();
app.Documents.Add("c:\\test.vst"); // creates new document from template
}
void init()
{
if (!initialised)
{
// only initialise once
app = new Visio.Application();
app.BeforeDocumentClose += app_BeforeDocumentClose;
initialised = true;
}
}
void app_BeforeDocumentClose(Visio.Document doc)
{
}
Issue #1: This is the main issue. Creating one or more Visio Documents, the Visio Window is not maximized after being minimized. No Exceptions thrown as far as I can see. Windows just does it's audible error 'ping'.
Issue #2: This is a secondary issue. Creating two or more Visio Documents, hovering over the Windows Taskbar, the preview windows show the waiting cursor instead of normal document preview.
Conditions: Issue #1 only occurs when using an event on the Application. Document, Page/Shape events don't cause any problem. All events are captured fine. Issue #2 always occurs, but this is less important for me.
I've been searching for this issue for a while, but can't find anything related to it, so any help is greatly appreciated.
I am not quite sure what is causing Visio to not respond to restore, but you can try the approach with "AddAdvise" instead:
[ComVisible(true)]
public partial class Form1 : Form, Visio.IVisEventProc
{
public Form1()
{
InitializeComponent();
}
Visio.Application app;
bool initialised = false;
private void button1_Click(object sender, EventArgs e)
{
init();
app.Documents.Add("C:\\test.vst"); // creates new document from template
}
void init()
{
if (!initialised)
{
// only initialise once
app = new Visio.Application();
// app.BeforeDocumentClose += app_BeforeDocumentClose;
app.EventList.AddAdvise(DocCloseEventCode, this, null, null);
initialised = true;
Application.DoEvents();
}
}
const short DocCloseEventCode = unchecked((short)Visio.VisEventCodes.visEvtDoc + (short)Visio.VisEventCodes.visEvtDel);
object Visio.IVisEventProc.VisEventProc(short eventCode, object source, int eventID, int eventSeqNum, object subject,object moreInfo)
{
if (eventCode == DocCloseEventCode)
app_BeforeDocumentClose(subject as Visio.Document);
return null;
}
void app_BeforeDocumentClose(Visio.Document doc)
{
}
}
To provide the completed solution for multiple events using Nikolay's advice, here is the completed code including both events and (de)initialisation of Visio Application, and without using templates. (Note that the Message boxes may turn up in the background, behind the Visio window.)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Visio = Microsoft.Office.Interop.Visio;
namespace VisioInteropTest
{
[ComVisible(true)]
public partial class TestForm : Form, Visio.IVisEventProc
{
Visio.Application app;
bool initialised = false;
// all AddAdvise events:
// https://msdn.microsoft.com/en-us/library/office/ff768620.aspx
const short appCloseEventCode = (short)(Visio.VisEventCodes.visEvtApp | Visio.VisEventCodes.visEvtBeforeQuit);
const short docCloseEventCode = (short)(Visio.VisEventCodes.visEvtDoc | Visio.VisEventCodes.visEvtDel);
public TestForm()
{
InitializeComponent();
}
private void visioButton_Click(object sender, EventArgs e)
{
if (init())
{
app.Documents.Add("");
}
}
bool init()
{
if (!initialised)
{
app = new Visio.Application();
app.EventList.AddAdvise(appCloseEventCode, this, null, null);
app.EventList.AddAdvise(docCloseEventCode, this, null, null);
initialised = true;
}
return initialised;
}
object Visio.IVisEventProc.VisEventProc(short eventCode, object source, int eventID, int eventSeqNum, object subject, object moreInfo)
{
switch (eventCode)
{
case appCloseEventCode: app_BeforeAppClose((Visio.Application)subject); break;
case docCloseEventCode: app_BeforeDocumentClose((Visio.Document)subject); break;
}
return null;
}
void app_BeforeAppClose(Visio.Application app)
{
initialised = false;
MessageBox.Show("App closed");
}
void app_BeforeDocumentClose(Visio.Document doc)
{
MessageBox.Show("Doc closed");
}
}
}
Related
In my windows form application I call, from the main MDIContainer form, a class in which I invoke the open of the child form.
In Main Form :
private void btnTemplate_ItemClick(object sender, ItemClickEventArgs e)
{
beiProgressBar.EditValue = "Form opening in progress...";
repositoryItemMarqueeProgressBar1.ShowTitle = true;
beiProgressBar.Visibility = BarItemVisibility.Always;
bwTemplate.RunWorkerAsync();
}
private void bwTemplate_DoWork(object sender, DoWorkEventArgs e)
{
FrmTemplate frm;
frm = new FrmTemplate();
Callback.SetFormTemplate(this, ref frm);
}
In the Callback class I show the child form :
public delegate void SetFormTemplateCallback(FrmMain pFormMain, ref FrmTemplate pForm);
public static void SetFormTemplate(FrmMain pFormMain, ref FrmTemplate pForm)
{
if (pFormMain.InvokeRequired)
{
SetFormTemplateCallback d = new SetFormTemplateCallback(SetFormTemplate);
pFormMain.Invoke(d, new object[] { pFormMain, pForm });
}
else
{
pForm.MdiParent = pFormMain;
pForm.InitForm();
pForm.Show();
}
}
This operation randomly hangs my application.
I also tried with BeginInvoke but the problem is still present.
I noticed that often the application freeze when it is minimized.
It is difficult also debug this error.
Anyone has ever reached a similar behavior ?
This question already has answers here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
(22 answers)
Closed 3 years ago.
Apologies as I know cross-thread operations have been addressed elsewhere, but all I see are fragments and can't quite figure out where to apply the solutions correctly. So I have distilled this problem to it's most basic essence in the hope that me and anybody else who comes across this can see a complete solution without a lot of support code to wade through or fragments with limited context.
I have looked at various posts such as here, here and here but can't quite place the solutions in context.
I have created a Windows Forms project with a single button and textbox. In the form.cs file here is the complete code to demonstrate the problem:
using System;
using System.Windows.Forms;
using System.Threading;
namespace SampleEventTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
TestClass();
}).Start();
}
private void TestClass()
{
TestEvent tv = new TestEvent();
tv.OnClsConnect += new TestEvent.clsConnect(OnConnected);
tv.DoSomethingThread();
}
public void OnConnected(string str)
{
textBox1.Text = str;
}
}
public class TestEvent
{
public delegate void clsConnect(string str);
public event clsConnect OnClsConnect;
public void DoSomethingThread()
{
if (OnClsConnect != null)
{
OnClsConnect("Thread run");
}
}
}
}
Click the button and you will get the "Cross thread operation not valid" error. How does one properly fix this code? TIA
Basically you can use the BackgroundWorker Control. here is a quick link. also here is an anther link which helps you.
also please read this:
How to: Make thread-safe calls to Windows Forms controls
H/T to S.A. Parkhid for this link.
Here is the solution: modify the method OnConnected to the following:
public void OnConnected(string str)
{
if (textBox1.InvokeRequired)
{
var d = new TestEvent.clsConnect(OnConnected);
textBox1.Invoke(d, new object[] { str });
}
else
{
textBox1.Text = str;
}
}
I'm trying to build a metronome application that has the capability to run in the background. As a starting point I decided to create something simple, a class that has a timer (the metronome itself), a class responsible for obtaining the MIDI output device and a class to play the sound. I'm having difficulty with how to make this run in the background. Additionally, another problem is the fact that the metronome needs to be executed when clicking an application button (in the main process).
Metronome Class:
public class Metronome
{
private DispatcherTimer timer = new DispatcherTimer();
private MidiDeviceSelector deviceSelector = new MidiDeviceSelector();
private void TimerStart()
{
timer.Start();
timer.Tick += timer_Tick;
}
private void timer_Tick(object sender, object e)
{
AudioPlayback.Beep1();
}
public void Start(int bpm)
{
double interval = (double)60.000f / (bpm);
timer.Interval = TimeSpan.FromSeconds(interval);
TimerStart();
}
public void Stop()
{
timer.Stop();
}
}
MidiDeviceSelector:
class MidiDeviceSelector
{
public MidiDeviceSelector()
{
GetOutputMidiDevice();
}
public async void GetOutputMidiDevice()
{
IMidiOutPort currentMidiOutputDevice;
DeviceInformation devInfo;
DeviceInformationCollection devInfoCollection;
string devInfoId;
devInfoCollection = await DeviceInformation.FindAllAsync(MidiOutPort.GetDeviceSelector());
if (devInfoCollection == null)
{
//notify the user that any device was found.
System.Diagnostics.Debug.WriteLine("Any device was found.");
}
devInfo = devInfoCollection[0];
if (devInfo == null)
{
//Notify the User that the device not found
System.Diagnostics.Debug.WriteLine("Device not found.");
}
devInfoId = devInfo.Id.ToString();
currentMidiOutputDevice = await MidiOutPort.FromIdAsync(devInfoId);
if (currentMidiOutputDevice == null)
{
//Notify the User that wasn't possible to create MidiOutputPort for the device.
System.Diagnostics.Debug.WriteLine("It was not possible to create the OutPort for the device.");
}
MidiDevice.midiDevice = currentMidiOutputDevice;
}
Class to Holds the MidiDevice:
class MidiDevice
{
public static IMidiOutPort midiDevice; //Bad practice i know.
}
Class to play the "toc" sound:
class AudioPlayback
{
static IMidiMessage beep1 = new MidiNoteOnMessage(9, 76, 90);
//static IMidiOutPort midiOutputDevice = (IMidiOutPort)MidiDeviceSelector.GetOutputMidiDevice();
public static void Beep1()
{
try
{
MidiDevice.midiDevice.SendMessage(beep1);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
}
Each class is contained in a different file. As you can see, it is a very simple code, if you see any bad programming practice, I apologise, I do not have much experience.
I was looking at the documentation, however, I did not succeed. How do I register an activity in the background and that there is interaction with the application's user interface (a button to stop and start the metronome).
My apologies for bad English, not my native language.
Thank you.
Two things you need to add to make this scenario work:
add the "backgroundMediaPlayback" capability as documented here: https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/background-audio
since you are using the MiDI APIs, you need to explicitly integrate with the SystemMediaTransportControls to prevent getting muted on minimize
I have update your repro sample and verified that it works correctly after adding those two things.
Sharing it here for your reference: https://1drv.ms/u/s!AovTwKUMywTNl9QJTeecnDzCf0WWyQ
I have an old application in Windows Forms, which in many places do some searches on database. Sometimes it takes a lot of time, so I decided to create a loading screen in wpf to show the user that something is loading in separate thread. Basically it's just a full transparent window with loading indicator(a turning circle). Everything works fine on My host computer and on my Virtual Machine, but when I'm trying to deploy it to our demo environments its like - it starts loading the indicator is shown and after few seconds it dissapear and application stops responding like forever. My first thought was that it's the problem with GPU acceleration, that it can't process transparency, but it's being shown for few seconds so it can't be the problem. So most likely I did something bad. Below You can see my code, do You notice something which might be wrong/cause deadlock or something ?
public class LoadingManager
{
public LoadingManager()
{ }
public LoadingManager(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private bool ThreadReadyToAbort = false;
private BusyIndicatorView loadingWindow;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { }; // I also tried to remove this while but it didn't help
}
this.thread.Abort();
}
public void RunThread()
{
this.loadingWindow = new BusyIndicatorView();
loadingWindow.tbLoadingCaption.Text = loadingText;
this.loadingWindow.Closing += new System.ComponentModel.CancelEventHandler(waitingWindow_Closed);
this.loadingWindow.ShowDialog();
}
void waitingWindow_Closed(object sender, System.ComponentModel.CancelEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeShutdown();
this.ThreadReadyToAbort = true;
}
EDIT.
I noticed that on this machines it usually(sometimes it also fails at first click) works when i click search for the first time. If i click another time it's showing for a second than dissapearing and application stops responding. So it seems like Thread is not beeing shutdown, Dispatcher shutdown failed ? But no exceptions are thrown ...
Your BeginLoading method can be called more than once before it has finished, and so can create more than one wpf window. This messes up all kinds of references. Also do not abort the thread, let it decide for itself. Here are the two changes:
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (this.loadingWindow == null) { } // <--- Add this line
}
public void EndLoading()
{
if (this.loadingWindow != null)
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{ this.loadingWindow.Close(); }));
while (!this.ThreadReadyToAbort) { };
}
//this.thread.Abort(); // <-- Remove this line
}
Also if this is all just for a busy screen, I would say there has to be a better and safer way than this. For academic purposes this is an interesting problem, but not production code, especially if some junior developer could fiddle with this in the future.
Edit: Still crashing on my machine if I reduce the delay between repeated callds to BeginLoading and EndLoading. It may be related to how the wpf window closes asynchronously. I removed the Closed event handler and just used a boolean flag to indicated that the window 'isLoaded' or not, and I have not seen any problems with this:
public class LoadingManager2
{
public LoadingManager2()
{ }
public LoadingManager2(string LoadingText)
{
loadingText = LoadingText;
}
private string loadingText = "Please wait ..";
private Thread thread;
private MyWindow loadingWindow;
private bool isLoaded = false;
public void BeginLoading()
{
this.thread = new Thread(this.RunThread);
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
while (!this.isLoaded) { };
}
public void RunThread()
{
this.loadingWindow = new MyWindow();
this.isLoaded = true;
this.loadingWindow.ShowDialog();
}
public void EndLoading()
{
this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
this.loadingWindow.Close();
this.isLoaded = false;
}));
while (this.isLoaded) { };
}
}
I was tasked with writing a real-time Excel automation add-in in C# using RtdServer for work. I relied heavily on the knowledge that I came across in Stack Overflow. I have decide to express my thanks by writing up a how to document that ties together all that I have learned. Kenny Kerr's Excel RTD Servers: Minimal C# Implementation article helped me get started. I found comments by Mike Rosenblum and Govert especially helpful.
(As an alternative to the approach described below you should consider using Excel-DNA. Excel-DNA allows you to build a registration-free RTD server. COM registration requires administrative privileges which may lead to installation headaches. That being said, the code below works fine.)
To create a real-time Excel automation add-in in C# using RtdServer:
1) Create a C# class library project in Visual Studio and enter the following:
using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace StackOverflow
{
public class Countdown
{
public int CurrentValue { get; set; }
}
[Guid("EBD9B4A9-3E17-45F0-A1C9-E134043923D3")]
[ProgId("StackOverflow.RtdServer.ProgId")]
public class RtdServer : IRtdServer
{
private readonly Dictionary<int, Countdown> _topics = new Dictionary<int, Countdown>();
private Timer _timer;
public int ServerStart(IRTDUpdateEvent rtdUpdateEvent)
{
_timer = new Timer(delegate { rtdUpdateEvent.UpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return 1;
}
public object ConnectData(int topicId, ref Array strings, ref bool getNewValues)
{
var start = Convert.ToInt32(strings.GetValue(0).ToString());
getNewValues = true;
_topics[topicId] = new Countdown { CurrentValue = start };
return start;
}
public Array RefreshData(ref int topicCount)
{
var data = new object[2, _topics.Count];
var index = 0;
foreach (var entry in _topics)
{
--entry.Value.CurrentValue;
data[0, index] = entry.Key;
data[1, index] = entry.Value.CurrentValue;
++index;
}
topicCount = _topics.Count;
return data;
}
public void DisconnectData(int topicId)
{
_topics.Remove(topicId);
}
public int Heartbeat() { return 1; }
public void ServerTerminate() { _timer.Dispose(); }
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(#"CLSID\{" + t.GUID.ToString().ToUpper() + #"}\Programmable");
var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(#"CLSID\{" + t.GUID.ToString().ToUpper() + #"}\InprocServer32", true);
if (key != null)
key.SetValue("", System.Environment.SystemDirectory + #"\mscoree.dll", Microsoft.Win32.RegistryValueKind.String);
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey(#"CLSID\{" + t.GUID.ToString().ToUpper() + #"}\Programmable");
}
}
}
2) Right click on the project and Add > New Item... > Installer Class. Switch to code view and enter the following:
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace StackOverflow
{
[RunInstaller(true)]
public partial class RtdServerInstaller : System.Configuration.Install.Installer
{
public RtdServerInstaller()
{
InitializeComponent();
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
var registrationServices = new RegistrationServices();
if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
Trace.TraceInformation("Types registered successfully");
else
Trace.TraceError("Unable to register types");
}
[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
var registrationServices = new RegistrationServices();
if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
Trace.TraceInformation("Types registered successfully");
else
Trace.TraceError("Unable to register types");
}
public override void Uninstall(IDictionary savedState)
{
var registrationServices = new RegistrationServices();
if (registrationServices.UnregisterAssembly(GetType().Assembly))
Trace.TraceInformation("Types unregistered successfully");
else
Trace.TraceError("Unable to unregister types");
base.Uninstall(savedState);
}
}
}
3) Right click on the project Properties and check off the following: Application > Assembly Information... > Make assembly COM-Visible and Build > Register for COM Interop
3.1) Right click on the project Add Reference... > .NET tab > Microsoft.Office.Interop.Excel
4) Build Solution (F6)
5) Run Excel. Go to Excel Options > Add-Ins > Manage Excel Add-Ins > Automation and select "StackOverflow.RtdServer"
6) Enter "=RTD("StackOverflow.RtdServer.ProgId",,200)" into a cell.
7) Cross your fingers and hope that it works!
Calling UpdateNotify from the timer thread will eventually cause strange errors or disconnections from Excel.
The UpdateNotify() method must only be called from the same thread that calls ServerStart(). It's not documented in RTDServer help, but it is restriction of COM.
The fix is simple. Use DispatcherSynchronizationContext to capture the thread that calls ServerStart and use that to dispatch calls to UpdateNotify:
public class RtdServer : IRtdServer
{
private IRTDUpdateEvent _rtdUpdateEvent;
private SynchronizationContext synchronizationContext;
public int ServerStart( IRTDUpdateEvent rtdUpdateEvent )
{
this._rtdUpdateEvent = rtdUpdateEvent;
synchronizationContext = new DispatcherSynchronizationContext();
_timer = new Timer(delegate { PostUpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return 1;
}
// Notify Excel of updated results
private void PostUpdateNotify()
{
// Must only call rtdUpdateEvent.UpdateNotify() from the thread that calls ServerStart.
// Use synchronizationContext which captures the thread dispatcher.
synchronizationContext.Post( delegate(object state) { _rtdUpdateEvent.UpdateNotify(); }, null);
}
// etc
} // end of class
Following the previous two answers for the RTD server worked for me. However I ran into an issue when on an x64 machine running Excel x64. In my case, until I switched the "target platform" of the project to x64, Excel always showed #N/A.