|
|
|
|
Programmieren mit C++Borland C++MFC mit BC++ benutzenSchaltfläche mit automatischer Wiederholung
|
Wie läßt sich in Zusammenarbeit mit MFC eine Schaltfläche realisieren, die permanent eine Nachricht auslöst, nicht nur beim Drücken oder Loslassen?
| Frage | |
Das Erzeugen von Nachrichten während des Drückens einer Schaltfläche ist von Windows nicht vorgesehen. Man muß es emulieren, indem eine Nachricht zyklisch ausgelöst wird.
Als Beispiellösung wird nachfolgend die Klasse CRepeatButton implementiert, die von CButton abgeleitet ist. Diese Klasse implementiert dabei gleich auch noch etwas Service zusätzlich.
| Lösung |
CRepeatButton(int InitialValue = 0,
int Direction = FORWARD,
int Increment = 1,
int min = -10000,
int max = 10000 );
Der Konstruktor erzeugt das Objekt und ruft zur Initialisierung die Methode SetParameters() auf, die alle fünf Parameter des Konstruktors übernimmt.
Im Parameter Direction wird festgelegt, ob die Laufrichtung aufsteigend oder absteigend sein soll. Als mögliche Werte sind definiert:
|
Konstante
|
Bedeutung
|
|
FORWARD
|
Der aktuelle Wert wird jeweils um Increment erhöht
|
|
BACKWARD
|
Aktueller Wert wird um Increment erniedrigt
|
Increment legt die Schrittweite fest, mit der erhöht bzw. erniedrigt werden soll. Dies spielt dann eine Rolle, wenn der aktuelle Wert von der darüberliegenden Anwendung bzw. dem Nachrichten empfangenden Fenster ausgewertet werden soll. Mithin wird dann nicht nur eine Nachricht erzeugt, sondern auch ein Fortschritt angezeigt.
Die Grenzen des Durchlaufens, während die Schaltfläche gedrückt ist, geben min und max vor. Per Default ist hier +/- 10.000 vorgegeben. Es lassen sich jedoch beliebige Grenzen angeben. Ist eine Grenze erreicht, setzt die Klasse den aktuellen Wert selbständig auf die jeweils andere Grenze, d.h. im Falle des Erreichens von min wird max gesetzt und umgekehrt. Als Defaultfenster, an das die Benachrichtigung gesendet wird, nimmt der Konstruktor das Elternfenster an, das vor dem Versenden einer Nachricht automatisch ermittelt wird, wenn als aktives Fenster NULL spezifiziert ist.
| Konstruktor CRepeatButton |
void SetParameters(HWND hWnd,
int InitialValue,
int Direction,
int Increment,
int min,
int max );
Die Methode SetParameters() setzt die internen Eigenschaften der Wiederholung und entspricht bis auf hWnd den Parametern des Konstruktors. Der Parameter hWnd legt das Fenster fest, an das die Benachrichtigung gesendet werden soll. Die Nachricht selbst, die versendet wird, ist in der Header-Datei der Klasse deklariert.
#define WM_SEEKBUTTONVALUECHANGED (WM_USER+10)
Zum Erzeugen der Benachrichtigungen klinkt sich die Klasse in das Ereignis WM_LBUTTONDOWN ein. Sobald der Anwender den linken Button drückt, übernimmt die Methode OnLButtonDown die Kontrolle in Form einer Schleife, die solange durchlaufen wird, bis der Button wieder losgelassen wird.
Als erstes ermittelt die Methode das Fenster, an das die Nachricht gesendet werden soll, falls mhWnd den Wert NULL besitzt.
| Methode SetParameters |
void CRepeatButton::OnLButtonDown(
UINT nFlags, CPoint point )
{
CButton::OnLButtonDown( nFlags, point );
int nbiter = 0;
MSG mess;
int id = GetDlgCtrlID( );
HWND hWnd = GetSafeHwnd();
if( mhWnd == NULL )
mhWnd = GetParent()->GetSafeHwnd();
Im Anschluß daran fällt die Methode in eine while-Schleife, die erst dann wieder verlassen wird, wenn mittels PeekMessage() festgestellt wird, daß WM_LBUTTONUP eingetroffen ist. Als spezielles Feature implementiert die Methode hier auch eine Beschleunigung beim Generieren der Benachrichtigungen. Die ersten Nachrichten werden in längeren Zeitabständen erzeugt, als nachfolgende. Ab der 50. Benachrichtigung bleibt die Geschwindigkeit jedoch konstant. Realisiert wird diese Verzögerung über einen Aufruf von Sleep() mit fallenden Werten, die über die interne Indexvariable mbiter gesteuert wird.
| Ereignishandler OnLButtonDown |
while( !PeekMessage( &mess, hWnd,
WM_LBUTTONUP,
WM_LBUTTONUP,
PM_REMOVE ) )
{
if( nbiter < 10 )
{
Sleep( 100 );
}
else if( nbiter < 20 )
{
Sleep( 75 );
}
else if( nbiter < 30 )
{
Sleep( 50 );
}
else {
Sleep( 25 );
}
nbiter++;
Nach der Verzögerung erfolgt das Inkrementieren des aktuellen Werts in Abhängigkeit der Laufrichtung.
mCurrentValue += ( mDirection == FORWARD ) ? mIncrement
: -mIncrement;
if( mCurrentValue < mMin )
{
mCurrentValue = mMax;
}
if( mCurrentValue > mMax)
{
mCurrentValue = mMin;
}
Der so aktualisierte Wert wird dann im lParam der Nachricht mit der ID der Control gemäß GetDlgCtrlID( ) im wParam an das gewünschte Fenster gesendet.
| Message-Schleife |
::SendMessage(mhWnd,
WM_REPEATBUTTONVALUECHANGED,
id, mCurrentValue);
}
Als Abschluß ist dann nach dem Verlassen der Schleife das Ereignis WM_LBUTTONUP neu zu erzeugen, da dieses von der Funktion PeekMessage() in der while-Schleife aus der Nachrichtenschlange gelöscht wurde.
| Nachricht versenden |
SendMessage( WM_LBUTTONUP );
}
Einsatz der Klasse
Das Einbinden der Klasse CRepeatButton erfolgt typischerweise durch das Erzeugen einer Instanz im OnInitDialog- oder OnCreate-Ereignis.
backwardBut.Create ("<", WS_CHILD | WS_VISIBLE,
CRect( 50, 10, 90, 30 ), this,
IDC_BACKWARD_BUTTON );
backwardBut.SetParameters(SomeCWnd.GetSafeHwnd(), 0,
CRepeatButton::BACKWARD, 1, 0, 10);
Soll das Elternfenster der Schaltfläche die Benachrichtigung erhalten, kann anstelle des Fensterhandles auch NULL als erster Parameter der Methode SetParameters() übergeben werden.
Für die Bearbeitung der Benachrichtigung muß eine Behandlungsmethode implementiert und deklariert werden.
afx_msg LONG OnRepeatButtonValueChanged(UINT id, LONG value);
Die Methode OnRepeatButtonValueChanged() übernimmt als Parameter die id und den aktuellen Wert des Button, die in wParam und lParam der Nachricht enthalten sind. In die zugehörige Nachrichtentabelle ist die Nachricht aufzunehmen, hier für eine von CEdit abgeleitete Klasse CMyEdit:
| WM_LBUTTONUP durchreichen |
BEGIN_MESSAGE_MAP(CMyEdit, CEdit)
//{{AFX_MSG_MAP(CMyEdit)
//}}AFX_MSG_MAP
ON_MESSAGE( WM_REPEATBUTTONVALUECHANGED,
OnRepeatButtonValueChanged )
END_MESSAGE_MAP()
Das folgende Beispiel für die Implementation des Nachrichtenhandlers ist rudimentär gehalten – es wird nur der Fenstertitel geändert, indem der aktuelle Wert des Button-Zählers angezeigt wird. Hier sind beliebige Aktionen denkbar.
| Nachrichtentabelle |
LONG CMyEdit:: OnRepeatButtonValueChanged (
UINT /* id */, LONG value )
{
static CString str;
str.Format("%d", value );
SetWindowText( str );
RedrawWindow();
return 0L;
}
Die Klasse CRepeatButton kann natürlich in Kombination mit beliebigen Fenstern eingesetzt werden, so wie auch die Reaktion auf das Eintreffen der Benachrichtigung individuell gestaltet werden kann.
| Methode OnRepeatButton-ValueChanged
|
|
|