How to setPixmap on a QLabel from QCamera image - pyqt5

I been trying an image to a Label via setPixmap() but to no avail...
In fact, i want that if i click to call the Methode def click_photo(self): to setthe image also
from PyQt5.QtWidgets import *
from PyQt5.QtMultimedia import *
from PyQt5.QtMultimediaWidgets import *
import os, sys, time
class MainWindow(QMainWindow):
def __init__(self): # constructor
super().__init__()
self.setGeometry(100, 100, 800, 600)
self.setStyleSheet("background : lightgrey;")
self.available_cameras = QCameraInfo.availableCameras()
self.Label_preview = QLabel(self, 'preview here')
self.status = QStatusBar()
self.status.setStyleSheet("background : white;")
self.setStatusBar(self.status) # adding status bar to the main window
self.save_path = "" # path to save
self.viewfinder = QCameraViewfinder() # creating a QCameraViewfinder object
self.viewfinder.show() # showing this viewfinder
self.setCentralWidget(self.viewfinder) # making it central widget of main window
self.select_camera(0) # Set the default camera.
toolbar = QToolBar("Camera Tool Bar") # creating a tool bar
self.addToolBar(toolbar) # adding tool bar to main window
click_action = QAction("Click photo", self) # creating a photo action to take photo
click_action.setStatusTip("This will capture picture") # adding status tip to the photo action
click_action.setToolTip("Capture picture")
click_action.triggered.connect(self.click_photo) # adding action to it
toolbar.addAction(click_action) # adding this to the tool bar
change_folder_action = QAction("Change save location", self) # similarly creating action for changing save folder
change_folder_action.setStatusTip("Change folder where picture will be saved saved.")
change_folder_action.setToolTip("Change save location") # adding tool tip to it
# setting calling method to the change folder action
# when triggered signal is emitted
change_folder_action.triggered.connect(self.change_folder)
toolbar.addAction(change_folder_action) # adding this to the tool bar
# creating a combo box for selecting camera
camera_selector = QComboBox()
# adding status tip to it
camera_selector.setStatusTip("Choose camera to take pictures")
# adding tool tip to it
camera_selector.setToolTip("Select Camera")
camera_selector.setToolTipDuration(2500)
# adding items to the combo box
camera_selector.addItems([camera.description()
for camera in self.available_cameras])
# adding action to the combo box
# calling the select camera method
camera_selector.currentIndexChanged.connect(self.select_camera)
# adding this to tool bar
toolbar.addWidget(camera_selector)
toolbar.setStyleSheet("background : white;")
self.setWindowTitle("PyQt5 Cam")
self.show()
# method to select camera
def select_camera(self, i):
self.camera = QCamera(self.available_cameras[i]) # getting the selected camera
self.camera.setViewfinder(self.viewfinder) # getting the selected camera
self.camera.setCaptureMode(QCamera.CaptureStillImage) # setting capture mode to the camera
self.camera.error.connect(lambda: self.alert(self.camera.errorString())) # if any error occur show the alert
self.camera.start() # start the camera
self.capture = QCameraImageCapture(self.camera) # creating a QCameraImageCapture object
self.capture.error.connect(lambda error_msg, error, msg: self.alert(msg)) # showing alert if error occur
self.capture.imageCaptured.connect(lambda d,
i: self.status.showMessage("Image captured : "
+ str(self.save_seq))) # when image captured showing message
self.capture.imageCaptured.connect(lambda d, i: self.status.showMessage("Image captured : " + str(self.save_seq))) # when image captured showing message
self.tipamu = i
# getting current camera name
self.current_camera_name = self.available_cameras[i].description()
# inital save sequence
self.save_seq = 0
# method to take photo
def click_photo(self):
# time stamp
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
self.capture.capture(os.path.join(self.save_path,
"%s-%04d-%s.jpg" % (
self.current_camera_name,
self.save_seq,
timestamp
))) # capture the image and save it on the save path
# increment the sequence
self.save_seq += 1
# change folder method
def change_folder(self):
path = QFileDialog.getExistingDirectory(self, "Picture Location", "") # open the dialog to select path
if path: # if path is selected
self.save_path = path # update the path
self.save_seq = 0 # update the sequence
def alert(self, msg):
error = QErrorMessage(self) # error message
error.showMessage(msg) # setting text to the error message
# Driver code
if __name__ == "__main__" :
App = QApplication(sys.argv) # create pyqt5 app
window = MainWindow() # create the instance of our Window
sys.exit(App.exec()) # start the app
i tried
self.Label_preview.setPixmap(QPixmap(self.capture))
but it didn't work, is there a way to approach this: setting the image captured via webcam to the preview Qlabel self.Label_preview before saving that image to the disk or without saving to the disk ?

I started from the same example you found. Here is how I did it with a bit of extra styling and ported to PyQt6.
# importing required libraries
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtMultimedia import *
from PyQt6.QtMultimediaWidgets import *
from PyQt6.QtCore import Qt
import os
import sys
import time
stylesheet = """
QWidget {
background-color: rgba(32.000, 33.000, 36.000, 1.000);
color: rgba(170.000, 170.000, 170.000, 1.000);
selection-background-color: rgba(138.000, 180.000, 247.000, 1.000);
selection-color: rgba(32.000, 33.000, 36.000, 1.000);
}
QWidget:disabled {
color: rgba(105.000, 113.000, 119.000, 1.000);
selection-background-color: rgba(83.000, 87.000, 91.000, 1.000);
selection-color: rgba(105.000, 113.000, 119.000, 1.000);
}
QToolTip {
background-color: rgba(41.000, 42.000, 45.000, 1.000);
color: rgba(228.000, 231.000, 235.000, 1.000);
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
}
QSizeGrip {
width: 0;
height: 0;
image: none;
}
QStatusBar {
background-color: rgba(42.000, 43.000, 46.000, 1.000);
}
QStatusBar::item {
border: none;
}
QStatusBar QWidget {
background-color: transparent;
padding: 0px;
border-radius: 0px;
margin: 0px;
}
QStatusBar QWidget:pressed {
background-color: rgba(79.000, 80.000, 84.000, 1.000);
}
QStatusBar QWidget:disabled {
background-color: rgba(32.000, 33.000, 36.000, 1.000);
}
QStatusBar QWidget:checked {
background-color: rgba(79.000, 80.000, 84.000, 1.000);
}
QToolBar {
background-color: rgba(41.000, 42.000, 45.000, 1.000);
padding: 1x;
font-weight: bold;
spacing: 1px;
margin: 1px;
}
QToolBar::separator {
background-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QToolBar::separator:horizontal {
width: 2px;
margin: 0 6px;
}
QToolBar::separator:vertical {
height: 2px;
margin: 6px 0;
}
QPushButton {
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
padding: 4px 8px;
border-radius: 4px;
color: rgba(138.000, 180.000, 247.000, 1.000);
}
QPushButton:hover {
background-color: rgba(30.000, 43.000, 60.000, 1.000);
}
QPushButton:pressed {
background-color: rgba(46.000, 70.000, 94.000, 1.000);
}
QPushButton:checked {
border-color: rgba(138.000, 180.000, 247.000, 1.000);
}
QPushButton:disabled {
border-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QPushButton[flat=true]:!checked {
border-color: transparent;
}
QDialogButtonBox QPushButton {
min-width: 65px;
}
QComboBox {
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
border-radius: 4px;
min-height: 1.5em;
padding: 0 4px;
background-color: rgba(63.000, 64.000, 66.000, 1.000);
}
QComboBox:focus,
QComboBox:open {
border: 1px solid rgba(138.000, 180.000, 247.000, 1.000);
}
QComboBox::drop-down {
subcontrol-position: center right;
border: none;
padding-right: 4px;
}
QComboBox::item:selected {
border: none;
background-color: rgba(0.000, 72.000, 117.000, 1.000);
color: rgba(228.000, 231.000, 235.000, 1.000);
}
QComboBox QAbstractItemView {
margin: 0;
border: 1px solid rgba(63.000, 64.000, 66.000, 1.000);
selection-background-color: rgba(0.000, 72.000, 117.000, 1.000);
selection-color: rgba(228.000, 231.000, 235.000, 1.000);
padding: 2px;
}
"""
# Main window class
class MainWindow(QMainWindow):
# constructor
def __init__(self):
super().__init__()
self.captured_image = None
self.save_seq = None
self.capture = None
self.camera = None
self.current_camera_name = None
self.mirror_h = True
# setting geometry
self.setGeometry(200, 200, 800, 600)
self.setStyleSheet("background : darkgrey;")
# getting available cameras
self.available_cameras = QMediaDevices.videoInputs()
# if no camera found
if not self.available_cameras:
# exit the code
sys.exit()
self.status = QStatusBar()
self.setStatusBar(self.status)
# path to save
self.save_path = ""
toolbar = QToolBar("Camera Tool Bar")
self.addToolBar(toolbar)
# creating a photo action to take photo
click_action = QAction("Get Photo", self)
click_action.setStatusTip("This will capture picture")
click_action.setToolTip("Capture picture")
click_action.triggered.connect(self.capture_picture)
toolbar.addAction(click_action)
# similarly creating action for changing save folder
change_folder_action = QAction("Save location", self)
change_folder_action.setStatusTip("Change folder where picture will be saved saved.")
change_folder_action.setToolTip("Change save location")
change_folder_action.triggered.connect(self.change_folder)
toolbar.addAction(change_folder_action)
# creating a combo box for selecting camera
camera_selector = QComboBox()
camera_selector.setStatusTip("Choose camera to take pictures")
camera_selector.setToolTip("Select Camera")
camera_selector.setToolTipDuration(2500)
camera_selector.addItems([camera.description() for camera in self.available_cameras])
camera_selector.currentIndexChanged.connect(self.select_camera)
toolbar.addWidget(camera_selector)
camera_mirror = QCheckBox("Mirror")
camera_mirror.setChecked(True)
camera_mirror.setStatusTip("Mirror the captured image horizontally")
camera_mirror.setToolTip("Mirror Camera")
camera_mirror.stateChanged.connect(self.on_mirror_changed)
toolbar.addWidget(camera_mirror)
# setting window title
self.setWindowTitle("PyQt6 Cam")
main_wdg = QWidget()
layout = QVBoxLayout(main_wdg)
glay = QGridLayout()
glay.setRowStretch(0, 1)
glay.setRowStretch(1, 0)
self.label = QLabel()
self.label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
self.save_btn = QPushButton("Save Picture")
self.save_btn.clicked.connect(self.on_save_picture)
glay.addWidget(self.label, 0, 0)
glay.addWidget(self.save_btn, 1, 0)
self.video_wdg = QVideoWidget()
hlay = QHBoxLayout()
hlay.addLayout(glay)
hlay.addWidget(self.video_wdg)
layout.addLayout(hlay, stretch=1)
self.setCentralWidget(main_wdg)
self.video_wdg.show()
# showing the main window
self.show()
self.select_camera(0)
# method to select camera
def select_camera(self, current_camera):
media_capture_session = QMediaCaptureSession(self)
self.camera = QCamera(self.available_cameras[current_camera])
self.camera.start()
media_capture_session.setCamera(self.camera)
media_capture_session.setVideoOutput(self.video_wdg)
self.camera.errorOccurred.connect(lambda err, err_str: self.alert(err_str))
self.capture = QImageCapture(self.camera)
media_capture_session.setImageCapture(self.capture)
self.capture.errorOccurred.connect(lambda error_msg, error, msg: self.alert(msg))
# when image captured showing message
self.capture.imageCaptured.connect(self.on_image_captured)
self.current_camera_name = self.available_cameras[current_camera].description()
# initial save sequence
self.save_seq = 0
def on_image_captured(self, id, image):
width = self.label.width()
height = self.label.height()
self.captured_image = image
if self.mirror_h is True:
self.captured_image = image.mirrored(horizontal=True, vertical=False)
pixmap = QPixmap().fromImage(self.captured_image)
self.label.setPixmap(pixmap.scaled(width, height, Qt.AspectRatioMode.KeepAspectRatio))
self.status.showMessage("Image captured.")
# method to take photo
def capture_picture(self):
self.capture.capture()
def on_save_picture(self):
# time stamp
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
file_path = os.path.join(
self.save_path, "%s-%04d-%s.jpg" % (self.current_camera_name, self.save_seq, timestamp))
try:
self.captured_image.save(file_path, format='jpg', quality=-1)
except Exception as err:
print(err)
self.status.showMessage("Image saved to: %s" % str(file_path))
# increment the sequence
self.save_seq += 1
def on_mirror_changed(self, state):
self.mirror_h = True if int(state) else False
# change folder method
def change_folder(self):
# open the dialog to select path
path = QFileDialog.getExistingDirectory(self, "Picture Location", "")
# if path is selected
if path:
# update the path
self.save_path = path
# update the sequence
self.save_seq = 0
# method for alerts
def alert(self, msg):
# error message
error = QErrorMessage(self)
# setting text to the error message
error.showMessage(msg)
# Driver code
if __name__ == "__main__":
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = MainWindow()
window.setStyleSheet(stylesheet)
# start the app
sys.exit(App.exec())

Related

QProgressDialog object AttributeError: 'ProgressBar' object has no attribute 'wasCanceled'

I am trying to DRY up a PyQt program that calls QProgressDialog at various times. Instead of each occurrence calling a variation of this:
self.cat_progress = QProgressDialog(self.root)
self.cat_progress.setMinimumWidth(800)
self.cat_progress.setWindowTitle('Downloading Data')
self.cat_progress.setMinimumDuration(0)
self.cat_progress.setWindowModality(Qt.ApplicationModal)
self.cat_progress_label = QLabel('')
self.cat_progress.setLabel(self.cat_progress_label)
total_requests = 25
self.cat_progress.setMaximum(total_requests)
self.current_progress = 1
self.cat_progress.setValue(self.current_progress)
self.message_phase = 'Starting Collection'
self.progress_bar_max = 0
self.cat_progress_label.setText(self.message_phase)
I made a class, so that each time a progressDialog is needed I can use 3 lines, instead of 20+.
class ProgressBar(QDialog):
def __init__(self, title, label, prog_max=100):
super().__init__()
self.progress = QProgressDialog(self)
self.progress.setMinimumWidth(600)
self.progress.setWindowTitle(title)
self.progress.setMinimum(0)
self.progress.setValue(0)
self.progress.setWindowModality(Qt.WindowModal)
self.progress.setMaximum(prog_max)
self.progress.setWindowFlag(Qt.WindowContextHelpButtonHint,False) # This removes the '?' from the dialog
self.progress.setStyleSheet("""
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
text-align: center
}
QProgressBar::chunk {
background-color: rgb(139, 183, 240);
width: 25px
}
QPushButton {
border: 2px solid grey;
border-radius: 5px;
padding: 5% 8%;
}
""")
self.progress_label = QLabel(label)
self.progress.setLabel(self.progress_label)
def advance_progress(self, label):
self.progress_label.setText(label)
self.progress.setValue(self.progress.value() + 1)
def canceled(self):
self.stop_progress()
def stop_progress(self):
self.deleteLater()
The issue is that one usage was relying on the built-in Cancel button's wasCanceled() boolean value. As the program runs a loop, if progress.wasCanceled(): ...start a different process. Now that progress is from a class and not directly made from QProgressDialog, I get an AttributeError: 'ProgressBar' object has no attribute 'wasCanceled'. Clicking this button will escape the dialog, but it does not progress into the next process as I need.
I've tried to add signals, connect a cancelled function, to set a userClicked boolean, etc. Some things broke the Cancel so it just resets and starts over, infinitely, when you click cancel. Some self 'clicked' the button (I had a print statement output) as the loop ran, but didn't stop the action. Once, it got to that if statement and broke out on its own.
How do I keep this as an object and also know when a user has clicked Cancel?
Small demo that now works! [the key is to inherit from the right widget and to not call a new instance of QProgressDialog]:
import sys
import time
from PyQt5.QtWidgets import QMainWindow, QApplication, QProgressDialog, QLabel, QPushButton
from PyQt5.QtCore import Qt
class ProgressBar(QProgressDialog):
def __init__(self, title, label, prog_max=100):
super().__init__()
self.setMinimumWidth(600)
self.setWindowTitle(title)
self.setMinimum(0)
self.setValue(0)
self.setWindowModality(Qt.WindowModal)
self.setMaximum(prog_max)
self.setStyleSheet("""
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
text-align: center
}
QProgressBar::chunk {
background-color: rgb(139, 183, 240);
width: 25px
}
QPushButton {
border: 2px solid grey;
border-radius: 5px;
padding: 5% 8%;
}
""")
self.progress_label = QLabel(label)
self.setLabel(self.progress_label)
def advance_progress(self, label):
self.progress_label.setText(label)
self.setValue(self.value() + 1)
def canceled(self):
self.stop_progress()
def stop_progress(self):
self.deleteLater()
class Testing(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Testing Progress')
btn = QPushButton('Start progress bar')
btn.clicked.connect(self.run_progress)
self.setCentralWidget(btn)
def run_progress(self):
# Title, Label, setMaximum
progress = ProgressBar('Window Running Progress', 'Testing from Element', 30)
progress.advance_progress('Starting Scan ...')
i = 0
while i < 30:
print(i)
time.sleep(2)
progress.advance_progress(f'Counting {i}')
i += 1
if progress.wasCanceled():
alert('User Stopped!')
progress.stop_progress()
progress.stop_progress()
if __name__ == '__main__':
app = QApplication(sys.argv)
test = Testing()
test.show()
sys.exit(app.exec_())

wxWidget::wxWebView->Find wxWEBVIEW_FIND_HIGHLIGHT_RESULT Segmentation Fault

wxWidget::wxWebView->Find - https://docs.wxwidgets.org/3.0/classwx_web_view.html#ad85a7aa0351b6e6a6bffd4220f9758ee
Sample Code
wxWebView *webView;
webView = wxWebView::New(this, wxID_ANY);
webView->SetPage("<html><head><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no' /><meta content='en-us' http-equiv=\"Content-Language\" /><meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" /><title>Demo App</title><style type=\"text/css\">.auto-style8 {font-family: \"Bookman Old Style\";font-weight:bold;color:#734024;}.underline{color:#ff0000;}.text {color:#230fd2;}#wrapper1 { width: 100% ; display: table;}#wrapper {width: 100% ; display: table;}#header1 {color: #E6F0F1; display: table; background-color: #0000ff;width: 100% ; text-align: center;height: 50px;font-size: 105% ; font-weight: bold;}#header2 {background-color: #B98264;display: table; width: 100% ; height: 40px;text-align: center;color: #FFFFFF; font-size: 75% ; font-weight: bold;}.auto-style13 {text-decoration: underline;}.text2{color:#000000;text-align: justify;}</style></head> <body style=\"color:#000000;background-color:#FFFFFF\"><div id=\"header2\"><p style=\"font-size: x-medium\" class=\"auto-style13\">Demo Page<br></p></div> <p class=\"auto-style8\" style=\"width: 100%;\">To Start - <br/><br/>STEP 1. Select data</p></body></html>","");
long lMatchCount = webView->Find("a",wxWEBVIEW_FIND_HIGHLIGHT_RESULT );
if(lMatchCount == wxNOT_FOUND){
wxMessageBox("Search not found");
}else{
//wxMessageBox("Search found" + lMatchCount);
wxLogMessage("Matches: %s ",std::to_string(lMatchCount));
}
Above code works correctly and returns number of occurrences of "a".
When webview->SetPage is updated again dynamically with large text ( any dummy content will do ) and if find call is called again it crashes with Segmentation fault
If SetPage is loaded with large text in 1st instance itself it crashes as well so ruled out possibility of update causing the crash.
Also tried loading large file from local file ( default.html contains above html code - replace any long string and it crashes with same result )
wxFile fFileIn("default.html", wxFile::read);
wxFileInputStream in(fFileIn);
webView->SetPage(in,"");
fFileIn.Close();
Similar issue is reported here http://trac.wxwidgets.org/ticket/15207
As per discussion on this thread http://trac.wxwidgets.org/ticket/15207.
We have workaround to this problem ( Still don't know why this is happening and what is the root cause )
Use the code mentioned in PR https://github.com/wxWidgets/wxWidgets/pull/2626.
Call Find before the setPage
wxFile fFileIn("default.html", wxFile::read);
wxFileInputStream in(fFileIn);
webView->Find("");
webView->SetPage(in,"");
fFileIn.Close();

How to plot inscribed circle in cytoscape.js nodes

i need to plot (in cytoscape.js) a circle inscibed to another circle.
I can make the external circle with:
shape:'ellipse',
height: 15,
width: 15,
'background-color': 'white',
'border-width':0.5,
'border-color':'black'
But how can i make the other circle inscribed?
EDIT: In particular, i have to put inside a white circle with black circumference, a smaller white circle with black circumference.
2th EDIT:
I solved by creating a fake node (equal to real one but smaller) that follows the original when dragged or grabbed.
var compAtrr = cy.$('node[type = "originalnode"]');
compAtrr.on('grabon drag',function(evt){
var node = evt.target;
var idnode = node.data('id');
var fakenode = cy.$id(idnode+'fake');
var ix = node.position('x');
var iy = node.position('y');
fakenode.position({
x: ix,
y: iy
});
});
var fakeAtrr = cy.$('node[type = "fakenode"]');
fakeAtrr.on('grabon drag',function(evt){
var node = evt.target;
var idnode = node.data('id');
var l = idnode.length;
idnode = idnode.slice(0,l-4); //remove 'fake' string
var realnode = cy.$id(idnode);
var ix = node.position('x');
var iy = node.position('y');
realnode.position({
x: ix,
y: iy
});
});
Thanks anyway
Have a look at this code pen, you can specify an inner circle by defining the background or a border:
style: [
{
selector: 'node',
css: {
'content': 'data(id)',
'text-valign': 'center',
'text-halign': 'center',
'height': '60px',
'width': '60px',
'border-color': 'black',
'border-opacity': '1',
'border-width': '10px'
}
},
...
I am afraid this is not possible in Cytoscape.js. Your best bet is to use a background image.
You can also try setting border style to double, but this is very limited - you won't be able to change the distance between lines.
You can draw arbitrary content on a node with one or more SVG background images.

Datatables - fixedHeader with scrollX

I am trying to use Datatables with fixedheader (v3) as well as enable horizontal scrolling. Attached is the fiddle http://jsfiddle.net/xF8hZ/344/
$(document).ready(function() {
var table = $('#example').DataTable({
searching: false,
paging: false,
ordering: false,
info: false,
fixedHeader: true,
scrollX: true
});
} );
.
When scrolling the fixedheader width doesn't align with the rest of the table. Can you help me solve this please?
Thanks
Pure css solution using css sticky (not work in ie 11):
remove the fixHeader plugin
add this css
.dataTables_scrollHead {
position: sticky !important;
top: 119px;
z-index: 99;
background-color: white;
box-shadow: 0px 5px 5px 0px rgba(82, 63, 105, 0.08);
}
I have read for 2 days about this, so joining all of them together, here's my contribution.
I got it figured out, hopefully this is useful for someone or help in the development as well.
My datatables is in a DIV and horizontal Scrolling enable due to huge table. When fixed header was set it was set as FIXED, and a new table is inserted at the BODY rather than inside the div.
I made it appended to the DIV instead of BODY so that the overflow rule might be inherited.
File:
dataTables.fixedHeader.min.js (search for "appendTo")
From:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo("body")
To:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo(".dataTables_scroll")
Now that it's appended to the the datatables-created-div, same level as dataTables_scrollHead, dataTables_scrollBody rather than stranded alone at body, whatever overflow still showing/sticking out.
File:
fixedHeader.bootstrap.min.css
From:
table.dataTable.fixedHeader-floating{position:fixed !important}
To
table.dataTable.fixedHeader-floating{position:absolute !important}
or File:
fixedHeader.dataTables.min.css
From:
table.fixedHeader-floating{position:fixed !important;background-color:white;}
To
table.fixedHeader-floating{position:absolute !important;background-color:white;}
Careful of CACHE of the CSS and JS files.
Now that the floating sticky row has appeared but out of place and overflow in effect.
Have this JS running, detecting when fixedHeader-floating appears, keep adjusting them to follow the horizontal scroll and stick to the top.
setInterval(function(){
if($('.fixedHeader-floating').is(':visible')){
var myoffset = Math.round($(window).scrollTop() - $('#Detail2Container').position().top + $('.topbar').height() - 145);
var positionleft = $('.dataTables_scrollHeadInner').position();
$('.fixedHeader-floating').css({ 'top': myoffset, 'left': positionleft.left + 10 });
}
}, 50); //every 50ms
Detail2Container is the DIV that wrap the Datatables.
I couldn't use dataTables_wrapper as reference as there are a few of them in the same page. In my page, I only one table that needs fixedHeader, if I need 2, it will be tough. But I will deal with it when the needs arise.
You could adjust the calculation according to your own design.
2 days for me to figure this out. So I feel like sharing it too.
I found a solution on my project by doing this:
$('#example').scroll(function() {
if ( $(".fixedHeader-floating").is(":visible") ) {
$(".fixedHeader-floating").scrollLeft( $(this).scrollLeft() );
}
});
DataTables creates a new table as the fixedHeader when you scroll down, what I'm doing here is to detect when the user scrolls horizontally on the $('#example') table and then I use scrollLeft() on the fixedHeader to match the scroll position.
I also added this to my .css so the user won't be able to scroll on the fixedHeader table:
.fixedHeader-floating {
overflow: hidden;
}
Following is working to me
$('.dataTables_scrollBody').on('scroll', function () {
$('.dataTables_scrollHead', $(this).parent()).scrollLeft($(this).scrollLeft());
});
This fixed the problem.
let tableParams = {
autoWidth: false,
// etc...
scrollX: true,
fixedHeader: true,
initComplete: function(settings, json) {
// To fix the issue of when scrolling on the X axis, the header needs also to scroll as well.
this.find('.dataTables_scrollBody').on('scroll', function() {
this.find('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft());
});
},
};
Also to hide the vertical scroll bar.
me.containerElement.find('.dataTables_scrollBody').css({'overflow-y': 'hidden'});
where containerElement is the parent element of the datatable element.
Based on this, I was able to make it working (issue: when FixedHeader is floating, sorting would not work ==> see update 1 below to fix it)
Explanations:
FixedHeader (.dataTables_scrollHeadInner) is a different table outside of datatable (.dataTables_scrollBody)
when scrolling vertically, it will check scrolltop and set FixedHeader top accordingly.
when scrolling horizontally, it will scroll FixedHeader with body ($('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft()))
JS
// sorry - had to use global variable
// global variable for scroll-body y position
var yPositionOfScrollBody;
function adjustDatatableInnerBodyPadding(){
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
let outerHeightOfInnerHeader = $dtScrollHeadInner.outerHeight(true);
//console.log('outerHeightOfInnerHeader => ' + outerHeightOfInnerHeader);
$('.dataTables_scrollBody').css('padding-top', outerHeightOfInnerHeader);
}
function setFixedHeaderTop(header_pos){
//console.log("header_pos : " + header_pos);
$('.dataTables_scrollHeadInner').css({"top": header_pos});
}
function fixDatatableHeaderTopPosition(){
//console.log("fixHeaderTop...");
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
//console.log("yPositionOfScrollBody: " + yPositionOfScrollBody);
setFixedHeaderTop(yPositionOfScrollBody);
}
function onDataTableInitComplete(settings, json) {
// for vertical scolling
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
// datatable padding adjustment
adjustDatatableInnerBodyPadding();
// data table fixed header F5 (refresh/reload) fix
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
//console.log("scrollTop => " + scrollTop);
if(scrollTop > 1){
let header_pos;
if (scrollTop < yPositionOfScrollBody){
header_pos = yPositionOfScrollBody - scrollTop;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
// horizontal scrolling
$('.dataTables_scrollBody').on('scroll', function () {
let $dtScrollBody = $(this);
// synchronize
let amountOfLeftScroll = $dtScrollBody.scrollLeft();
$dtScrollHeadInner.scrollLeft(amountOfLeftScroll);
let scrollDiff = $dtScrollHeadInner.scrollLeft() - amountOfLeftScroll;
//console.log("scrollDiff: " + scrollDiff);
if(scrollDiff < 0){
$dtScrollHeadInner.css('left', scrollDiff);
}else{
//console.log("scroll back to left side");
$dtScrollHeadInner.css('left', '');
}
});
//console.log("adjusment mergin: " + yPositionScrollHeadInner);
$(document).on('scroll', function () {
let scroll_pos = $(this).scrollTop();
if(scroll_pos <= 0){
fixDatatableHeaderTopPosition();
}else{
let margin = yPositionOfScrollBody; // Adjust it to your needs
let cur_pos = $('.dataTables_scrollHeadInner').position();
let header_pos = cur_pos.top;
if (scroll_pos < margin){
header_pos = margin - scroll_pos;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
});
}
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: true,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
CSS
/* data table - scroll and fixed header */
table.dataTable.fixedHeader-floating {
display: none !important; /*Hide the fixedHeader since we dont need it*/
}
.dataTables_scrollHeadInner{
margin-left: 0px;
width: 100% !important;
position: fixed;
display: block;
overflow: hidden;
/*margin-right: 30px;*/
background: white;
z-index: 1;
}
.dataTables_scrollBody{
padding-top: 2.5em;
}
div.dataTables_scrollHead table.dataTable {
padding-right: 0;
}
Update 1 - sort issue fix
use fixedHeader: false
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: false,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
You can change 'left' but saving initial value first:
var initLeft = 0;
$(".dataTables_scrollBody").scroll(function () {
if ($(".fixedHeader-floating").is(":visible")) {
if (initLeft == 0)
initLeft = $(".fixedHeader-floating").position().left;
$(".fixedHeader-floating").css("left", $(this).scrollLeft() * (-1) + initLeft);
}
});
$(document).ready(function() {
var table = $('#example').DataTable({
searching: true,
paging: true,
ordering: true,
info: false,
scrollY: 400,
dom: 'Blfrtip',
});
} );

How to print a custom grid app?

I have added a print button and a PrintDialog to my custom app and now need to write a print function.
Do I need to open up a new window and build a table containing the grid data formatted with css styles fitted to A4 paper size or is there something built into Rally that I can use?
I am new to Rally and Ext JS, so any advice would be appreciated!
Ext.create('Rally.ui.dialog.PrintDialog', {
height: 250,
autoShow: true,
autoCenter: false,
shouldShowFormatOptions: false,
defaultTitle: 'Book Of Work Report',
listeners: {
print: function(event) {
//How do I print a grid?
},
To print the grid, I created a Rally.data.WsapiDataStore and loaded the store into an array. I passed this array(StoreData) to the below function which opens a print window displaying the grid in a table.
_printDetails: function(Storedata) {
var myData = Storedata;
var htmlTable ='<table>';
htmlTable +='<width="100%">';
var r,c;
for(r= 0 ; r<myData.length; r++){
htmlTable+= '<tr>';
for(c = 0 ; c<myData[0].length; c++){
htmlTable+='<td>'+myData[r][c]+'</td>';
}
htmlTable+='</tr>';
}
htmlTable+='</table>';
var cssTable = '<style type="text/css">';
cssTable +='table {border-collapse:collapse;...}';
cssTable +='th {color:#080808;border-bottom-style: solid; ...}';
cssTable +='tr {color:#000000; border-bottom-style: solid; ..}';
cssTable +='td {padding:3px 4px; text-align:left; vertical-align:top;}';
cssTable +='#filter {text-align:left; ...}';
cssTable += '</style>';
var printwindow=window.open('', '', 'width=1000,height=500');
var myDate = new Date;
printwindow.document.write('<div id="todayDate">' + Ext.Date.format(myDate,'F j, Y, g:i a') + '</div>');
printwindow.document.write('<div id="header">Book Of Work Report</div>');
printwindow.document.write('<div id="filter"><p>' + this._GetFilterString() + '</p></div>');
printwindow.document.write(htmlTable);
printwindow.document.write(cssTable);
printwindow.document.close();
printwindow.focus();
printwindow.print();
printwindow.close();
},
Your inclination is correct - the best option here is to open a window that you populate with table output/grid content and apply whatever CSS formatting you prefer. Rally doesn't have any server-side printing output functionality (at least that is exposed to AppSDK2).