OLD | NEW |
| (Empty) |
1 | |
2 namespace Microsoft.VsSDK.IntegrationTestLibrary | |
3 { | |
4 using System; | |
5 using System.Collections.Generic; | |
6 using System.Text; | |
7 using System.Runtime.InteropServices; | |
8 using System.Threading; | |
9 using Microsoft.VisualStudio.Shell.Interop; | |
10 using Microsoft.VisualStudio.Shell; | |
11 | |
12 /// <summary> | |
13 /// This class is responsible to close dialog boxes that pop up during diffe
rent VS Calls | |
14 /// </summary> | |
15 internal class DialogBoxPurger : IDisposable | |
16 { | |
17 /// <summary> | |
18 /// The default number of milliseconds to wait for the threads to signal
to terminate. | |
19 /// </summary> | |
20 private const int DefaultMillisecondsToWait = 3500; | |
21 | |
22 /// <summary> | |
23 /// Object used for synchronization between thread calls. | |
24 /// </summary> | |
25 internal static volatile object Mutex = new object(); | |
26 | |
27 /// <summary> | |
28 /// The IVsUIShell. This cannot be queried on the working thread from th
e service provider. Must be done in the main thread.!! | |
29 /// </summary> | |
30 private IVsUIShell uiShell; | |
31 | |
32 /// <summary> | |
33 /// The button to "press" on the dialog. | |
34 /// </summary> | |
35 private int buttonAction; | |
36 | |
37 /// <summary> | |
38 /// Thread signales to the calling thread that it is done. | |
39 /// </summary> | |
40 private bool exitThread = false; | |
41 | |
42 /// <summary> | |
43 /// Calling thread signales to this thread to die. | |
44 /// </summary> | |
45 private AutoResetEvent threadDone = new AutoResetEvent(false); | |
46 | |
47 /// <summary> | |
48 /// The queued thread started. | |
49 /// </summary> | |
50 private AutoResetEvent threadStarted = new AutoResetEvent(false); | |
51 | |
52 /// <summary> | |
53 /// The result of the dialogbox closing for all the dialog boxes. That i
s if there are two of them and one fails this will be false. | |
54 /// </summary> | |
55 private bool dialogBoxCloseResult = false; | |
56 | |
57 /// <summary> | |
58 /// The expected text to see on the dialog box. If set the thread will c
ontinue finding the dialog box with this text. | |
59 /// </summary> | |
60 private string expectedDialogBoxText = String.Empty; | |
61 | |
62 /// <summary> | |
63 /// The number of the same dialog boxes to wait for. | |
64 /// This is for scenarios when two dialog boxes with the same text are p
opping up. | |
65 /// </summary> | |
66 private int numberOfDialogsToWaitFor = 1; | |
67 | |
68 /// <summary> | |
69 /// Has the object been disposed. | |
70 /// </summary> | |
71 private bool isDisposed; | |
72 | |
73 /// <summary> | |
74 /// Overloaded ctor. | |
75 /// </summary> | |
76 /// <param name="buttonAction">The botton to "press" on the dialog box.<
/param> | |
77 /// <param name="numberOfDialogsToWaitFor">The number of dialog boxes wi
th the same message to wait for. This is the situation when the same action pops
up two of the same dialog boxes</param> | |
78 /// <param name="expectedDialogMesssage">The expected dialog box message
to check for.</param> | |
79 internal DialogBoxPurger(int buttonAction, int numberOfDialogsToWaitFor,
string expectedDialogMesssage) | |
80 { | |
81 this.buttonAction = buttonAction; | |
82 this.numberOfDialogsToWaitFor = numberOfDialogsToWaitFor; | |
83 this.expectedDialogBoxText = expectedDialogMesssage; | |
84 } | |
85 | |
86 /// <summary> | |
87 /// Overloaded ctor. | |
88 /// </summary> | |
89 /// <param name="buttonAction">The botton to "press" on the dialog box.<
/param> | |
90 /// <param name="numberOfDialogsToWaitFor">The number of dialog boxes wi
th the same message to wait for. This is the situation when the same action pops
up two of the same dialog boxes</param> | |
91 internal DialogBoxPurger(int buttonAction, int numberOfDialogsToWaitFor) | |
92 { | |
93 this.buttonAction = buttonAction; | |
94 this.numberOfDialogsToWaitFor = numberOfDialogsToWaitFor; | |
95 } | |
96 | |
97 /// <summary> | |
98 /// Overloaded ctor. | |
99 /// </summary> | |
100 /// <param name="buttonAction">The botton to "press" on the dialog box.<
/param> | |
101 /// <param name="expectedDialogMesssage">The expected dialog box message
to check for.</param> | |
102 internal DialogBoxPurger(int buttonAction, string expectedDialogMesssage
) | |
103 { | |
104 this.buttonAction = buttonAction; | |
105 this.expectedDialogBoxText = expectedDialogMesssage; | |
106 } | |
107 | |
108 /// <summary> | |
109 /// Overloaded ctor. | |
110 /// </summary> | |
111 /// <param name="buttonAction">The botton to "press" on the dialog box.<
/param> | |
112 internal DialogBoxPurger(int buttonAction) | |
113 { | |
114 this.buttonAction = buttonAction; | |
115 } | |
116 | |
117 /// <summary> | |
118 #region IDisposable Members | |
119 | |
120 void IDisposable.Dispose() | |
121 { | |
122 if (this.isDisposed) | |
123 { | |
124 return; | |
125 } | |
126 | |
127 this.WaitForDialogThreadToTerminate(); | |
128 | |
129 this.isDisposed = true; | |
130 } | |
131 | |
132 /// <summary> | |
133 /// Spawns a thread that will start listening to dialog boxes. | |
134 /// </summary> | |
135 internal void Start() | |
136 { | |
137 // We ask for the uishell here since we cannot do that on the therad
that we will spawn. | |
138 IVsUIShell uiShell = Package.GetGlobalService(typeof(SVsUIShell)) as
IVsUIShell; | |
139 | |
140 if (uiShell == null) | |
141 { | |
142 throw new InvalidOperationException("Could not get the uiShell f
rom the serviceProvider"); | |
143 } | |
144 | |
145 this.uiShell = uiShell; | |
146 | |
147 System.Threading.Thread thread = new System.Threading.Thread(new Thr
eadStart(this.HandleDialogBoxes)); | |
148 thread.Start(); | |
149 | |
150 // We should never deadlock here, hence do not use the lock. Wait to
be sure that the thread started. | |
151 this.threadStarted.WaitOne(3500, false); | |
152 } | |
153 | |
154 /// <summary> | |
155 /// Waits for the dialog box close thread to terminate. If the thread do
es not signal back within millisecondsToWait that it is shutting down, | |
156 /// then it will tell to the thread to do it. | |
157 /// </summary> | |
158 internal bool WaitForDialogThreadToTerminate() | |
159 { | |
160 return this.WaitForDialogThreadToTerminate(DefaultMillisecondsToWait
); | |
161 } | |
162 | |
163 /// <summary> | |
164 /// Waits for the dialog box close thread to terminate. If the thread do
es not signal back within millisecondsToWait that it is shutting down, | |
165 /// then it will tell to the thread to do it. | |
166 /// </summary> | |
167 /// <param name="millisecondsToWait">The number milliseconds to wait for
until the dialog purger thread is signaled to terminate. This is just for safe
precaution that we do not hang. </param> | |
168 /// <returns>The result of the dialog boxes closing</returns> | |
169 internal bool WaitForDialogThreadToTerminate(int numberOfMillisecondsToW
ait) | |
170 { | |
171 bool signaled = false; | |
172 | |
173 // We give millisecondsToWait sec to bring up and close the dialog b
ox. | |
174 signaled = this.threadDone.WaitOne(numberOfMillisecondsToWait, false
); | |
175 | |
176 // Kill the thread since a timeout occured. | |
177 if (!signaled) | |
178 { | |
179 lock (Mutex) | |
180 { | |
181 // Set the exit thread to true. Next time the thread will ki
ll itselfes if it sees | |
182 this.exitThread = true; | |
183 } | |
184 | |
185 // Wait for the thread to finish. We should never deadlock here. | |
186 this.threadDone.WaitOne(); | |
187 } | |
188 | |
189 return this.dialogBoxCloseResult; | |
190 } | |
191 | |
192 /// <summary> | |
193 /// This is the thread method. | |
194 /// </summary> | |
195 private void HandleDialogBoxes() | |
196 { | |
197 // No synchronization numberOfDialogsToWaitFor since it is readonly | |
198 IntPtr[] hwnds = new IntPtr[this.numberOfDialogsToWaitFor]; | |
199 bool[] dialogBoxCloseResults = new bool[this.numberOfDialogsToWaitFo
r]; | |
200 | |
201 try | |
202 { | |
203 // Signal that we started | |
204 lock (Mutex) | |
205 { | |
206 this.threadStarted.Set(); | |
207 } | |
208 | |
209 // The loop will be exited either if a message is send by the ca
ller thread or if we found the dialog. If a message box text is specified the lo
op will not exit until the dialog is found. | |
210 bool stayInLoop = true; | |
211 int dialogBoxesToWaitFor = 1; | |
212 | |
213 while (stayInLoop) | |
214 { | |
215 int hwndIndex = dialogBoxesToWaitFor - 1; | |
216 | |
217 // We need to lock since the caller might set context to nul
l. | |
218 lock (Mutex) | |
219 { | |
220 if (this.exitThread) | |
221 { | |
222 break; | |
223 } | |
224 | |
225 // We protect the shell too from reentrency. | |
226 this.uiShell.GetDialogOwnerHwnd(out hwnds[hwndIndex]); | |
227 | |
228 } | |
229 | |
230 if (hwnds[hwndIndex] != IntPtr.Zero) | |
231 { | |
232 StringBuilder windowClassName = new StringBuilder(256); | |
233 NativeMethods.GetClassName(hwnds[hwndIndex], windowClass
Name, windowClassName.Capacity); | |
234 | |
235 // The #32770 is the class name of a messagebox dialog. | |
236 if (windowClassName.ToString().Contains("#32770")) | |
237 { | |
238 IntPtr unmanagedMemoryLocation = IntPtr.Zero; | |
239 string dialogBoxText = String.Empty; | |
240 try | |
241 { | |
242 unmanagedMemoryLocation = Marshal.AllocHGlobal(1
0 * 1024); | |
243 NativeMethods.EnumChildWindows(hwnds[hwndIndex],
new NativeMethods.CallBack(FindMessageBoxString), unmanagedMemoryLocation); | |
244 dialogBoxText = Marshal.PtrToStringUni(unmanaged
MemoryLocation); | |
245 } | |
246 finally | |
247 { | |
248 if (unmanagedMemoryLocation != IntPtr.Zero) | |
249 { | |
250 Marshal.FreeHGlobal(unmanagedMemoryLocation)
; | |
251 } | |
252 } | |
253 | |
254 lock (Mutex) | |
255 { | |
256 | |
257 // Since this is running on the main thread be s
ure that we close the dialog. | |
258 bool dialogCloseResult = false; | |
259 if (this.buttonAction != 0) | |
260 { | |
261 dialogCloseResult = NativeMethods.EndDialog(
hwnds[hwndIndex], this.buttonAction); | |
262 } | |
263 | |
264 // Check if we have found the right dialog box. | |
265 if (String.IsNullOrEmpty(this.expectedDialogBoxT
ext) || (!String.IsNullOrEmpty(dialogBoxText) && String.Compare(this.expectedDia
logBoxText, dialogBoxText.Trim(), StringComparison.OrdinalIgnoreCase) == 0)) | |
266 { | |
267 dialogBoxCloseResults[hwndIndex] = dialogClo
seResult; | |
268 if (dialogBoxesToWaitFor++ >= this.numberOfD
ialogsToWaitFor) | |
269 { | |
270 stayInLoop = false; | |
271 } | |
272 } | |
273 } | |
274 } | |
275 } | |
276 } | |
277 } | |
278 finally | |
279 { | |
280 //Let the main thread run a possible close command. | |
281 System.Threading.Thread.Sleep(2000); | |
282 | |
283 foreach (IntPtr hwnd in hwnds) | |
284 { | |
285 // At this point the dialog should be closed, if not attempt
to close it. | |
286 if (hwnd != IntPtr.Zero) | |
287 { | |
288 NativeMethods.SendMessage(hwnd, NativeMethods.WM_CLOSE,
0, new IntPtr(0)); | |
289 } | |
290 } | |
291 | |
292 lock (Mutex) | |
293 { | |
294 // Be optimistic. | |
295 this.dialogBoxCloseResult = true; | |
296 | |
297 for (int i = 0; i < dialogBoxCloseResults.Length; i++) | |
298 { | |
299 if (!dialogBoxCloseResults[i]) | |
300 { | |
301 this.dialogBoxCloseResult = false; | |
302 break; | |
303 } | |
304 } | |
305 | |
306 this.threadDone.Set(); | |
307 } | |
308 } | |
309 } | |
310 | |
311 /// <summary> | |
312 /// Finds a messagebox string on a messagebox. | |
313 /// </summary> | |
314 /// <param name="hwnd">The windows handle of the dialog</param> | |
315 /// <param name="unmanagedMemoryLocation">A pointer to the memorylocatio
n the string will be written to</param> | |
316 /// <returns>True if found.</returns> | |
317 private static bool FindMessageBoxString(IntPtr hwnd, IntPtr unmanagedMe
moryLocation) | |
318 { | |
319 StringBuilder sb = new StringBuilder(512); | |
320 NativeMethods.GetClassName(hwnd, sb, sb.Capacity); | |
321 | |
322 if (sb.ToString().ToLower().Contains("static")) | |
323 { | |
324 StringBuilder windowText = new StringBuilder(2048); | |
325 NativeMethods.GetWindowText(hwnd, windowText, windowText.Capacit
y); | |
326 | |
327 if (windowText.Length > 0) | |
328 { | |
329 IntPtr stringAsPtr = IntPtr.Zero; | |
330 try | |
331 { | |
332 stringAsPtr = Marshal.StringToHGlobalAnsi(windowText.ToS
tring()); | |
333 char[] stringAsArray = windowText.ToString().ToCharArray
(); | |
334 | |
335 // Since unicode characters are copied check if we are o
ut of the allocated length. | |
336 // If not add the end terminating zero. | |
337 if ((2 * stringAsArray.Length) + 1 < 2048) | |
338 { | |
339 Marshal.Copy(stringAsArray, 0, unmanagedMemoryLocati
on, stringAsArray.Length); | |
340 Marshal.WriteInt32(unmanagedMemoryLocation, 2 * stri
ngAsArray.Length, 0); | |
341 } | |
342 } | |
343 finally | |
344 { | |
345 if (stringAsPtr != IntPtr.Zero) | |
346 { | |
347 Marshal.FreeHGlobal(stringAsPtr); | |
348 } | |
349 } | |
350 return false; | |
351 } | |
352 } | |
353 | |
354 return true; | |
355 } | |
356 | |
357 #endregion | |
358 } | |
359 } | |
OLD | NEW |