Introduction
In Windows 7, when I open WindowsExplorer (not Internet Explorer) to see and manage my files, upon opening a new window, it remembers the last closed windows size, and it usually opens in screen position overlapping other explorer windows:
I wanted to open explorer windows always the same size, and never partially/completely hiding other explorer windows:
After searching (with no luck) for some Windows7 options to enable these features, one rainy Saturday I started developing this. (BTW: if you happen to know how to do the same thing in a better/faster way (i.e. some windows settings or some existing utility), I would like to know it, thanks.)
Background
It is a simple program that uses:
- user32.dll functions to manage desktop windows
- a form overriding the
WndProc
to manage windows messages and to set a shell hook for the Creation/Activation/Destruction of a window (although only the creation event will be used here; the other are for 'future use') - a Mutex to allow only one running instance of the application
- XML serialization of the config object, that will be written to and read from the hard disk (in the same folder of the application)
Using the Code
The code is structured into 4 files, herein below described, starting from the least important:
- WindowsApi.cs contains only
DllImports
to Windows API, in particular: user32.dll; this one doesn't need any further explanation - XmlHelper.cs is a very simple bare-bone
abstract
class for XML serialization. By inheriting this one, a class can be saved as an XML file, without further effort - Config.cs contains the configuration that is read/written to disk
- WindowsHookForm.cs is a form that is never shown to the user; it is provided only to use the from's
WndProc
, that allows to easily hook to Windows shell events and consequently do something when a window is opened.
It provides three events: WindowCreatedEvent
, WindowActivatedEvent
and WindowDestroyedEvent
, but only the first one is actually used in the program.
In the constructor, a hook is registered:
private readonly int windowMessage_ShellHook;
public WindowsHookForm()
{
windowMessage_ShellHook = WindowsApi.RegisterWindowMessage("SHELLHOOK");
WindowsApi.RegisterShellHookWindow(this.Handle);
}
then, in the WndProc
, messages are intercepted and, if the messageID
is our shellhook, then an event is risen (but only for the three kinds mentioned above):
public event Action<IntPtr> WindowCreatedEvent;
public event Action<IntPtr> WindowActivatedEvent;
public event Action<IntPtr> WindowDestroyedEvent;
protected override void WndProc(ref Message message)
{
if(message.Msg == windowMessage_ShellHook)
{
WindowsApi.ShellEvents shellEvent = (WindowsApi.ShellEvents)message.WParam.ToInt32();
IntPtr windowHandle = message.LParam;
switch(shellEvent)
{
case WindowsApi.ShellEvents.HSHELL_WINDOWCREATED:
if(WindowCreatedEvent != null)
WindowCreatedEvent(windowHandle);
break;
case WindowsApi.ShellEvents.HSHELL_WINDOWACTIVATED:
if(WindowActivatedEvent != null)
WindowActivatedEvent(windowHandle);
break;
case WindowsApi.ShellEvents.HSHELL_WINDOWDESTROYED:
if(WindowDestroyedEvent != null)
WindowDestroyedEvent(windowHandle);
break;
}
}
base.WndProc(ref message);
}
- Program.cs contains the main logic of the program:
Upon start, a Mutex allows only one instance of the program to be run and, if an instance is already running, it kindly asks if you want to terminate it (and in this case, it calls the RemoveAllInstancesFromMemory
method, that gets the running processes with the "WindowsMover
" name and kills 'em).
Once started, three actions are performed: first the config file (if it exists) is read (and if it does not exist, then it is written with default values, so that you may find it easy to edit); then, in the ArrangeExplorerWindows
method, every existing explorer window on the desktop is arranged (i.e. resized and moved); and finally the resident part is started, where the WindowsHookForm
and the WindowCreatedEvent
defined in it are used:
WindowsHookForm whf = new WindowsHookForm();
whf.WindowCreatedEvent += (data) => { ArrangeOneWindow(data); };
Every time we open a new window, the ArrangeOneWindow
method receives the handle to the new window that has just been created; then it checks if it is an Explorer window:
private static void ArrangeOneWindow(IntPtr newhandle)
{
List<windowInfo> wlist = GetActiveExplorerWindowsInfo();
windowInfo newWindow = (from windowInfo wi in wlist
where wi.handle == newhandle
select wi).FirstOrDefault();
if (newWindow != null)
and resizes and (optionally) moves that window, using the WindowsApi
functions to do the work, and the config data to know how to proceed.
Basically, any time an explorer window is opened, the program searches a position to put it, starting at the top left corner of the desktop and then moving right (like in a row) until a suitable place is found; once the right of the desktop is reached, the same search is repeated in the row below. If too many explorer windows are open on the desktop, and there is no empty place to put the new opening one, then nothing is done, and the new window will be left where Windows7 decided to put it.
The Config File
<width>346</width>
<height>460</height>
<startLeft>0</startLeft>
<startTop>0</startTop>
<left>0</left>
<top>0</top>
<horizontalGap>0</horizontalGap>
<verticalGap>0</verticalGap>
<doResizeOnly>false</doResizeOnly>
<canArrangeFixedPositions>true</canArrangeFixedPositions>
<canAppendToNext>false</canAppendToNext>
<ExcludedWindowsTitles>
<string>Control Panel\All Control Panel Items\Personalization</string>
<string>Shut Down Windows</string>
</ExcludedWindowsTitles>
<logFileName>WindowsMover.log</logFileName>
<doLogFile>false</doLogFile>
width
and height
are the default resizing dimensions for the resized windows. You know that explorer always uses the last closed window size as default for the new one, so these values in config file override the ones set by explorer. - The two
gap
values should be the distance between the arranged windows. doResizeOnly
if true
does not move the windows. canArrangeFixedPositions
allows the search of a suitable position even if there is no free place available next to an existing window (it maps the windows as if it was made of tiles starting at startLeft
and startRight
, with the defaut width
and height
set in the first lines of the config file; then it searches if one of this tiles is free) canAppendToNext
if true
it first tries to append the new window to the right of an existing explorer window. ExcludedWindowsTitles
is a list of explorer window titles the we don't want to be arranged doLogFile
if true
, a logFileName
is written for every arranged window (mainly for debug purposes)
And that's it. Structurally, it is very simple and it could give some interesting insights to the newbie programmer.
Update
After receiving a couple of requests, I've changed the program so that it can resize and move every kind of window, not only explorer windows (So I changed also the title of the article). To allow this feature, I've added a new field in the config file:
<ProcessesToWatch>
<string>explorer</string>
</ProcessesToWatch>
It is a list of strings and you may replace 'explorer' with something else, or just add the name of a new process to be watched.
For example, the following xml in the config file:
<ProcessesToWatch>
<string>explorer</string>
<string>notepad</string>
</ProcessesToWatch>
will make the program watch for windows opened by both explorer and notepad, and it will resize/move them.
If unsure about the process name to use in the list, change temporarly the following line in the config file:
<doLogFile>false</doLogFile>
to true, and, upon start, in the log file there will be a list of all the active processes names, where the needed one can be picked.
Update 2019
1) Made minor changes to better adapt this program to Windows10 (it was about time...)
2) In config file added support for a wildcard: '*' (but only at the end of a name), in tag <ExcludedWindowsTitles>
Example:
<ExcludedWindowsTitles>
<string>Control Panel\*</string>
3) Added a new boolean key in configuration file:
<enableKeyboardShortcutsToArrangeWindows>
When this key is true (which is also the default), then by pressing the combination of keys:
CTRL+SHIFT+ALT+A
all defined windows (which by default are all Explorer windows) are rearranged and shown on top of every other window.
To intercept the key combination, a new hook (similar to the one used to intercept the windows messages, already used in the file 'WindowsHookForm.cs') has been used:
public static bool RegisterKeyHandler(Form form, int key, int mod = 0) => WindowsApi.RegisterHotKey(form.Handle, mod ^ key ^ form.Handle.ToInt32(), mod, key);
which is used in a manner similar to this:
RegisterKeyHandler(this, (int)Keys.A, ALT + CTRL + SHIFT);
Attached files (src + exe) have been updated.