# How to create a command with key values?

I am trying to create a command that the user can enter keys for values. How can I create one, for example:

\myparbox[width=50,height=10,color=blue, align=left -10px]{}
-
If you already have code that does this, a \newcommand will allow you to wrap it up in the way you want above. –  Mark S. Everitt Nov 10 '11 at 6:09
Related (but not a dupe): tex.stackexchange.com/questions/13270/… –  Joseph Wright Nov 10 '11 at 7:00
Also related: tex.stackexchange.com/questions/26771/… –  Marco Daniel Nov 12 '11 at 20:11

Use pgfkeys! There are three steps to this: first, you must make your command accept keys as options. Traditionally, this is as you wrote it: an optional argument. In that case, you should start your definition like this:

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

The syntax is that the optional argument is expected to contain a list of keys (possibly set to values) that are then passed to \pgfkeys for processing. The second step is to figure out what you're going to do with the results: that is, you imagine that \pgfkeys does some kind of magic and produces a bunch of macros, or conditionals, and you need to make these things affect the operation of \myparbox.

Let's take the easy ones as an example: width and height. You will probably just pass them to \parbox as the optional parameters that control the width and height, and a good way to do that is to store their values in a macro. Let's say that width goes to the macro \myparboxWidth and height goes to \myparboxHeight. Then your definition of \myparbox will look more like:

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

I had to write [t] for the first optional argument in \parbox, which specifies the position in the surrounding text, because height is the second argument. This suggests that we ought to have a position key as well that corresponds to a macro \myparboxPosition. There's a third optional argument that I didn't give, but it's the "inner position", which can be either top, bottom, centered, or stretched. Might as well have an inner position key that sets \myparboxInnerPos. That gives:

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

That's enough for now. In order to make this work, you have to define your keys, and that's where pgfkeys is far, far better than its competitors. You can tell it to do all sorts of things with the values other than just storing them, though for height and width that will be enough. You define keys by using \pgfkeys in the preamble to set things up:

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

This has a few features. The real action is that I've said that both of these keys will "pass through" their arguments to the respective macros; if you said "width = 10pt" in the options to \myparbox, then \myparboxWidth would get set to 10pt. I wrote .estore in rather than plain .store in to force the value to be expanded before being saved; this prevents subtle errors if someone passes a macro that could get changed somehow before being used in \myparbox.

The other feature is that I've put the keys in a family, called /myparbox. In pgfkeys jargon, this is a "directory", like in a file system. Calling the /myparbox key changes directory to this one, and then all keys are private to that directory. This prevents name clashes with the (very common) key names width and height. Now you have to modify your \pgfkeys call in \myparbox as well to change directory:

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

For the position arguments, it would be nice if they could have more...logical names than simply "t", "b", "c", or "s". Since pgfkeys is, at heart, a lookup engine, it is pretty easy to have it map logical names to various actions: you just make each name a key that points to the corresponding action. I would do the following:

\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,
}

This is much more intricate than width and height, so I'll take it apart.

• First, we have the basic position and inner position keys, which are passed values. \pgfkeys treats these keys like macros with one argument, so the value is available as #1. We tell them to store the values in the appropriate place. The /.style suffix is a "handler" that defines a more complex behavior for a key than just setting a value; in this case, it makes the key "expand" to other keys that are then called to continue the work.

• What gets stored, though, has to be properly formatted: \parbox expects those one-character options and not words. So we define a positions subdirectory containing all the words we want to accept, defined to contain their translations into \parbox-speak. (As before, we isolate these special keys in a directory where they can't be seen and won't conflict with real options.) The /.initial handler sets values for keys the first time they are seen (these keys will never be redefined, actually).

• Back in position and inner position, the way we actually store the values is by using the /.get handler for the appropriate positions/ subkey. What this does is simply copy the value in that key into the named macro, which is what we wanted: position = top becomes \def\myparboxPosition{t} (effectively).

There is one more complication to take care of: what happens if you only specify half the options? The remaining macros \myparboxWhatever will be undefined or, more insidiously, defined to be whatever they got set to the last time they called \myparbox. We need to establish some defaults. The easiest way of doing that is to make a default style key that we run before processing the options in \myparbox. It may look like this:

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

Then the \pgfkeys call in \myparbox becomes

\pgfkeys{/myparbox, default, #1}

Here is the final result:

\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}

Using these techniques, you can (perhaps not easily at first) craft your own options and make them tweak the behavior of \myparbox. For example, if you wanted to have a color option, you would link it to the argument of a \textcolor command.

-
Wow, that's a really nice, detailed and well-written answer! –  Jake Nov 10 '11 at 11:15
@Jake: Thank you! –  Ryan Reich Nov 10 '11 at 16:22
@RyanReich Thanks for your detailed answer. –  mathspasha Nov 13 '11 at 8:21
The same general approach (define keys, process as the content of an optional argument) applies to all possible keyval packages. –  Joseph Wright Jan 5 '12 at 12:42
Yes, that's true. Only the part about setting up keywords is somewhat pgfkeys specific. –  Ryan Reich Jan 6 '12 at 13:36

Here is a short example of how to use keys by virtue of the keyval package.

The steps you need to follow are all based on the following macro:

\define@key{<family>}{<key>}{<function>}
1. Define your <family>: Use the above command by choosing some <family> name that all the keys will be associated with. In my example below, I chose the family name myparbox, since the keys should be associated with the macro \myparbox. They need not be the same as in my case.

2. Define your <key>s: You list all the keys that should be allowed for the <family>. In my example I defined the keys fontcolor, color, width and align, since these will have a meaning within my \myparbox command.

3. Define a <function> for each <key>: Whenever someone uses the <key>=<value>, <function> takes <value> as its argument #1. So, in the example below, I assign a macro for each one of the assigned values. This allows me to capture <value> so it can be used later. Note how the macro for each <key> is prefixed with pb@ (short for parbox@). This is because you may have a whole bunch of keys that you define, and you don't want the macros to clash with other packages, so the prefix makes each macro even more unique and further avoid clashes.

As a more elaborate discussion on functions, consider the color key. The macro associated with the color key is defined as \def\pb@color{#1}. That is, whenever someone uses color=<some color>, it executes \def\pb@color{<some color>}, thereby assigning the color <some color> to the macro \pb@color.

4. Set your default values: This is done by using the \setkeys{<family>}{<key>=<value> list} command. In the example below, I listed the default key-value pairs as

fontcolor=black
color=white
width=5cm
align=t

5. Write your macro that uses the created <key>s: You macro has the following basic form:

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

This defines \<mymacro> with 2 arguments, the first of which is optional. That is, \<mymacro>[<key>=<value> list]{<stuff>}. At the first step, I assign whatever is passed as the optional argument #1 to \setkeys under the same family name as my key set (myparbox in the example below). Secondly, I typeset #2 using the macros defined by the keys.

\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}

The xkeyval package provides a similar, yet more advanced interface.

-
If I add another \myparbox call without any options after the last one in this example, the output is still formatted red on blue etc. Apparently, options persist between calls. One should probably define \myparbox so that it calls \setkeys{myparbox}{<defaults>} each time before executing \setkeys{myparbox}{#1}. –  Michael Palmer Feb 24 '12 at 15:17
@MichaelPalmer: Thanks for spotting this - I've used grouping to localize the setting of keys within \myparbox. –  Werner Feb 24 '12 at 16:40
@Werner: Please is there any reason why you have \begingroup before \setkeys? I can't see the need for localization here. –  Ahmed Musa Feb 24 '12 at 19:34
@AhmedMusa: See @ MichaelPalmer's comment. –  Werner Feb 24 '12 at 19:41
@Werner: I have seen it. That is why the ltxkeys package has \...initializekeys and \...launchkeys. Sometimes grouping can be costly. Anyway, the original \setkeys by the keyval package is rather cheap computationally. –  Ahmed Musa Feb 24 '12 at 20:33

As noted at here, \setkeys from the keyval package may not be nested. This is because it doesn't push any current state before commencing process. Besides this, we no longer have to repeatedly call \define@key to define several keys. Here is a key command approach.

\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}

-