3

I want to redefine frame as frame + itemize. I've so far tried this -

\documentclass{beamer}
\newenvironment{myframe}{\begin{frame}\begin{itemize}}{\end{itemize}\end{frame}}

\begin{document}
\begin{myframe}
\item
\end{myframe}
\end{document}

What exactly is wrong in this and then how to correctly redefine this?

6

The beamer environment frame collects its body using the following technique: it reads and stores tokens following \begin{frame} looking for \end{frame} without expanding them except in one case (see below), but it will accept an \end{frame} as the end of the environment body being collected only if it has seen the same number of \begin{...} as \end{...} during this collection process (it does not try to match them). This is what \beamer@begin@stack is used for in \beamer@collect@@body from beamerbaseframe.sty.

In case the stack is empty (i.e., it has seen as many \end{...} as \begin{...} after \begin{frame}) and the following \end{...} is not an \end{frame}, it expands this \end hoping this will make \end{frame} appear.1 But this only happens under the condition I gave (as many \end{...} as \begin{...} after the initial \begin{frame} must have been seen).

In your example, the \begin{itemize} pushes one b (i.e., opens one level) to the \beamer@begin@stack stack. This b is popped when \end{myframe} is read. At this point, \beamer@collect@@body knows it has seen as many \begin{...} as \end{...} after the \begin{frame}. This is true but misleading, since if \end{myframe} were expanded, it would yield two pops. So, \beamer@begin@stack thinks that the next \end{...} must provide \end{frame}, possibly after expanding the \end... token (see footnote 1). Alas, this is incorrect; the next \end{...} is \end{document}. \enddocument is thus expanded, then \beamer@collect@@body continues to look for \end{frame} but of course never finds it, reaches the end of the file, which triggers the error:

Runaway argument?
\let \AtEndDocument \@firstofone \@enddocumenthook \@checkend {docume\ETC.
! File ended while scanning use of \beamer@collect@@body.
<inserted text> 
                \par 

So, basically, the problem is that \beamer@collect@@body can't keep a proper count of the \end{...} because your \end{myframe} hides two of these (\end{itemize}\end{frame}) and \beamer@collect@@body will not discover them via expansion of \endmyframe despite the presence of \end{myframe}, because it has seen more \begin{...} than \end{...} at the point where it sees this \end{myframe} (non-empty stack due to the \begin{itemize}).

The solutions require you not to hide \end{...} in macros that \beamer@collect@@body is not going to expand. One is using the environ package as in ferahfeza's answer, another is using \itemize and \enditemize as follows (neither of them affect the \beamer@begin@stack stack, therefore when \beamer@collect@@body sees \end{myframe}, the stack is empty, thus \endmyframe is expanded once, which makes the \end{frame} visible to the environment body scanning process):

\documentclass{beamer}

\newenvironment*{myframe}[1]
  {\begin{frame}{#1}%
   \begingroup\itemize}
  {\enditemize\endgroup
   \end{frame}}

\begin{document}

\begin{myframe}{Frame title}
\item An item
\item Another item
\end{myframe}

\end{document}

Screenshot


Footnote

  1. More precisely, what happens is the following (in beamer 2018/12/02 v3.55). If:

    • the \beamer@begin@stack stack is empty (as many \end{...} as \begin{...} have been seen after the \begin{frame} whose body is being collected) and
    • the next \end{...} is not an \end{frame}—let's say it is an \end{foobar},

    then \beamer@collect@@body substitutes the first-level expansion of \endfoobar\endgroup for this \end{foobar} in the collected material. This is similar to what expanding \end{foobar} would produce, though the latter would do a few more things (see source2e.pdf p. 272):

    • check that the replacement text of \@currenvir is foobar (this would fail in the beamer frame body collection process we are talking about, as \@currenvir is frame);
    • honor a previous \@endparenv command (used to suppress paragraph indentation at the beginning of text following some paragraph-making environments, unless said text is preceded by a blank line or by \par);
    • honor a previous \ignorespacesafterend command (used to make the next \end{...} ignore the spaces that may follow it).
  • 1
    Great! This doesn't require additional use of a package, so loved this one. Thank you so much! – Niranjan Aug 6 at 16:49
  • Is there any way to add the argument \begin{frame}{frame-title} in this code? With the environ package @ferahfeza managed to incorporate it too, but I think it would be better to do it without the help of packages. – Niranjan Aug 7 at 5:10
  • Sure, please see the last edit. If desired, one could add a star variant, optional arguments, arbitrary delimited arguments, whatever. – frougon Aug 7 at 6:56
4

Using package environ:

\documentclass{beamer}
\usepackage{environ}

\NewEnviron{myframe}[1][]{%
\begin{frame}{#1}
\begin{itemize}
\BODY
\end{itemize}
\end{frame}
}

\begin{document}
\begin{myframe}
\item First
\item Second
\end{myframe}
\end{document}

enter image description here

  • Thanks, this worked. I just have one question, why those arguments are necessary? I removed them and compiled, still got the same result. Is there any special function associated with them? – Niranjan Aug 6 at 16:10
  • (I mean \NewEnviron{myframe}[1][] these two arguments) – Niranjan Aug 6 at 16:11
  • It is for title of frame; \begin{myframe}[Title of frame]. – ferahfeza Aug 6 at 16:13
  • Oh, great. Thanks. One more doubt, what was wrong with my code? Why it did not work? – Niranjan Aug 6 at 16:15
  • I am sorry but that argument is not working for the frame-title. I'm getting errors. I added something like \begin{myframe}{Niranjan} – Niranjan Aug 6 at 16:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.