Yes, this can be done. For your simple example, what you want amounts to an "OR proof", a proofs that shows that one of two statements is true. Let $(c, c')$ be your ciphertext; to show that it encrypts either 1 or 2, you just have to prove that "$(g,h,c,c')$ is a DDH tuple OR $(g,hc,c'/2)$ is a DDH tuple".
Proving that a tuple $(g,h,g',h')$ is a DDH tuple is the classical "proof of same discrete logarithm in different bases" (you prove that $\log_g(g') = \log_h(h')$) (see e.g. this answer). To perform an OR proof, the idea is simply to perform two proofs for the two statements in the clause in parallel, but sending a challenge that binds only the prover to, say, the sum of the challenges. This gives him the freedom to choose one of the challenges before the execution of the protocol, and therefore to forge the corresponding proof, but not both; therefore, the prover will be able to forge at most one proof, and the verifier cannot know which one. See for example this article.
This generalizes to OR with arbitrarily many clauses, and so to "sets of messages" of arbitrary size, but the size of this proof is linear in the size of the set of messages. I know that if you use the additive ElGamal instead (a ciphertext is of the form $(g^r, h^rg^m)$), there are way more efficient methods: this article, for example, provides a zero knowledge proof of membership of an (additive) Elgamal plaintext to a known set of size $n$ in communication $O(\log n)$. The same holds for other cryptosystems with additive properties; for the standard ElGamal scheme, there might also be efficient proofs (in fact, there are necessarily such proofs, as we have ZK arguments for any statement in communication polylog, but the constants involved in these generic construction would be highly impractical here), but I do not know much about this specific case.