ContextMenuStrip에 ToolStripTextBox를 넣어 두면 우클릭 메뉴에서 바로 검색어나 값을 입력받을 수 있어서 편합니다. 그런데 막상 써 보면 글자를 다 치고 Enter를 눌러도 메뉴가 그대로 떠 있습니다. 일반 메뉴 항목은 클릭하면 알아서 닫히지만, 텍스트 박스는 입력을 받는 컨트롤이라 Enter를 "항목 선택"으로 보지 않기 때문입니다. 그래서 사용자가 메뉴 밖을 한 번 더 클릭하거나 Esc를 눌러야 닫히는데, 입력하고 Enter로 끝내는 흐름에 익숙한 사람에게는 꽤 거슬립니다.
해결은 간단합니다. 텍스트 박스의 키 이벤트에서 Enter를 잡아 메뉴의 Close()를 직접 호출하면 됩니다.
private void toolStripTextBox1_KeyUp(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter) {
menu.Close();
}
}
여기서 menu는 폼에 올려 둔 ContextMenuStrip의 이름입니다. 디자이너에서 toolStripTextBox1을 선택하고 속성 창의 이벤트 목록에서 KeyUp에 이 메서드를 연결해 주면 됩니다.
닫기 전에 입력값부터 처리하기
실제로는 Enter를 눌렀을 때 메뉴만 닫고 끝나는 경우는 드뭅니다. 보통은 입력한 값으로 검색을 하거나, 그리드 필터를 걸거나, 설정값을 저장하죠. 그런 작업은 menu.Close() 앞에 넣어 두는 게 자연스럽습니다. toolStripTextBox1.Text로 값을 읽어 처리하고 나서 닫으면, 사용자는 Enter 한 번으로 입력과 실행을 끝낼 수 있습니다. 다음에 메뉴를 다시 열었을 때 이전 입력이 남아 있는 게 싫다면 닫은 뒤에 Text를 비워 두면 됩니다.
입력값이 비어 있을 때도 생각해 둬야 합니다. 아무것도 안 치고 Enter를 누른 경우 필터를 해제할지, 그냥 메뉴만 닫을지, 아니면 닫지 않고 그대로 둘지는 화면마다 다릅니다. string.IsNullOrWhiteSpace(toolStripTextBox1.Text)로 먼저 걸러서 원하는 동작으로 갈라 두면 사용자가 실수로 Enter를 쳤을 때 엉뚱한 검색이 실행되는 일을 막을 수 있습니다.
KeyUp과 KeyDown 중 어느 쪽에 거나
원래 코드는 KeyUp에 걸려 있습니다. 키를 뗄 때 처리하므로 Enter를 누르고 있는 동안 반복 입력이 들어와도 한 번만 반응한다는 장점이 있습니다. KeyDown에 걸어도 동작은 하지만, 이 경우 키를 누르는 순간 메뉴가 닫히고 키를 떼는 이벤트는 메뉴 밖의 컨트롤로 넘어갈 수 있습니다. 폼 쪽에서 Enter에 다른 동작(기본 버튼 클릭 등)을 걸어 둔 경우라면 어느 이벤트를 쓰느냐에 따라 엉뚱한 동작이 같이 일어날 수 있으니, 그런 화면에서는 직접 눌러 보며 확인하는 게 좋습니다.
처리기 하나로 여러 메뉴를 닫으려면
텍스트 박스가 들어간 메뉴가 여러 개라면 메뉴 이름을 코드에 박아 두는 대신 sender에서 거슬러 올라가면 됩니다. ToolStripTextBox.Owner는 자기가 들어 있는 ToolStrip을 돌려주고, 우클릭 메뉴라면 그게 ContextMenuStrip(정확히는 ToolStripDropDown 계열)입니다.
private void MenuTextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode != Keys.Enter) return;
var box = sender as ToolStripTextBox;
if (box == null) return;
var dropDown = box.Owner as ToolStripDropDown;
if (dropDown != null)
{
dropDown.Close();
}
}
폼 생성자에서 toolStripTextBox1.KeyUp += MenuTextBox_KeyUp;처럼 텍스트 박스마다 같은 처리기를 연결해 두면 됩니다. 입력값 처리는 box.Text나 box.Name을 보고 갈라 주면 되고요. 다만 텍스트 박스가 최상위 메뉴가 아니라 어떤 항목의 하위 메뉴 안에 들어 있다면 Owner는 그 하위 메뉴의 드롭다운입니다. 이때는 하위 메뉴만 닫히고 바깥 메뉴가 남는지 실제로 확인해 보고, 남는다면 최상위 ContextMenuStrip을 직접 닫아 주세요.
메뉴가 닫힌 이유를 구분해야 할 때도 있습니다. Closed 이벤트의 e.CloseReason을 보면 Close()를 코드에서 부른 경우 ToolStripDropDownCloseReason.CloseCalled, 메뉴 밖을 클릭해서 닫힌 경우 AppClicked, 키보드로 닫힌 경우 Keyboard로 들어옵니다. Enter로 확정했을 때만 저장하고 바깥 클릭으로 닫히면 입력을 버리는 식의 처리를 여기서 나눌 수 있습니다.