Άρθρο που υπέβαλε ο Marcus Junglas
Κατά τον προγραμματισμό ενός χειριστή συμβάντων στους Δελφούς (όπως το Στο κλικ περίπτωση ενός TButton), έρχεται η ώρα που η εφαρμογή σας πρέπει να είναι απασχολημένη για κάποιο χρονικό διάστημα, π.χ. ο κώδικας πρέπει να γράψει ένα μεγάλο αρχείο ή να συμπιέσει κάποια δεδομένα.
Αν το κάνετε αυτό, θα το παρατηρήσετε η εφαρμογή σας φαίνεται να είναι κλειδωμένη. Η φόρμα σας δεν μπορεί πλέον να μετακινηθεί και τα κουμπιά δεν δείχνουν κανένα σημάδι ζωής. Φαίνεται να καταρρέει.
Ο λόγος είναι ότι μια εφαρμογή Delpi είναι μονής σπείρας. Ο κώδικας που γράφετε αντιπροσωπεύει απλά μια δέσμη διαδικασιών που ονομάζονται από το κύριο νήμα των Δελφών κάθε φορά που συμβαίνει κάποιο συμβάν. Το υπόλοιπο χρονικό διάστημα το κύριο νήμα χειρίζεται τα μηνύματα του συστήματος και άλλα πράγματα όπως λειτουργίες χειρισμού μορφής και εξαρτημάτων.
Έτσι, αν δεν ολοκληρώσετε το χειρισμό του συμβάντος κάνοντας κάποια μακρά εργασία, θα εμποδίσετε την εφαρμογή να χειριστεί αυτά τα μηνύματα.
Μια κοινή λύση για τέτοιου είδους προβλήματα είναι να καλέσετε την "Εφαρμογή". ProcessMessages ". Η "εφαρμογή" είναι ένα παγκόσμιο αντικείμενο της κλάσης TApplication.
Η εφαρμογή. Τα μηνύματα επεξεργασίας χειρίζονται όλα τα μηνύματα αναμονής, όπως κινήσεις παραθύρων, κλικ στο κουμπί και ούτω καθεξής. Χρησιμοποιείται συνήθως ως απλή λύση για να κρατήσει την εφαρμογή σας "λειτουργική".
Δυστυχώς ο μηχανισμός πίσω από το "ProcessMessages" έχει τα δικά του χαρακτηριστικά, τα οποία μπορεί να προκαλέσουν μεγάλη σύγχυση!
Τι κάνει το ProcessMessages;
Το PprocessMessages χειρίζεται όλα τα μηνύματα του συστήματος αναμονής στην ουρά μηνυμάτων εφαρμογών. Τα Windows χρησιμοποιούν μηνύματα για να "μιλήσουν" σε όλες τις τρέχουσες εφαρμογές. Η αλληλεπίδραση χρήστη μεταφέρεται στη φόρμα μέσω μηνυμάτων και τα "ProcessMessages" τα χειρίζεται.
Εάν το ποντίκι κατεβαίνει σε ένα TButton, για παράδειγμα, το ProgressMessages κάνει ό, τι πρέπει να συμβεί σε αυτό το γεγονός, όπως επανατύπωση του κουμπιού σε κατάσταση "πατημένου" και, φυσικά, κλήση στη διαδικασία χειρισμού OnClick () αν τον έχετε αντιστοιχίσει.
Αυτό είναι το πρόβλημα: κάθε κλήση στο ProcessMessages ενδέχεται να περιέχει μια επαναληπτική κλήση σε οποιονδήποτε χειριστή συμβάντων ξανά. Ακολουθεί ένα παράδειγμα:
Χρησιμοποιήστε τον ακόλουθο κώδικα για το πρόγραμμα χειρισμού του αρχείου OnClick του κουμπιού ("εργασία"). Το for-statement προσομοιώνει μια μακρά εργασία επεξεργασίας με κάποιες κλήσεις προς ProcessMessages κάθε τόσο.
Αυτό είναι απλοποιημένο για καλύτερη αναγνωσιμότητα:
{στο MyForm:}
Επίπεδο εργασίας: ακέραιο;
{OnCreate:}
WorkLevel: = 0;
διαδικασία TForm1.WorkBtnClick (αποστολέας: TObject);
var
κύκλος: ακέραιο;
ξεκινήσει
inc (Επίπεδο εργασίας).
Για κύκλος: = 1 προς το 5 κάνω
ξεκινήσει
Memo1.Lines. Προσθέστε ('- Work' + IntToStr (WorkLevel) + ', Cycle' + IntToStr (κύκλος);
Εφαρμογή. ProcessMessages;
ύπνος (1000); // ή κάποια άλλη εργασία
τέλος;
Memo1.Lines. Προσθέστε ('Work' + IntToStr (WorkLevel) + 'έληξε.');
dec (Επίπεδο εργασίας).
τέλος;
ΧΩΡΙΣ "ProcessMessages", οι ακόλουθες γραμμές εγγράφονται στη σημείωση, αν το πλήκτρο πιέστηκε ΔΥΟ φορές σε σύντομο χρονικό διάστημα:
- Εργασία 1, Κύκλος 1
- Εργασία 1, Κύκλος 2
- Εργασία 1, Κύκλος 3
- Εργασία 1, Κύκλος 4
- Εργασία 1, Κύκλος 5
Η εργασία 1 τελείωσε.
- Εργασία 1, Κύκλος 1
- Εργασία 1, Κύκλος 2
- Εργασία 1, Κύκλος 3
- Εργασία 1, Κύκλος 4
- Εργασία 1, Κύκλος 5
Η εργασία 1 τελείωσε.
Ενώ η διαδικασία είναι απασχολημένη, η φόρμα δεν εμφανίζει καμία αντίδραση, αλλά το δεύτερο κλικ τέθηκε στην ουρά μηνυμάτων από τα Windows. Αμέσως μετά την ολοκλήρωση του "OnClick", θα καλείται ξανά.
ΣΥΜΠΕΡΙΛΑΜΒΑΝΟΜΕΝΩΝ "ProcessMessages", η παραγωγή μπορεί να είναι πολύ διαφορετική:
- Εργασία 1, Κύκλος 1
- Εργασία 1, Κύκλος 2
- Εργασία 1, Κύκλος 3
- Εργασία 2, Κύκλος 1
- Εργασία 2, Κύκλος 2
- Εργασία 2, Κύκλος 3
- Εργασία 2, Κύκλος 4
- Εργασία 2, Κύκλος 5
Η εργασία 2 τελείωσε.
- Εργασία 1, Κύκλος 4
- Εργασία 1, Κύκλος 5
Η εργασία 1 τελείωσε.
Αυτή τη φορά η φόρμα φαίνεται να λειτουργεί πάλι και δέχεται οποιαδήποτε αλληλεπίδραση χρηστών. Έτσι το κουμπί πιέζεται κατά το ήμισυ κατά τη διάρκεια της πρώτης σας λειτουργίας "εργαζόμενος" ΑΝΩ, η οποία θα αντιμετωπιστεί αμέσως. Όλα τα εισερχόμενα συμβάντα αντιμετωπίζονται όπως οποιαδήποτε άλλη κλήση λειτουργίας.
Θεωρητικά, κατά τη διάρκεια κάθε κλήσης στο "ProgressMessages", οποιοδήποτε ποσό κλικ και μηνυμάτων χρήστη μπορεί να συμβεί "στη θέση".
Έτσι, προσέξτε με τον κωδικό σας!
Διαφορετικό παράδειγμα (σε απλό ψευδοκώδικα!):
διαδικασία OnClickFileWrite ();
var myfile: = TFileStream;
ξεκινήσει
myfile: = TFileStream.create ('myOutput.txt');
δοκιμάστε
ενώ BytesReady> 0 κάνω
ξεκινήσει
myfile. Γράψτε (DataBlock);
dec (BytesReady, sizeof (DataBlock)).
DataBlock [2]: = # 13; {γραμμή δοκιμής 1}
Εφαρμογή. ProcessMessages;
DataBlock [2]: = # 13; {δοκιμαστική γραμμή 2}
τέλος;
τελικά
myfile.free;
τέλος;
τέλος;
Αυτή η λειτουργία καταγράφει ένα μεγάλο όγκο δεδομένων και προσπαθεί να "ξεκλειδώσει" την εφαρμογή χρησιμοποιώντας το "ProcessMessages" κάθε φορά που γράφεται ένα μπλοκ δεδομένων.
Εάν ο χρήστης κάνει ξανά κλικ στο κουμπί, ο ίδιος κώδικας θα εκτελεστεί ενώ το αρχείο είναι ακόμα γραμμένο σε. Επομένως, το αρχείο δεν μπορεί να ανοίξει για 2η φορά και η διαδικασία αποτυγχάνει.
Ίσως η αίτησή σας να κάνει κάποια ανάκτηση λάθους, όπως την απελευθέρωση των buffer.
Ως πιθανό αποτέλεσμα θα απελευθερωθεί το "κλείδωμα δεδομένων" και ο πρώτος κώδικας θα "ξαφνικά" εγείρει μια "παραβίαση πρόσβασης" όταν το αποκτήσει πρόσβαση. Στην περίπτωση αυτή: η γραμμή δοκιμής 1 θα λειτουργήσει, η γραμμή δοκιμής 2 θα καταρρεύσει.
Ο καλύτερος τρόπος:
Για να το κάνετε εύκολο, μπορείτε να ορίσετε ολόκληρη τη φόρμα "enabled: = false", η οποία αποκλείει όλες τις εισόδους των χρηστών, αλλά ΔΕΝ εμφανίζει αυτό στον χρήστη (όλα τα κουμπιά δεν είναι γκρίζα).
Ένας καλύτερος τρόπος θα ήταν να θέσετε όλα τα κουμπιά σε "απενεργοποιημένο", αλλά αυτό μπορεί να είναι περίπλοκο αν θέλετε να κρατήσετε ένα κουμπί "Ακύρωση" για παράδειγμα. Επίσης, πρέπει να περάσετε από όλα τα εξαρτήματα για να τα απενεργοποιήσετε και όταν αυτά είναι ενεργοποιημένα ξανά, θα πρέπει να ελέγξετε αν θα πρέπει να μείνουν κάποια σε κατάσταση αναπηρίας.
Θα μπορούσες απενεργοποιήστε ένα στοιχείο ελέγχου παιδιού κοντέινερ όταν αλλάζει η ιδιότητα Enabled.
Όπως υποδηλώνει το όνομα της κατηγορίας "TNotifyEvent", θα πρέπει να χρησιμοποιείται μόνο για βραχυπρόθεσμες αντιδράσεις στο συμβάν. Για τον χρονοβόρο κώδικα ο καλύτερος τρόπος είναι το IMHO να βάλει όλους τους "αργούς" κώδικες σε ένα δικό του Thread.
Όσον αφορά τα προβλήματα με το "PrecessMessages" και / ή την ενεργοποίηση και απενεργοποίηση των στοιχείων, η χρήση ενός δεύτερο νήμα φαίνεται να μην είναι πολύ περίπλοκο καθόλου.
Θυμηθείτε ότι ακόμα και απλές και γρήγορες γραμμές κώδικα μπορεί να κολλήσουν για δευτερόλεπτα, π.χ. το άνοιγμα ενός αρχείου σε μια μονάδα δίσκου ίσως χρειαστεί να περιμένει έως ότου ολοκληρωθεί η περιστροφή του δίσκου. Δεν φαίνεται πολύ καλό αν η εφαρμογή σας φαίνεται να συντρίβεται επειδή η μονάδα δίσκου είναι πολύ αργή.
Αυτό είναι. Την επόμενη φορά που προσθέτετε "Εφαρμογή. ProcessMessages ", σκεφτείτε δύο φορές)