如何创建带有键值的命令?

如何创建带有键值的命令?

我正在尝试创建一个命令,用户可以输入键作为值。我该如何创建一个,例如:

 \myparbox[width=50,height=10,color=blue, align=left -10px]{}

答案1

使用pgfkeys!这有三个步骤:首先,您必须让您的命令接受键作为选项。传统上,这就像您写的那样:一个可选参数。在这种情况下,您应该像这样开始您的定义:

\newcommand\myparbox[2][]{%
 \pgfkeys{#1}%
 ...
}

语法是,可选参数应包含键列表(可能设置为值),然后将其传递给\pgfkeys进行处理。第二步是弄清楚您要对结果做什么:也就是说,您想象它\pgfkeys会施展某种魔法并产生一堆宏或条件,您需要让这些东西影响 的操作\myparbox

让我们以简单的为例:widthheight。您可能只是将它们\parbox作为控制宽度和高度的可选参数传递给,而一个好方法是将它们的值存储在宏中。假设width转到宏\myparboxWidthheight转到\myparboxHeight。那么您的定义\myparbox将更类似于:

\newcommand\myparbox[2][]{%
 \pgfkeys{#1}%
 \parbox[t][\myparboxHeight]{\myparboxWidth}{#2}%
}

我必须[t]为 中的第一个可选参数编​​写\parbox,它指定了周围文本中的位置,因为高度是第二个参数。这表明我们position也应该有一个与宏 相对应的键\myparboxPosition。还有第三个可选参数我没有给出,但它是“内部位置”,可以是顶部、底部、居中或拉伸。不妨有一个inner position设置 的键\myparboxInnerPos。这样可以得到:

\newcommand\myparbox[2][1]{%
 \pgfkeys{#1}%
 \parbox[\myparboxPosition][\myparboxHeight]
        [\myparboxInnerPos]{\myparboxWidth}{#2}
}

现在就够了。为了使它工作,你必须定义你的键,这就是pgfkeys它比竞争对手好得多的地方。你可以告诉它用值做各种各样的事情,而不仅仅是存储它们,不过,height这已经足够了。你可以在序言中width使用来定义键来设置:\pgfkeys

\usepackage{pgfkeys}
\pgfkeys{
 /myparbox/.is family, /myparbox,
 width/.estore in = \myparboxWidth,
 height/.estore in = \myparboxHeight,
}

它有几个功能。实际操作是,我说过这两个键都会将它们的参数“传递”给相应的宏;如果您在 的选项中说“width = 10pt”,\myparbox则会\myparboxWidth设置为10pt。我写的.estore in是 而不是 plain,.store in以强制在保存之前扩展该值;如果有人传递了一个在 中使用之前可能会以某种方式更改的宏,这可以防止出现细微错误\myparbox

另一个功能是,我将密钥放在一个名为的系列中/myparbox。用pgfkeys行话来说,这是一个“目录”,就像文件系统一样。调用密钥/myparbox会将目录更改为此目录,然后所有密钥都属于该目录。这可以防止与(非常常见的)密钥名称width和发生名称冲突。现在您还height必须修改\pgfkeys调用以更改目录:\myparbox

\newcommand\myparbox[2][1]{%
 \pgfkeys{/myparbox, #1}%
 ...
}

对于位置参数,如果它们可以有更多逻辑名称,而不仅仅是“t”、“b”、“c”或“s”,那就太好了。由于pgfkeys本质上是一个查找引擎,因此很容易将其将逻辑名称映射到各种操作:您只需将每个名称设为指向相应操作的键。我会执行以下操作:

\pgfkeys{
 /myparbox,
 position/.style = {positions/#1/.get = \myparboxPosition},
 inner position/.style = {positions/#1/.get = \myparboxInnerPos},
 positions/.cd,
  top/.initial = t,
  center/.initial = c,
  bottom/.initial = b,
  stretch/.initial = s,
}

这是很多width比和更复杂height,所以我将把它拆开。

  • 首先,我们有基本的positioninner position键,它们是传递的值。 \pgfkeys将这些键视为具有一个参数的宏,因此值可用作#1。我们告诉它们将值存储在适当的位置。/.style后缀是一个“处理程序”,它为键定义了比设置值更复杂的行为;在这种情况下,它使键“扩展”到其他键,然后调用这些键继续工作。

  • 但是,存储的内容必须经过正确格式化:\parbox需要那些单字符选项,而不是单词。因此,我们定义一个positions子目录,其中包含我们想要接受的所有单词,并定义为包含它们到\parbox-speak 的翻译。(与以前一样,我们将这些特殊键隔离在一个目录中,它们无法被看到,并且不会与实际选项冲突。)/.initial处理程序在第一次看到键时设置它们的值(实际上,这些键永远不会被重新定义)。

  • 回到和中positioninner position我们实际存储值的方式是使用/.get相应positions/子键的处理程序。它所做的只是将该键中的值复制到命名宏中,这就是我们想要的:position = top变成\def\myparboxPosition{t}(有效)。

需要处理的复杂程度更高:如果您只指定了一半的选项,会发生什么情况?剩余的宏\myparboxWhatever将未定义,或者更隐蔽的是,它们被定义为上次调用时设置的值\myparbox。我们需要建立一些默认值。最简单的方法是创建一个default样式键,在处理选项之前运行它\myparbox。它可能看起来像这样:

\pgfkeys{
 /myparbox,
 default/.style = 
  {width = \textwidth, height = \baselineskip,
   position = center, inner position = center}
}

然后\pgfkeys呼叫\myparbox变成

\pgfkeys{/myparbox, default, #1}

以下是最终结果:

\documentclass{article}
\usepackage{pgfkeys}

% Set up the keys.  Only the ones directly under /myparbox
% can be accepted as options to the \myparbox macro.
\pgfkeys{
 /myparbox/.is family, /myparbox,
 % Here are the options that a user can pass
 default/.style = 
  {width = \textwidth, height = \baselineskip,
   position = center, inner position = center},
 width/.estore in = \myparboxWidth,
 height/.estore in = \myparboxHeight,
 position/.style = {positions/#1/.get = \myparboxPosition},
 inner position/.style = {positions/#1/.get = \myparboxInnerPos},
 % Here is the dictionary for positions.
 positions/.cd,
  top/.initial = t,
  center/.initial = c,
  bottom/.initial = b,
  stretch/.initial = s,
}

% We process the options first, then pass them to `\parbox` in the form of macros.
\newcommand\myparbox[2][]{%
 \pgfkeys{/myparbox, default, #1}%
 \parbox[\myparboxPosition][\myparboxHeight]
        [\myparboxInnerPos]{\myparboxWidth}{#2}
}

\begin{document}
 % This should print "Some text, and"
 % followed by "a box" raised about one line above the natural position
 % followed by "and more text" after a large space.
 Some text, and \myparbox[width = 50pt, height = 20pt, position = bottom, inner position = top]{a box} and more text.
 
 % Should look pretty much like normal text, with slight offsets down and over around the box.
 Some text, and \myparbox[width = 30pt]{a box} and more text.
 
 % The box should have very spread-out lines
 Some text, and
 \myparbox[width = 30pt, height = 100pt, inner position = stretch]
 {a box\par \vspace{\stretch{1}}with\par\vspace{\stretch{1}}words}
 and more text.
\end{document}

使用这些技术,您可以(一开始可能并不容易)制作自己的选项并让它们调整 的行为\myparbox。例如,如果您想要一个color选项,您可以将其链接到命令的参数\textcolor

答案2

以下是如何使用按键的简短示例keyval包裹

您需要遵循的步骤均基于以下宏:

\define@key{<family>}{<key>}{<function>}
  1. 定义您的<family>:使用上述命令,选择<family>所有键都将与之关联的名称。在下面的示例中,我选择了家族名称myparbox,因为键应与宏相关联\myparbox。它们不必与我的情况相同。

  2. 定义您的<key>s:列出 应允许的所有键。在我的示例中,我定义了 、 和键,<family>因为这些键在我的命令中具有含义。fontcolorcolorwidthalign\myparbox

  3. <function>为每个定义一个<key>:每当有人使用 时<key>=<value><function>都会将<value>作为其参数#1。因此,在下面的示例中,我为每个分配的值分配一个宏。这允许我捕获<value>它以便以后使用。请注意,每个宏的<key>前缀都是( ar oxpb@的缩写)。这是因为您可能定义了一大堆键,并且您不希望宏与其他包发生冲突,因此前缀使每个宏更加独特并进一步避免冲突。pb@

    作为对函数的更详细讨论,请考虑color键。与键关联的宏color定义为\def\pb@color{#1}。也就是说,每当有人使用时color=<some color>,它就会执行\def\pb@color{<some color>},从而将颜色分配<some color>给宏\pb@color

  4. 设置默认值:使用命令即可完成\setkeys{<family>}{<key>=<value> list}。在下面的示例中,我将默认键值对列为

    fontcolor=black
    color=white
    width=5cm
    align=t
    
  5. 编写使用创建的<key>s 的宏:您的宏具有以下基本形式:

    \newcommand{\<mymacro>}[2][]{%
      \setkeys{<family>}{#1}% Set the keys
      % do something with #2
    }
    

    这个定义\<mymacro>有 2 个参数,其中第一个是可选的。即。\<mymacro>[<key>=<value> list]{<stuff>}在第一步中,我将作为可选参数传递的任何内容分配#1给与\setkeys我的键集相同的系列名称(在下面的示例中)。其次,我使用键定义的宏myparbox进行排版。#2

在此处输入图片描述

\documentclass{article}
\usepackage{xcolor}% http://ctan.org/pkg/xcolor
\usepackage{keyval}% http://ctan.org/pkg/keyval

\makeatletter
% ========= KEY DEFINITIONS =========
\newlength{\pb@width}
\define@key{myparbox}{fontcolor}{\def\pb@fontcolor{#1}}
\define@key{myparbox}{color}{\def\pb@color{#1}}
\define@key{myparbox}{width}{\setlength\pb@width{#1}}
\define@key{myparbox}{align}{\def\pb@align{#1}}
% ========= KEY DEFAULTS =========
\setkeys{myparbox}{fontcolor=black,color=white,width=5cm,align=t}%
\newcommand{\myparbox}[2][]{%
  \begingroup%
  \setkeys{myparbox}{#1}% Set new keys
  \colorbox{\pb@color}{\parbox[\pb@align]{\pb@width}{%
    \color{\pb@fontcolor}#2
  }}%
  \endgroup%
}
\makeatother

\begin{document}

\myparbox{%
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
}

\myparbox[width=10cm]{%
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
}

\myparbox[width=7cm,fontcolor=red,color=blue]{%
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
  Here is some text that should fit in this paragraph box.
}
\end{document}

xkeyval包裹提供了类似但更高级的界面。

答案3

问题并不明确涉及软件包编写,因此我想知道,为什么到现在为止没有人提到keycommand,为文档编写者提供“一种使用可选键定义命令或环境的简便方法”(引自软件包手册),但这个软件包也可以在软件包内部使用。所以,让我在这里展示一下。

请注意,此包中有一个错误,因此必须添加 Joseph Wright 提供的补丁他的回答针对这个问题如何使用 \ifcommandkey ,或者如何检查是否给出了密钥? (从这个问题的标题也可以推断,严格来说,如果有人想使用该命令,那么补丁才是必要的,\ifcommandkey但这种情况经常发生)

\usepackage{keycommand}
% patch by Joseph Wright ("bug in the definition of \ifcommandkey (2010/04/27 v3.1415)"),
% https://tex.stackexchange.com/a/35794/
\begingroup
  \makeatletter
  \catcode`\/=8 %
  \@firstofone
    {
      \endgroup
      \renewcommand{\ifcommandkey}[1]{%
        \csname @\expandafter \expandafter \expandafter
        \expandafter \expandafter \expandafter \expandafter
        \kcmd@nbk \commandkey {#1}//{first}{second}//oftwo\endcsname
      }
    }
%=======================%

现在有了这个包,并使用 Ryan Reich 的简化方法(给出整个可选参数),的定义\myparbox将是

\newkeycommand{\myparbox}
  [enum vertalign={c,t,b}, boxheight=\height, enum innervertalign={c,t,b}, width][1]
  {%
  \parbox[\commandkey{vertalign}][\commandkey{boxheight}]
        [\commandkey{innervertalign}]{\commandkey{width}}{#1}%
  }

enum …是密钥定义的两种不同选择类型之一。(顺便说一句:以我的拙见,软件包作者混淆了两者的含义,因为enum后来不是在密钥中给出一个数字,但要用密钥choice
请注意,尽管width像所有其他键一样作为可选参数给出,但它是必需的,因为底层\parbox有一个必需width参数。如果后面省略了其他三个键,则使用定义中给出的值,对于两种choice类型的键,这是值列表中的第一个。

添加颜色键和条件的定义\ifcommandkey{<key>}{<key> value not blank}{<key> value blank}使其变得更加复杂:

\newkeycommand{\myparbox}
  [enum vertalign={c,t,b}, boxheight=\height, enum innervertalign={c,t,b},
  width, backgroundcolor, textcolor][1]
  {%
    \ifcommandkey{backgroundcolor}{\colorbox{\commandkey{backgroundcolor}}
      {\parbox[\commandkey{vertalign}][\commandkey{boxheight}]
            [\commandkey{innervertalign}]{\commandkey{width}}
            {\ifcommandkey{textcolor}{\color{\commandkey{textcolor}}}{}#1}%
    }}
    {\parbox[\commandkey{vertalign}][\commandkey{boxheight}]
            [\commandkey{innervertalign}]{\commandkey{width}}
            {\ifcommandkey{textcolor}{\color{\commandkey{textcolor}}}{}#1}%
    }%
  }

最困难的部分是正确使用牙套,并且不要忘记牙套。

全部一起:

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[svgnames]{xcolor}
\usepackage{keycommand}
% patch by Joseph Wright ("bug in the definition of \ifcommandkey (2010/04/27 v3.1415)"),
% https://tex.stackexchange.com/a/35794/
\begingroup
  \makeatletter
  \catcode`\/=8 %
  \@firstofone
    {
      \endgroup
      \renewcommand{\ifcommandkey}[1]{%
        \csname @\expandafter \expandafter \expandafter
        \expandafter \expandafter \expandafter \expandafter
        \kcmd@nbk \commandkey {#1}//{first}{second}//oftwo\endcsname
      }
    }
%=======================%
\newkeycommand{\myparbox}
  [enum vertalign={c,t,b}, boxheight=\height, enum innervertalign={c,t,b},
  width, backgroundcolor, textcolor][1]
  {%
    \ifcommandkey{backgroundcolor}{\colorbox{\commandkey{backgroundcolor}}
      {\parbox[\commandkey{vertalign}][\commandkey{boxheight}]
            [\commandkey{innervertalign}]{\commandkey{width}}
            {\ifcommandkey{textcolor}{\color{\commandkey{textcolor}}}{}#1}%
    }}
    {\parbox[\commandkey{vertalign}][\commandkey{boxheight}]
            [\commandkey{innervertalign}]{\commandkey{width}}
            {\ifcommandkey{textcolor}{\color{\commandkey{textcolor}}}{}#1}%
    }%
  }
\begin{document}
X\myparbox[width=0.65em]{Z Z}X --
X\myparbox[width=0.65em, backgroundcolor=SkyBlue]{Z Z}X --
X\myparbox[vertalign=b, width=0.65em, backgroundcolor=SkyBlue]{Z Z}X --
X\myparbox[vertalign=b, boxheight=3\baselineskip, width=0.65em,
          backgroundcolor=SkyBlue]{Z Z}X --
X\myparbox[vertalign=b, innervertalign=b, boxheight=3\baselineskip,
          width=0.65em, backgroundcolor=SkyBlue]{Z Z}X --
X\myparbox[vertalign=t, innervertalign=b, boxheight=3\baselineskip,
          width=0.65em, backgroundcolor=SkyBlue,textcolor=Gold]{Z Z}X --
X\myparbox[vertalign=t, innervertalign=t, boxheight=3\baselineskip,
          width=0.65em, backgroundcolor=SkyBlue,textcolor=Gold]{Z Z}X
\end{document}

第一个例子的屏幕截图


一个功能在 中是无法实现的keyval,但在更新、更高级的软件包(例如pgfkeys)中,就是处理未明确定义的(“未知”)键。当新命令定义本身内的命令已经使用键值方法时,这很有用。keycommand为这些情况提供另一个可选参数,其中必须给出任意键名(最有用的是“OtherKeys”/“OrigKeys”)。然后,新键命令不知道的所有键都将简单地移交给底层命令。

看下面的例子,我需要使用版本,其中为成对的命令提供扩展延迟|(在新命令名之前的可选参数中定义):

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{graphicx,transparent}
\usepackage{keycommand}
% patch by Joseph Wright ("bug in the definition of \ifcommandkey (2010/04/27 v3.1415)"),
% https://tex.stackexchange.com/a/35794/
\begingroup
  \makeatletter
  \catcode`\/=8 %
  \@firstofone
    {
      \endgroup
      \renewcommand{\ifcommandkey}[1]{%
        \csname @\expandafter \expandafter \expandafter
        \expandafter \expandafter \expandafter \expandafter
        \kcmd@nbk \commandkey {#1}//{first}{second}//oftwo\endcsname
      }
    }
%=======================%
\newkeycommand+[\|]{\transparentimage}[opacity][origkeys][1]
{%
  \begingroup
  \ifcommandkey{opacity}{|\transparent|{\commandkey{opacity}}}{}
    |\includegraphics|[\commandkey{origkeys}]{#1}
  \endgroup%
}
\begin{document}
\centering
\transparentimage{example-grid-100x100pt.pdf}
\transparentimage[opacity=0.33]{example-grid-100x100pt.pdf}
\transparentimage[width=75pt]{example-grid-100x100pt.pdf}
\transparentimage[width=75pt,opacity=0.33]{example-grid-100x100pt.pdf}
\transparentimage[angle=45,width=106pt]{example-grid-100x100pt.pdf}
\transparentimage[angle=45,width=106pt,opacity=0.33]{example-grid-100x100pt.pdf}
\end{document}

这里唯一新定义的键是opacity,键widthangle是的原始键\includegraphics

第二个示例的屏幕截图

答案4

正如所指出的这里\setkeys来自键值包不能嵌套。这是因为它在开始流程之前不会推送任何当前状态。除此之外,我们不再需要反复调用\define@key来定义几个键。这是一个键盘命令方法。

\documentclass{article}
\usepackage{xcolor}
\usepackage{ltxkeys}
\makeatletter

% To avoid local groups when using \fbox parameters, the development version of
% ltxkeys package introduces the commands \ltxkeys@savefboxparam and 
% \ltxkeys@restorefboxparam.

% The commands \ltxkeys@initializekeys and \ltxkeys@launchkeys can be used to 
% re-initialize keys to their default values. This avoids creating local 
% groups when setting keys, but (by design) these commands will not re-initialize 
% 'option keys' (ie, keys that are package or class options). The ltxkeys 
% package deals with this via the hooks \ltxkeys@beforekeycmdsetkeys,
% \ltxkeys@beforekeycmdbody, \ltxkeys@afterkeycmdbody, and the commands
% \ltxkeys@savecmdkeyvalues and \ltxkeys@restorecmdkeyvalues, all of which apply
% to only key commands.
% 
\new@def*\ltxkeys@fboxparamstack{}
\robust@def*\ltxkeys@savefboxparam{%
  \xdef\ltxkeys@fboxparamstack{%
    \fboxrule=\the\fboxrule\relax\fboxsep=\the\fboxsep\relax
    \noexpand\@nil{\expandcsonce\ltxkeys@fboxparamstack}%
  }%
}
\robust@def*\ltxkeys@restorefboxparam{%
  \begingroup
  \def\x##1\@nil{\endgroup##1\gdef\ltxkeys@fboxparamstack}%
  \expandafter\x\ltxkeys@fboxparamstack
}
% \myparbox is defined as a robust command:
\ltxkeysrobust\ltxkeyscmd\myparbox[2][](%
  cmd/textcolor/black;
  cmd/framecolor/white;
  cmd/fillcolor/white;
  cmd/framerule/.4pt;
  cmd/framesep/3pt;
  cmd/width/5cm;
  cmd/align/t;
  bool/testbool/true;
){%
  \ltxkeys@savefboxparam
  \let\kval\keyval
  \fboxrule=\kval{framerule}\relax
  \fboxsep=\kval{framesep}\relax
  \fcolorbox{\kval{framecolor}}{\kval{fillcolor}}{%
    \parbox[\kval{align}]{\kval{width}}{%
      \color{\kval{textcolor}}%
      #2\ifkeyvalTF{testbool}{\texttt{\textcolor{black}{<<#1>>}}}{}%
    }%
  }%
  \ltxkeys@restorefboxparam
}
\makeatother

\begin{document}
\newcommand*\sometext[1][1]{%
  \cptdotimes{#1}{%
    Here is some text that should fit in this paragraph box.
  }%
}

% No keys called here:
\myparbox{\sometext[3]}

\par\medskip
% Keys come last, if they are called:
\myparbox{\sometext[3]}
(width=10cm,framecolor=green,framerule=1pt,fillcolor=gray!15)

\par\medskip
\myparbox[Optional text]{\sometext[4]}
(width=7cm,framerule=4pt,framesep=20pt,textcolor=red,framecolor=brown,
fillcolor=yellow!25,testbool)

\end{document}

在此处输入图片描述

相关内容