Repository: myremylar/pygame_gui
Issue: changing items in a disabled UISelectionList partly re-enables the element

**Describe the bug**
Changing items in a disabled UISelectionList partly re-enables the element, leaving it in an indeterminate state.

**To Reproduce**
Steps to reproduce the behaviour:

Easy MRE using `pygame_gui_examples.general_ui_test_app.py`

1. Change line 269 and those immediately following to:

```
        s = UISelectionList(pygame.Rect(10, 50, 174, 200),
                        item_list=['Item 1',
                                   'Item 2',
                                   'Item 3',
                                   'Item 4',
                                   'Item 5'
                                   ],
                        manager=self.ui_manager,
                        container=self.panel,
                        allow_multi_select=True)
        s.disable()
        s.add_items(["new"])
```

2. Run the program and note that the whole selection list is _not_ disabled, despite having been explicitly set so.
3. Click the "Disable" button in the GUI bottom right and notice that it no longer properly toggles the enabled state of the selection items (until it's pressed a couple of times)

**Expected behaviour**
Adding or removing items should lead to new sub-elements that inherit the top-level `is_enabled` state.

**Platform and software (please complete the following information):**
 - Mac (fully up to date OS)
 - Pygame GUI version: 0.6.9
 - Pygame version: 2.3.0


Changed files:
pygame_gui/elements/ui_text_box.py, pygame_gui/elements/ui_tool_tip.py, pygame_gui/elements/ui_button.py, pygame_gui/core/drawable_shapes/drawable_shape.py, pygame_gui/elements/ui_horizontal_slider.py, pygame_gui/elements/ui_window.py, pygame_gui/core/drawable_shapes/rect_drawable_shape.py, pygame_gui/elements/ui_selection_list.py, pygame_gui/elements/ui_label.py, pygame_gui/elements/ui_horizontal_scroll_bar.py, pygame_gui/elements/ui_panel.py, pygame_gui/elements/ui_vertical_scroll_bar.py, pygame_gui/windows/ui_colour_picker_dialog.py, pygame_gui/core/ui_container.py, pygame_gui/core/drawable_shapes/rounded_rect_drawable_shape.py, pygame_gui/core/ui_element.py, pygame_gui/elements/ui_scrolling_container.py, pygame_gui/elements/ui_image.py, pygame_gui/elements/ui_drop_down_menu.py, pygame_gui/core/drawable_shapes/ellipse_drawable_shape.py

diff --git a/pygame_gui/core/drawable_shapes/drawable_shape.py b/pygame_gui/core/drawable_shapes/drawable_shape.py
index 4bdc17f..722183c 100644
--- a/pygame_gui/core/drawable_shapes/drawable_shape.py
+++ b/pygame_gui/core/drawable_shapes/drawable_shape.py
@@ -344,14 +344,14 @@ class DrawableShape:
         self.should_trigger_full_rebuild = False
         self.full_rebuild_countdown = self.time_until_full_rebuild_after_changing_size
 
-    def redraw_all_states(self):
+    def redraw_all_states(self, force_full_redraw: bool = False):
         """
         Starts the redrawing process for all states of this shape that auto pre-generate.
         Redrawing is done one state at a time so will take a few loops of the game to
         complete if this shape has many states.
         """
         self.states_to_redraw_queue = deque([state_id for state_id, state in self.states.items()
-                                             if state.should_auto_pregen])
+                                             if (state.should_auto_pregen or force_full_redraw)])
         initial_state = self.states_to_redraw_queue.popleft()
         self.redraw_state(initial_state)
 
@@ -595,7 +595,10 @@ class DrawableShape:
         """
         self.theming['text'] = text
         self.build_text_layout()
-        self.redraw_all_states()
+        if 'disabled' in self.states and self.active_state == self.states['disabled']:
+            self.redraw_all_states(force_full_redraw=True)
+        else:
+            self.redraw_all_states()
 
     def set_text_alpha(self, alpha: int):
         """
diff --git a/pygame_gui/core/drawable_shapes/ellipse_drawable_shape.py b/pygame_gui/core/drawable_shapes/ellipse_drawable_shape.py
index 1b2b2c6..0107ec9 100644
--- a/pygame_gui/core/drawable_shapes/ellipse_drawable_shape.py
+++ b/pygame_gui/core/drawable_shapes/ellipse_drawable_shape.py
@@ -82,7 +82,10 @@ class EllipseDrawableShape(DrawableShape):
                                             self.border_width + self.shadow_width),
                                            (self.click_area_shape.width - (2 * self.border_width),
                                             self.click_area_shape.height - (2 * self.border_width)))
-        self.redraw_all_states()
+        if 'disabled' in self.states and self.active_state == self.states['disabled']:
+            self.redraw_all_states(force_full_redraw=True)
+        else:
+            self.redraw_all_states()
 
     def collide_point(self, point: Union[pygame.math.Vector2,
                                          Tuple[int, int],
diff --git a/pygame_gui/core/drawable_shapes/rect_drawable_shape.py b/pygame_gui/core/drawable_shapes/rect_drawable_shape.py
index 41398e6..3b51e94 100644
--- a/pygame_gui/core/drawable_shapes/rect_drawable_shape.py
+++ b/pygame_gui/core/drawable_shapes/rect_drawable_shape.py
@@ -92,7 +92,10 @@ class RectDrawableShape(DrawableShape):
                                             self.border_width + self.shadow_width),
                                            (self.click_area_shape.width - (2 * self.border_width),
                                             self.click_area_shape.height - (2 * self.border_width)))
-        self.redraw_all_states()
+        if 'disabled' in self.states and self.active_state == self.states['disabled']:
+            self.redraw_all_states(force_full_redraw=True)
+        else:
+            self.redraw_all_states()
 
     def collide_point(self, point: Union[pygame.math.Vector2,
                                          Tuple[int, int],
diff --git a/pygame_gui/core/drawable_shapes/rounded_rect_drawable_shape.py b/pygame_gui/core/drawable_shapes/rounded_rect_drawable_shape.py
index 3c584ae..8085b4a 100644
--- a/pygame_gui/core/drawable_shapes/rounded_rect_drawable_shape.py
+++ b/pygame_gui/core/drawable_shapes/rounded_rect_drawable_shape.py
@@ -150,7 +150,10 @@ class RoundedRectangleShape(DrawableShape):
                                             self.border_width + self.shadow_width),
                                            (self.click_area_shape.width - (2 * self.border_width),
                                             self.click_area_shape.height - (2 * self.border_width)))
-        self.redraw_all_states()
+        if 'disabled' in self.states and self.active_state == self.states['disabled']:
+            self.redraw_all_states(force_full_redraw=True)
+        else:
+            self.redraw_all_states()
 
     def collide_point(self, point: Union[pygame.math.Vector2,
                                          Tuple[int, int],
diff --git a/pygame_gui/core/ui_container.py b/pygame_gui/core/ui_container.py
index c43a549..f0d1cad 100644
--- a/pygame_gui/core/ui_container.py
+++ b/pygame_gui/core/ui_container.py
@@ -93,6 +93,11 @@ class UIContainer(UIElement, IUIContainerInterface, IContainerLikeInterface):
         element.change_layer(self._layer + element.get_starting_height())
         self.elements.append(element)
         self.calc_add_element_changes_thickness(element)
+        if not self.is_enabled:
+            element.disable()
+        if not self.visible:
+            if hasattr(element, 'hide'):
+                element.hide()
 
     def remove_element(self, element: IUIElementInterface):
         """
diff --git a/pygame_gui/core/ui_element.py b/pygame_gui/core/ui_element.py
index 6ba9eae..cf01eb9 100644
--- a/pygame_gui/core/ui_element.py
+++ b/pygame_gui/core/ui_element.py
@@ -148,6 +148,8 @@ class UIElement(GUISprite, IUIElementInterface):
         self.border_width = None  # type: Union[None, int]
         self.shape_corner_radius = None  # type: Union[None, int]
 
+        self.tool_tip = None
+
         self._setup_container(container)
 
         self.dirty = 1
diff --git a/pygame_gui/elements/ui_button.py b/pygame_gui/elements/ui_button.py
index bc46661..1659ec1 100644
--- a/pygame_gui/elements/ui_button.py
+++ b/pygame_gui/elements/ui_button.py
@@ -89,7 +89,6 @@ class UIButton(UIElement):
         self.tool_tip_text_kwargs = {}
         if tool_tip_text_kwargs is not None:
             self.tool_tip_text_kwargs = tool_tip_text_kwargs
-        self.tool_tip = None
         self.tool_tip_object_id = tool_tip_object_id
         self.ui_root_container = self.ui_manager.get_root_container()
 
@@ -282,7 +281,8 @@ class UIButton(UIElement):
         Called when we leave the hover state. Resets the colours and images to normal and kills any
         tooltip that was created while we were hovering the button.
         """
-        self.drawable_shape.set_active_state(self._get_appropriate_state_name())
+        if self.drawable_shape is not None:
+            self.drawable_shape.set_active_state(self._get_appropriate_state_name())
         if self.tool_tip is not None:
             self.tool_tip.kill()
             self.tool_tip = None
@@ -430,7 +430,8 @@ class UIButton(UIElement):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.drawable_shape.set_active_state('disabled')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
 
             # clear other button state
             self.held = False
@@ -444,7 +445,8 @@ class UIButton(UIElement):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.drawable_shape.set_active_state('normal')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('normal')
 
     def _set_active(self):
         """
@@ -768,6 +770,10 @@ class UIButton(UIElement):
                                                         ['normal', 'hovered', 'disabled',
                                                          'selected', 'active'], self.ui_manager)
 
+        if not self.is_enabled:
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
+
         self.on_fresh_drawable_shape_ready()
 
         self._on_contents_changed()
diff --git a/pygame_gui/elements/ui_drop_down_menu.py b/pygame_gui/elements/ui_drop_down_menu.py
index 9ad8314..c30bd48 100644
--- a/pygame_gui/elements/ui_drop_down_menu.py
+++ b/pygame_gui/elements/ui_drop_down_menu.py
@@ -418,19 +418,23 @@ class UIClosedDropDownState:
         """
         Disables the closed state so that it is no longer interactive.
         """
-        self.selected_option_button.disable()
+        if self.selected_option_button is not None:
+            self.selected_option_button.disable()
         if self.open_button is not None:
             self.open_button.disable()
-        self.drop_down_menu_ui.drawable_shape.set_active_state('disabled')
+        if self.drop_down_menu_ui.drawable_shape is not None:
+            self.drop_down_menu_ui.drawable_shape.set_active_state('disabled')
 
     def enable(self):
         """
         Re-enables the closed state so we can once again interact with it.
         """
-        self.selected_option_button.enable()
+        if self.selected_option_button is not None:
+            self.selected_option_button.enable()
         if self.open_button is not None:
             self.open_button.enable()
-        self.drop_down_menu_ui.drawable_shape.set_active_state('normal')
+        if self.drop_down_menu_ui.drawable_shape is not None:
+            self.drop_down_menu_ui.drawable_shape.set_active_state('normal')
 
     def rebuild(self):
         """
@@ -916,12 +920,15 @@ class UIDropDownMenu(UIContainer):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Sets the dimensions of this drop down, updating all subordinate button
         elements at the same time.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
@@ -941,12 +948,13 @@ class UIDropDownMenu(UIContainer):
         if self.is_enabled:
             self.is_enabled = False
             # switch back to the closed state if we are in the expanded state
-            if self.current_state is self.menu_states['expanded']:
-                self.current_state.finish()
-                self.current_state = self.menu_states['closed']
-                self.current_state.selected_option = self.selected_option
-                self.current_state.start()
-            self.current_state.disable()
+            if self.current_state is not None:
+                if self.current_state is self.menu_states['expanded']:
+                    self.current_state.finish()
+                    self.current_state = self.menu_states['closed']
+                    self.current_state.selected_option = self.selected_option
+                    self.current_state.start()
+                self.current_state.disable()
 
     def enable(self):
         """
@@ -954,7 +962,8 @@ class UIDropDownMenu(UIContainer):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.current_state.enable()
+            if self.current_state is not None:
+                self.current_state.enable()
 
     def show(self):
         """
diff --git a/pygame_gui/elements/ui_horizontal_scroll_bar.py b/pygame_gui/elements/ui_horizontal_scroll_bar.py
index 57a1675..9978f80 100644
--- a/pygame_gui/elements/ui_horizontal_scroll_bar.py
+++ b/pygame_gui/elements/ui_horizontal_scroll_bar.py
@@ -39,6 +39,10 @@ class UIHorizontalScrollBar(UIElement):
                  anchors: Optional[Dict[str, Union[str, UIElement]]] = None,
                  visible: int = 1):
 
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor
+        self.button_container = None
+
         super().__init__(relative_rect, manager, container,
                          layer_thickness=2,
                          starting_height=1,
@@ -509,11 +513,14 @@ class UIHorizontalScrollBar(UIElement):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of an element.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
@@ -547,9 +554,11 @@ class UIHorizontalScrollBar(UIElement):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.button_container.disable()
+            if self.button_container is not None:
+                self.button_container.disable()
 
-            self.drawable_shape.set_active_state('disabled')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
 
     def enable(self):
         """
@@ -557,9 +566,11 @@ class UIHorizontalScrollBar(UIElement):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.button_container.enable()
+            if self.button_container is not None:
+                self.button_container.enable()
 
-            self.drawable_shape.set_active_state('normal')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('normal')
 
     def show(self):
         """
@@ -576,5 +587,5 @@ class UIHorizontalScrollBar(UIElement):
         will propagate and hide all the buttons.
         """
         super().hide()
-
-        self.button_container.hide()
+        if self.button_container is not None:
+            self.button_container.hide()
diff --git a/pygame_gui/elements/ui_horizontal_slider.py b/pygame_gui/elements/ui_horizontal_slider.py
index c8ec817..482607f 100644
--- a/pygame_gui/elements/ui_horizontal_slider.py
+++ b/pygame_gui/elements/ui_horizontal_slider.py
@@ -45,6 +45,8 @@ class UIHorizontalSlider(UIElement):
                  click_increment: Union[float, int] = 1
                  ):
 
+        self.sliding_button = None
+        self.button_container = None
         super().__init__(relative_rect, manager, container,
                          layer_thickness=2,
                          starting_height=1,
@@ -491,11 +493,14 @@ class UIHorizontalSlider(UIElement):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of an element.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
@@ -525,12 +530,14 @@ class UIHorizontalSlider(UIElement):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.sliding_button.disable()
+            if self.sliding_button is not None:
+                self.sliding_button.disable()
             if self.left_button:
                 self.left_button.disable()
             if self.right_button:
                 self.right_button.disable()
-            self.drawable_shape.set_active_state('disabled')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
 
     def enable(self):
         """
@@ -538,12 +545,14 @@ class UIHorizontalSlider(UIElement):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.sliding_button.enable()
+            if self.sliding_button is not None:
+                self.sliding_button.enable()
             if self.left_button:
                 self.left_button.enable()
             if self.right_button:
                 self.right_button.enable()
-            self.drawable_shape.set_active_state('normal')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('normal')
 
     def show(self):
         """
@@ -563,6 +572,7 @@ class UIHorizontalSlider(UIElement):
         """
         super().hide()
 
-        self.sliding_button.hide()
+        if self.sliding_button is not None:
+            self.sliding_button.hide()
         if self.button_container is not None:
             self.button_container.hide()
diff --git a/pygame_gui/elements/ui_image.py b/pygame_gui/elements/ui_image.py
index aca54bc..06b5fba 100644
--- a/pygame_gui/elements/ui_image.py
+++ b/pygame_gui/elements/ui_image.py
@@ -55,11 +55,14 @@ class UIImage(UIElement):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Set the dimensions of this image, scaling the image surface to match.
 
         :param dimensions: The new dimensions of the image.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
diff --git a/pygame_gui/elements/ui_label.py b/pygame_gui/elements/ui_label.py
index 86c3a4f..d134b28 100644
--- a/pygame_gui/elements/ui_label.py
+++ b/pygame_gui/elements/ui_label.py
@@ -270,7 +270,8 @@ class UILabel(UIElement, IUITextOwnerInterface):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.drawable_shape.set_active_state('disabled')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
 
     def enable(self):
         """
@@ -278,7 +279,8 @@ class UILabel(UIElement, IUITextOwnerInterface):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.drawable_shape.set_active_state('normal')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('normal')
 
     def on_locale_changed(self):
         font = self.ui_theme.get_font(self.combined_element_ids)
diff --git a/pygame_gui/elements/ui_panel.py b/pygame_gui/elements/ui_panel.py
index dcd216c..03eb2b2 100644
--- a/pygame_gui/elements/ui_panel.py
+++ b/pygame_gui/elements/ui_panel.py
@@ -156,12 +156,15 @@ class UIPanel(UIElement, IContainerLikeInterface):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Set the size of this panel and then re-sizes and shifts the contents of the panel container
         to fit the new size.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         # Don't use a basic gate on this set dimensions method because the container may be a
@@ -280,7 +283,8 @@ class UIPanel(UIElement, IContainerLikeInterface):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.panel_container.disable()
+            if self.panel_container is not None:
+                self.panel_container.disable()
 
     def enable(self):
         """
@@ -288,7 +292,8 @@ class UIPanel(UIElement, IContainerLikeInterface):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.panel_container.enable()
+            if self.panel_container is not None:
+                self.panel_container.enable()
 
     def show(self):
         """
diff --git a/pygame_gui/elements/ui_scrolling_container.py b/pygame_gui/elements/ui_scrolling_container.py
index c2bf93b..c0f43c2 100644
--- a/pygame_gui/elements/ui_scrolling_container.py
+++ b/pygame_gui/elements/ui_scrolling_container.py
@@ -160,7 +160,8 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of an element.
 
@@ -168,6 +169,8 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         may make a mess of them.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
@@ -416,7 +419,8 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self._root_container.disable()
+            if self._root_container is not None:
+                self._root_container.disable()
 
     def enable(self):
         """
@@ -424,7 +428,8 @@ class UIScrollingContainer(UIElement, IContainerLikeInterface):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self._root_container.enable()
+            if self._root_container is not None:
+                self._root_container.enable()
 
     def show(self):
         """
diff --git a/pygame_gui/elements/ui_selection_list.py b/pygame_gui/elements/ui_selection_list.py
index 923ad60..e61d94a 100644
--- a/pygame_gui/elements/ui_selection_list.py
+++ b/pygame_gui/elements/ui_selection_list.py
@@ -65,7 +65,9 @@ class UISelectionList(UIElement):
                     List[str], List[Tuple[str, str]]  # Multi-selection lists
                     ]] = None,
                  ):
-
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor
+        self.list_and_scroll_bar_container = None
         super().__init__(relative_rect,
                          manager,
                          container,
@@ -488,12 +490,15 @@ class UISelectionList(UIElement):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Set the size of this panel and then resizes and shifts the contents of the panel container
         to fit the new size.
 
+
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: clamp these dimensions to the size of the element's container.
 
         """
         # Don't use a basic gate on this set dimensions method because the container may be a
@@ -680,11 +685,13 @@ class UISelectionList(UIElement):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.list_and_scroll_bar_container.disable()
+            if self.list_and_scroll_bar_container is not None:
+                self.list_and_scroll_bar_container.disable()
 
             # clear selections
-            for item in self.item_list:
-                item['selected'] = False
+            if self.item_list is not None:
+                for item in self.item_list:
+                    item['selected'] = False
 
     def enable(self):
         """
@@ -692,7 +699,8 @@ class UISelectionList(UIElement):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.list_and_scroll_bar_container.enable()
+            if self.list_and_scroll_bar_container is not None:
+                self.list_and_scroll_bar_container.enable()
 
     def show(self):
         """
@@ -713,5 +721,5 @@ class UISelectionList(UIElement):
         there is no need to call their hide() methods separately.
         """
         super().hide()
-
-        self.list_and_scroll_bar_container.hide()
+        if self.list_and_scroll_bar_container is not None:
+            self.list_and_scroll_bar_container.hide()
diff --git a/pygame_gui/elements/ui_text_box.py b/pygame_gui/elements/ui_text_box.py
index 04c4c1e..c58d540 100644
--- a/pygame_gui/elements/ui_text_box.py
+++ b/pygame_gui/elements/ui_text_box.py
@@ -98,6 +98,9 @@ class UITextBox(UIElement, IUITextOwnerInterface):
                  allow_split_dashes: bool = True,
                  plain_text_display_only: bool = False,
                  should_html_unescape_input_text: bool = False):
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor
+        self.scroll_bar = None
         relative_rect.height = -1 if wrap_to_height else relative_rect.height
         super().__init__(relative_rect, manager, container,
                          starting_height=starting_height,
@@ -129,7 +132,7 @@ class UITextBox(UIElement, IUITextOwnerInterface):
 
         self.active_text_effect = None  # type: Optional[TextEffect]
         self.active_text_chunk_effects = []
-        self.scroll_bar = None
+
         self.scroll_bar_width = 20
 
         self.border_width = None
@@ -497,11 +500,14 @@ class UITextBox(UIElement, IUITextOwnerInterface):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of a text box.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         self.relative_rect.width = int(dimensions[0])
diff --git a/pygame_gui/elements/ui_tool_tip.py b/pygame_gui/elements/ui_tool_tip.py
index 714d772..0bc2b89 100644
--- a/pygame_gui/elements/ui_tool_tip.py
+++ b/pygame_gui/elements/ui_tool_tip.py
@@ -192,11 +192,14 @@ class UITooltip(UIElement, IUITooltipInterface):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Directly sets the dimensions of this tool tip. This will overwrite the normal theming.
 
-        :param dimensions: The new dimensions to set
+        :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         self.rect_width = dimensions[0]
diff --git a/pygame_gui/elements/ui_vertical_scroll_bar.py b/pygame_gui/elements/ui_vertical_scroll_bar.py
index c11c96b..4626635 100644
--- a/pygame_gui/elements/ui_vertical_scroll_bar.py
+++ b/pygame_gui/elements/ui_vertical_scroll_bar.py
@@ -38,6 +38,9 @@ class UIVerticalScrollBar(UIElement):
                  object_id: Optional[Union[ObjectID, str]] = None,
                  anchors: Optional[Dict[str, Union[str, UIElement]]] = None,
                  visible: int = 1):
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor
+        self.button_container = None
 
         super().__init__(relative_rect, manager, container,
                          layer_thickness=2,
@@ -86,8 +89,6 @@ class UIVerticalScrollBar(UIElement):
         self.sliding_button = None
         self.enable_arrow_buttons = True
 
-        self.button_container = None
-
         self.rebuild_from_changed_theme_data()
 
         scroll_bar_height = max(5, int(self.scrollable_height * self.visible_percentage))
@@ -501,11 +502,14 @@ class UIVerticalScrollBar(UIElement):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Method to directly set the dimensions of an element.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         super().set_dimensions(dimensions)
@@ -539,9 +543,11 @@ class UIVerticalScrollBar(UIElement):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self.button_container.disable()
+            if self.button_container is not None:
+                self.button_container.disable()
 
-            self.drawable_shape.set_active_state('disabled')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('disabled')
 
     def enable(self):
         """
@@ -549,9 +555,11 @@ class UIVerticalScrollBar(UIElement):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self.button_container.enable()
+            if self.button_container is not None:
+                self.button_container.enable()
 
-            self.drawable_shape.set_active_state('normal')
+            if self.drawable_shape is not None:
+                self.drawable_shape.set_active_state('normal')
 
     def show(self):
         """
@@ -568,5 +576,5 @@ class UIVerticalScrollBar(UIElement):
         propagate and hide all the buttons.
         """
         super().hide()
-
-        self.button_container.hide()
+        if self.button_container is not None:
+            self.button_container.hide()
diff --git a/pygame_gui/elements/ui_window.py b/pygame_gui/elements/ui_window.py
index 0790bfb..54a4928 100644
--- a/pygame_gui/elements/ui_window.py
+++ b/pygame_gui/elements/ui_window.py
@@ -107,12 +107,15 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
 
     def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                                Tuple[int, int],
-                                               Tuple[float, float]]):
+                                               Tuple[float, float]],
+                       clamp_to_container: bool = False):
         """
         Set the size of this window and then re-sizes and shifts the contents of the windows
         container to fit the new size.
 
         :param dimensions: The new dimensions to set.
+        :param clamp_to_container: Whether we should clamp the dimensions to the
+                                   dimensions of the container or not.
 
         """
         # Don't use a basic gate on this set dimensions method because the container may be a
@@ -696,7 +699,8 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
         """
         if self.is_enabled:
             self.is_enabled = False
-            self._window_root_container.disable()
+            if self._window_root_container is not None:
+                self._window_root_container.disable()
 
     def enable(self):
         """
@@ -704,7 +708,8 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
         """
         if not self.is_enabled:
             self.is_enabled = True
-            self._window_root_container.enable()
+            if self._window_root_container is not None:
+                self._window_root_container.enable()
 
     def show(self):
         """
@@ -712,8 +717,8 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
         propagate and show all the children.
         """
         super().show()
-
-        self._window_root_container.show()
+        if self._window_root_container is not None:
+            self._window_root_container.show()
 
     def hide(self):
         """
@@ -721,8 +726,8 @@ class UIWindow(UIElement, IContainerLikeInterface, IWindowInterface):
         propagate and hide all the children.
         """
         super().hide()
-
-        self._window_root_container.hide()
+        if self._window_root_container is not None:
+            self._window_root_container.hide()
 
     def get_relative_mouse_pos(self):
         """
diff --git a/pygame_gui/windows/ui_colour_picker_dialog.py b/pygame_gui/windows/ui_colour_picker_dialog.py
index a79a09f..e95784a 100644
--- a/pygame_gui/windows/ui_colour_picker_dialog.py
+++ b/pygame_gui/windows/ui_colour_picker_dialog.py
@@ -52,6 +52,9 @@ class UIColourChannelEditor(UIElement):
                  object_id: Optional[Union[ObjectID, str]] = None,
                  anchors:  Optional[Dict[str, Union[str, UIElement]]] = None,
                  visible: int = 1):
+        # Need to move some declarations early as they are indirectly referenced via the ui element
+        # constructor when hiding on creation
+        self.element_container = None
 
         super().__init__(relative_rect,
                          manager,
@@ -289,8 +292,8 @@ class UIColourChannelEditor(UIElement):
         - which will propagate to the sub-elements - label, entry and slider.
         """
         super().hide()
-
-        self.element_container.hide()
+        if self.element_container is not None:
+            self.element_container.hide()
 
 
 class UIColourPickerDialog(UIWindow):