diff --git a/src/simudator/gui/dialogs/module_state_dialog.py b/src/simudator/gui/dialogs/module_state_dialog.py
index 622112d17d1d88ac6ec09483cd4b13e0d4a95c00..fc768d7ade04490453e641d1f77fc3e2d5a76266 100644
--- a/src/simudator/gui/dialogs/module_state_dialog.py
+++ b/src/simudator/gui/dialogs/module_state_dialog.py
@@ -1,7 +1,6 @@
-from typing import Optional
-
 from qtpy.QtCore import Qt
 from qtpy.QtCore import Signal as pyqtSignal
+from qtpy.QtCore import Slot
 from qtpy.QtWidgets import (
     QComboBox,
     QDialog,
@@ -12,65 +11,97 @@ from qtpy.QtWidgets import (
 )
 
 from simudator.core import Module
+from simudator.gui.formatting import format_to_str, parse_str
+from simudator.gui.module_graphics_item.module_widget import ModuleWidget
 
 
 class ModuleStateDialog(QDialog):
-    accepted = pyqtSignal(str, str, str, name='okSignal')
+    """
+    A dialog for letting the user edit the state of a processor module.
+
+    Parameters
+    ----------
+    module : Module
+        The module to edit the state of.
+    module_widget : ModuleWidget
+        The corresponding module widget for the module. Used mainly for
+        formatting and parsing the values of the state variables of the module.
+    parent : QWidget | None
+        Optional parent widget of this dialog.
+    flags : Qt.WindowFlags | Qt.WindowType
+        Optional window flags for the window of the dialog.
+    """
+
+    state_edited = pyqtSignal()
 
     def __init__(
         self,
         module: Module,
-        parent: Optional['QWidget'] = None,
+        module_widget: ModuleWidget,
+        parent: QWidget | None = None,
         flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags(),
     ) -> None:
         super().__init__(parent, flags)
 
-        self.module = module
-        states = module.get_gui_state()
-
-        # Remove the 'name' state of the module from the drop down menu so
-        # that the user cannot e.g. edit or add a breakpoint of a module
-        states.pop('name')
+        self._module = module
+        self._state_vars = module_widget.get_state_vars()
 
-        # Set up the drop down menu for selecting a state of the module
-        # to perform an action to
-        self.stateSelectWidget = QComboBox()
-        self.stateSelectWidget.addItems(states.keys())
-        self.stateSelectWidget.activated.connect(self.updateState)
+        # Set up the drop down menu for selecting a state variable of the module
+        self._state_var_widget = QComboBox()
+        self._state_var_widget.addItems(self._state_vars.keys())
 
-        # Set up a text field for entering the value used for the e.g. editing
-        # the selected state
-        self.valueWidget = QLineEdit()
+        # Set up a text field for displaying and editing the value of the
+        # selected state variable
+        self._value_widget = QLineEdit()
 
         # Set up buttons
-        self.okButton = QPushButton('OK')
-        self.okButton.clicked.connect(self.signalOK)
-        self.cancelButton = QPushButton('Cancel')
-        self.cancelButton.clicked.connect(self.close)
+        self._accept_button = QPushButton('OK')
+        self._accept_button.clicked.connect(self.edit_state)
+        self._cancel_button = QPushButton('Cancel')
+        self._cancel_button.clicked.connect(self.close)
 
         # Set up the layout of the widget
-        self.HBoxLayout = QHBoxLayout()
-        self.HBoxLayout.addWidget(self.stateSelectWidget, 0)
-        self.HBoxLayout.addWidget(self.valueWidget, 1)
-        self.HBoxLayout.addWidget(self.okButton, 2)
-        self.HBoxLayout.addWidget(self.cancelButton, 3)
-        self.setLayout(self.HBoxLayout)
+        self._layout = QHBoxLayout()
+        self._layout.addWidget(self._state_var_widget, 0)
+        self._layout.addWidget(self._value_widget, 1)
+        self._layout.addWidget(self._accept_button, 2)
+        self._layout.addWidget(self._cancel_button, 3)
+        self.setLayout(self._layout)
 
         # Show the widget
-        self.updateState()
-        self.show()
-
-    def updateState(self) -> None:
-        selectedState = self.stateSelectWidget.currentText()
-        value = self.module.get_gui_state()[selectedState]
-        self.valueWidget.setText(str(value))
-
-    def signalOK(self) -> None:
-        module_name = self.module.get_state()['name']
-        selectedState = self.stateSelectWidget.currentText()
-        enteredValue = self.valueWidget.text()
-        self.accepted.emit(module_name, selectedState, enteredValue)
-        self.close()
-
-    def close(self) -> bool:
-        return super().close()
+        self.select_state_var()
+        self.exec()
+
+    @Slot()
+    def select_state_var(self) -> None:
+        """
+        Select a state variable of the module to edit.
+        """
+        selected_state = self._state_var_widget.currentText()
+        format_info = self._state_vars[selected_state]
+        value = format_to_str(format_info, self._module.get_state()[selected_state])
+        self._value_widget.setText(value)
+
+    @Slot()
+    def edit_state(self) -> None:
+        """
+        Parse the user input and edit the state of the module associated with
+        this dialog.
+
+        This triggers the `state_edited` signal if the parsing is
+        successful. Else, the user is given an error message.
+        """
+        state = self._module.get_state()
+        selected_state_var = self._state_var_widget.currentText()
+        try:
+            value = parse_str(
+                self._state_vars[selected_state_var], self._value_widget.text()
+            )
+            state[selected_state_var] = value
+            self._module.set_state(state)
+            self.state_edited.emit()
+            self.close()
+        except ValueError as e:
+            # TODO: error handling
+            pass
+
diff --git a/src/simudator/gui/formatting.py b/src/simudator/gui/formatting.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b164161b0aeb39f674430ccf29ca53243af477a
--- /dev/null
+++ b/src/simudator/gui/formatting.py
@@ -0,0 +1,119 @@
+import math
+from enum import Enum, auto
+from typing import Any
+
+
+class Format(Enum):
+    """
+    An Enum class containing the different valid formats for formatting and
+    parsing values of different data types.
+    """
+
+    Str = (auto(),)
+    Int = (auto(),)
+    Bin = (auto(),)
+    Hex = (auto(),)
+    Decimal = (auto(),)
+    DecimalSigned = (auto(),)
+
+
+class FormatInfo:
+    """
+    A class for neatly keeping data about the supported formats and selected
+    format for correctly displaying and parsing values of different data types.
+
+    Parameters
+    ----------
+    supported_format : list[Format]
+        List of formats that are supported for some variable.
+    selected_format : Format
+        The format that should be used on initialization.
+    **kwargs
+        Arguments passed to `format_to_str` and `parse_str`. Required by some
+        formats.
+
+    Attributes
+    ----------
+    supported_formats : list[Format]
+        List of format that are supported.
+    selected_format : Format
+        The selected format to use when formatting/parsing. Should be one of
+        the supported formats.
+    """
+
+    NUMERICAL_FORMATS = (
+        Format.Int,
+        Format.Bin,
+        Format.Hex,
+        Format.Decimal,
+        Format.DecimalSigned,
+    )
+    """
+    List of standard numerical formats for handling numbers. It is possible to
+    switch between these formats easily as they all require the same
+    keyword arguments.
+    """
+
+    __slots__ = ("selected_format", "supported_formats", "kwargs")
+
+    def __init__(
+        self, supported_formats: list[Format], selected_format: Format, **kwargs
+    ) -> None:
+        self.selected_format = selected_format
+        self.supported_formats = supported_formats
+        self.kwargs = kwargs
+
+
+def format_to_str(format_info: FormatInfo, value: Any) -> str:
+    """
+    Create a formatted string from a given value.
+
+    Parameters
+    ----------
+    format_info : FormatInfo
+        A class containing information about what format to use.
+    value : Any
+        An arbitrary value that is to be formatted to a string.
+
+    Returns
+    -------
+    The input value formatted to a string according to the input variable
+    format.
+    """
+    match format_info.selected_format:
+        case Format.Int:
+            pass
+        case Format.Str:
+            pass
+        case Format.Bin:
+            return f"{value:0{format_info.kwargs["bit_length"]}b}"
+        case Format.Hex:
+            return f"{value:0{math.ceil(format_info.kwargs["bit_length"]/4)}x}"
+        case Format.Decimal:
+            pass
+        case Format.DecimalSigned:
+            pass
+
+
+def parse_str(format_info: FormatInfo, value: str) -> Any:
+    """
+    Parse an input string formatted using the given format.
+
+    Parameters
+    ----------
+    format_info : FormatInfo
+        An instance containing information about the format used on the string.
+    value : str
+        A formatted string to parse into a value.
+
+    Returns
+    -------
+    The parsed value from the formatted string. The data type of the value
+    depends on the format.
+    """
+    match format_info.selected_format:
+        case Format.Int:
+            return int(value)
+        case Format.Hex:
+            return int(value, base=16)
+
diff --git a/src/simudator/gui/gui.py b/src/simudator/gui/gui.py
index 9b3fdc9cb05e293a70869cc1c078057c23668475..63c39edcea6018b47973b3e2fa9edb5997e1d0e6 100644
--- a/src/simudator/gui/gui.py
+++ b/src/simudator/gui/gui.py
@@ -2,7 +2,6 @@ import ast
 import sys
 
 from qtpy import QtCore, QtWidgets
-from qtpy.QtCore import Signal as pyqtSignal
 from qtpy.QtCore import Slot
 from qtpy.QtWidgets import (
     QAction,
@@ -19,6 +18,7 @@ from simudator.gui.cpu_graphics_scene import CpuGraphicsScene
 from simudator.gui.dialogs.lambda_breakpoint_dialog import LambdaBreakpointDialog
 from simudator.gui.menu_bar import MainMenuBar
 from simudator.gui.module_graphics_item.module_graphics_item import ModuleGraphicsItem
+from simudator.gui.module_graphics_item.module_widget import ModuleWidget
 from simudator.gui.pipeline import PipeLine
 from simudator.gui.processor_handler import ProcessorHandler
 from simudator.gui.signal_viewer import SignalViewer
@@ -182,12 +182,6 @@ class GUI(QMainWindow):
         if self._breakpoint_window is not None:
             self._breakpoint_window.update()
 
-    """
-    @Slot is used to explicitly mark a python method as a Qt slot
-    and specify a C++ signature for it, which is used most commonly
-    in order to select a particular overload.
-    """
-
     @Slot(str, str, str)
     def editModuleState(self, module_name, state, value) -> None:
         """
@@ -297,6 +291,20 @@ class GUI(QMainWindow):
         self.connectModuleActions(item.getActionSignals())
         self._processor_handler.changed.connect(item.update)
 
+    def add_module_widget(self, widget: ModuleWidget) -> None:
+        """
+        Add a module widget to the processor scene and connect its QT signals
+        and slots.
+
+        Parameters
+        ----------
+        widget : ModuleWidget
+        The module widget to add to the processor scene.
+        """
+        self._processor_handler.changed.connect(widget.update)
+        self._graphics_scene.addItem(widget)
+        widget.state_changed.connect(self._processor_handler.handle_module_change)
+
     def add_all_signals(self) -> None:
         """
         Add visual representations of all processor signals between all modules
diff --git a/src/simudator/gui/module_graphics_item/actions.py b/src/simudator/gui/module_graphics_item/actions.py
new file mode 100644
index 0000000000000000000000000000000000000000..1747bec59fd1cf99501b600788e452d18bd9797c
--- /dev/null
+++ b/src/simudator/gui/module_graphics_item/actions.py
@@ -0,0 +1,47 @@
+from enum import Enum, auto
+
+from PyQt5.QtWidgets import QAction
+
+from simudator.core.module import Module
+from simudator.gui.dialogs.module_state_dialog import ModuleStateDialog
+from simudator.gui.module_graphics_item.module_widget import ModuleWidget
+
+
+class ActionType(Enum):
+    """
+    An Enum class containing the different valid actions for interacting with
+    module widgets.
+    """
+
+    EditState = (auto(),)
+
+
+def edit_state_action(module: Module, module_widget: ModuleWidget) -> QAction:
+    """
+    Create a QAction for opening a state-edit dialog for a module widget.
+    The dialog triggers the state_changed signal of the module widget if the
+    user accepts the dialog.
+
+    Parameters
+    ----------
+    module : Module
+        The module that should be edited when triggering the created edit action.
+    module_widget : ModuleWidget
+        The module widget for the module that should be edited. Used for
+        displaying and parsing information correctly.
+
+    Returns
+    -------
+    QAction
+        A Qt action for opening a dialog for editing the state of a module.
+        Intended to be added as an action to the given module widget.
+    """
+    action = QAction("Edit state", module_widget)
+
+    def action_triggered():
+        dialog = ModuleStateDialog(module, module_widget)
+        dialog.state_edited.connect(module_widget.state_changed)
+
+    action.triggered.connect(action_triggered)
+    return action
+
diff --git a/src/simudator/gui/module_graphics_item/module_widget.py b/src/simudator/gui/module_graphics_item/module_widget.py
index b9cc6d81384e902cd7ed68cf9ee5f817530dcafe..b1a6a8b286ca0d317d9e03c5f6c47cb3d1d004d5 100644
--- a/src/simudator/gui/module_graphics_item/module_widget.py
+++ b/src/simudator/gui/module_graphics_item/module_widget.py
@@ -1,33 +1,60 @@
 from typing import Any
-from qtpy.QtCore import Property, QPointF, QRectF, Qt, Slot
-from qtpy.QtGui import QBrush, QColor, QFont, QFontMetrics, QPainter, QPainterPath, QPen
+
+from qtpy.QtCore import Property, QRectF, Qt
 from qtpy.QtCore import Signal as pyqtSignal
 from qtpy.QtCore import Slot
+from qtpy.QtGui import QBrush, QColor, QFont, QFontMetrics, QPainter, QPainterPath, QPen
 from qtpy.QtWidgets import (
+    QErrorMessage,
     QGraphicsItem,
+    QGraphicsSceneContextMenuEvent,
     QGraphicsWidget,
+    QMenu,
     QStyleOptionGraphicsItem,
     QWidget,
-    QAction,
 )
 
 from simudator.core.module import Module
+from simudator.gui.formatting import Format, FormatInfo, format_to_str
 
-class ModuleWidget(QGraphicsWidget):
-    DEFAULT_WIDTH = 50*3
-    DEFAULT_HEIGHT = 50*3
-
-    update = pyqtSignal()
 
-    def __init__(self, module: Module, state_vars: list[str], actions: list[QAction], formatters: dict[str, Any], parent: QGraphicsItem | None = None, flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags()) -> None:
+class ModuleWidget(QGraphicsWidget):
+    """
+    A general class for displaying an arbitrary processor module. The
+    appearance of a module can be customized by changing its properties and
+    by choosing which state variables to display and in what formats.
+
+    Parameters
+    ----------
+    module : Module
+        The processor module to display.
+    state_vars : dict[str, FormatInfo]
+        The state variables of the module to display and in what formats to
+        display them.
+    parent : QGraphicsItem | None
+        Optional parent item of this module widget.
+    flags : Qt.WindowFlags | Qt.WindowType
+        Optional window flags for the window of this widget.
+    """
+
+    DEFAULT_WIDTH = 50 * 3
+    DEFAULT_HEIGHT = 50 * 3
+
+    state_changed = pyqtSignal()
+
+    def __init__(
+        self,
+        module: Module,
+        state_vars: dict[str, FormatInfo],
+        parent: QGraphicsItem | None = None,
+        flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags(),
+    ) -> None:
         super().__init__(parent, flags)
         super().__init__(parent, flags)
         self.setFlag(QGraphicsItem.ItemIsMovable)
 
         self._module = module
         self._state_vars = state_vars
-        self._actions = actions
-        self._formatters = formatters
 
         # Default values for properties for appearance
         # TODO: Put these values in constants?
@@ -41,9 +68,11 @@ class ModuleWidget(QGraphicsWidget):
         width = self.DEFAULT_WIDTH
         height = self.DEFAULT_HEIGHT
         if len(self._state_vars) <= 1:
-           height = QFontMetrics(self._text_font).height() + 2*self._padding
+            height = QFontMetrics(self._text_font).height() + 2 * self._padding
         else:
-            height = (self._padding + QFontMetrics(self._text_font).height()) * (len(state_vars) + 1) + self._padding
+            height = (self._padding + QFontMetrics(self._text_font).height()) * (
+                len(state_vars) + 1
+            ) + self._padding
         self.resize(width, height)
 
     def paint(
@@ -72,24 +101,30 @@ class ModuleWidget(QGraphicsWidget):
         text_rect = self.shape().boundingRect()
 
         # Draw only name of the module if no state variables are to be shown
-        if len(self._state_vars) == 0:
+        if len(self._state_vars.keys()) == 0:
             painter.drawText(text_rect, text_flags, self._module.get_state()["name"])
 
         # Special case: only 1 state variable to show
-        elif len(self._state_vars) == 1:
-            value = self._module.get_state()[self._state_vars[0]]
-            painter.drawText(text_rect, text_flags, f"{self._module.get_state()["name"]}: {value}")
+        elif len(self._state_vars.keys()) == 1:
+            state_var = list(self._state_vars.keys())[0]
+            format_info = self._state_vars[state_var]
+            value = format_to_str(format_info, self._module.get_state()[state_var])
+            painter.drawText(
+                text_rect, text_flags, f"{self._module.get_state()["name"]}: {value}"
+            )
 
         # Draw the name of the module and each state variable with its value on one row each
         else:
-            text_rect = QRectF(0,  0, width, height/(len(self._state_vars)+1))
+            text_rect = QRectF(0, 0, width, height / (len(self._state_vars) + 1))
             painter.drawText(text_rect, text_flags, self._module.get_state()["name"])
 
             for i, state_var in enumerate(self._state_vars):
-                y_start = height / (len(self._state_vars)+1) * (i+1)
-                y_end = height / (len(self._state_vars) + 1 )
+                y_start = height / (len(self._state_vars) + 1) * (i + 1)
+                y_end = height / (len(self._state_vars) + 1)
                 text_rect = QRectF(0, y_start, width, y_end)
-                value = self._module.get_state()[state_var]
+
+                format_info = self._state_vars[state_var]
+                value = format_to_str(format_info, self._module.get_state()[state_var])
                 painter.drawText(text_rect, text_flags, f"{state_var}: {value}")
 
         painter.restore()
@@ -98,7 +133,9 @@ class ModuleWidget(QGraphicsWidget):
     def update(self, rect: QRectF | None = None):
         # This "override" is needed in order to decorate update() as a
         # pyqt slot
-        super().update()
+        if rect is None:
+            rect = self.boundingRect()
+        super().update(rect)
 
     def shape(self) -> QPainterPath:
         path = QPainterPath()
@@ -111,6 +148,60 @@ class ModuleWidget(QGraphicsWidget):
         margin = self.outline_width / 2
         return QRectF(0 - margin, 0 - margin, width + margin, height + margin)
 
+    @Slot(bool)
+    def show_ports(self, value: bool) -> None:
+        """
+        Toggle the visibility of the signal ports of the displayed module.
+
+        Parameters
+        ----------
+        value : bool
+            `True` to show all ports, `False` to hide them.
+        """
+        pass
+
+    def contextMenuEvent(self, event: 'QGraphicsSceneContextMenuEvent') -> None:
+        """
+        Show a context menu for this module, allowing the user to perform any
+        of the actions available to this module.
+        """
+        menu = QMenu()
+        menu.addActions(self.actions())
+        menu.exec_(event.screenPos())
+
+    def get_state_vars(self) -> dict[str, FormatInfo]:
+        """
+        Return a mapping from displayed state variables of the displayed
+        module to their respective format information.
+
+        Useful for viewing the supported formats and the selected format for a
+        state variable.
+
+        Returns
+        -------
+        dict[str, FormatInfo]
+            Mapping from state variable to FormatInfo instance.
+        """
+        return self._state_vars
+
+    @Slot()
+    def set_format(self, state_var: str, format: Format) -> None:
+        """
+        Set the format to be used when displaying and parsing a given state
+        variable of the displayed module.
+
+        Parameters
+        ----------
+        state_va : str
+            The state variable to set the formats of.
+        format : Format
+            The format to use for the value of the state variable. Should be
+            one of the supported formats in the FormatInfo instance for the
+            state variable.
+        """
+        self._state_vars[state_var].selected_format = format
+        self.update()
+
     def outline_width(self) -> float:
         """Return the outline pen width.
 
@@ -216,4 +307,5 @@ class ModuleWidget(QGraphicsWidget):
     outline = Property(QBrush, outline, set_outline)
     background = Property(QBrush, background, set_background)
     text_color = Property(QColor, text_color, set_text_color)
-    text_font = Property(QFont, text_font, set_text_font)
\ No newline at end of file
+    text_font = Property(QFont, text_font, set_text_font)
+
diff --git a/src/simudator/gui/processor_handler.py b/src/simudator/gui/processor_handler.py
index 9a9e9b3321f5fcb82c00be6bd77dbd362751ab53..e0ccb9ac0f5fe249540a5304ea10ad4cb149dff3 100644
--- a/src/simudator/gui/processor_handler.py
+++ b/src/simudator/gui/processor_handler.py
@@ -1,4 +1,3 @@
-from qtpy import QtCore
 from qtpy.QtCore import QObject, QThreadPool
 from qtpy.QtCore import Signal as pyqtSignal
 from qtpy.QtCore import Slot
@@ -21,12 +20,12 @@ class ProcessorHandler(QObject):
 
     running = pyqtSignal(bool)
     """
-    PyQT signal emitted when the processor has started or finished running. 
+    PyQT signal emitted when the processor has started or finished running.
     Emits ``True`` when it has started and ``False`` when it has finished.
     """
     cycle_changed = pyqtSignal(int)
     """
-    PyQT signal emitted when the current clock cycle count of the processor 
+    PyQT signal emitted when the current clock cycle count of the processor
     has changed. Emits the current clock cycle count as an ``int``.
     """
     changed = pyqtSignal()
@@ -105,7 +104,7 @@ class ProcessorHandler(QObject):
     @Slot(int)
     def step_asm_instructions(self, instructions: int):
         """
-        Run some numer of asm instructions.
+        Run some number of asm instructions.
 
         Parameters
         ----------
@@ -333,3 +332,12 @@ class ProcessorHandler(QObject):
         )
         if ok:
             self._update_delay = delay
+
+    @Slot()
+    def handle_module_change(self):
+        """
+        Handle propagating changes when the state of a module has been edited
+        by the user.
+        """
+        pass
+