docs: def make_grid_frame(self, event):
          """
          Create a GridFrame for data type of the button that was clicked
          """
          if self.grid_frame:
              print('-I- You already have a grid frame open')
              pw.simple_warning("You already have a grid open")
              return
  
          try:
              grid_type = event.GetButtonObj().Name[:-4] # remove '_btn'
          except AttributeError:
              grid_type = self.FindWindowById(event.Id).Name[:-4] # remove ('_btn')
          wait = wx.BusyInfo('Making {} grid, please wait...'.format(grid_type))
          wx.SafeYield()
          # propagate site lat/lon info into locations if necessary
          if grid_type == 'locations' and 'sites' in self.contribution.tables:
              self.contribution.get_min_max_lat_lon()
              self.contribution.propagate_cols_up(['lithologies',
                                                   'geologic_classes'],
                                                  'locations', 'sites')
          # propagate lithologies/type/class information from sites to samples/specimens
          if grid_type in ['specimens', 'samples']:
              self.contribution.propagate_lithology_cols()
          # propagate average lat/lon info from samples table if
          # available in samples and missing in sites
          if grid_type == 'sites':
              self.contribution.propagate_average_up(cols=['lat', 'lon', 'height'],
                                             target_df_name='sites',
                                             source_df_name='samples')
              self.contribution.propagate_lithology_cols()
          # hide mainframe
          self.on_open_grid_frame()
          # choose appropriate size for grid
          if grid_type == 'measurements':
              huge = True
          else:
              huge = False
          # make grid frame
          self.grid_frame = grid_frame.GridFrame(self.contribution, self.WD,
                                                 grid_type, grid_type,
                                                 self.panel, huge=huge)
          row_string = ""
          # paint validations if appropriate
          if self.validation_mode:
              if grid_type in self.validation_mode:
                  if grid_type == 'measurements':
                      skip_cell_render = True
                  else:
                      skip_cell_render = False
                  self.grid_frame.toggle_help(None, "open")
                  row_problems = self.failing_items[grid_type]["rows"]
                  missing_columns = self.failing_items[grid_type]["missing_columns"]
                  missing_groups = self.failing_items[grid_type]["missing_groups"]
                  #all_cols = row_problems.columns
                  #col_nums = range(len(all_cols))
                  #col_pos = dict(zip(all_cols, col_nums))
                  if len(row_problems):
                      row_string = "Columns and rows with problem data have been highlighted in blue.\n"
                      if not skip_cell_render:
                          row_string += "Cells with problem data are highlighted according to the type of problem.\nRed: incorrect data\n"
                      row_string += "For full error messages, see {}.".format(grid_type + "_errors.txt")
                      # reset codes button to show error file instead
                      self.grid_frame.toggle_codes_btn.SetLabel("Show errors")
                      self.grid_frame.Bind(wx.EVT_BUTTON, self.grid_frame.show_errors,
                                           self.grid_frame.toggle_codes_btn)
                      # paint cells
                      for row in row_problems['num']:
                          self.grid_frame.grid.paint_invalid_row(row)
                          mask = row_problems["num"] == row
                          items = row_problems[mask]
                          cols = items.dropna(how="all", axis=1).drop(["num", "issues"], axis=1)
                          for col in cols:
                              pre, col_name = val_up3.extract_col_name(col)
                              col_ind = self.grid_frame.grid.col_labels.index(col_name)
                              self.grid_frame.grid.paint_invalid_cell(row, col_ind,
                                                                      skip_cell=skip_cell_render)
                  current_label = self.grid_frame.msg_text.GetLabel()
                  if len(missing_columns):
                      col_string = "You are missing the following required columns: {}\n\n".format(", ".join(missing_columns))
                  else:
                      col_string = ""
                  if len(missing_groups):
                      group_string = "You must have at least one column from each of the following groups: {}\n\n".format(", ".join(missing_groups))
                  else:
                      group_string = ""
                  #
                  add_text = """{}{}{}""".format(col_string, group_string, row_string)
                  self.grid_frame.msg_text.SetLabel(add_text)
          #self.on_finish_change_dir(self.change_dir_dialog)
          self.grid_frame.do_fit(None)
          del wait