为什么我不能在这个宏的 tikzpicture 里面使用 \newcommand?

为什么我不能在这个宏的 tikzpicture 里面使用 \newcommand?

考虑Ulrich Diez 的回答这篇文章:\pgfmathsetlengthmacro 与循环不兼容?

\documentclass[]{article}

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

% Problem ===================================
%\pgfmathsetlengthmacro\LeftWidth{width("\ImageLeft")}
% =======================================

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)
  \newpage
}

\end{document}

编译效果非常好。

它也适用于tikz。请参阅此工作 MWE:

\documentclass[]{article}

\usepackage{tikz}% 

答案1

您即将到达:

\newcommand在 -loop 中无关紧要,\foreach因为 -loop 的每次迭代都\foreach在其自己的组/本地范围内进行。迭代之后,\newcommand该迭代中执行的操作将随迭代发生的本地范围一起消失。

但是您需要将表示在循环内定义的宏的参数的哈希值加倍\foreach

原因是:

基本模式为\foreach

\foreach⟨comma-list or macro expanding to comma-list⟩{%
  ⟨Tokens to carry out repeatedly⟩%
}

在其他操作之下\foreach触发定义一个临时宏。\pgffor@body⟨Tokens to carry out repeatedly⟩

就像是。\def\pgffor@body{⟨Tokens to carry out repeatedly⟩}

\pgffor@body没有参数文本/不处理参数。但如果包含诸如或 之类的⟨Tokens to carry out repeatedly⟩内容,则 LaTeX 会假定这些内容旨在表示 的参数,并引发错误消息:#1#2\pgffor@body

 Illegal parameter number in definition of \pgffor@body.
<to be read again> 
                   1

因此,当⟨Tokens to carry out repeatedly⟩其本身包含宏定义时,这些宏定义的哈希值需要加倍 - 当在其他宏定义中嵌套宏定义时,始终需要使用内部宏定义将哈希值加倍。

我不知道\foreachpgfmanual.pdf 中是否记录了将内部宏定义哈希值加倍的必要性。如果没有,可能值得向 tikz/pgf 的作者/维护者提供一点提示。

但我认为这很少需要,因为通常你可以在循环外定义宏。

但有时我使用\foreach-variables 作为宏名称的一部分,这些宏将在以下\foreach方面进行定义:

\expandafter\newcommand\csname\foreachvariable CommonNamePart\endcsname[1]{...}
\global\expandafter\let\csname\foreachvariable CommonNamePart\expandafter\endcsname\csname\foreachvariable CommonNamePart\endcsname

以便它们在\foreach循环之外也可用。

这里与加倍哈希值的需求相关。

当遇到此类错误消息时Illegal parameter number in definition of...,最好检查宏定义中是否存在宏定义嵌套,以及是否正确完成了内部宏定义的哈希加倍。

\documentclass[]{article}

\usepackage{tikz}%

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)

  \begin{tikzpicture}%
  \filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};%
  %
  % DOUBLE THE HASHES IN THE DEFINITION OF \xRuler
  % DOUBLE THE HASHES IN THE DEFINITION OF \xRuler
  %
  \newcommand\xRuler[2]{%
   \filldraw[black] (##1,##2) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};
  }%
  \end{tikzpicture}%
  \newpage
}

\end{document}

意识到:

由于 的定义\xRuler不依赖于\foreach-variable-macros 的扩展,因此\xRuler可以在 之外进行定义\foreach,这将为 LaTeX 节省大量重复的定义工作:

\documentclass[]{article}

\usepackage{tikz}%

\usepackage[most]{tcolorbox}

% Define the command \NiceForEachElement to ensure error-message in case it is already defined.
% This way you can ensure to a certain degree that using `\niceelement` as Foreach-variable
% does not override something that alerady exists.
\newcommand\NiceForEachElement{}%

\newcommand\UseImageLeft[2]{%
% #1 preceding phrase "image-"
% #2 number of image
\IfFileExists{#1#2.jpg}%
  {\includegraphics[scale=0.2]{#1#2.jpg}}%
    {\includegraphics[scale=0.2]{example-image.jpg}}%
}

\newcommand*\ImageLeft[1]{\UseImageLeft{Image-}{#1}}

\newcommand\xRuler[2]{%
 \filldraw[black] (#1,#2) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};
}%

\begin{document}

% outside the loop the width of Image-7.jpg or example-image.jpg:

\pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{7}")}%
%Activate the following line in case you wish to see on screen/console what the definition of \LeftWidth looks like now:
%\show\LeftWidth

% inside the loop:

\foreach \NiceForEachElement in {1,...,2}{%
  \pgfmathsetlengthmacro\LeftWidth{width("\noexpand\noexpand\noexpand\ImageLeft{\NiceForEachElement}")}%
  %Activate the following line in case you wish to see on screen/console  what the definition of \LeftWidth looks like now:
  %\show\LeftWidth
  % On any page (begin)
  \begin{tcolorbox}[]%
  \begin{tcbitemize}[]%
  \tcbitem[] \ImageLeft{\NiceForEachElement}%
  \end{tcbitemize}%
  \end{tcolorbox}%
  % On any page (end)

  \begin{tikzpicture}%
  \filldraw[black] (0,0) circle (2pt) node[anchor=west] {This was drawn inside a tikzpicture};%
  \end{tikzpicture}%
  \newpage
}

\end{document}

相关内容