outcoldman
outcoldman Denis Gladkikh

Работа с окнами как в Windows 7 при помощи hotkeys: Win + [Up|Down|Left|Right]

WinForms, WinAPI, and VistaKeysExtender

Редко пользуешься тем, что пишешь сам. Но вот тот самый редкий случай. В Windows 7 мне очень понравилась возможность позиционирования окон при помощи сочетаний клавиш:

Причем крепление окон к правому краю и левому, по-моему, уж очень удобная штука, ведь сколько раз приходилось самому располагать два окна на экране, с возможностью сравнения или перепечатывания...

Windows 7 не хочу использовать в качестве основной ОСь - потому что бета (или CR, главное что не Release), а возможность описанную выше использовать хочу. И вот - не поленился, и написал программку на C#, которая реализует данный функционал в Vista (скорее всего работает и более ранних версиях - просто не проверял). И как оказалось - задача не такая уж и сложная. Пришлось проимпортировать множество WinApi функций, а сама реализация разделилась на две: а) функционал, который перехватывает нажатия необходимых сочетаний клавиш б) позиционирование окон.

Первую задачу решил при помощи статьи Low-Level Keyboard Hook in C #, все что осталось сделать мне - найти возможность узнать когда была нажата клавиша LWin:

private static IntPtr HookCallback(
  int nCode, IntPtr wParam, IntPtr lParam)
{
  if (nCode >= 0 && wParam == (IntPtr)WAKeysHook.WmKeyDown)
  {
    if ((int)WAKeysHook.GetAsyncKeyState((IntPtr)Keys.LWin) != 0) // Left Win Key
    {
      Keys key = (Keys) Marshal.ReadInt32(lParam);
      if (key == Keys.Left || key == Keys.Right || key == Keys.Up || key == Keys.Down) // Arrows keys
        SetWindowLocation(key);
    }
  }
  return WAKeysHook.CallNextHookEx(WaKeysHook.HookId, nCode, wParam, lParam);
}

Вторая часть задачи: найти активное окно и установить ему грамотный размер (само собой нужно учитывать, что менять размеры можно только окнам с такой возможностью (WS_SIZEBOX), а так же что некоторые окна имеют максимально разрешенные размеры своего окна, как у cmd.exe):

private static void SetWindowLocation(Keys k)
{
  //Active Window
  IntPtr window = WAWindows.GetForegroundWindow();
  // Check SIZEBOX Style (Window can sizable)
  if (((int)WAWindows.GetWindowLongPtr(window, WAWindows.GwlStyle) & WAWindows.WsSizebox)
    == WAWindows.WsSizebox)
  {
    // Show window in normal state (if always maximized)
    WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowNormal);
    
    //Need Maximazed
    if (k != Keys.Down)
    {
      WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowMaximized);
 
      // Place Window on left or right
      if (k != Keys.Up)
      {
        WAWindows.Rect rect, rectDesktop;
        if (WAWindows.GetWindowRect(window, out rect) && WAWindows.GetWindowRect(WAWindows.GetDesktopWindow(), out rectDesktop))
        {
          WAWindows.SetWindowPos(window, WAWindows.HwndTop, (k == Keys.Left) ? Math.Min(rect.Left, 0) : rectDesktop.Right / 2, 
                rect.Top, Math.Min(rect.Width, rectDesktop.Right / 2 - Math.Min(rect.Left, 0)),
                 Math.Min(rect.Height, rectDesktop.Bottom), WAWindows.SwpShowwindow);
        }
      }
    }
  }
}

И последнее, в своей программе я ввел возможность установки - необходимо ли отображать значек в трее или нет, который помогает в закрытии программы, для этого нужно установить значение в соответствующей секции app.config.

<applicationSettings>
  <VistaKeysExtender.Properties.Settings>
    <setting name="ShowNotifyIcon" serializeAs="String">
      <value>False</value>
    </setting>
  </VistaKeysExtender.Properties.Settings>
</applicationSettings>

Мне просто сам значек не нужен, программа крутиться всегда в фоне, а место в трее итак переполнено. Функциональные клавиши я описал вверху статьи. Вопросы и предложения готов выслушать и ответить на них. Надеюсь, что кому то будет так же полезна данная программа как и мне.

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.