3

I am trying to edit macro enabled Excel file using c#.

I have done with the editing and need to validate all that data using already defined macro [VBA - Button]. I am able to run macro using code mentioned below:

workbook.Application.Run("Sheet1.validate_Click");

Now, the problem is, whenever the macro is called that will validate the data which I had inserted in Excel sheet and give the output shown below:

enter image description here

Now I need to click on Yes Button Programmatically. which will ask to save that file which will be automatically created by that Validate Button.

Here I am stuck, that how could I programmatically click on Yes Button of Alert Dialog that appears.

Please Help me, I am without clues here, I googled a lot but could find nothing which serves my purpose.

skkakkar
  • 2,624
  • 2
  • 13
  • 28
Mr. Go
  • 552
  • 1
  • 4
  • 20
  • 1
    Its possible that setting `Application.EnableEvents = False` would bypass this problem – RGA Jul 26 '16 at 11:52
  • @RGA Thanks for your ans, `Application.EnableEvents = False` will disables events which are generated by excel. this alert is generated by VBA Excel Macro, So I need to click Yes | No Compolsory. Any other Idea! – Mr. Go Jul 26 '16 at 11:56
  • "Generated by VBA Excel Macro" meaning its generated by a macro that you are calling from your code? – RGA Jul 26 '16 at 11:59
  • @RGA : Yes. That macro is already code in that excel file. i need to validate my data from that file by using Validate button which you will see in my Image. – Mr. Go Jul 26 '16 at 12:01
  • 2
    If you are generating that message box in your own macro, why can you not just disable the message box? – Rabbitman14 Jul 26 '16 at 12:06
  • @Rabbitman14 : I did not created that Macro. Its a pre-generated file. that's the big reason that I am not able to do so. – Mr. Go Jul 26 '16 at 12:08
  • Do you have access to the VBA code? If so, find the box and remove it. Beyond that, you'll need to detect the box launching event and handle that (which i believe would be beyond the scope of VBA, though a class _may_ be able to handle that) – RGA Jul 26 '16 at 12:08
  • Agreed with @RGA on that – Rabbitman14 Jul 26 '16 at 12:11
  • @RGA : Nope. i don't have access to VBA Code. – Mr. Go Jul 26 '16 at 12:17
  • I think there is a way to build a workaround for that, but it would be MUCH easier to just change it in the original code. What exactly hinders you? Most of the times it's not that hard to get access to the VBA editor, even if you're not supposed to. – Tom K. Jul 26 '16 at 13:50
  • @Tom : VBA code is password protected.. So I am not able to make changes in VBA code. – Mr. Go Jul 26 '16 at 14:30
  • Take a look at this: http://stackoverflow.com/questions/1026483/is-there-a-way-to-crack-the-password-on-an-excel-vba-project – Tom K. Jul 26 '16 at 14:35

1 Answers1

6

As @Tom mentioned in the comments, the real solution would be to change the VBA code. But since this isn't an option that's available to you, you'll have to use some sort of hackish work-around. I haven't done much testing, but I'd assume that your COM Interop calls are also blocking because of the modal dialog.

About the only way I've come up with to handle dialogs generated by VBA from managed code is to use win32 functions to poll for the target window, then send a simulated mouse click when it's found. This is the class I use:

public class DialogClicker
{
    private delegate bool EnumWindowsProc(int hWnd, int lParam);

    private const int BM_SETSTATE = 0x00F3;
    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;

    [DllImport("user32.dll")]
    private static extern int EnumWindows(EnumWindowsProc callbackFunc, int lParam);
    [DllImport("user32.dll")]
    private static extern int EnumChildWindows(int hWnd, EnumWindowsProc callbackFunc, int lParam);
    [DllImport("user32.dll")]
    private static extern int GetWindowText(int hWnd, StringBuilder buff, int maxCount);
    [DllImport("user32.dll")]
    private static extern int SendMessage(int hWnd, int Msg, int wParam, int lParam);

    private const int MsgBufferSize = 256;
    private bool _textFound;
    private int _btnhWnd;
    private readonly Timer _timer;

    public string TargetHeader { get; private set; }
    public string ButtonText { get; private set; }
    public string SearchText { get; private set; }
    public int TimerInterval { get; private set; }

    public DialogClicker(string header, string button, string search, int interval)
    {
        TargetHeader = header;
        ButtonText = button;
        SearchText = search;
        TimerInterval = interval;
        _timer = new Timer(interval);
        _timer.Elapsed += ElapsedHandler;            
    }

    public void Toggle(bool active)
    {
        _timer.Enabled = active;
    }

    private void ElapsedHandler(object sender, ElapsedEventArgs e)
    {
        _btnhWnd = 0;
        _textFound = string.IsNullOrEmpty(SearchText);
        EnumWindows(EnumProc, 0);
    }

    private bool EnumProc(int hWnd, int lParam)
    {
        var heading = new StringBuilder(MsgBufferSize);
        GetWindowText(hWnd, heading, MsgBufferSize);
        var title = heading.ToString();

        if (string.IsNullOrEmpty(title) || !title.Equals(TargetHeader)) return true;
        EnumChildWindows(hWnd, EnumChildProc, 0);
        return false;
    }

    private bool EnumChildProc(int hWnd, int lParam)
    {
        var title = new StringBuilder(MsgBufferSize);
        GetWindowText(hWnd, title, MsgBufferSize);
        var text = title.ToString();

        if (string.IsNullOrEmpty(text)) return true;
        if (!_textFound) _textFound = text.Contains(SearchText);
        if (text.Equals(ButtonText)) _btnhWnd = hWnd;
        if (_btnhWnd <= 0 || !_textFound) return true;

        SendMessage(_btnhWnd, BM_SETSTATE, 1, 0);
        SendMessage(_btnhWnd, WM_LBUTTONDOWN, 0, 0);
        SendMessage(_btnhWnd, WM_LBUTTONUP, 0, 0);
        SendMessage(_btnhWnd, BM_SETSTATE, 1, 0);
        return false;
    }
}

Calling code:

var clicker = new DialogClicker("Microsoft Excel", "&Yes", "No error found in sheet.", 100);
clicker.Toggle(true);  //Start polling.

//Do whatever triggers the dialog.

clicker.Toggle(false); //Stop polling.
Comintern
  • 20,878
  • 5
  • 30
  • 73
  • @Comintern : Thanks for the code, this code will probably work for me. I am not sure, But I need to call this DialogClicker in background process, because when i validate my Excel with VBA code, its a continuos Process. Please guide me for this. Again thanks – Mr. Go Jul 27 '16 at 03:49
  • @Mr.Go - It's event based and the VBA code is running in an STA thread. It should work fine as is. – Comintern Jul 27 '16 at 03:55
  • @Comintern: Hey, I removed that comment, Its work perfectly with my Code. Thanks alot buddy. and here i am makring your answer as accepted. again thanks alot. – Mr. Go Jul 27 '16 at 04:09