Saving Window Placement Information | CodeGuru

Saving Window Placement Information

Whenever I start the average Windows application, it shows up at some arbitrary location on my screen. In many cases, my window positions have been very carefully set. I don’t want to reset them every time. So I decided it would be a Good Idea if every program I wrote from now on kept track […]

Written By
CodeGuru Staff
CodeGuru Staff
Feb 18, 2001
11 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Whenever I start the average Windows application, it shows up at some arbitrary location
on my screen. In many cases, my window positions have been very carefully set. I don’t
want to reset them every time. So I decided it would be a Good Idea if every program I
wrote from now on kept track of its size and position (unless it either can’t be resized
or doesn’t have a window). In the process of doing this, I made so many mistakes that I
thought it would be another Good Idea if I told other people what those mistakes were, why
they were mistakes, and how to avoid them.

Saving the position isn’t hard to do. All I have to do is get the application’s main
frame window position, and save that to the registry. When I start the application, I
read the last known position of the window, and put it back. This is actually really
simple, but I made it rather more complex than it needed to be the first time through.

Remember, until you get to “fixing the problems”, the code you are reading is WRONG.
It’s BAD CODE. Do not write code like this. If you have already written code like this,
either fix it, hide it, or blame it on an intern. I actually started out with worse code,
since I used “extern CMyApp *theApp;” in most of my classes. In the old code, this
persists as the variable name, but had been changed some time before. I just wanted to
remind people “don’t do that”.

What Went Wrong

Initially, I wanted to save two major pieces of information.

First, I saved the window rectangle, acquired with GetWindowRect():

void CMainFrame::OnMove(int cx, int cy)
{
 CFrameWnd::OnMove(x, y);
 CRect r;
 CMyApp *theApp=(CMyApp *)AfxGetApp();
 this->GetWindowRect(&r);
 theApp->SetUpperLeft(r.left,r.top);
 theApp->SetLowerRight(r.right-r.left,r.bottom-r.top);
}

Then, I wanted to save the maximised state of the window:


void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
 CFrameWnd::OnSize(nType, cx, cy);
 CRect r;
 CMyApp *theApp=(CMyApp *)AfxGetApp();
 if(nType==SIZE_MAXIMIZED)
 {
  theApp->SetMaximized(1);
 }
 else
 {
  theApp->SetMaximized(0);
  this->GetWindowRect(&r);
  theApp->SetUpperLeft(r.left,r.top);
  theApp->SetLowerRight(r.right-r.left,
                        r.bottom-r.top);
 }
}

In the application object, I implemented this rather simply with five member variables:

void CMyApp::SetMaximized(int max)
{
 m_wnd_max=max;
}
void CMyApp::SetLowerRight(int width, int height)
{
 if(m_wnd_max==0)
 {
  m_wnd_right=width;
  m_wnd_bottom=height;
 }
}
void CMyApp::SetUpperLeft(int left,int top)
{
 if(m_wnd_max==0)
 {
  m_wnd_left=left;
  m_wnd_top=top;
 }
}

And then I stuck this information into the registry when I was leaving the application:


int CMyApp::ExitInstance()
{
 WriteProfileInt(“Window”,”Maximize”,m_wnd_max);
 WriteProfileInt(“Window”,”Top”,m_wnd_top);
 WriteProfileInt(“Window”,”Left”,m_wnd_left);
 WriteProfileInt(“Window”,”Right”,m_wnd_right);
 WriteProfileInt(“Window”,”Bottom”,m_wnd_bottom);
 return CWinApp::ExitInstance();
}

There are a myriad problems here. First, and most importantly, the message handling


is stupid. Incredibly stupid. I track the position of the window every time it changes,


but I only save it when I exit. Why didn

t I just get the position once, immediately


before the window was destroyed? I should have done this:


void CMainFrame::OnClose()
{
 CRect r;
 CMyApp *theApp=(CMyApp *)AfxGetApp();
 this->GetWindowRect(&r);
 theApp->SetUpperLeft(r.left,r.top);
 theApp->SetLowerRight(r.right-r.left,r.bottom-r.top);
 CFrameWnd::OnClose();
}

But now I don

t get the maximised state. Not so it really matters, since this is how I


load the information in my app

s InitInstance():


m_wnd_max=GetProfileInt(“Window”,”Maximized”,0);
m_wnd_top=GetProfileInt(“Window”,”Top”,0);
m_wnd_left=GetProfileInt(“Window”,”Left”,0);
m_wnd_right=GetProfileInt(“Window”,”Right”,500);
m_wnd_bottom=GetProfileInt(“Window”,”Bottom”,300);

And then I did this to initialise the window:


BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
 CMyApp *theApp=(CMyApp *)AfxGetApp();
 cs.x=theApp->m_wnd_left;
 cs.y=theApp->m_wnd_top;
 cs.cx=theApp->m_wnd_right;
 cs.cy=theApp->m_wnd_bottom;
 if(theApp->m_wnd_max)
  cs.style|=WS_MAXIMIZE;
 if( !CFrameWnd::PreCreateWindow(cs) )
  return FALSE;
 return TRUE;
}

Stupid, stupid, stupid. All these cross-object calls are ridiculous. The design of this


method is so incredibly stupid, I

m ashamed to post it, but if you

re trying to do this


you might very well have done the same thing. Let

s look at how I do things in this dumb


initial attempt:

  • CMyApp::InitInstance reads the window position
  • CMainFrame::PreCreateWindow sets the position from member variables of CMyApp
  • CMainFrame::OnMove calls two methods of CMyApp to set member variables
  • CMainFrame::OnSize calls three methods of CMyApp to set member variables
  • CMyApp::ExitInstance saves the window position

We’ve already established that to improve application performance and maintainability,
we need to call back to CMyApp in CMainFrame::OnClose, but that doesn’t know if the
window’s maximised. However, it doesn’t even really matter! Look back up there at
CMainFrame::PreCreateWindow() again:

	if(theApp->m_wnd_max) cs.style|=WS_MAXIMIZE;

The MSDN viewer made this mistake in some earlier releases. This does not maximise the


window! It sets the window to the proper maximised dimensions, but the window is not


actually maximised

it merely fills the whole window. If you click the maximise button,


the border disappears. If you click it again to restore the window, it simply redraws the


border. And the original position of the window when it wasn

t maximised has been lost!


Clearly, this isn

t what we wanted in the first place. So we need to address this.



Now I’ll call your attention to the hardcoded defaults in the GetProfileInt() calls.
That’s exceptionally stupid. It breaks any window with a predefined size, and has to
be modified for each application. The defaults ought to come from somewhere that makes
more sense. At the very least, they should be coming from #defines, more preferably
from enums, even better from const variables, but best of all from the O/S… which
knows a whole hell of a lot more about this user’s system than I did when I wrote the
application in the first place.


Any object-oriented design aficionados will also note that we called SetUpperLeft() and
SetLowerRight() to write to the members of CMyApp, but then read them directly when we
restored the window size. That means they’re public members. So why did we call (or write)
these access methods in the first place? And SetLowerRight() doesn’t take the coordinates
of the lower right corner of the window, either — it takes the width and height of the
window. It would be okay if it calculated the width and height to save them, but that has
to be done before you call it. The name of the function is wrong! In fact, so are the names
of the member variables and registry entries used to store it!


What a godawful piece of crap!



Fixing the Problems


Before we write any new code, we

re going to turn to some philosophical issues. First, the


initialisation of the application is the job of the application object. Reading and


writing the configuration should all happen in CMyApp. That way, CMyApp is responsible


for all the configuration variables, and when you want to know whether something is in


the configuration you only have to examine CMyApp. From a coding style standpoint, we


will furthermore go so far as to say that all configuration variables will be private


and retrieved or updated only from public member functions. We will then go on to say that


all member variable retrieval functions will be called

TYPE

&

CMyApp::GetVariable()

and


mapped to a variable named m_variable of type TYPE. Similarly, we will say that functions


to set such variables will be named

void CMyApp::PutVariable(TYPE

&

newval)

. So any member


variable accessible from external classes will be declared as follows:


private:
 TYPE m_variable;
public:
 const TYPE& GetVariable() const;
 void PutVariable(const TYPE& newval);

We will also provide two functions for the configuration:

void CMyApp::ReadConfig()

and


void CMyApp::WriteConfig()

. Furthermore, we will divide the configuration into several


groups, which will reside in specific keys. For any given subkey, we will provide two


member functions:

void CMyApp::ReadKeyConfig()

and

void CMyApp::WriteKeyConfig()

where


there is a subkey of the configuration named

Key

. Currently, these will look like this:


void CMyApp::ReadConfig(void)
{
 ReadWindowConfig();
}
void CMyApp::WriteConfig(void)
{
 WriteWindowConfig();
}
void CMyApp::ReadWindowConfig(void)
{
 m_wnd_max=GetProfileInt(“Window”,”Maximized”,0);
 m_wnd_top=GetProfileInt(“Window”,”Top”,0);
 m_wnd_left=GetProfileInt(“Window”,”Left”,0);
 m_wnd_right=GetProfileInt(“Window”,”Right”,500);
 m_wnd_bottom=GetProfileInt(“Window”,”Bottom”,300);
}
void CMyApp::WriteWindowConfig(void)
{
 WriteProfileInt(“Window”,”Maximize”,m_wnd_max);
 WriteProfileInt(“Window”,”Top”,m_wnd_top);
 WriteProfileInt(“Window”,”Left”,m_wnd_left);
 WriteProfileInt(“Window”,”Right”,m_wnd_right);
 WriteProfileInt(“Window”,”Bottom”,m_wnd_bottom);
}

In CMyApp::InitInstance(), we can now simply set our registry key and add a ReadConfig()


call. At this point, we have just about everything we need from an architecture point of


view. However, we still have the problem of a flawed design, so before we move on to


setting up the CMainFrame class, we

ll address the design.

It turns out that GetWindowRect() is the wrong solution. What we really want here is a
function called GetWindowPlacement(), which has a corresponding SetWindowPlacement()
function. (Paul DiLascia wrote more about this in Microsoft Systems Journal in March of
1996.) These functions do everything we want done. These functions take a WINDOWPLACEMENT
structure, which means we only need to pass a single reference around. Furthermore,
we can call GetWindowPlacement() directly from CMyApp() through the m_pMainWnd member.
So we can actually do this:

private:
 WINDOWPLACEMENT m_WP;
public:
 const WINDOWPLACEMENT& GetWP() const;
 void PutWP(const WINDOWPLACEMENT& newval);

Then we implement them as follows, along with new implementations of ReadWindowConfig()
and WriteWindowConfig():

const WINDOWPLACEMENT& CMyApp::GetWP(void) const
{
 return m_WP;
}
void CMyApp::PutWP(const WINDOWPLACEMENT& newval)
{
 m_WP=newval;
 m_WP.length=sizeof(m_WP); // validation 😉
}
void CMyApp::ReadWindowConfig(void)
{
 m_WP.length=sizeof(m_WP);
 m_WP.showCmd=GetProfileInt(“Window”,”show”,0);
 m_WP.flags=GetProfileInt(“Window”,”flags”,0);
 m_WP.ptMinPosition.x=GetProfileInt(“Window”,”minposx”,0);
 m_WP.ptMinPosition.y=GetProfileInt(“Window”,”minposy”,0);
 m_WP.ptMaxPosition.x=GetProfileInt(“Window”,”maxposx”,0);
 m_WP.ptMaxPosition.y=GetProfileInt(“Window”,”maxposy”,0);
 m_WP.rcNormalPosition.left=GetProfileInt(“Window”,”left”,0);
 m_WP.rcNormalPosition.top=GetProfileInt(“Window”,”top”,0);
 m_WP.rcNormalPosition.right=GetProfileInt(“Window”,”right”,0);
 m_WP.rcNormalPosition.bottom=GetProfileInt(“Window”,”bottom”,0);
}
void CMyApp::WriteWindowConfig(void)
{
 WriteProfileInt(“Window”,”show”,m_WP.showCmd);
 WriteProfileInt(“Window”,”flags”,m_WP.flags);
 WriteProfileInt(“Window”,”minposx”,m_WP.ptMinPosition.x);
 WriteProfileInt(“Window”,”minposy”,m_WP.ptMinPosition.y);
 WriteProfileInt(“Window”,”maxposx”,m_WP.ptMaxPosition.x);
 WriteProfileInt(“Window”,”maxposy”,m_WP.ptMaxPosition.y);
 WriteProfileInt(“Window”,”left”,m_WP.rcNormalPosition.left);
 WriteProfileInt(“Window”,”top”,m_WP.rcNormalPosition.top);
 WriteProfileInt(“Window”,”right”,m_WP.rcNormalPosition.right);
 WriteProfileInt(“Window”,”bottom”,m_WP.rcNormalPosition.bottom);
}

Now, here we have something of a catch-22. I

d like to read my entire configuration


immediately after LoadStdProfileSettings(), so I can set the registry key and load


the config all in one localised block of code. That would look nice and clean:


SetRegistryKey(_T(“Darklock Communications”));
// Load standard INI file options (including MRU)
LoadStdProfileSettings(10);
ReadConfig();

Unfortunately, this isn

t going to work. In ReadWindowConfig(), we don

t have any


reasonable defaults. It makes sense to me that these defaults should come from what


MFC thinks they ought to be, so I should get them from a call to m_pMainWnd

s


GetWindowPlacement() function. But until ProcessShellCommand() is called, m_pMainWnd


is NULL. So I can

t call anything in m_pMainWnd until after ProcessShellCommand().



But I also like to have a configuration setting that determines whether to load the file
that was loaded when the application was last running. In order to do that, I need to
tell the app to load this file instead of creating a new one, so I need to change the
processing of cmdInfo in ProcessShellCommand(). This means some of my config has to be read
before ProcessShellCommand(), and some of it has to be read afterward. This can’t be helped,
so we need to figure out how to address this issue — specifically, which part of the
configuration is a special case, and which part is normal.


On further examination, I determine that there are two varieties of configuration data:
data that modifies document templates and load commands, and data that doesn’t — which
is just about everything else. Since it makes things a lot easier to separate these
functions into a small special-case group and a large generic group, the main ReadConfig()
call should come after ProcessShellCommand(), and any configuration subkeys that need to
modify the document template registration or the command processing will have to be
specifically read beforehand. In my case, I stick all these under a subkey called “Init”,
and call ReadInitConfig() immediately after the “LoadStdProfileSettings()” call.


Not that it affects what we’re doing here, but it affects your use of it later.


So now we go into ReadWindowConfig, and add in some reasonable defaults:

void CMyApp::ReadWindowConfig(void)
{
 WINDOWPLACEMENT wp;
 wp.length=sizeof(wp);
 m_pMainWnd->GetWindowPlacement(&wp);
 m_WP.length=sizeof(m_WP);
 m_WP.showCmd=GetProfileInt(“Window”,”show”,wp.showCmd);
 m_WP.flags=GetProfileInt(“Window”,”flags”,wp.flags);
 m_WP.ptMinPosition.x=GetProfileInt(“Window”,”minposx”,
 wp.ptMinPosition.x);
 m_WP.ptMinPosition.y=GetProfileInt(“Window”,”minposy”,
 wp.ptMinPosition.y);
 m_WP.ptMaxPosition.x=GetProfileInt(“Window”,”maxposx”,
 wp.ptMaxPosition.x);
 m_WP.ptMaxPosition.y=GetProfileInt(“Window”,”maxposy”,
 wp.ptMaxPosition.y);
 m_WP.rcNormalPosition.left=GetProfileInt(“Window”,”left”,
 wp.rcNormalPosition.left);
 m_WP.rcNormalPosition.top=GetProfileInt(“Window”,”top”,
 wp.rcNormalPosition.top);
 m_WP.rcNormalPosition.right=GetProfileInt(“Window”,”right”,
 wp.rcNormalPosition.right);
 m_WP.rcNormalPosition.bottom=GetProfileInt(“Window”,”bottom”,
 wp.rcNormalPosition.bottom);
}

And now, we add in the real meat of the matter, the validation. Who knows what may have


happened since the last time we ran? Maybe the user went mucking about in the registry.


Maybe the user changed screen resolutions. So we

re going to check a few things.

In my initial cut at this, I used some code written by Paul DiLascia in the March,
1996 issue of Microsoft Systems Journal. This code initialised rcScreen to (0,0,
GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN)). I’m sure this worked just
fine for anyone who either kept the system taskbar on the bottom of the screen or
set the taskbar to “autohide”. Unfortunately, I keep my system taskbar on the right
side of the screen, and I hate autohiding taskbars. So when I went to test the program
by setting it partially offscreen, I dragged it off the right side of the screen and
the code proceeded to stick it on the screen — under the taskbar. The minimise, maximise,
and close buttons, as well as the handy resize handle on the lower right, were covered.
While this was only a minor annoyance, imagine if the application had been at the top of
the screen and someone had the Microsoft Office toolbar up there with large icons
enabled… concealing the entire title bar and menu. What we really need here is the
::SystemParametersInfo(SPI_GETWORKAREA) call, which in all fairness was either very new
or not even in the SDK when Paul wrote the article in question.

CRect rcTemp;
CRect rcScreen(0,0,
               GetSystemMetrics(SM_CXSCREEN),
               GetSystemMetrics(SM_CYSCREEN));
// ::SystemParametersInfo(SPI_GETWORKAREA) gives us the screen space
// minus the system taskbar and application toolbars, which is what 
// we want our application to fit into.
::SystemParametersInfo(SPI_GETWORKAREA,0,&rcScreen,0);
// Normalise the existing rectangle, and force equality
rcTemp=m_WP.rcNormalPosition;
rcTemp.NormalizeRect();
m_WP.rcNormalPosition=rcTemp;
// See if any part of the window is on the screen
::IntersectRect(&m_WP.rcNormalPosition, &rcTemp, &rcScreen);
// if intersection==window, entire window is on screen
if(rcTemp!=m_WP.rcNormalPosition)
{
 // some part of the window is off the screen
 // off the left edge
 if(rcTemp.left<rcScreen.left)
  rcTemp.OffsetRect(rcScreen.left-rcTemp.left,0);
 // off the right edge
 if(rcTemp.right>rcScreen.right)
  rcTemp.OffsetRect(rcScreen.right-rcTemp.right,0);
 // won’t fit, shrink
 if(rcTemp.left<rcScreen.left)
  rcTemp.left=rcScreen.left;
 // off the top edge
 if(rcTemp.top<rcScreen.top)
  rcTemp.OffsetRect(0,rcScreen.top-rcTemp.top);
 // off the bottom edge
 if(rcTemp.bottom>rcScreen.bottom)
  rcTemp.OffsetRect(0,rcScreen.bottom-rcTemp.bottom);
 // won’t fit, shrink
 if(rcTemp.top<rcScreen.top)
  rcTemp.top=rcScreen.top;
 m_WP.rcNormalPosition=rcTemp;
}

However, I think if the whole window is off the screen, something really weird is going on.


The user has probably changed the screen resolution drastically since the last run, and the


application

s window size is undoubtedly far too large for the new resolution. There are


really very few cases I can think of where moving the window from entirely offscreen to


entirely onscreen will do what the user wants. In fact, I tend to think

window is


completely off the screen

indicates that the registry information is likely to be corrupt


to begin with. So I

m going to change that logic above a little:


if(::IntersectRect(&m_WP.rcNormalPosition, &rcTemp, &rcScreen))
{
 if(rcTemp!=m_WP.rcNormalPosition)
 {
  // some part of the window is off the screen
  // off the left edge
  if(rcTemp.left<rcScreen.left)
   rcTemp.OffsetRect(rcScreen.left-rcTemp.left,0);
  // off the right edge
  if(rcTemp.right>rcScreen.right)
   rcTemp.OffsetRect(rcScreen.right-rcTemp.right,0);
  // won’t fit, shrink
  if(rcTemp.left<rcScreen.left)
   rcTemp.left=rcScreen.left;
  // off the top edge
  if(rcTemp.top<rcScreen.top)
   rcTemp.OffsetRect(0,rcScreen.top-rcTemp.top);
  // off the bottom edge
  if(rcTemp.bottom>rcScreen.bottom)
   rcTemp.OffsetRect(0,rcScreen.bottom-rcTemp.bottom);
  // won’t fit, shrink
  if(rcTemp.top<rcScreen.top)
   rcTemp.top=rcScreen.top;
  m_WP.rcNormalPosition=rcTemp;
 }
}
else // entire window is offscreen
{
 m_WP=wp;
}

By using the initial defaults, we let MFC do the work of figuring out where the


application goes. Now that we

ve validated the window

s normal size and position,


let

s go a little further with this. In the m_WP.showCmd, we store the state of


an application as minimised or maximised. If the application was minimised when


it was closed, there are four user desires involved.



If the application is minimised in its normal running state, the user wants it to
start (and end) minimised, so the application will be set to run minimised. If the
application is not minimised in its normal running state, there are three reasons
it might think it is supposed to be minimised. Either the user minimised it and
closed it expecting it to remember it was minimised, the user closed it while it
was minimised without expecting this, or the program was minimised during system
shutdown. There is no way to distinguish between the final three cases. So here’s
our list of things to handle:


  • Program is set to start minimised: start minimised
  • Program was minimised and expected to start minimised: start minimised
  • Program was minimised and expected to start restored: start restored
  • Program was minimised and system was shut down: start restored

If the program is set to start minimised, we can check this by looking at the


default settings MFC found on the window. Since it was set to start minimised,


MFC would have minimised it. In the other three cases, we can

t distinguish the


user

s expectations without asking him. We don

t want to ask him

Do you want


to run the application minimised?

that

s just plain dumb. In two out of


three cases, the answer is

no

. So instead of annoying everyone with this stupid


dialog box, we

ll just assume the program is not supposed to start minimised. If


the user ever gets frustrated, he has

run minimised

in the program

s properties,


so he

s not overly inconvenienced.


if(m_WP.showCmd==SW_MINIMIZE
&& wp.showCmd!=SW_MINIMIZE)
{	// check default to see if this was requested
 m_WP.showCmd=SW_RESTORE;		// set restore flag
}

The next question is whether to do this on maximised windows, and the answer is NO.


Starting a program maximised is something users commonly want to do. The basic rule


of thumb on maximised applications is that you either have it maximised all the time,


or you never maximise it in the first place. So if we

re set to start maximised, we


will simply trust that this is what the user wants. However, the user may have

start


maximised

set in the shortcut, and he may have only just turned this option on. In


that case, we will be set not to start maximised, but the user will have specifically


asked us to start maximised

and we

ll be doing the wrong thing. So instead, we

ll


do the reverse, forcing maximise on if MFC thinks we

re supposed to be maximised:


if(m_WP.showCmd!=SW_MAXIMIZE
&& wp.showCmd==SW_MAXIMIZE)
{	// check default to see if this was requested
 m_WP.showCmd=SW_MAXIMIZE;		// set maximise flag
}

And now that we

ve done all this work, we

ll simply go back to InitInstance() and call


SetWindowPlacement() to put the window where it belongs


	m_pMainWnd->SetWindowPlacement(&m_WP);

That concludes all our processing needs in this section of the configuration. The only


thing left to do now is save the configuration. This is incredibly simple; we


simply tell CMainFrame to update the application

s window information:


void CMainFrame::OnClose()
{
 CMyApp *app=(CMyApp *)AfxGetApp();
 WINDOWPLACEMENT wp;
 this->GetWindowPlacement(&wp);
 app->PutWP(wp);
 CFrameWnd::OnClose();
}

And finally, in CMyApp::ExitInstance(), we add a call to WriteConfig():


int CMyApp::ExitInstance()
{
 WriteConfig();
 return CWinApp::ExitInstance();
}

WriteConfig will, of course, include a call to WriteWindowConfig(). That

s it, pretty much.


But it

s an awful lot. In the process of getting here, we

ve defined a complete


configuration mechanism which can be extended easily to cover all of your app

s needs.


While the code changes above have been made on an SDI application, the demo project is


MDI just to prove that the process is identical (excepting the base class of CMainFrame).

Advertisement

Downloads

Download source – 2 Kb

Download demo project – 18 Kb

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.