Using User32.dll SendMessage to handle the download popup - c#

I'm navigating through a webapp using WatiN and I needed to download a document. However, I've found out that WatiN doesn't support the download with Internet Explorer 11. That's why I'm trying to do it using the method described here :
How to enable automatic downloads in IE11 (the top answer)
Using User32.dll SendMessage To Send Keys With ALT Modifier
Basically I'm calling user32.dll to handle the small popup DL window (F6 to select it, tab, enter, etc.)
However nothing happens, the popup isn't responding to my commands.
Is this the right way to do it ?
My code looks like this :
//Before my method
[DllImport("user32.dll")]
public static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, uint wParam, uint lParam);
private void button1_Click(object sender, EventArgs e)
{
//some code....
IE myPopup = IE.AttachTo<IE>(Find.ByUrl("http://abcd.do"));
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler("test");
myPopup.AddDialogHandler(fileDownloadHandler);
myPopup.Link(Find.ByUrl("httpabcdefg.do")).ClickNoWait();
ushort action = (ushort)260; //WM_SYSKEYDOWN
System.Threading.Thread.Sleep(2000);
ushort key = (ushort)System.Windows.Forms.Keys.F6;
ushort key2 = (ushort)System.Windows.Forms.Keys.Tab;
ushort key3 = (ushort)System.Windows.Forms.Keys.Enter;
ushort key4 = (ushort)System.Windows.Forms.Keys.Right;
SendMessage(myPopup.hWnd, action, key, 0);
SendMessage(myPopup.hWnd, action, key2, 0);
SendMessage(myPopup.hWnd, action, key3, 0);
SendMessage(myPopup.hWnd, action, key4, 0);
SendMessage(myPopup.hWnd, action, key4, 0);
SendMessage(myPopup.hWnd, action, key3, 0);
}
I know the popup (myPopup) is handled correctly, I can click on it using watin.
A couple of things are unclear to me, and I put the action WM_SYSKEYDOWN as default, to follow the example cited above.
Thanks in advance for any kind of help !
--------------EDIT-----------------
I managed to do it, I know it's not optimal at all but I dropped the Dllimport and the SendMessage all together and used SendKeys.Send instead. Just like that :
private void button1_Click(object sender, EventArgs e)
{
//some code....
IE myPopup = IE.AttachTo<IE>(Find.ByUrl("http://abcd.do"));
FileDownloadHandler fileDownloadHandler = new FileDownloadHandler("test");
myPopup.AddDialogHandler(fileDownloadHandler);
myPopup.Link(Find.ByUrl("httpabcdefg.do")).ClickNoWait();
SendKeys.Send("{F6}");
SendKeys.Send("{ENTER}");
SendKeys.Send("{RIGHT}");
SendKeys.Send("{RIGHT}");
SendKeys.Send("{ENTER}");
}

The code that you are using is not a proper way to automate and WatiN does not completely interact with Windows controls and help in windows is very little. I had the same problem in handling the windows controls as we had multiple versions of IE. IE 9.0 and higher have different file handling when compared to <= IE 8.0 version. The below code works fine for IE 9.0 and higher. Please make sure proper references are added (refer using's).
Refer below code and modify it as per you requirement.
using System.Threading;
using System.Windows.Automation;
using WatiN.Core;
using WatiN.Core.Native.Windows;
namespace TestFramework.Util
{
public static class WindowsHelper
{
#region Public Methods
/// <summary>
/// Download IE file.
/// </summary>
/// <param name="action">Action can be Save/Save As/Open/Cancel.</param>
/// <param name="path">Path where file needs to be saved (for Save As function).</param>
public static void DownloadIEFile(string action, string path = "", string regexPatternToMatch = "")
{
Browser browser = null;
if (Utility.Browser != null) // Utility.Browser is my WatiN browser instance.
{
if (string.IsNullOrEmpty(regexPatternToMatch))
{
browser = Utility.Browser;
}
else
{
Utility.Wait(() => (browser = Browser.AttachTo<IE>(Find.ByUrl(new System.Text.RegularExpressions.Regex(regexPatternToMatch)))) != null);
}
}
else
{
return;
}
// If doesn't work try to increase sleep interval or write your own waitUntill method
Thread.Sleep(3000);
// See information here (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633515(v=vs.85).aspx)
Window windowMain = null;
Utility.Wait(() => (windowMain = new Window(NativeMethods.GetWindow(browser.hWnd, 5))).ProcessID != 0);
TreeWalker trw = new TreeWalker(Condition.TrueCondition);
AutomationElement mainWindow = trw.GetParent(AutomationElement.FromHandle(browser.hWnd));
Window windowDialog = null;
Utility.Wait(() => (windowDialog = new Window(NativeMethods.GetWindow(windowMain.Hwnd, 5))).ProcessID != 0);
windowDialog.SetActivate();
AutomationElementCollection amc = null;
Utility.Wait(() => (amc = AutomationElement.FromHandle(windowDialog.Hwnd).FindAll(TreeScope.Children, Condition.TrueCondition)).Count > 1);
foreach (AutomationElement element in amc)
{
// You can use "Save ", "Open", ''Cancel', or "Close" to find necessary button Or write your own enum
if (element.Current.Name.Equals(action))
{
// If doesn't work try to increase sleep interval or write your own waitUntil method
// WaitUntilButtonExsist(element,100);
Thread.Sleep(1000);
AutomationPattern[] pats = element.GetSupportedPatterns();
// Replace this for each if you need 'Save as' with code bellow
foreach (AutomationPattern pat in pats)
{
// '10000' button click event id
if (pat.Id == 10000)
{
InvokePattern click = (InvokePattern)element.GetCurrentPattern(pat);
click.Invoke();
}
}
}
else if (element.Current.Name.Equals("Save") && action == "Save As")
{
AutomationElementCollection bmc = element.FindAll(TreeScope.Children, Automation.ControlViewCondition);
InvokePattern click1 = (InvokePattern)bmc[0].GetCurrentPattern(AutomationPattern.LookupById(10000));
click1.Invoke();
Thread.Sleep(1000);
AutomationElementCollection main = mainWindow.FindAll(TreeScope.Children, Condition.TrueCondition);
foreach (AutomationElement el in main)
{
if (el.Current.LocalizedControlType == "menu")
{
// First array element 'Save', second array element 'Save as', third second array element 'Save and open'
InvokePattern clickMenu = (InvokePattern)
el.FindAll(TreeScope.Children, Condition.TrueCondition)[1].GetCurrentPattern(AutomationPattern.LookupById(10000));
clickMenu.Invoke();
Thread.Sleep(1000);
ControlSaveDialog(mainWindow, path);
break;
}
}
}
}
}
/// <summary>
/// Control for save dialog.
/// </summary>
/// <param name="mainWindow">Main window.</param>
/// <param name="path">Path.</param>
private static void ControlSaveDialog(AutomationElement mainWindow, string path)
{
// Obtain the save as dialog
var saveAsDialog = mainWindow
.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.NameProperty, "Save As"));
// Get the file name box
var saveAsText = saveAsDialog
.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "File name:"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit)))
.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
// Fill the filename box
saveAsText.SetValue(path);
Thread.Sleep(500);
Utility.PressKey("LEFT");
Utility.PressKey("LEFT");
Thread.Sleep(1000);
// Find the save button
var saveButton =
saveAsDialog.FindFirst(TreeScope.Descendants,
new AndCondition(
new PropertyCondition(AutomationElement.NameProperty, "Save"),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button)));
// Invoke the button
var pattern = saveButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
pattern.Invoke();
}
#endregion
}
}
public static class Utility
{
public static IE Browser { get; set; }
// Wait specified number of seconds
public static void Wait(int seconds)
{
System.Threading.Thread.Sleep(seconds * 1000);
}
// Wait for condition to evaluate true, timeout after 30 seconds
public static void Wait(Func<bool> condition)
{
int count = 0;
while (!condition() && count < 30)
{
System.Threading.Thread.Sleep(1000);
count++;
}
}
//Send tab key press to browser
public static void PressTab()
{
System.Windows.Forms.SendKeys.SendWait("{TAB}");
System.Threading.Thread.Sleep(300);
}
//Send specified key press to browser
public static void PressKey(string keyname)
{
System.Windows.Forms.SendKeys.SendWait("{" + keyname.ToUpper() + "}");
System.Threading.Thread.Sleep(300);
}
}

Related

Clipboard: Copy and receive array

In a Winform application I have an array of this format:
float[][,]
I need to copy it via the clipboard to another Winform app.
How could I achieve this?
Thanks
Short answer: Using the System.Windows.Forms.Clipboard is simple.
Use Clipboard.SetData("CustomDataName", nonStandardData); to put your custom data on the clipboard and use var clipboardData = (float[][,])Clipboard.GetData("CustomDataName"); to get your custom data off the clipboard. You should check if the data is available on the clipboard using Clipboard.ContainsData("CustomDataName")
Note: By default the Windows clipboard only contains one entry at
a time be it an "image", "text" or "custom data". By putting your
custom data on the clipboard the previous data contained on the
clipboard will be gone.
The clipboard is best used for sharing more universal data between
unrelated programs, such as an image which can be pasted into a web browser or image editor. A grid of csv values which can be pasted into different data capturing and spreadsheet programs. float[][,] on the other hand has no real meaning other than to your software.
Long Answer (Here is an example):
// A Format/"Type" name for your clipboard data
static readonly string JOE_SOAP_DATA_V1 =
"JOE_SOAP_DATA_V1 {F1193E97-0B29-4ACD-935C-835A8EDFBB9D}";
// Some dummy data used to demonstrate
private readonly float[][,] JoeSoapData = new float[3][,]
{
new float[,]{{54,90},{34,56}},
new float[,]
{
{
// random values to create some variation
// will be different each time program is started
(float)new Random().NextDouble() * 2,
(float)new Random().NextDouble() * 6
},
{
(float)new Random().NextDouble() * 4,
(float)new Random().NextDouble() * 7
},
{
(float)new Random().NextDouble() * 4,
(float)new Random().NextDouble() * 7
}
},
new float[,]{{54,90},{34,56}}
};
private string JoeSoapDataToString(float[][,] data)
{
var sb = new StringBuilder();
foreach(var x in data)
{
foreach (var y in x)
sb.Append(y).Append(", ");
sb.Append('\n');
}
return sb.ToString();
}
private void BtnCopyToClipboard_Click(object sender, EventArgs e)
{
// On button click add the dummy data to the clipboard
Clipboard.SetData(JOE_SOAP_DATA_V1, JoeSoapData);
}
private void BtnPasteFromClipboard_Click(object sender, EventArgs e)
{
// Check if the clipboard contains our unique Format and notify the user if it does not
if (!Clipboard.ContainsData(JOE_SOAP_DATA_V1))
{
MessageBox.Show($"No {nameof(JOE_SOAP_DATA_V1)} clipboard data found :(");
return;
}
// extract the clipboard data and show the user
var clipboardData = (float[][,])Clipboard.GetData(JOE_SOAP_DATA_V1);
// show the copied data
LblFromClipboard.Text = "Data from clipboard\n" + JoeSoapDataToString(clipboardData);
}
private void Form1_Load(object sender, EventArgs e)
{
LblDataToCopy.Text = "Data that will be copied to the clipboard\n"
+ JoeSoapDataToString(JoeSoapData);
}
You can use AddClipboardFormatListener (for listening the clipboard update message) combined with Clipboard class to copy the data.
Receiver app:
public partial class ReceiverAppMainForm : Form
{
public ReceiverAppMainForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
ClipboardWrapper.AddClipboardFormatListener(Handle);
base.OnLoad(e);
}
protected override void OnClosing(CancelEventArgs e)
{
ClipboardWrapper.RemoveClipboardFormatListener(Handle);
base.OnClosing(e);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == ClipboardWrapper.WM_CLIPBOARDUPDATE)
{
var data = Clipboard.GetDataObject();
if (data != null && data.GetDataPresent("clipboard-demoapp-data"))
{
var array = (float[][,])data.GetData("clipboard-demoapp-data");
// TODO: use the shared data
}
}
}
internal static class ClipboardWrapper
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
internal const int WM_CLIPBOARDUPDATE = 0x031D;
}
}
Sender app:
public partial class SenderAppMainForm : Form
{
// ... ignored for brevity
private static void CopyAppDataToClipboard(float[][,] data)
{
// variant #1 (to avoid duplication)
var dataObject = new DataObject();
dataObject.SetData("clipboard-demoapp-data", data);
Clipboard.SetDataObject(dataObject, false, 10, 100);
// variant #2 (the event is sending twice on my computer running Win10)
//Clipboard.SetData("cliboard-demoapp-data", data);
}
}

How to set a hotkey to a Non-Form Program

Okay so I'm making this simple screenshot program using bitmaps and such, but when i try to make a hotkey like f12, for instance, nothing happens, i coded it just to show a message box, but it doesn't even do that. So i set it back to do both message box and take a screenshot but still doesn't work.
private static NotifyIcon notifyIcon;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
// We need to dispose here, or the icon will not remove until the
// system tray is updated.
System.Windows.Forms.Application.ApplicationExit += delegate
{
notifyIcon.Dispose();
};
CreateNotifyIcon();
System.Windows.Forms.Application.Run();
}
/// <summary>
/// Creates the icon that sits in the system tray.
/// </summary>
///
static void Program_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.F12)
{
MessageBox.Show("ScreenShot Taken");
TakeFullScreenShot();
}
else if(e.KeyCode == Keys.F11)
{
Application.Exit();
}
}
private static void CreateNotifyIcon()
{
notifyIcon = new NotifyIcon
{
Icon = Resources.AppIcon, ContextMenu = GetContextMenu()
};
notifyIcon.Visible = true;
}
private static ContextMenu GetContextMenu()
{
string myPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString();
System.Diagnostics.Process prc = new System.Diagnostics.Process();
prc.StartInfo.FileName = myPath;
ContextMenu menu = new ContextMenu();
menu.MenuItems.Add("Take Screenshot (F12)", delegate { TakeFullScreenShot(); });
menu.MenuItems.Add("Open Folder", delegate { prc.Start(); });
menu.MenuItems.Add("Exit", delegate { System.Windows.Forms.Application.Exit(); });
return menu;
}
private static void TakeFullScreenShot()
{
int width = Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Height;
using (Bitmap screenshot = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
using (Graphics graphics = Graphics.FromImage(screenshot))
{
Point origin = new Point(0, 0);
Size screenSize = Screen.PrimaryScreen.Bounds.Size;
//Copy Entire screen to entire bitmap.
graphics.CopyFromScreen(origin, origin, screenSize);
}
//Check to see if the file exists, if it does, append.
int append = 1;
while (File.Exists($"Screenshot{append}.jpg"))
append++;
string fileName = $"Screenshot{append}.jpg";
screenshot.Save(fileName, ImageFormat.Jpeg);
}
}
You may not need all of that but its just to make sure i didn't mess anything up when trying to build it. Also, this program has no form to it, its just a icon in your taskbar, if you right click on it and click Take Screenshot(F12) It will take the screenshot with out problem. Thank you!
The hotkey will not work unless the program has focus. The Key events are only sent to your application when it has focus.

How can I programmatically stop debugging?

I want to perform action on button click that debugging should be stopped when the button is clicked:
private void button3_Click(object sender, EventArgs e)
{
//write code here to stop debugging
}
How do you mean 'stop debugging' you can use the Detach option in Visual Studio, but that isn't in code. To exit the application while debugging use this (windows forms code):
if (Debugger.IsAttached)
{
Application.Exit();
}
If you are after programmatically detaching debugger, you need to get a reference to the currently running EnvDTE80.DTE2 object. Once you have that, you could try:
var dte = ...
dte.Debugger.DetachAll()
To get a reference to EnvDTE80.DTE2, adabyron's approach seems to work:
Get the reference of the DTE2 object in Visual C# 2010
You can wrap it all in some class like so:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE80;
class DetachDebugger
{
[DllImport("ole32.dll")]
private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
public static void Detach()
{
var dte = GetCurrent();
dte.Debugger.DetachAll();
}
/// <summary>
/// Gets the current visual studio's solution DTE2
/// </summary>
private static DTE2 GetCurrent()
{
List<DTE2> dte2s = new List<DTE2>();
IRunningObjectTable rot;
GetRunningObjectTable(0, out rot);
IEnumMoniker enumMoniker;
rot.EnumRunning(out enumMoniker);
enumMoniker.Reset();
IntPtr fetched = IntPtr.Zero;
IMoniker[] moniker = new IMoniker[1];
while (enumMoniker.Next(1, moniker, fetched) == 0)
{
IBindCtx bindCtx;
CreateBindCtx(0, out bindCtx);
string displayName;
moniker[0].GetDisplayName(bindCtx, null, out displayName);
// add all VisualStudio ROT entries to list
if (displayName.StartsWith("!VisualStudio"))
{
object comObject;
rot.GetObject(moniker[0], out comObject);
dte2s.Add((DTE2)comObject);
}
}
// get path of the executing assembly (assembly that holds this code) - you may need to adapt that to your setup
string thisPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
// compare dte solution paths to find best match
KeyValuePair<DTE2, int> maxMatch = new KeyValuePair<DTE2, int>(null, 0);
foreach (DTE2 dte2 in dte2s)
{
int matching = GetMatchingCharsFromStart(thisPath, dte2.Solution.FullName);
if (matching > maxMatch.Value)
maxMatch = new KeyValuePair<DTE2, int>(dte2, matching);
}
return (DTE2)maxMatch.Key;
}
/// <summary>
/// Gets index of first non-equal char for two strings
/// Not case sensitive.
/// </summary>
private static int GetMatchingCharsFromStart(string a, string b)
{
a = (a ?? string.Empty).ToLower();
b = (b ?? string.Empty).ToLower();
int matching = 0;
for (int i = 0; i < Math.Min(a.Length, b.Length); i++)
{
if (!char.Equals(a[i], b[i]))
break;
matching++;
}
return matching;
}
}
Then, using your event handler:
private void button3_Click(object sender, EventArgs e)
{
//write code here to stop debugging
DetachDebugger.Detach();
}

Force to close MessageBox programmatically

Let me give you the background.
We have an Application(medium sized) that is using MessageBox.Show (....) at various places (in hundreds).
These message boxes are part of workflow and being used for informing,warning or taking input from an user. Application is supposed to automatically log off after certain time if there is no activity. We have a requirement that while logging out the application, just to clean the session data , to clear views and to hide itself so that in next launch, it won't have to execute the startup process which is costly in terms of time.
Everything is working fine but in a scenario when there is some message box on the screen and user left the machine without responding to message box and then due to no activity to make the application to log out. Problem is Message box won't disappear.
How I can close the opened messagebox, if any, while hiding the application?
Here is a piece of code based on UIAutomation (a cool but still not very used API) that attempts to close all modal windows (including the one opened with MessageBox) of the current process:
/// <summary>
/// Attempt to close modal windows if there are any.
/// </summary>
public static void CloseModalWindows()
{
// get the main window
AutomationElement root = AutomationElement.FromHandle(Process.GetCurrentProcess().MainWindowHandle);
if (root == null)
return;
// it should implement the Window pattern
object pattern;
if (!root.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
return;
WindowPattern window = (WindowPattern)pattern;
if (window.Current.WindowInteractionState != WindowInteractionState.ReadyForUserInteraction)
{
// get sub windows
foreach (AutomationElement element in root.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)))
{
// hmmm... is it really a window?
if (element.TryGetCurrentPattern(WindowPattern.Pattern, out pattern))
{
// if it's ready, try to close it
WindowPattern childWindow = (WindowPattern)pattern;
if (childWindow.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction)
{
childWindow.Close();
}
}
}
}
}
For example, if you have a WinForms application that pops up a MessageBox when you press some button1, you will still be able to close the app using Windows "Close Window" menu (right click in the task bar):
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Don't click me. I want to be closed automatically!");
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (m.Msg == WM_SYSCOMMAND) // this is sent even if a modal MessageBox is shown
{
if ((int)m.WParam == SC_CLOSE)
{
CloseModalWindows();
Close();
}
}
base.WndProc(ref m);
}
You could use CloseModalWindows somewhere else in your code of course, this is just a sample.
This link on MSDN forums shows how to close a message box by using FindWindow and sending a WM_CLOSE message. Although the question was asked for .NET/WindowsCE, it might solve your problem, its worth a look
Refer to DmitryG post in "Close a MessageBox after several seconds"
Auto-Close MessageBox after timeout reach
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
public class AutoClosingMessageBox
{
System.Threading.Timer _timeoutTimer;
string _caption;
AutoClosingMessageBox(string text, string caption, int timeout)
{
_caption = caption;
_timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
null, timeout, System.Threading.Timeout.Infinite);
MessageBox.Show(text, caption);
}
public static void Show(string text, string caption, int timeout)
{
new AutoClosingMessageBox(text, caption, timeout);
}
void OnTimerElapsed(object state)
{
IntPtr mbWnd = FindWindow(null, _caption);
if (mbWnd != IntPtr.Zero)
SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
_timeoutTimer.Dispose();
}
const int WM_CLOSE = 0x0010;
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
and Call it via
AutoClosingMessageBox.Show("Content", "Title", TimeOut);
First a Question: If messages boxes are used as part of workflow, won't programatically closing message box cause the flow to change/continue?
I think you have three options
Create your own version of the messagebox class that opens a dialog window that looks like a messagebox with added functionality so it closed automatically after a period of time.
Implement something like this in c# to close message boxes programtically.
http://www.codeproject.com/KB/dialog/AutoCloseMessageBox.aspx
Get rid of the message boxes from interupting the workflow. This is probably the best solution as from the sound of it closing a message box programatically will cause workflow to continue/change, and perhaps even cause another messagebox to show which may not be desirable. But obviously fixing the root problem might be best, but isn't always the easiest.
1 and 2 would need to be done from a separate thread, so you will need to think about the implications of that as showing the messagebox will be blocking.
Heres my example with SendKeys - tested and working:
lets say we have backgroundworker and button in form. After button was click - start worker and show message box. In workers DoWork event sleep for 5s and then send enter key - messsage box closed.
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
MessageBox.Show("Close this message!");
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
SendKeys.SendWait("{Enter}");//or Esc
}
This topic has been abundantly covered in other SO questions but since this particular one has several answers about using UI automation/window lookup techniques (which I don't particularly like) and generic suggestions about creating own dialog without provided code, I decided post my own solution. One can create an instantiable MessageBox like class as it follows:
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Text.RegularExpressions;
namespace Common
{
// Loosely based on: https://www.codeproject.com/Articles/17253/A-Custom-Message-Box
class MsgBox : Form
{
private Panel _plHeader = new Panel();
private Panel _plFooter = new Panel();
private Panel _plIcon = new Panel();
private PictureBox _picIcon = new PictureBox();
private FlowLayoutPanel _flpButtons = new FlowLayoutPanel();
private Label _lblMessage;
private MsgBox()
{
FormBorderStyle = FormBorderStyle.FixedDialog;
BackColor = Color.White;
StartPosition = FormStartPosition.CenterScreen;
MinimizeBox = false;
MaximizeBox = false;
ShowIcon = false;
Width = 400;
_lblMessage = new Label();
_lblMessage.Font = new Font("Segoe UI", 10);
_lblMessage.Dock = DockStyle.Fill;
_lblMessage.TextAlign = ContentAlignment.MiddleLeft;
_flpButtons.FlowDirection = FlowDirection.RightToLeft;
_flpButtons.Dock = DockStyle.Fill;
//_plHeader.FlowDirection = FlowDirection.TopDown;
_plHeader.Dock = DockStyle.Fill;
_plHeader.Padding = new Padding(20);
_plHeader.Controls.Add(_lblMessage);
_plFooter.Dock = DockStyle.Bottom;
_plFooter.BackColor = Color.FromArgb(240, 240, 240);
_plFooter.Padding = new Padding(10);
_plFooter.Height = 60;
_plFooter.Controls.Add(_flpButtons);
_picIcon.Location = new Point(30, 50);
_plIcon.Dock = DockStyle.Left;
_plIcon.Padding = new Padding(20);
_plIcon.Width = 70;
_plIcon.Controls.Add(_picIcon);
Controls.Add(_plHeader);
Controls.Add(_plIcon);
Controls.Add(_plFooter);
}
public static DialogResult Show(IWin32Window owner, string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
{
var msgBox = Create(message, title, buttons, icon);
return msgBox.ShowDialog(owner);
}
public static DialogResult Show(string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
{
var msgBox = Create(message, title, buttons, icon);
return msgBox.ShowDialog();
}
public static MsgBox Create(string message, string title = null, MessageBoxButtons? buttons = MessageBoxButtons.OK, MessageBoxIcon icon = MessageBoxIcon.Information)
{
var msgBox = new MsgBox();
msgBox.Init(message, title, buttons, icon);
return msgBox;
}
void Init(string message, string title, MessageBoxButtons? buttons, MessageBoxIcon icon)
{
_lblMessage.Text = message;
Text = title;
InitButtons(buttons);
InitIcon(icon);
Size = MessageSize(message);
}
void InitButtons(MessageBoxButtons? buttons)
{
if (!buttons.HasValue)
return;
switch (buttons)
{
case MessageBoxButtons.AbortRetryIgnore:
AddButton("Ignore");
AddButton("Retry");
AddButton("Abort");
break;
case MessageBoxButtons.OK:
AddButton("OK");
break;
case MessageBoxButtons.OKCancel:
AddButton("Cancel");
AddButton("OK");
break;
case MessageBoxButtons.RetryCancel:
AddButton("Cancel");
AddButton("Retry");
break;
case MessageBoxButtons.YesNo:
AddButton("No");
AddButton("Yes");
break;
case MessageBoxButtons.YesNoCancel:
AddButton("Cancel");
AddButton("No");
AddButton("Yes");
break;
}
}
void InitIcon(MessageBoxIcon icon)
{
switch (icon)
{
case MessageBoxIcon.None:
_picIcon.Hide();
break;
case MessageBoxIcon.Exclamation:
_picIcon.Image = SystemIcons.Exclamation.ToBitmap();
break;
case MessageBoxIcon.Error:
_picIcon.Image = SystemIcons.Error.ToBitmap();
break;
case MessageBoxIcon.Information:
_picIcon.Image = SystemIcons.Information.ToBitmap();
break;
case MessageBoxIcon.Question:
_picIcon.Image = SystemIcons.Question.ToBitmap();
break;
}
_picIcon.Width = _picIcon.Image.Width;
_picIcon.Height = _picIcon.Image.Height;
}
private void ButtonClick(object sender, EventArgs e)
{
Button btn = (Button)sender;
switch (btn.Text)
{
case "Abort":
DialogResult = DialogResult.Abort;
break;
case "Retry":
DialogResult = DialogResult.Retry;
break;
case "Ignore":
DialogResult = DialogResult.Ignore;
break;
case "OK":
DialogResult = DialogResult.OK;
break;
case "Cancel":
DialogResult = DialogResult.Cancel;
break;
case "Yes":
DialogResult = DialogResult.Yes;
break;
case "No":
DialogResult = DialogResult.No;
break;
}
Close();
}
private static Size MessageSize(string message)
{
int width=350;
int height = 230;
SizeF size = TextRenderer.MeasureText(message, new Font("Segoe UI", 10));
if (message.Length < 150)
{
if ((int)size.Width > 350)
{
width = (int)size.Width;
}
}
else
{
string[] groups = (from Match m in Regex.Matches(message, ".{1,180}") select m.Value).ToArray();
int lines = groups.Length+1;
width = 700;
height += (int)(size.Height+10) * lines;
}
return new Size(width, height);
}
private void AddButton(string caption)
{
var btn = new Button();
btn.Text = caption;
btn.Font = new Font("Segoe UI", 8);
btn.BackColor = Color.FromArgb(225, 225, 225);
btn.Padding = new Padding(3);
btn.Height = 30;
btn.Click += ButtonClick;
_flpButtons.Controls.Add(btn);
}
}
}
One can then just keep the reference of the dialog in a class scope, show the dialog and get the result, or just close it in an application exit event handler.
MsgBox _msgBox;
void eventHandler1(object sender, EventArgs e)
{
_msgBox = MsgBox.Create("Do you want to continue", "Inquiry", MessageBoxButtons.YesNo);
var result = _msgBox.ShowDialog();
// do something with result
}
void applicationExitHandler(object sender, EventArgs e)
{
if (_msgBox != null)
_msgBox.Close();
}
I think the cleanest way would be to implement you own message box form like
class MyMessageBox : Form {
private MyMessageBox currentForm; // The currently active message box
public static Show(....) { // same as MessageBox.Show
// ...
}
public static Show(...) { // define additional overloads
}
public static CloseCurrent() {
if (currentForm != null)
currentForm.Close();
}
// ...
}
In some of my larger projects, I found this approach useful also for other purposes (such as automatic logging of error messages etc.)
The second idea I have would be to use GetTopWindow() (or maybe some other WIN32 function) to get the current top-level window of your application and send a WM_CLOSE message to it.
Taking as an assumption that you can edit the code that's calling the
MessageBox.Show() method, I would recommend not use
MessageBox. Instead, just use your own custom form, calling ShowDialog()
on it to do basically the same thing as the MessageBox class. Then, you
have the instance of the form itself, and you can call Close() on that
instance to close it.
A good example is here.
I used .net 2 and two approaches with the same trick.
Open the MessageBox from stub-Form with MessageBox.Show(this,"message")
When the form is not visible or doesn't has really UI.
Keep the form handler and close it with:
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
or
holding the form as class parameter and using FormX.Close().
Since the Form is the owner of the MessageBox, Closing it will close the MessageBox.
The easiest solution is to create a form that will close on timer_tick
private int interval = 0;
private string message = "";
public msgBox(string msg = "", int i = 0)
{
InitializeComponent();
interval = i;
message = msg;
}
private void MsgBox_Load(object sender, EventArgs e)
{
if (interval > 0)
timer1.Interval = interval;
lblMessage.Text = message;
lblMessage.Width = panel1.Width - 20;
lblMessage.Left = 10;
}
private void Timer1_Tick(object sender, EventArgs e)
{
this.Close();
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
ControlPaint.DrawBorder(e.Graphics, this.panel1.ClientRectangle, Color.DarkBlue, ButtonBorderStyle.Solid);
}
Method to use in main form
private void showMessage(string msg, int interval = 0)
{
msgBox mb = new msgBox(msg, interval);
mb.ShowDialog(this);
}
Call it
showMessage("File saved");
Create your own control for this and implement behavior you like to have there. As an option there may be a timer to close this MessageBox.

Setting FlashVars of AxShockwaveFlash

a program of mine uses AxShockwaveFlash component used as stream player.
The problem is that my code works with most stream-providers (livestream, ustream, own3d.tv) but Justin.TV's player is somewhat problematic.
Before moving on the actual problem let me summarize my code;
Inherited FlashControl - this allows me to override the flashplayer's built-in menu:
public class FlashPlayer : AxShockwaveFlashObjects.AxShockwaveFlash // Customized Flash Player.
{
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_MOUSEWHEEL = 0x020A;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
public new event MouseEventHandler DoubleClick;
public new event MouseEventHandler MouseDown;
public new event MouseEventHandler MouseUp;
public new event MouseEventHandler MouseMove;
public FlashPlayer():base()
{
this.HandleCreated += FlashPlayer_HandleCreated;
}
void FlashPlayer_HandleCreated(object sender, EventArgs e)
{
this.AllowFullScreen = "true";
this.AllowNetworking = "all";
this.AllowScriptAccess = "always";
}
protected override void WndProc(ref Message m) // Override's the WndProc and disables Flash activex's default right-click menu and if exists shows the attached ContextMenuStrip.
{
if (m.Msg == WM_LBUTTONDOWN)
{
if (this.MouseDown != null) this.MouseDown(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_LBUTTONUP)
{
if (this.MouseUp != null) this.MouseUp(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_MOUSEMOVE)
{
if (this.MouseMove != null) this.MouseMove(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0));
}
else if (m.Msg == WM_RBUTTONDOWN)
{
if (this.ContextMenuStrip != null) this.ContextMenuStrip.Show(Cursor.Position.X, Cursor.Position.Y);
m.Result = IntPtr.Zero;
return;
}
else if (m.Msg == WM_LBUTTONDBLCLK)
{
if (this.DoubleClick != null) this.DoubleClick(this, new MouseEventArgs(System.Windows.Forms.MouseButtons.Left, 2, Cursor.Position.X, Cursor.Position.Y, 0));
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
}
Player window code: (Player is an instance of FlashPlayer)
private void Player_Load(object sender, EventArgs e)
{
try
{
this.Text = string.Format("Stream: {0}", this._stream.Name); // set the window title.
this.Player.LoadMovie(0, this._stream.Movie); // load the movie.
if (this._stream.ChatAvailable && Settings.Instance.AutomaticallyOpenChat) this.OpenChatWindow();
}
catch (Exception exc)
{
// log stuff.
}
}
So this works great for livestream.com, ustream.com, own3d.tv but when it come's to justin.tv's player i'm getting a 1337 error (invalid embed code). So i tried to ask them for support but could't get a valid answer.
_stream.movie variable actually holds a valid URL for the stream source like;
http://cdn.livestream.com/grid/LSPlayer.swf?channel=%slug%&autoPlay=true (livestream sample)
or
http://www.justin.tv/widgets/live_embed_player.swf?channel=%slug%&auto_play=true&start_volume=100 (justin.tv sample)
Tried to urlencode the 'channel=%slug%&auto_play=true&start_volume=100' part for justin.tv but that did not work also.
So i started trying some work-arounds which at first place i thought setting flashVars variable of the control.
But i've a strange problem there, whenever i try to set flashVars variable it never get's set. I found a sample screenshot on the issue;
So if i was able to set the flashVariables may be i could work-around the justin.tv player's error. Btw, i also tried setting variables using Player.SetVariable(key,value) - that didn't work also.
Notes:
I'm running on .net 4.0 client profile.
Using the Flash10l.ocx.
Have generated the AxShockwaveFlashObjects.dll, ShockwaveFlashObjects.dll wrappers using "aximp.exe –source "C:\WINDOWS\system32\Macromed\Flash\Flash10l.ocx"
I recently had an issue with making justin.tv work, but in the end it was as simple as
axShockwaveFlash1.FlashVars = "auto_play=true&channel=adventuretimed&start_volume=25";
axShockwaveFlash1.Movie = "http://www.justin.tv/widgets/live_embed_player.swf";
and it works perfectly

Categories