I have a GUI, in it I have a properties window that opens in a new window. In some of the times, (randomly and not deterministicly reproducible) when I open the window it gives the fallowing error:
grab failed: window not viewable
It doesn't interfere with the normal function of the program nor doesn't seem to have any affect on anything besides printing that message.
The code for creating the new window is:
proc _prop_menu_make_top {{elem {}}} {
toplevel .prop_menu
#...initialize some variables...
wm title .prop_menu "Properties for $_prop_attr(name)"
#...create and display the window widgets...
bind .prop_menu <Key-KP_Enter> {_prop_menu_ok_button}
bind .prop_menu <Return> {_prop_menu_ok_button}
bind .prop_menu <Escape> {_prop_menu_cancel_button}
# catch presses on the window's `x` button
wm protocol .prop_menu WM_DELETE_WINDOW {
_prop_menu_cancel_button
}
# make the top window unusable
center_the_toplevel .prop_menu
focus .prop_menu.main_frame.model_name.entry
grab release .
grab set .prop_menu
}
proc center_the_toplevel { window } {
if { [string equal $window [winfo toplevel $window]] } {
set width [winfo reqwidth $window]
set height [winfo reqheight $window]
set x [expr {([winfo vrootwidth $window] - $width) / 2}]
set y [expr {([winfo vrootheight $window] - $height) / 2 }]
wm geometry $window +${x}+${y}
}
return
}
proc _prop_menu_ok_button {} {
#....saving the needed data...
_prop_menu_cancel_button
}
proc _prop_menu_cancel_button {} {
destroy .prop_menu
# make the top window usable again
grab set .
# redraw the canvas
nlv_draw
}
Does anyone has any idea as to what may be causing this problem?
Does anyone has any advice as to how make the bug easier to reproduse?
EDIT:
running Tcl version 8.4.6 for 64bit, don't know which tk version.
Explanation
For various reasons (some technical, some design principles), Tk only permits grabs to be set on windows that are mapped onto the screen. This is almost certainly what you want; mouse clicks should be going to a window you can see after all.
The problem you've got is that you're trying to do the grab too early. In particular, Tk postpones the creation of the underlying X11/OS window (depending on platform) for each widget until it has finished deciding what the configuration of that widget will be, which is taken to be when Tk becomes “idle”. Idle is defined to be when the event loop is entered and there are no pending events to be serviced. At that point, Tk tells the basic system graphics engine to allocate a rectangular chunk of screen estate (the window) and to put it on the screen. That in turn triggers a whole cascade of events and processing (there's a lot going on at that point) that ends up with the window being shown to you; it's only once the window is shown that you can set a grab on it.
So how do you know when you can set a grab? Well, you've got to wait for the window to turn up. That means waiting for an event: the key ones that you might care about for this task are <Map>, <Visibility> and <Expose>. They indicate respectively when the window is logically present within the root window, when there is a change in what is actually viewable, and when there is something to redraw. (There are equivalents on Windows to the first and last, which Tk remaps internally, but Windows doesn't tell you about actual visibility changes at all. Ever.)
Solution
The simplest way to wait for a widget to become grabbable (and then do the grabbing) is to use:
tkwait visibility $theWidget
grab set $theWidget
That just waits in the event loop for a <Visibility> event to turn up on that window. (It doesn't work on Windows though because of the absence of the event type on that platform.) You could rewrite the above as this:
bind $theWidget <Visibility> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Visibility> {} ;# remove the binding again!
grab set $theWidget
If you're on Windows[*], you'll have to use that second technique, but replacing <Visibility> with <Map>.
bind $theWidget <Map> [list set waiting($theWidget) gotit]
vwait waiting($theWidget)
bind $theWidget <Map> {} ;# remove the binding again!
grab set $theWidget
I can't remember if <Expose> is available to scripts in Tk 8.4; that's an event that Tk handles for you normally. In any case, <Map> works and <Visibility> is perfect on X11.
You should also be aware that both tkwait and vwait can cause problems with reentrant event handling — you don't want it if you can help it! — so take care. You can deal with the problems by rewriting it all into continuation-passing style, which happens to be fairly easy in this case:
bind $theWidget <Map> {
bind %W <Map> {}
grab set %W
}
But if you're doing anything more than setting a grab, it can become really complex. (Tcl 8.6's coroutines can help untangle this mess, but they're definitely not portable to 8.4.)
[*] I forget whether there are issues round here with OSX with a non-X11 build of Tk. Check for yourself if you care.
Related
I'm using <paper-dialog> to display an SVG image I'll construct programatically. I need to know the size of the rendered container before I begin. I am waiting for the opened property to change to true however that is apparently too soon as .clientWidth is 0 when it fires however later .clientWidth does provide the correct value.
<paper-dialog class="dialog" opened={{modalOpen}} modal>
<svg width="100%", height="100%" />
</paper-dialog>
How can I wait for the SVG clientWidth and clientHeight to be computed?
Sometimes JS events are fired before the DOM has time to finish processing, which appears to be the case here.
To get around this (or at least to provide additional clues on what's happening), try putting your code (for the clientWidth property) within a setTimeout() function and give it a zero (0) millisecond timeout. This will simply move your code to the end of the JS execution stack, which should process only after the DOM has finished updating... and subsequently be when the DOM element's size and positioning properties are available.
Here's an example...
modalOpen() {
setTimeout( () => {
/* Your code here... for example... */
const paperDialog = document.getElementsByTagName("paper-dialog")[0];
console.log("paperDialog width = ", paperDialog.clientWidth);
}, 0); /* 0 milliseconds = Execute immediately after everything else processes. */
}
WARNING: Using setTimeout() is typically frowned upon since it doesn't fully adhere to asynchronous development and can produce unexpected results (such as firing too soon or not firing as soon as possible). While this approach is quick and can work fine some of the times, triggering and handling events are generally the best way to solve these timing issues.
Polymer's <paper-dialog> implements iron-resizable-behavior so we can listen for the iron-resize event:
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('iron-resize', this.onIronResize);
}
Overview
DOM elements that are dynamically-rendered within dom-if, dom-repeat <templates> seem to be rendered asynchronously thus making unit-testing a bit of a pain.
The Polymer Component
template(is='dom-if', if='{{!defaultPrintAll}}')
template(is='dom-repeat', items='{{_pageBucketItems}}')
button(type='button', class$='{{_computeDefaultClass(item)}}', on-tap='_togglePageClick') {{item}}
The Test
test("Clicking 'Export All' to off, reveals board-selection tiles", function() {
$("#export-pdf-checkbox-all").siblings(".checkbox").trigger("click");
Polymer.dom.flush()
expect($(".board-range__button")).to.be.visible;
});
Why it seems to fail:
When clicking a button which triggers the dom-if/dom-repeat the elements don't render in a synchronous order.
The dom-if and it's subsequent/nested dom-repeat render asynchronously.
To make matters worse, the button itself get's it's class in a computed/binded manner (mind the class$= on the button).
So the question boils down to this:
Is it possible to force render the dom-if, dom-repeat, and the computed-binding of the class in a synchronous order after I simulate the click to the button which activates all 3 of those conditions?
Notes:
I'm using Polymer's official WCT as the test harness.
I'm also using chai-jquery.
I've also used Polymer.dom.flush() but it still doesn't, ahem.. flush.
I'm aware that I can use chai-as-promised.js instead but it adds unnecessary complexity to my tests for a trivial matter such as this, so I'd like to avoid it.
Rather than using Polymer.dom.flush(), try using the flush function that WCT puts on the window. This will enqueue a callback function to be executed, in theory, after the template has rendered.
test("Clicking 'Export All' to off, reveals board-selection tiles", function(done) {
$("#export-pdf-checkbox-all").siblings(".checkbox").trigger("click");
flush(function () {
expect($(".board-range__button")).to.be.visible;
done();
}
});
Important to notice: Asynchronous tests require the done function to be passed into the test callback, and require done to be called after your conditions have been evaluated.
I try to make a photo-app that can read Qr-Codes with the ZXing-Library.
Most parts work but now somehow my LowLagPhotoCapture doesn't return anything useful:
var photoProperties = MediaProperties.ImageEncodingProperties.createJpeg();
mediaCaptureMgr.prepareLowLagPhotoCaptureAsync(photoProperties)
.done(function (_lowLagPhotoCapture) {
lowLagPhotoCapture = _lowLagPhotoCapture;
lowLagPhotoCapture.captureAsync()
.done(function (capturedPhoto) {
...
The MediaCaptureMgr works, I see a preview of the cam on the screen. But now I need to make a photo. The usual PhotoCapture didn't work with JavaScript and so I found this solution.
Somehow the lowLagPhotoCapture.captureAsync() crashes saying that lowLagPhotoCapture is empty. lowLagPhotoCapture is defined outside of this class because I need it later. But even if I pass the variable directly to the new method it fails =/
Any ideas what might go wrong with this?
Edit:
Okay, after every Async-operation I had a following nameless function and one exitOnError-function that was calles every time. If I remove thatv exitOnError-function out of .done(complete, error), it exits in the same place. But if I set a breakpoint on .captureAsync it goes 1-2 steps further, creates an ImageStream and exits somewhere there. Why the different behaviour with and without the breakpoint?
I'm trying to display a google map on click with angular JS. But my function seems to be toggling the visibility based on a function call (via ng-show). This means that my trigger of the 'resize' event executes before the map div is actually visible so it doesn't work correctly.
$scope.mapVisible = false;
$scope.toggleMap = function() {
$scope.myMap.panTo($scope.myMarkers[0].getPosition());
$scope.mapVisible = !$scope.mapVisible;
// this executes too soon. How to block until the div is really visible?
google.maps.event.trigger($scope.myMap, 'resize');
}
<div ng-show="mapVisible">
<!-- map here -->
</div>
So how can I block triggering the 'resize' event on my map until the div is truly visible?
$scope.$watch("mapVisible", function (val) {
if (val) {
google.maps.event.trigger($scope.myMap, "resize");
}
});
This would make sure the map is visible before triggering the event.
Angularjs handles two-way binding by doing dirty-checking. It basically means that the value being watched is compared to the one in previous cycle. Each cycle ($digest) starts to run when something happens which could possibly change any value in the scope. If there is a change in the value, it would be reflected after the cycle has completed.
Back to your problem, when you toggle the mapVisible property, the view does not update immediately. It waits for the cycle to end before redrawing the view. But you fire the resize event so early, when the map is still invisible, therefore rendering invalid.
$watch does indeed watch the property and the changes will be reflected in the next cycle of the change, which means the view would be updated by the time watcher function has been invoked. Putting the resize function here would hence solve your issue.
I figured the easiest case is to use the $timeout service:
scope.$on "map:ui:shown", (event, args)->
$timeout ->
#use a delay because most of the time, the resizing should occur immediately after an angular cycle
#like when an ng-show has been set to the True condition
googleMaps.event.trigger(map, 'resize')
map.fitBounds scope.bounds if scope.bounds
I want to set (manually) the skinState (for example 'disabled') of a button (that I skinned) in ActionScript.
For example:
I have a button skin with hostComponent: components.backend.btnMenuBakComp
The button skin has the default button states (up, over, down, ...), but I want to set one of this skinStates in ActionScript.
For example:
subMenu.btnDashboard.currentState = "disabled";
This doesn't work because the state "disabled" is not known in the component (it is only known in the skinState of btnDashboard).
How can I fix this?
Is there another solution then load a new skinClass?
Thanks
Quick and dirty
You can access the skin of any component and just set its state directly:
subMenu.btnDashboard.skin.currentState = "disabled";
That is however not a very clean way to do it. You are telling a Skin class directly what to do and completely bypassing the host component. Hence the host component has no idea of the changes that were made to its skin.
The proper way
A cleaner way to approach this is to expose a property on the host component and then tell the skin to adjust itself to possible changes by overriding the getCurrentSkinState() method.
You could for instance create a property 'enabled' and then tell the skin to update its state by calling invalidateSkinState() whenever 'enabled' is being set.
public function set enabled(value:Boolean):void {
_enabled = value;
invalidateSkinState();
}
Calling invalidateSkinState() will make the skin call getCurrentSkinState() in the next render cycle. This method will then look something like this:
override protected function getCurrentSkinState():String {
return _enabled ? "normal" : "disabled";
}
Do note that since you are skinning a Button (or a subclass of it) all that I've written here is already baked into that component. So the answer to your question might be as simple as : "just set the 'enabled' property to true.
subMenu.btnDashboard.enabled = true;