How to cancel a long time operation in WxPerl - perl

I have a program like this
...
$self->{bn1}=Wx::Button->new( $tb1, -1, 'Start');
EVT_BUTTON( $self, $self->{bn1}, \&on_click_start );
...
...
sub on_click_start
{
my( $this, $event ) = #_;
$this->{bn1}->SetLabel("Cancel");
$event->Skip;
for (...) {
long_time_operation();
last if ( Cancel_clicked );
}
}
...
My problem is when I click the Start button, on_click_start() will be called, and I want change the label of Start button to Cancel, that allow I to click the button to break out the long_time_operation() loop.
How do I make right code for it?

The only real solution is to use multiple threads and perform the long operation in a background thread. If you want to keep your code simple, you can use wxYield() to handle the events from inside this event handler, but be aware that this may (and will) result in difficult to debug problems due to reentrancy, so at the very least you need to disable the rest of your UI if you're doing it like this.

Try with a wxTimer and start it in your on_click_start function. Put the long time operation code under the event of the timer (ensure that it dont trigger repeteadly, just stop the timer there too).
Inside your long operation function use some global var to know if you want to cancel. In the event of your button now change the value of your global var so your long term code knows about that and cancels/break from the loop.
I am not sure if a loop under the event of the timer can hang your UI, but it appears to use threads, so that may not happen. Try it anyway, i always use wxTimer when I need something like that (dont hanging the UI and not using threads directly).

Related

Override 'Cancel' in event procedures

There is data validation in my MS Word user form which returns the focus to the textbox where the user entered something incorrect. Now I am trying to accommodate the user's change of mind: instead of correcting the entry, I want him to be able to exit the form (click the Exit command button), in which case the entry would be discarded. I suppose that a solution would start with not using the text box's exit event. I little help from someone who knows the answer would save me a lot of testing time, perhaps to find out that I can't do it.
Does anyone know?
I understand that you are handling the Exit event of the Textbox, setting the Cancel output parameter if the data is not valid.
There's a tricky but simple solution that permits to keep that working and still have an Exit button. It permits to activate the handler of the Exit button without requiring the focus to leave the Textbox. This way you can unload the Form safely in this handler.
Try this it works pretty smoothly:
1- Set the property TakeFocusOnClick of the Exit command button to False. You can do that at design time in the property-sheet, or at run-time i.e. at UserForm_Activate
2- just unload the form when the Exit button is clicked:
Private Sub ExitButton_Click()
Unload Me
End Sub
#A.S.H provided the key to the solution below. His point is that it is possible to call another event procedure while Cancel is active in the Exit procedure of a control. That other procedure can be used to rectify the condition in the first control which is triggering the Cancel, thereby enabling an orderly exit. The all-enabling condition is that the control on whose click event the rectifying procedure is to run must not take the focus when clicked (meaning it can run without triggering an exit from the control stuck on Cancel). I have added code to the exit procedure to set CmdExit.TakeFocusOnClick = False when a Cancel condition arises there. Now, ...
Private Sub CmdExit_Click()
' 12 May 2017
' if CmdExit can't take the focus it can't be the ActiveControl
If Not ActiveControl Is CmdExit Then
Select Case ActiveControl.Name
Case "Cbx107"
Cbx107.Value = ""
Case "Tbx53"
Tbx53.Value = "0"
End Select
With CmdExit
If Not .TakeFocusOnClick Then
.TakeFocusOnClick = True
.SetFocus
End If
End With
End If
' now CmdExit is the ActiveControl
MsgMe "Cmd Exit: ActiveControl = " & ActiveControl.Name
Me.Hide
End Sub

QApplication::processEvents never returns

In my application I need to wait until external program (using QProcess) is finished. I want to keep the application responsible so blocking methods are unacceptable.
Also I need to disallow user input. I've tried to make QEventLoop and exec it with QEventLoop::ExcludeUserInputEvents flag, but as documentation says it only delays an event handling:
the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.
So I implemented simple event filter and install it on qApp (the idea is took from Qt Application: Simulating modal behaviour (enable/disable user input)). It works well, but sometimes QApplication::processEvents function never returns even if I specify the maximum timeout. Could anyone help me to understand for what reasons it periodically happens?
class UserInputEater : public QObject
{
public:
bool eventFilter(QObject *object, QEvent *event)
{
switch(event->type())
{
case QEvent::UpdateRequest:
case QEvent::UpdateLater:
case QEvent::Paint:
return QObject::eventFilter(object, event);
default:
return true;
}
}
};
-
UserInputEater eventEater;
qApp->installEventFilter(&eventEater);
QProcess prc;
prc.start("...");
while(!prc.waitForFinished(10))
{
if(qApp->hasPendingEvents())
{
// Sometimes it never returns from processEvents
qApp->processEvents(QEventLoop::AllEvents, 100);
}
}
qApp->removeEventFilter(&eventEater);
UPD: Seems like it depends of the timeout value for QProcess::waitForFinished.
I guess you are filtering some useful events (for example, QEvent::SockAct could be involved). Try to add some debug output and find out which event types you're actually filtering. Or it might be better to specify the black list of events you want to block instead of white list of events you want to allow. See this answer.
Also you shouldn't use return QObject::eventFilter(object, event);. You should use return false. All other event filters will be called automatically.
This solution however seems weird and unreasonable to me because you can just call setEnabled(false) for your top level widget to block user input, and then you can use QApplication::processEvents without any flags.

Multiple instances of window in perl, using gtk builder

To get started, I am inexperienced scripting in perl, or using gtk, but I've been googling and researching how to for the past two or so weeks. It was difficult just figuring out where I could find the PMs for gtk on windows, and then even more so getting it to some semblance of 'working'. However, there have of course still been problems.
Skipping the above, I have two problems. For a slight bit of relevant background, I am trying to port an mirc script over to xchat, but to do that I obviously need to learn a whole 'nother language.. but anyway. The two problems are thus:
The window consists of several buttons, labels, and text areas. However, the window is.. 'frozen' in time unless one clicks on the title bar and holds. Clicking a button does nothing, not even to show it has been clicked, unless of course the title bar is clicked and held.
I have no idea how to initialize multiple instances of the same window. I have of course tried researching, but it's either not out there or I just haven't found it yet. To be specific.. My mirc script requires that multiple instances be allowed to exist, but I need the buttons for the specific instance to only affect that instance.. and so on.
In regards to problem 1, I do not know if the .xml glade file is important, so I won't post it immediately. I will however post the code that calls it:
my $glade_file = "window3.xml";
my $glade = Gtk2::Builder->new();
$glade->add_from_file($glade_file);
sub charopen {
my $window = $glade->get_object('window1');
$glade->connect_signals(undef, $window);
my $hp_cur = $glade->get_object('HP_Cur');
$window->set("title"=>$_[0][1]);
$hp_cur->set("label"=>$ini->val($_[0][1],"HPC"));
$window->show();
}
Graphical interface design relies on event processing. To work properly, it is important to reserve a thread to process user events (keyboard, mouse clicks...). That is the aim of calling Gtk2->main() when user interface is ready to accept user interaction.
To make the event thread exiting the event loop, an event callback method may invoke Gtk2->main_quit()
The Gtk2::Builder creates Gtk widget hierarchy from XML. To get multiple instance of the same window, you have to create a builder for each one.
Then your event callback methods has to get information about which window has sent the event, and the $user_data parameter may be used in that aim.
Here is a code proposal with a simple button click callback which use Perl reference to a hash so you can pass as many information as you want between window creator code and event callbacks:
sub createWindow($)
my $windowTitle = $_[0];
my $windowBuilder = Gtk2::Builder->new();
$windowBuilder->add_from_file($glade_file);
my $window = $windowBuilder->get_object('window1');
my $hp_cur = $windowBuilder->get_object('HP_Cur');
# Create hash with data (alternative: use Class::Struct for better code)
my %window_user_data = {
"title" => $windowTitle,
"window" => $window,
"hp_cur" => $hp_cur };
# Pass hash reference as user data
$windowBuilder->connect_signals(\%window_user_data);
# prepare interface: set data model into view and then...
$window->show();
}
# Click callback method defined on a button in window
sub button_click_callback($$) {
my $button = $_[0];
my $window_user_data_ref = $_[1];
# get back data model from view
print "Click received from button on "
. $window_user_data_ref->{"title"} . "\n";
}
There is another way to handle callbacks per window but it requires more Perl skills: you can design a Perl package to create an object instance for a specific window, and use $windowbuilder->connect_signals ($user_data, $windowcallbackinstance). In that case, such an object is called controller, and you have built your graphical interface based on Model-View-Controller (MVC) pattern.

What is better approach to wait for elements on a page Waitforcomplete() or system.threading.thread.sleep()

I am Using WaitforComplete() in watiN but it doesnt seems to work well. As it executes the next statement even if you have given longer time to wait. I am using thread.sleep() to stop my application until it gets the desired page or element. But the thing is pages are so much dynamic that sometimes it takes much longer time as specified.
Any better solution. Any thing that will catch the page return dynamically and dont go to execute next statments in application.
Sample of Code
'Show Details page
Assert.AreEqual("Confirmation", _internetExplorer.Title)
If (_internetExplorer.Button(Find.ById(New Regex("btnFinish"))).Exists) Then
_internetExplorer.Button(Find.ById(New Regex("btnFinish"))).Click()
Else
Assert.Fail("Could not Find Finish Booking Button on Confirmation Page")
End If
System.Threading.Thread.Sleep(100000)
'Show Booking Summary page
Assert.AreEqual("Display Booking", _internetExplorer.Title)
I want something that detect the return of page dynamically. instead of giving some constant value.
WaitForComplete only works well if there is a postback after some action. Otherwise you have to find something else to wait for. Following an example on how to wait for the specified title:
_internetExplorer.Element("title", "Confirmation").WaitUntilExists();
I would always prefer to use one of the WaitXXX methods instead of Thread.Sleep cause the WaitXXX methods do only wait until the contraint is met. Where as Sleep waits for the time you specified. If its to long, time is waisted. If its to short, problems arise.
HTH,
Jeroen
The WaitForComplete method esentially moves on once the browser has set it's readystate to comllete and the busy state to false.
What I typically do is to try and access what you need to, then perform a thread.sleep for say half a second, then try again. I also have a global timeout that quits after say 10 seconds.
int timeout = 20;
bool controlFound = false;
for (int i = 0; i < timeout; i++)
{
if (_internetExplorer.Button(Find.ById(New Regex("btnFinish"))).Exists)
{
_internetExplorer.Button(Find.ById(New Regex("btnFinish"))).Click();
controlFound = true;
break;
}
else
{
System.Threading.Thread.Sleep(500);
}
}
if (!controlFound)
{
Assert.Fail("Control not found");
}
If it is executing the next statement, it should be finding the corresponding element. I suggest posting a sample of the code you are trying.

Signal fires twice from gtkmm popup list

It's been a while since I used GTK+, and the last time I did was in C, not using gtkmm and C++ as I am now. Anyway, I have what I think should be an easy problem to solve:
I have a pop-up menu consisting of a list of radio buttons, and when I click one of them I want some action to occur. The code goes like this:
Gtk::RadioMenuItem::Group group;
for ( size_t i = 1; i < LH_MAX; ++i )
{
Gtk::RadioMenuItem* pItem = new Gtk::RadioMenuItem( group, names[i], names[i] );
pItem->set_name( names[i] );
pItem->signal_activate().connect( sigc::mem_fun(*this, &MyClass::on_item_activated) );
pItem->show();
m_Menu.append( *Gtk::manage(pItem) );
}
The only problem I see is that MyClass::on_item_activated gets called twice when a previously-unselected radio button is chosen from the menu. It's called only once when the already-selected radio button is clicked.
I'm guessing that the first firing is to say "something is no longer activate," and the second is for the new radio button activation. Whether I'm right or wrong, the question is the same: how best can I have my handler only take action once per click? Either I need the handler to get called only once, or I need something to check from inside it to know if the callback is a "duplicate" or not.
You could use sigc::bind to supply the item as a argument to the callback function.
pItem->signal_activate().sigc::bind(sigc::mem_fun(*this,&MyClass::on_item_activated),pItem));
Then you can use item->get_active() in the callback to respond to activations only.
void MyClass::on_item_activated(Gtk::RadioMenuItem* item) {
if (item->get_active()) {
// Do some stuff
}
}
That's what I do too, connect to signal_toggled() and check if get_active() is true.
I don't know exactly what you're trying to accomplish (or what MyClass is and what base classes it inherits from), but connecting to signal_toggled() might be more useful than signal_activate()
/Agree with Johannes. Check if the item is activated when receiving the signal.