Forum: PC-Programmierung MFC nicht modaler Dialog Datenaustausch


von A. R. (redegle)


Lesenswert?

Hallo,

ich habe in MFC einen nicht modalen Dialog erstellt. Das war auch 
ersteinmal kein großes Problem. Es stellt sich jedoch die Frage, wie ich 
den Datenaustausch am besten gestalte. Also sobald sich Daten im 
Dokument ändern müssen die angezeigten Werte im Dialog aktualisiert 
werden.

Alle Daten auf die der Dialog zugreift werden nur von einer Methode im 
Dokument verändert. Ich würde es gerne so handhaben, dass bei jedem 
Aufruf der Methode auch die Daten im Dialog aktualisiert werden.

Hat jemand einen Vorschlag, wie so etwas am geschicktesten realisiert 
werden könnte?

Ich habe mehrere Ideen gehabt, diese sind jedoch alle etwas unschön.

-Zyklische Abfrage alle n ms.
-Aufruf einer statischen Methode des Dialoges vom Dokument aus. 
(Schlecht Skalierbar, da ich das Dokument immer anpassen muss)

von Uwe (Gast)


Lesenswert?

OnChange,OnMouseButton,OnMouseClick,... ruft UpdateData() auf

von Der Weise (Gast)


Lesenswert?

MFC? Das verwendet noch jemand? Man gucke sich Gtk oder Qt an...

von Der Weise (Gast)


Lesenswert?

... und ja, das Dokument müsste, wenn es sich ändern, dem Dialog 
Bescheid geben. Dieser fragt dann alle Werte ab und zeigt sie an, wenn 
sich ein Wert gar nicht geändert hat, macht man halt etwas zu viel 
arbeit. Das regelmäßig Abfragen ist, sagen wir, völlig bescheuert - denn 
das Programm weiß ja, wann sich die Daten ändern, und muss sich selbst 
dennoch regelmäßig danach fragen?!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. R. schrieb:
> Ich würde es gerne so handhaben, dass bei jedem
> Aufruf der Methode auch die Daten im Dialog aktualisiert werden.

Sende eine Nachricht an den Dialog, der sich daraufhin die 
aktualisierten Daten aus dem Dokument besorgt. So eine Nachricht kannst 
Du durch Nutzen von WM_USER zuzüglich einer von Dir zu verwaltenden 
Konstante erzeugen.

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644931%28v=vs.85%29.aspx

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nachtrag:

Du musst also im Dokument eine Funktion zur Verfügung stellen, die die 
aktuellen Daten liefert. Der Dialog kann diese Funktion auch beim 
Initialisieren (also als Reaktion auf WM_INITDIALOG bzw. in 
OnInitDialog) nutzen, somit ist das die einzige erforderliche 
Schnittstelle, um die Daten in den Dialog hineinzubekommen.

von A. R. (redegle)


Lesenswert?

>OnChange,OnMouseButton,OnMouseClick,... ruft UpdateData() auf

UpdateData() geht leider nur in einem View nicht in einem Dialog. Bzw. 
in der CPropertyPage wo ichs verwenden möchte lässt es sich per 
Klassenassistent nicht einfügen.

>MFC? Das verwendet noch jemand? Man gucke sich Gtk oder Qt an...

Was spricht gegen MFC, warum ist Gtk oder Qt besser? Eine kurze 
Ausführung währe sehr interessant.

>... und ja, das Dokument müsste, wenn es sich ändern, dem Dialog
>Bescheid geben. Dieser fragt dann alle Werte ab und zeigt sie an, wenn
>sich ein Wert gar nicht geändert hat, macht man halt etwas zu viel
>arbeit. Das regelmäßig Abfragen ist, sagen wir, völlig bescheuert - denn
>das Programm weiß ja, wann sich die Daten ändern, und muss sich selbst
>dennoch regelmäßig danach fragen?!

Eben genau das wollte ich nach Möglichkeit etwas vermeiden. Der Dialog 
darf wissen, dass es ein Dokument gibt. Im Dialog kann ich den Header zu 
dem Dokument einfügen. Jedoch wollte ich nicht zusätzlich noch dem 
Dokument mitteilen, dass es den Dialog gibt. Dieser wird schließlich von 
der VIEW-Klasse aufgerufen. Das wird mir sonst etwas zu verschachtelt.

-VIEW ruft dynamisch den Dialog auf.
-VIEW teilt Dokument mit, dass es den Dialog gibt.
-Dokument teilt dem Dialog mit, dass sich Werte geändert haben.
-Dialog ruft Methode des Dokuments auf und hohlt sich die Daten.

>Sende eine Nachricht an den Dialog, der sich daraufhin die
>aktualisierten Daten aus dem Dokument besorgt. So eine Nachricht kannst
>Du durch Nutzen von WM_USER zuzüglich einer von Dir zu verwaltenden
>Konstante erzeugen.

Das klingt sehr vielversprechend.
Habe mich nun durch verschiedene Lektüren im Internet glesen und ein 
bisschen programmiert. Leider ist der Erfolg ausgeblieben. Könnte bitte 
mal wer drübergucken.

Im Dokument habe ich folgenden Code erstellt.
1
PostMessage(HWND_BROADCAST,WM_APP+1,0,0);//HWND_BROADCAST sollte heißen, dass die Nachricht an alles gesendet wird. Weiß nicht, wie ich ein HWND bekommen soll

Im Header des Dialogs steht folgendes.
1
LRESULT Daten_aktuallisieren(WPARAM, LPARAM);

Im Dialog steht:
1
BEGIN_MESSAGE_MAP(CGraph_Dialog_Einstellungen_S1, CPropertyPage)
2
  ON_WM_CREATE()
3
  ON_MESSAGE(WM_APP+1, Daten_aktuallisieren)
4
END_MESSAGE_MAP()

Sowie

1
LRESULT CGraph_Dialog_Einstellungen_S1::Daten_aktuallisieren(WPARAM wParam, LPARAM lParam)
2
{
3
4
  LRESULT result = NULL;
5
  return result;
6
}

Leider wird die Methode im Dialog nicht aufgerufen. Also die Message 
kommt nicht an.

Vielen Dank für sämtliche Hilfe!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

HWND_BROADCAST sendet an alle Applikationshauptfenster. Du aber willst 
nur an den Dialog senden. Ist der Dialog (also die Instanz der Klasse 
CGraph_Dialog_etc.) Deinem Dokument bekannt?

Dann kannst Du eine Memberfunktion der von CDialog abgeleiteten Klasse 
verwenden (CDialog ist wiederum von CWnd abgeleitet, und das wiederum 
hat eine Memberfunktion namens PostMessage).

Also:

Angenommen, Dein Dialogobjekt hieße Dlg. Dann musst Du nur schreiben:

Dlg.PostMessage(WM_USER + 1, 0, 0);

Statt PostMessage kannst (und solltest) Du übrigens das synchrone 
SendMessage verwenden. Das kehrt erst dann zum Aufrufer zurück, wenn der 
Adressat die Nachricht verarbeitet hat.

Du kannst aber auch das Fensterhandle des Dialogs bestimmen, das ist die 
Membervariable m_hwnd (definiert in der Basisklasse CWnd).

von Karl H. (kbuchegg)


Angehängte Dateien:

Lesenswert?

Es gibt wahrscheinlich viele Möglichkeiten, wie man einen nicht modalen 
Dialog ins System einbringen kann.

Ich hab mal ganz schnell ein Beispiel zusammengestellt für eine MDI 
Applikation, die einen nicht modalen Einstelldialog für Daten im 
Dokument beinhaltet.

Der Dialog gehört dabei zum Mainframe und wird vom View, wenn er 
aktiviert wird über das gerade aktive Dokument informiert. Damit können 
sich mehrere Dokumente problemlos denselben Dialog teilen. Wird ein View 
eines Dokuments aktiviert, dann schaltet der Dialog auf das jeweilige 
Dokument um, und holt sich von ihm die anzuzeigenden Daten.
Wird im Dialog ein Wert verändert, dann teilt der Dialog das auch dem 
zugehörigen Dokument mit (welches dann seinerseits wieder alle Views von 
der Änderung informiert).

Damit gibt es eine 2-Weg Kommunikation
* Aus dem Dialog heraus, können Dokumentdaten verändert werden
  (und der Rest des Systems [View, Menues, Toolbars] passt sich an)
* Aus dem Menue/View heraus können die Daten verändert werden und
  der Dialog reagiert auf die Änderung

In diesem Beispiel wissen die Klassen voneinander und rufen ihre 
Methoden gegenseitig auf. Die allgemeinere Form wäre natürlich, wenn da 
Message Passing betrieben werden würde. In der App, aus der ich die 
Einzeilteile zusammenge'klaut' habe, passiert das auch so, hauptsächlich 
deshalb, weil diese App zur Laufzeit mit zusätzlich ladbaren DLLs 
erweiterbar ist, die sich nahtlos während des Betriebs in das Programm 
einklinken können müssen. Weder Dokument noch Dialog wissen voneinander 
in Form von Member-Funktionen, sondern nur in Form von zu sendenden 
Messages bzw Aufrufen von UpdateData.


Der springende Punkt ist aber der, dass hier der Dialog zu keinem 
Document gehört und auch nicht zu einem View, sondern die Mainfrm für 
die Erzeugung und Verwaltung zuständig ist.


(Die App wurde mit VC++ 6.0 gemacht. Also himmelalt. Mit neueren 
Versionen wirst du wahrscheinlich ein paar Fehlermeldungen oder 
zumindest Warnings bekommen.
Interessant sind die Menüpunkte:
  Ansicht / Eigenschaften        -> Dialog geht auf / zu
  Form / Rechteck, Form / Kreis  -> Stellvertreter für Datenmanipualtion

und natürlich Datei/Neu, Fenster/Neu etc.


> in der CPropertyPage wo ichs verwenden möchte lässt es sich
> per Klassenassistent nicht einfügen.

Der Klassenassistent ist nicht alles. Manches muss man einfach von Hand 
einfügen :-)

von A. R. (redegle)


Lesenswert?

Vielen Dank an euch beide!

Ich werde es vorraussichtlich vorerst mit Membermethoden machen. Werde 
mich aber trotzdem in die Thematik mit den Nachrichten etwas 
einarbeiten.

von A. R. (redegle)


Lesenswert?

Mittlerweile habe ich verstanden, wie das gelöst worden ist. Mein 
Problem war ständig, dass ich einen Zirkelinklude programmiert habe weil 
2 Klassen gegenseitig aufeinander zugreifen müssen.

Das habe ich jetzt so wie im Beispiel von Karl Heinz Buchegger gelöst. 
Im Header des Dokuments und des nicht modalen Dialogs ist lediglich eine 
Forward-Declaration zu der jeweils anderen Klasse. Die Funktionen werden 
erst in der .cpp verwendet, so das erst dort das Einbinden des Headers 
der jeweils anderen Klasse notwendig wird.

Der Datenaustausche zwischen Dokument und Dialog funktioniert 
einwandfrei.

Nun würde ich gerne noch einen Datenaustausch zwischen Dialog und einer 
Klasse für ein Diagramm realisieren. Dieses befindet sich im Dialog und 
gibt die Werte des Dokuments an. Hierbei wäre es sehr schön, wenn ich 
eine Standardklasse des Diagramms an mehreren Stellen im Programm 
verwenden kann.

Ist es möglich, einer Klasse (Diagramm) einen Funktionpointer bzw. einen 
Funktor einer beliebigen Methode einer beliebigen Klasse zu übergeben, 
so dass diese aufgerufen wird? Eine Parameterübergabe ist nicht 
notwendig und der Rückgabewert kann auch void sein.

von A. R. (redegle)


Angehängte Dateien:

Lesenswert?

Ich habe nun einen Funktionspointer definiert. Das scheint mit folgender 
Syntax möglich zu sein:
1
typedef void(__stdcall *MyFunctionPointer)(void);

Anschließend habe ich ein Element erstellt.
1
MyFunctionPointer * MeineFunktion;//Deklariert einen Funktionspointer

Mit folgenden Quellcode sollte ich dann später die Funktion die sich 
dort befindet wo der Funktionspointer hinzeigt aufrufen können.
1
if(MeineFunktion)
2
  (*MeineFunktion)();//Ruft Funktion auf, auf die der Funktionspointer zeigt.

Zum setzten des Funktionspointers habe ich folgende Settermethode 
hinzugefügt.
1
void SetMeineFunktion(MyFunctionPointer * Funktionspointer);//Prototyp
2
3
void MyCug_Standard::SetMeineFunktion(MyFunctionPointer * Funktionspointer)
4
{
5
  MeineFunktion=Funktionspointer;
6
}

Um nun keinen Funktor übermitteln zu müssen habe ich die Methode die 
aufgerufen werden soll mit dem Zusatz static versehen.

Trotzdem bekomme ich beim ausführen folgender Methode eine Fehlermeldung 
die sich im Anhang befindet.
1
m_grid1.SetMeineFunktion(Diagramm_Wert_geändert);

Ich werde nun noch versuchen, eine Funktion zu übergeben, die keine 
Methode ist. Innerhalb dieser Funktion könnte ich dann versuchen die 
Methoden aufzurufen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

A. R. schrieb:
> Ich werde nun noch versuchen, eine Funktion zu übergeben, die keine
> Methode ist

Wenn die Funktion eine Memberfunktion einer Klasse ist, dann muss sie 
eine statische Memberfunktion sein, also eine, die auch ohne eine 
existierende Objektinstanz der Klasse auskommt (und folglich auch keinen 
this-Pointer hat).

von A. R. (redegle)


Lesenswert?

Das selbe dachte ich mir auch.

Jedoch bekomme ich die selbe Fehlermeldung schon bei dem unteren Code, 
obwohl dort gar keine Klasse beteiligt ist.
1
typedef void ( *MyFunctionPointer)(void);
2
void Diagramm_Wert_geändert(void);
3
MyFunctionPointer * MeineFunktion;
4
MeineFunktion=Diagramm_Wert_geändert;

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das ist exakt der Code, den Du Deinem Compiler vorwirfst?

Und nicht nur etwas, was Du aus dem Gedächtnis hier so ähnlich abgetippt 
hast?

Einerseits: Was Du da oben gepostet hast (als Screenshot) ist keine 
Compilerfehlermeldung, sondern nur eine von "Intellisense", was für das 
Syntaxhighlighting zuständig ist. Das aber kann durchaus danebenliegen.

Übersetze den Code, und sieh Dir an, was im Output-Window steht.

Andererseits: Erlaubt Dein Compiler wirklich Funktionsnamen, die nicht 
mit 7-Bit-ASCII codiert sind?

von A. R. (redegle)


Lesenswert?

>Das ist exakt der Code, den Du Deinem Compiler vorwirfst?

>Und nicht nur etwas, was Du aus dem Gedächtnis hier so ähnlich abgetippt
>hast?

Ja, nur Testweise in einem Probleprogramm um zu testen ob die Syntax 
stimmt.

>Einerseits: Was Du da oben gepostet hast (als Screenshot) ist keine
>Compilerfehlermeldung, sondern nur eine von "Intellisense", was für das
>Syntaxhighlighting zuständig ist. Das aber kann durchaus danebenliegen.

Bis jetzt hat Initellisense immer richtig gelegen oder ich habe es nicht 
gemerkt. Es wäre auch äußerst unschöner Code wenn mir Intellisense 
etliche Fehlermedlungen ausgibt die nicht existieren.

>Andererseits: Erlaubt Dein Compiler wirklich Funktionsnamen, die nicht
>mit 7-Bit-ASCII codiert sind?

Ja




Habe das Problem nun gelöst.

Im Header der Klasse der Tabelle befindet sich folgender Code.
1
typedef void ( *MyFunctionPointer)(void*);//Legt den Funktionspointer fest
2
3
class MyCug_Standard:public CUGCtrl
4
{
5
protected:
6
  MyFunctionPointer Callbackfunktion;//Deklariert einen Funktionspointer
7
  void *  Callbackfunktion_Parameter;//Deklariert einen Übergabeparameter des Typs Void
8
public:
9
  void SetCallbackfunktion(MyFunctionPointer Funktionspointer, void* data);//Übergibt einen Pointer auf die Callbackfunktion die bei einer Änderung aufgerufen wird.
10
  //Der Void Pointer wird von der Callbackfunktion übergeben und kann für beliebige Date verwendet werden.
11
  //Z.B. um von der Callbackfunktion ausgehend eine Methode einer Klasse aufzurufen
12
}

Von der Dialogklasse rufe ich die Settermethode wie folgt auf:
1
m_grid1.SetCallbackfunktion((MyFunctionPointer)forward_Werte_Diagramm_verändern,this);

Die Settermethode ist wie folgt aufgebaut:
1
void MyCug_Standard::SetCallbackfunktion(MyFunctionPointer Funktionspointer, void * data)
2
{
3
  Callbackfunktion=Funktionspointer;
4
  Callbackfunktion_Parameter = data;
5
}
1
Wenn nun eine Änderung in der Klasse stattfinden wird die Callbackfunktion wie folgt aufgerufen:
2
3
  if(Callbackfunktion)
4
    Callbackfunktion(Callbackfunktion_Parameter);//Ruft die Callbackfunktion auf

In der Callbackfunktion verweiße ich mit dem übergebenem Parameter auf 
die Callbackmethode.
1
void forward_Werte_Diagramm_verändern(CGraph_Dialog_Einstellungen_S1* data)
2
{
3
  data->Werte_Diagramm_verändern();
4
}

Fertig. Nun bin ich in der Lage der Klasse des Diagramms eine beliebe 
Funktion inklusive Objektpointer zu übergeben. Wenn sich ein Wert im 
Diagramm ändert ruft die Klasse die Funktion auf und übergibt Ihr den 
Objektpointer. Von dieser Funktion aus rufe ich die Methode auf.

Nutzen: Ich kann die Klasse von jeder beliebigen anderne Klasse aus 
aufrufen und bekomme eine Methode meienr Klasse aufgerufen, sobald sich 
ein Wert ändert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.