import sys from PySide6.QtCore import Qt from PySide6.QtGui import QAction # WICHTIG: QAction wird für Menüeinträge gebraucht! from PySide6.QtWidgets import ( QApplication, QDialog, QDialogButtonBox, QFormLayout, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMainWindow, QScrollArea, QSizePolicy, QVBoxLayout, QWidget, ) # 1. Wir definieren unsere eigene klickbare "Zelle" class ClickableCell(QFrame): def __init__(self, text): super().__init__() # Qt Style Sheets (QSS) für das Design (Rahmen, Hintergrund, Hover) self.setStyleSheet(""" ClickableCell { background-color: white; border: 1px solid #e2e8f0; border-radius: 8px; } ClickableCell:hover { background-color: #eff6ff; border: 1px solid #60a5fa; } """) # Layout in der Zelle, um den Text zu zentrieren layout = QVBoxLayout(self) label = QLabel(text) label.setWordWrap(True) # Textumbruch erzwingen! label.setAlignment(Qt.AlignCenter) label.setStyleSheet("border: none; background: transparent;") layout.addWidget(label) # Klick-Event abfangen def mousePressEvent(self, event): if event.button() == Qt.LeftButton: print("Zelle wurde geklickt!") class HeaderCell(QLabel): def __init__(self, text): super().__init__(text) # Textausrichtung zentrieren self.setAlignment(Qt.AlignCenter) # Styling: Fetter Text, grauer Hintergrund (entspricht slate-200), leicht abgerundet self.setStyleSheet(""" HeaderCell { background-color: #e2e8f0; color: #475569; font-weight: bold; padding: 10px; border-radius: 6px; } """) class NewEntryDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Neuer Eintrag") self.setMinimumWidth(350) # Ein FormLayout richtet Labels und Eingabefelder automatisch sauber aus layout = QFormLayout(self) # Eingabefelder erstellen self.input_c1 = QLineEdit() self.input_c2 = QLineEdit() self.input_c3 = QLineEdit() self.input_c4 = QLineEdit() self.input_date = QLineEdit() # Felder zum Layout hinzufügen layout.addRow("Name:", self.input_c1) layout.addRow("Beschreibung:", self.input_c2) layout.addRow("Abteilung (optional):", self.input_c3) layout.addRow("Status:", self.input_c4) layout.addRow("Datum:", self.input_date) # Standard-Buttons (OK und Abbrechen) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) # Schließt Dialog und meldet "Erfolg" buttons.rejected.connect(self.reject) # Schließt Dialog und meldet "Abbruch" layout.addWidget(buttons) def get_data(self): # Liest die Textfelder aus und gibt sie als Dictionary zurück return { "c1": self.input_c1.text(), "c2": self.input_c2.text(), # Wenn das Feld leer ist, speichern wir None (für unseren Platzhalter-Effekt) "c3": self.input_c3.text() if self.input_c3.text().strip() else None, "c4": self.input_c4.text(), "date": self.input_date.text(), } # 2. Das Hauptfenster mit dem Grid-Layout class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Native PySide Tabelle") self.resize(1800, 200) # --- 1. DAS MENÜ ERSTELLEN --- self.create_menu() # --- 2. DAS ZENTRALE WIDGET --- # Da das QMainWindow den Rahmen vorgibt, brauchen wir ein Container-Widget für die Mitte central_widget = QWidget() self.setCentralWidget(central_widget) # Das Haupt-Layout des Fensters (Horizontal) outer_layout = QHBoxLayout(central_widget) # Ein Container-Widget für deine Tabelle/Grid container = QWidget() # 2. NEU: Dem Container sagen: "Dehne dich so weit aus, wie du darfst!" # container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # --- NEU: DER SCROLL-BEREICH --- scroll_area = QScrollArea() scroll_area.setWidgetResizable( True ) # WICHTIG: Erlaubt dem Grid im Inneren, sich an die Breite anzupassen scroll_area.setMinimumWidth(700) scroll_area.setMaximumWidth( 1500 ) # Die Breiten-Begrenzung wandert nun auf die ScrollArea scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Optional: Rahmen der ScrollArea entfernen, damit es "flacher" und moderner aussieht scroll_area.setFrameShape(QFrame.NoFrame) # --- WICHTIG: VERKNÜPFUNG --- # 1. Den Container in die ScrollArea stecken scroll_area.setWidget(container) # 3. Zentrierung durch "Stretches" (wie mx-auto) # Wir fügen links und rechts vom Container Platzhalter ein outer_layout.addStretch(1) outer_layout.addWidget(scroll_area, stretch=100) outer_layout.addStretch(1) # Optional: Damit der Container oben am Rand klebt outer_layout.setAlignment(Qt.AlignTop) # Wir geben dem Container ein vertikales Layout container_layout = QVBoxLayout(container) container_layout.setContentsMargins(0, 0, 0, 0) # Entfernt unnötige Ränder # Das Grid für unsere Tabelle # Das Grid-Layout kommt in den Container # self.grid = QGridLayout(container) # Das Grid wird nun OHNE direkten Container erstellt self.grid = QGridLayout() self.grid.setSpacing(10) # Entspricht gap-2 self.grid.setColumnStretch(0, 1) self.grid.setColumnStretch(1, 1) self.grid.setColumnStretch(2, 1) self.grid.setColumnStretch(3, 1) # 3. Wir fügen das Grid oben in das vertikale Layout ein container_layout.addLayout(self.grid) # 4. DER ZAUBERTRICK: Wir setzen eine Feder unter das Grid. # Diese Feder drückt das gesamte Grid nach oben und absorbiert den leeren Raum! container_layout.addStretch() # Zeilen-Zähler (0 ist für die Überschriften) self.current_row = 0 # Beispieldaten (Projekt Gamma hat kein 'c3') headers = ["Name", "Beschreibung", "Abteilung", "Status", "Datum"] for col_idx, title in enumerate(headers): self.grid.addWidget(HeaderCell(title), self.current_row, col_idx) self.current_row += 1 data = [ { "c1": "Projekt Alpha", "c2": "Alles komplett.", "c3": "Teamleitung", "c4": "Hoch", "date": "17.04.", }, { "c1": "Projekt Beta", "c2": "Abteilung fehlt.", "c4": "Wartend", "date": "21.04.", }, { "c1": "Projekt Gamma", "c2": "Alles komplett.", "c3": "Teamleitung", "c4": "Mittel", "date": "30.04.", }, { "c1": "Projekt Delta", "c2": "Abteilung fehlt.", "c4": "Wartend", "date": "05.05.", }, ] # Wir gehen die Datenliste mit enumerate durch, um den row_index für das Grid zu bekommen for entry in data: self.add_row_to_grid(entry) # --- HILFSMETHODE UM EINE ZEILE EINZUFÜGEN --- def add_row_to_grid(self, entry): row = self.current_row self.grid.addWidget(ClickableCell(entry.get("c1", "")), row, 0) self.grid.addWidget(ClickableCell(entry.get("c2", "")), row, 1) c3_value = entry.get("c3") if c3_value: self.grid.addWidget(ClickableCell(c3_value), row, 2) else: empty_box = QFrame() empty_box.setStyleSheet( "QFrame { background-color: #f8fafc; border: 2px dashed #e2e8f0; border-radius: 8px; }" ) self.grid.addWidget(empty_box, row, 2) self.grid.addWidget(ClickableCell(entry.get("c4", "")), row, 3) self.grid.addWidget(ClickableCell(entry.get("date", "")), row, 4) self.current_row += 1 # Zähler für den nächsten Eintrag erhöhen # --- MENÜ LOGIK --- def create_menu(self): menu_bar = self.menuBar() file_menu = menu_bar.addMenu("Datei") new_action = QAction("Neuer Eintrag...", self) new_action.setShortcut("Ctrl+N") # VERKNÜPFUNG: Wenn geklickt, rufe die Methode zum Öffnen des Dialogs auf new_action.triggered.connect(self.open_new_entry_dialog) file_menu.addAction(new_action) file_menu.addSeparator() exit_action = QAction("Beenden", self) exit_action.setShortcut("Ctrl+Q") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # 2. Hauptmenü-Punkt: "Hilfe" help_menu = menu_bar.addMenu("Hilfe") about_action = QAction("Über", self) help_menu.addAction(about_action) # --- DIALOG AUFRUFEN & DATEN ÜBERNEHMEN --- def open_new_entry_dialog(self): dialog = NewEntryDialog(self) # .exec() pausiert das Programm, bis der Dialog geschlossen wird. # Es gibt True zurück, wenn der Nutzer "OK" geklickt hat. if dialog.exec(): new_data = dialog.get_data() # Dictionary aus dem Dialog holen self.add_row_to_grid(new_data) # Ins Grid zeichnen if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())