모니터를 두 대 쓰는 현장에서는 "조회 화면은 왼쪽, 상세 화면은 오른쪽 모니터에" 같은 요청이 자주 나옵니다. 매장 계산대의 고객용 화면처럼 아예 두 번째 모니터 전용 폼을 띄우는 경우도 있고요. 이런 걸 하려면 먼저 모니터 크기와 개수를 알아야 합니다.
화면 크기와 작업 영역
Dim ScreenSize As Size = New Size(My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height)
Dim ScreenSize1 As Size = New Size(My.Computer.Screen.WorkingArea.Width, My.Computer.Screen.WorkingArea.Height)
Bounds 는 모니터 해상도 전체이고, WorkingArea 는 거기서 작업 표시줄이 차지한 부분을 뺀 영역입니다. 폼을 화면에 꽉 차게 배치하거나 가운데 놓을 때는 보통 WorkingArea 를 써야 폼 아래쪽이 작업 표시줄에 가려지지 않습니다. My.Computer.Screen 은 주 모니터(primary)를 가리키니, 모니터가 여러 대여도 이 값은 주 모니터 기준입니다. Screen.PrimaryScreen 과 같은 값이라고 보면 됩니다.
지금 폼이 떠 있는 모니터의 크기가 필요하다면 주 모니터 대신 Screen.FromControl(Me) 를 씁니다. 폼이 두 모니터에 걸쳐 있으면 더 많이 걸친 쪽 모니터를 돌려줍니다. 팝업 창을 부모 폼과 같은 모니터 가운데에 띄울 때 이 값을 기준으로 계산하면 됩니다.
모니터가 두 대인지 확인
연결된 모니터 전체는 Screen.AllScreens 배열로 얻습니다. 개수가 2면 듀얼 모니터입니다.
Dim myScreens() As Screen = Screen.AllScreens
If (myScreens.Length = 2) Then
MsgBox("Width: " & myScreens(0).Bounds.Width & "/" & "Height: " & myScreens(0).Bounds.Height)
첫 번째 모니터의 해상도를 메시지로 확인해 보는 조각입니다(원래 메모에서도 End If 없이 확인용 줄만 적혀 있었습니다). 세 대 이상을 쓰는 환경도 있으니, 실제 코드에서는 = 2 보다 >= 2 로 확인하는 편이 덜 깨집니다.
두 번째 모니터 가운데에 폼 띄우기
Dim myScreens() As Screen = Screen.AllScreens
If (myScreens.Length = 2) Then
'Position form 1 in the middle of screen 1
Me.Left = myScreens(0).WorkingArea.Width / 2 - Me.Width / 2
Me.Top = myScreens(0).WorkingArea.Height / 2 - Me.Height / 2
'Position the top left corner of form 2 in the middle of screen 2
Dim myForm2 As New Form2
myForm2.Show()
myForm2.Left = myScreens(0).Bounds.Width + myScreens(1).WorkingArea.Width / 2
myForm2.Top = myScreens(1).WorkingArea.Height / 2
End If
현재 폼은 첫 번째 모니터 정가운데에 놓고, Form2 는 두 번째 모니터 가운데 지점에 왼쪽 위 모서리가 오도록 띄웁니다. 윈도우는 모든 모니터를 하나의 큰 좌표 평면으로 다루기 때문에, 두 번째 모니터가 첫 번째 오른쪽에 있다면 첫 번째 모니터 너비만큼 X를 더하면 그쪽 화면으로 넘어갑니다.
모니터 배치가 다르면 어긋난다
위 계산은 "0번이 왼쪽, 1번이 오른쪽, 위쪽 끝이 나란히" 놓인 배치를 가정합니다. 그런데 AllScreens 의 순서가 항상 왼쪽부터라는 보장이 없고, 디스플레이 설정에서 두 번째 모니터를 왼쪽이나 위에 두면 좌표가 음수가 되기도 합니다. 실제로 PC마다 0번과 1번이 뒤바뀌어 엉뚱한 화면에 폼이 뜨는 일이 생깁니다. 더 안전하게 하려면 각 모니터의 Bounds.Left·Top(또는 WorkingArea.Left·Top)을 기준으로 계산하고, 주 모니터가 아닌 쪽을 Primary 속성으로 골라내면 됩니다. 아래는 그렇게 고친 버전입니다.
Dim second As Screen = Screen.AllScreens.FirstOrDefault(Function(s) Not s.Primary)
If second IsNot Nothing Then
Dim wa As Rectangle = second.WorkingArea
Dim myForm2 As New Form2
myForm2.StartPosition = FormStartPosition.Manual
myForm2.Location = New Point(wa.Left + (wa.Width - myForm2.Width) \ 2,
wa.Top + (wa.Height - myForm2.Height) \ 2)
myForm2.Show()
End If
StartPosition 을 Manual 로 두고 Show 전에 Location 을 정하면 폼이 기본 위치에 잠깐 떴다가 옮겨지는 깜빡임도 없습니다. 두 번째 모니터를 꽉 채우고 싶으면 Location 대신 Bounds = second.Bounds 로 주고 FormBorderStyle 을 None 으로 두면 됩니다. 모니터가 한 대뿐인 PC에서도 오류 없이 넘어가도록 Nothing 확인을 꼭 넣어 두세요.
FirstOrDefault 는 LINQ 확장 메서드라 System.Linq 를 쓸 수 있어야 합니다. 보통 VB 프로젝트는 기본으로 가져와 있지만, 오래된 프로젝트에서 "FirstOrDefault 은(는) 멤버가 아닙니다" 오류가 나면 Imports System.Linq 를 넣어 보세요.
지난번 위치를 기억했다가 띄울 때
사용자가 옮겨 둔 위치를 저장했다가 다음 실행 때 그대로 띄우는 경우도 많은데, 여기서 흔히 생기는 문제가 "모니터를 뺐더니 창이 안 보인다"입니다. 저장된 좌표가 지금은 없는 두 번째 모니터 영역이라 화면 밖에 창이 뜨는 거죠. 불러온 좌표가 현재 연결된 모니터 안에 있는지 확인하고, 아니면 기본 위치로 띄우게 해 둡니다.
Dim saved As New Point(My.Settings.FormX, My.Settings.FormY)
If Screen.AllScreens.Any(Function(s) s.WorkingArea.Contains(saved)) Then
Me.StartPosition = FormStartPosition.Manual
Me.Location = saved
End If
FormX, FormY 는 프로젝트 속성의 설정 탭에서 정수형으로 만들어 둔 사용자 설정이고, 폼을 닫을 때 Me.Location 값을 넣고 My.Settings.Save() 로 저장하는 쪽은 따로 작성합니다. 이 코드는 폼의 Load 이벤트처럼 창이 화면에 나타나기 전에 실행해야 위치가 바로 적용됩니다.
모니터마다 디스플레이 배율(100%, 125%, 150% 등)이 다르게 설정돼 있으면, 프로그램의 DPI 인식 설정에 따라 폼 크기나 좌표가 예상과 다르게 나올 수 있습니다. 배치가 미묘하게 어긋난다면 두 모니터의 배율을 같게 맞춰 놓고 다시 확인해 보는 것이 원인을 가르는 빠른 방법입니다.