I deal with the dialogs issue by cheating. My MainWindow implements an IWindowServices interface that exposes all of the application-specific dialogs. My other ViewModels can then import the services interface (I use MEF, but you could easily just pass the interface through constructors manually) and use it to accomplish what is necessary. For instance, here is what the interface looks like for a little utility application of mine:
//Wrapper interface for dialog functionality to allow for mocking during tests
public interface IWindowServices
{
bool ExecuteNewProject(NewProjectViewModel model);
bool ExecuteImportSymbols(ImportSymbolsViewModel model);
bool ExecuteOpenDialog(OpenFileDialog dialog);
bool ExecuteSaveDialog(SaveFileDialog dialog);
bool ExecuteWarningConfirmation(string text, string caption);
void ExitApplication();
}
This puts all of the Dialog executions in one place and it can be easily stubbed out for unit testing. I follow the pattern that the client of the dialog has to create the appropriate ViewModel, which they can then configure as needed. The Execute call blocks and afterward the client can look at the contents of the ViewModel to see the Dialog results.
A more 'pure' MVVM design may be important for a large application, where you need cleaner insulation and more complex composition, but for small to medium sized apps, I think a practical approach, with appropriate services to expose required hooks, is quite sufficient.