[WPF] x:Name 지정 없이 특정 컨트롤을 기본적으로 포커스된 상태로 설정하기

Focus

  어떤 프로그램이든 사용자 경험은 중요한 부분입니다. 그중 개인적으로 기본이라고 생각하는 것이 바로 포커싱입니다. 윈도우가 부모 창의 가운데, 혹은 적절한 위치에서 팝업하는 것 부터 시작해서 핵심 컨트롤에 포커스가 맞춰지는 것까지. 사용자가 눈을 최대한 적게 굴리고, 마우스를 최대한 적게 움직이며, 키보드를 최대한 적게 입력하게 설계하는 것이 중요합니다.
  Focus 설정은, 다들 아시겠지만 텍스트박스와 같이 커서가 옮겨갈 수 있는 컨트롤에 커서를 옮겨주는 역할을 합니다. 예를 들어서 로그인 창이 있다고 생각해보죠. 로그인 창엔 아이디, 비밀번호, 아이디 저장, 비밀번호 저장, 로그인 등의 텍스트박스 및 체크박스, 버튼이 있을 겁니다. 그럼 사용자가 로그인 창에서 처음으로 볼 곳은 어디일까요? 아이디 텍스트 박스죠.  그럼 여러분은 로그인 창이 떴을 때, 아이디 텍스트 박스에 포커스가 설정되도록 해야 합니다.
  물론 대부분 바로 이해하시겠지만, 이렇게 설명하면 잘 이해가 되지 않으실 분을 위해 반대로 얘기해보죠. 만약 여러분이 네이버에 로그인 화면에 접속하려고 한다고 가정해봅시다. 네이버 메인 페이지에 들어가서, 로그인 버튼을 누릅니다. 그럼 로그인 페이지로 이동하게 됩니다. 헌데 여기서 만약 기본 포커싱이 ID 텍스트 박스가 아닌 패스워드 텍스트 박스로 되어있다면? 혹은 로그인 버튼으로 되어있다면?
  전자의 경우 로그인 페이지로 넘어왔으니 당연히 아이디를 입력할 수 있을 것이라 생각하고 아이디를 honjasal...까지 입력하고 뭔가 이상함을 발견하게 될 것입니다. 패스워드에 입력되고 있었던 것이죠! 그럼 여러분은 깊은 빡침을 느끼며 패스워드를 지우고 Shift-Tab 혹은 마우스 클릭으로 아이디 텍스트박스를 선택하고 다시 입력할 겁니다.
  후자의 경우 아예 입력조차 안 되죠. 뭐, 차라리 지우는 수고가 덜어지니 전자보단 나을지도?

  아무튼, 이래서 사용자 경험이 중요하고, 포커싱이 중요합니다. 이번 포스트에서는 WPF 앱에서 창이 띄워질 경우 자동으로 원하는 컨트롤에 포커스가 맞춰지도록 하는 방법을 알려드리도록 하겠습니다.

FocusManager 클래스의 FocusedElement 프로퍼티

  FocusManager 클래스PresentationCore 어셈블리에 들어있는 이름 그래도 포커스 관리를 도와주는 클래스입니다. 이 클래스는 모든 Focusable 컨트롤에 AttachedProperty FocusedElement를 지원하며, FocusManager.FocusedElement로 지정된 컨트롤은 Logical Focus를 얻게 되며, 따라서 Initialization 단계에서 Logical Focus를 얻게 된 지정된 컨트롤은 창이 로드되고 활성화된 뒤 Keyboard Focus를 얻어 해당 엘리먼트에 커서가 옮겨지게 됩니다.

  간단히 말해서, FocusedElement Attached Property에 지정된 컨트롤은 창이 로드된 후 Keyboard Focus를 얻게 된다는 말입니다.
  

FocusManager.FocusedElement Attached Property 사용 방법

  사용 방법은 크게 두가지가 있습니다. 모두 프로퍼티에 바인딩한다는 점에선 차이가 없지만...

  우선 첫 번째 방법으로, 포커스될 엘리먼트의 이름을 적는 방법입니다. 보통 최상위 엘리먼트인 Window 엘리먼트에 적어 넣습니다.

<Window FocusManager.FocusedElement="{Binding ElementName=_txtId}"
<!-- Window 엘리먼트의 프로퍼티 설정 -->
>
<!-- Grid 등 기타 엘리먼트 작성 -->
<TextBox x:Name="_txtId" />
</Window>

  가장 흔히 검색으로 찾을 수 있는 방법입니다. 물론 작동하지요. 하지만 문제가 있습니다. 텍스트박스의 이름으로 사용된 _txtId오로지 FocusedElement 속성을 위해서만 지정되었다는 것입니다. 코드에서, 혹은 XMAL 내의 다른 곳에서 전혀 _txtId에 접근할 일이 없음에도 불구하고 오로지 FocusedElement 하나를 위해 이름을 지정하는 것은 불편합니다.

  그럼 어떻게 하면 될까요? FocusManager.FocusedElement는 Window 엘리먼트에 지정해야 하는데, Window 엘리먼트에서 이름 없이 _txtId 텍스트박스를 알아내는 게 가능할까요?

  당연히 아닙니다. 뭐가 아니냐면, Window 엘리먼트에서 이름 없이 _txtId 텍스트박스 객체를 가져오는 것이 가능한것도 아니고, FocusManager.FocusedElement 프로퍼티를 Window 엘리먼트에 지정해야 하는 것도 아닙니다.


Logical Focus, Keyboard Focus

  이건 솔직히 저도 제대로 이해하고 있지는 않다고 생각하지만, 조금이라도 여러분이 이해하시기 쉽도록 적습니다. MSDN에 나와있는 포커스에 대한 정보를 보면 두 가지의 포커스 상태가 존재한다고 합니다. 하나는 Logical, 하나는 Keyboard 포커스이죠.

  Keyboard Focus는 전체 데스크탑에서 단 한 곳에만 존재할 수 있습니다. 쉽게 설명하면 메모장에서 커서가 깜빡이는데, 동시에 네이버 로그인 창의 아이디 박스에도 커서가 깜빡일 수는 없다는 것이죠. 키보드 포커스는 전체 데스크탑에 한 곳에서만 지정되어, 동시에 여러곳에서 키보드 입력이 발생하는 불상사가 생기지 않도록 해줍니다.

  Logical Focus는 하나의 응용 프로그램 내에 여러개가 존재할 수 있지만, 특정 포커스 범위에는 하나만 존재할 수 있습니다. 한 응용 프로그램의 여러 창에 모두 Logical Focus가 존재할 수 있지만, 한 창, 혹은 한 구역엔 두 개 이상 존재할 수 없다는 것이죠. 이는 Logical Focus의 목적이 창이 포커스를 잃었다가 다시 얻었을 때, Logical Focus가 존재하는 곳으로 Keyboard Focus를 이동시켜주는 데 있기 때문입니다.

  자, 그럼 여기서 생각해봅시다. FocusManager.FocusedElement에 설정된 컨트롤은 Logical Focus를 갖게 됩니다. XAML에서 지정했으므로 창이 Activated되기 전, 즉 포커스를 얻기 전에 Logical Focus가 부여되게 됩니다. 슬슬 눈치채셨나요?
  FocusManager.FocusedElement로 지정한 엘리먼트는 Window 엘리먼트에 지정하든 자기 자신에 지정하든 상관없이 Logical Focus를 얻게 되므로, 창이 Focus를 얻게 될 때 해당 엘리먼트에 Keyboard Focus가 옮겨가게 됩니다!

RelativeSource를 이용한 셀프 포커싱

  자, 그럼 이제 어떻게 하는지 알려드리도록 하겠습니다. 정말 간단합니다.

<TextBox Text="블라블라" FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}" />

  참 쉽죠? 어디서든 FocusManager.FocusedElement Attached Property에 지정된 엘리먼트는 Logical Focus를 얻게 되므로 저런 식으로 쓸 수 있습니다.

  본론은 이렇게 짧은데 서론이 너무 길었네요. 그래도 링크를 타고 들어가면 여러 지식을 얻을 수 있으실 테니 한 번 보시는것도 나쁘진 않다고 생각해 이대로 올리도록 하겠습니다. 모두 행복한 하루 되세요.

댓글

이 블로그의 인기 게시물

C# 남아도는 메모리에도 불구하고 OutOfMemoryException이 발생한다면?

MySQL 데이터 타입과 Java 데이터 타입 비교/매칭

테일즈위버 OST 전곡 모음