| 

.NET C# Java Javascript Exception

4
Wie kann man programmatisch feststellen (in C# z.B.), ob eine ANDERE Applikation (native oder java oder ...) mit graphische Benutzeroberfläche gerade auf Benutzereingabe wartet? Kann man generisch diese "Modal Dialog Boxes" detektieren?

Was ich suche ist in etwa die Implementierung von:
static Boolean IsWaitingForUserInput(String processName)
{
???
}

Ich möchte das System nicht standig überwachen (keine Hooks). Ich möchte ein Program regelmäßig starten, der mir sagt ob die Applikation gerade wartet oder nicht. Ein Mensch sieht ja auch ob ein Popup da ist oder nicht. Wer das Problem in Pure managed Code löst verdient mein höchstest Respekt!

2009/11/15: leider immer noch keine 100% zufriedenstellende Lösung in Sicht... Die Lösung unten schläg z.B. Nicht an wenn eine Applikation abgekracht ist und der JIT debugger mitmischt... Es werden immer mehr Workarounds! Mein Watchdog program mutiert immer weiter zu einem Monstrum.
News:
10.09.2009
jdehaan 434 2 7
jdehaan 434 2 7
ich frage lieber nicht was du damit anstellen willst... :)
MiW 10.09.2009
Ich verstehe nicht was Du meinst. Egal, es ist so: wir haben automatisierte Abläufe die z.B. Excel involvieren. Manchmal meint Excel beim Schließen, dass sich was geändert hat und frägt nach. Diese Fälle sollte detektiert werden damit wir da benachrichtigt werden und zeitnah eingreifen können. Wir können niemand gerade bezahlen, der sich den ganzen Tag das Bildschirm anguckt ;-)
jdehaan 10.09.2009
1
Ist es legitim, Fragen sowohl hier als auch bei stackoverflow.com zu posten, ohne eine Antwort abzuwarten? Beide Male wurde die Frage am 10. September gestellt.
knivil 22.09.2009
Warum nicht ? Man kann doch fragen wann und wo man will. Glaube kaum daß dafür jemandem der Kopf abgerissen wird.
MiW 20.10.2009
Auf die Beantwortung dieser Frage war ein Kopfgeld in Höhe von 50 Reputationspunkten ausgesetzt. Das Kopfgeld wurde bereits vergeben.
7 Antworten
5
Eine Lösung wäre ein globaler Systemhook.
siehe z.Bsp. hier -> http://www.codeproject.com/KB/system/WilsonSystemGlobalHooks.aspx

Hooks im Detail hier zu erklären wäre jetzt ein bisschen aufwendig. Solltest Du spezielle Fragen haben schick mir ne PM. (Ich kenne nicht besonders C# aus, weiß aber wie es mit C++ unter WIN32 funktioniert.)

Hin und wieder gibt es bei Verwendung von Hooks auch Probleme mit Virenscannern (muß man halt dann auf die Ingore-Liste setzen)

[UPDATE]

habe noch ein bisschen deutsche Info gefunden
http://support.microsoft.com/kb/318804
10.09.2009
MiW 1,0k 1 8
MiW 1,0k 1 8
2
Ein globaler System-Hook ist meiner Meinung nach die beste Lösung. Damit erschlägt er all seine Anforderungen.
BTW. Was über die WIN32-API funktioniert geht auch mit C#.
klaus_b 10.09.2009
gut zu wissen.
habe total den Anschluss bei C# verloren und hänge immer noch gedanklich bei c++ fest. Sollte mich wirklich mal eingehend damit beschäftigen.
MiW 10.09.2009
Auf jedem Fall hiflreich! Die Einzige Anforderung die mir nicht gefällt ist, dass ständig diese Hooks aktiv sein müssen. Falls ich nichts besseres finde werde ich es so machen.
jdehaan 10.09.2009
Wenn die autmatisierten Abläufe, die Du angesprochen hast zeitgesteuert sind, kannst du ja vorher den Hook initialisieren und dann irgendwann wieder beenden.
MiW 11.09.2009
2
mmm vielen Dank für die Tipps. Ich habe mir selber eine Lösung erarbeitet, die nun richtig gut zu funktionieren scheint. Wie gefällt euch das:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace Util
{
public class ModalChecker
{
public static Boolean IsWaitingForUserInput(String processName)
{
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length == 0)
throw new Exception("No process found matching the search criteria");
if (processes.Length > 1)
throw new Exception("More than one process found matching the search criteria");
// for thread safety
ModalChecker checker = new ModalChecker(processes[0]);
return checker.WaitingForUserInput;
}

#region Native Windows Stuff
private const int WS_EX_DLGMODALFRAME = 0x00000001;
private const int GWL_EXSTYLE = (-20);
private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
[DllImport("user32")]
private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
#endregion

// The process we want the info from
private Process _process;
private Boolean _waiting;

private ModalChecker(Process process)
{
_process = process;
_waiting = false; //default
}

private Boolean WaitingForUserInput
{
get
{
EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
return _waiting;
}
}

private int WindowEnum(IntPtr hWnd, int lParam)
{
IntPtr processId;
GetWindowThreadProcessId(hWnd, out processId);
if (processId.ToInt32() != _process.Id)
return 1;
// belongs to our process, check it!
uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
if ((style & WS_EX_DLGMODALFRAME) != 0)
{
_waiting = true;
return 0; // stop searching further
}
return 1;
}
}
}
10.09.2009
jdehaan 434 2 7
jdehaan 434 2 7
1
Schaut gut aus gibt bei mir aber immer true zurück. Weiß aber nicht warum.
gfoidl 10.09.2009
Vermutlich weil ein Programm solange es aktiv ist immer auf die eingabe eines Users wartet, wenn es im idle zustand ist, also nicht aktiv arbeitet.
Müßtest das noch mit ner abfrage machen ob das programm versucht sich zu schließen.
Jesman 10.09.2009
gfoidl, Du hast Recht. Es liefert sehr oft true zurück auch im Falle normaler User-interaktion. Ich glaube die Lösung muss noch aufgebohrt werden.
jdehaan 10.09.2009
Ich glaub des ist deshalb da die Anwendung in der Nachrichtenschleife die meiste Zeit wartet.
gfoidl 10.09.2009
2
Ich weiß, dass es in keiner Weise deine Frage beantwortet, aber manchmal schaut man ja einfach nur in die falsche Richtung: falls bei dir - wie in deinem Kommentar oben angedeutet - nur Office-Produkte, wie z.B. Excel, "zicken", kannst du in der "Workbook.SaveAs-Methode" den Parameter "ConflictResolution" auf "xlLocalSessionChanges" setzen und es erscheint keine Nachfrage mehr.
25.02.2011
tb 220 3
0
Natürlich geht das!

Einfach dem Ereignis "Application.EnterThreadModal" ein EventHandler zuordnen und fertig ;-)

Ok noch kleines Bsp.:
public partial class Form1 : Form
{
public Form1()
{
Application.EnterThreadModal += new EventHandler(OnEnterModal);
Application.LeaveThreadModal += new EventHandler(OnLeaveModal);
InitializeComponent();
}

public void OnEnterModal(object sender, EventArgs e)
{
textBox1.Text += "start Modal..." + Environment.NewLine;
}

public void OnLeaveModal(object sender, EventArgs e)
{
textBox1.Text += "stop Modal..." + Environment.NewLine;
}

private void button1_Click(object sender, EventArgs e)
{
DialogResult dlgres = MessageBox.Show("You must enter a name.", "Name Entry Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}
}


Grüßle
10.09.2009
Scout 1,4k 2 8
Scout 1,4k 2 8
1
Das bezieht aber nur auf die eigene Anwendung und nicht auf eine ANDERE - oder gilt das global?
gfoidl 10.09.2009
@gfoidl: sorry keine Ahnung, habs nicht probiert. Aber ich denke wenn die Anwendung aus deiner Anwendung heraus aufgerufen wird, dann gilt dies auch dafür ;-)
Scout 10.09.2009
Ich will die andere Prozesse quasi nebenbei überwachen nicht starten und es gilt definitiv nicht global, denn nicht alle Apps sind "managed" (leider ;-) )
jdehaan 10.09.2009
aber global ist doch nicht verkehrt, prüfste halt vorher ob der Prozeß einer ist, der gemanaged werden soll. (Anhand des Prozeßnamens)
MiW 10.09.2009
danke für den Minuspunkt, nur weil ich davon ausgegangen bin, dass er die Applicationen aus seinem Programm aufruft... naja egal. Seine Lösung ist doch super.
Scout 10.09.2009
ich hab ihn dir nicht gegeben. Hab für diese Frage gar keine Bewertungen abgegeben.
MiW 10.09.2009
0
Schau mal bei pInvoke nach:
EnumDesktopWindows
EnumChildWindows
GetCapture

Es gibt sicher noch mehr und erfahrungsgemäss machen die Funtionen nicht immer was ihr Name verspricht, aber man wird eigentlich immer fündig :)

Statt Hook (was ich auch für gut halte) könnte Dein Programm folgendes machen:
- Alle Fenster suchen
- Für jedes Fenster alle Child-Fenster suchen
- Für jedes Child-Fenster den Typ prüfen (modal/normal)

Oder:
- Alle Fenster suchen
- Für jedes Fenster das Child-Fenster mit dem Mausfocus suchen
- Für dieses Fenster den Type prüfen (modal/normal)

Oder oder oder :)
11.09.2009
DaSpors 4,2k 2 8
DaSpors 4,2k 2 8
0
Hab mal die Lösung von jdehaan ein bischen geändert/aufgebohrt:
public class ModalChecker
{
#region Native Windows Stuff
private const int WS_EX_DLGMODALFRAME = 0x00000001;
private const int GWL_EXSTYLE = (-20);
private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
[DllImport("user32")]
private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
#endregion

public delegate void ProcessModalDelegate(ModalChecker sender, Process p, IntPtr handleOfModalWindow);
public event ProcessModalDelegate ProcessModal;

// The process we want the info from
private Thread _checker;
private Process _current;
private bool _waiting;
private List<string> _ignoreList = new List<string>();

public ModalChecker()
{
_checker = new Thread(new ThreadStart(CheckerThread));
_checker.Start();
}

public void Stop()
{
_checker.Abort();
}

public void AddToIgnoreList(Process p)
{
lock (_ignoreList)
_ignoreList.Add(p.ProcessName.ToLower());
}

private void CheckerThread()
{
while (true)
{
Process[] par = Process.GetProcesses();
foreach (Process p in par)
{
lock( _ignoreList )
if (_ignoreList.Contains(p.ProcessName.ToLower()))
continue;

_current = p;
EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
}
}
}

private bool WaitingForUserInput(Process p)
{
EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
return _waiting;
}

private int WindowEnum(IntPtr hWnd, int lParam)
{
IntPtr processId;
GetWindowThreadProcessId(hWnd, out processId);
if (processId.ToInt32() != _current.Id)
return 1;
// belongs to our process, check it!
uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
if ((style & WS_EX_DLGMODALFRAME) != 0)
{
if (ProcessModal != null)
ProcessModal(this, _current, hWnd);
return 0; // stop searching further
}
return 1;
}
}

static void mc_ProcessModal(ModalChecker sender, Process p, IntPtr handleOfModalWindow)
{
if (p.ProcessName == "PrivacyIconClient")
{
Console.WriteLine(p.ProcessName + " added to ignore list");
sender.AddToIgnoreList(p);
}
else
Console.WriteLine(p.ProcessName + " reported as modal. Handle it or it will be reported again.");
}

[MTAThread]
static void Main()
{
ModalChecker mc = new ModalChecker();
mc.ProcessModal += new ModalChecker.ProcessModalDelegate(mc_ProcessModal);
while (true)
{
Thread.Sleep(1000);
}
}
17.09.2009
DaSpors 4,2k 2 8
Uiuiui toll: Die eckigen Klammern gehen endlich :)
DaSpors 17.09.2009
0
Als generelle Lösung ist das so nicht Möglich.
Auf eine Benutzereingabe warten ist kein subjektiver Status, kann also nicht von einem Programm ermittelt werden!

Nehmen wir als Beispiel eine bekannte Applikation: Word. Hier kennt jeder die Korrektur für die Rechtschreibung. Rein optisch sieht es so aus als würde nach jeder Benutzereingabe auf Fehler überprüft werden, in Wirklichkeit aber wird in kleine periodischen Zeitabständen das Dokument gespeichert und darin auf Rechtschreibfehler geprüft.

Was du Intern bei deiner Applikation alles machst ist natürlich wieder was anderes.
Stichwort: Event Bubbling
09.05.2011
mindengine 83 1 5

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH