Dialog implementation fails, is already the logical child of another element - wpf

I am trying to implement dialogue window in WPF + PRISM + MVVM application. for now I managed to create sample service and each module is able to use this service to show any view in window, but the problem is something very unusual and can not make it work.
Here is the contract of the window service.
public interface IUiDialogueService : IDisposable
{
void Show<TView>(TView view) where TView : IViewModel;
}
public class UiDialogueService : IUiDialogueService, IDisposable
{
private Window _dialogueWindow;
#region Implementation of IUiDialogueService
public void Show<TView>(TView view) where TView : IViewModel
{
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
_dialogueWindow.ShowDialog();
_dialogueWindow = null;
}
}
Here is how I access my window service from module.
private void OnStartWizard()
{
_dialogueService.Show(ServiceLocator.Current
.GetInstance<IOrgManagementOrganizatioSetupViewViewModel>());
}
everything works well when I first open window but after I close it and open same or other view inside window I revive following exception
Specified element is already the logical child of another element. Disconnect it first.
this exception is thrown by following code.
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
Could anyone explain what is going on wrong here and Is there any better way to get child(dialogue) window in MVVM architectur?

Try adding the following code before the last line of Show:
_dialogueWindow.Content = null;
Show should now look like this:
public void Show<TView>(TView view) where TView : IViewModel
{
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
_dialogueWindow.ShowDialog();
_dialogueWindow.Content = null;
_dialogueWindow = null;
}

Each element in WPF can only belong to one parent element. Even if an element is not shown (or shown anymore), the parent-child relationship remains. If you want to give an element a new parent you need to remove it first from the old parent.
In your case, in Show() you are setting the Content of the window to your view. Even after that window was shown, the view still is the child of that window. If you now try to show that same view in a different window, you'll get that exception.
The best way is to remove the view from the Window (described in Daniel Hilgarth's answer). Alternatively, you could check if the view already has a Parent, and manually remove it from that parent first.

Related

Prism MVVM - Showing Pop up window from viewmodel on a button click command in WPF

I have a datagrid in my view(UserControl) in which I have a button, The requirement is to show a window on click of that button. I achieved it by creating an object for the window and calling it's show method and yes it's a wrong way, So, I want a standard way of achieving it.
I'm using Prism framework, So I mapped the window view with Window viewmodel using ViewModelLocationProvider in the windowviewmodel.
var table = new Window();
table.Content = new windowViewModel();
table.Show();
This is the code I implemented in the main viewmodel on click of the button.
It is actually opening a window, but, view is not getting loaded on the window.
Use the IDialogService Introduced in Prism 7
first the dialog view or ViewModel needs to implement the IDialogAware interface like this:
class PopupViewModel : BindableBase, IDialogAware
{
public string Title => "Popup";
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public DelegateCommand OkCommand => new DelegateCommand(() =>
{
//you can also pass any parameters you want to back pass to the caller
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
});
public void OnDialogOpened(IDialogParameters parameters)
{
//here you get the parameters you passed;
}
}
then you need to register your view like this
containerRegistry.RegisterDialog<Views.Popup>();
to use you inject into your ViewModel the IDialogService and call it
this.dialogService.ShowDialog(nameof(Views.Popup), dialogParmaters, (result)=>
{
if(result.Result == ButtonResult.OK)
{
}
});
you can also register your custom window if you want the window needs to implement IDialogWindow
containerRegistry.RegisterDialogWindow<MyDialogWindow>();
First, you want the view model to be the DataContext (the Content is in fact the view). I'd advise againt the ViewModelLocator here, because it makes it impossible to pass parameters to the new window. Probably, you want to show different content depending on which row you click the button in.
new Window { DataContext = new WindowViewModel( myRow ) }.Show();
Second, there's nothing wrong with this approach but for one minor remark: put the code that shows the window in a service. You want to unittest your view models and that doesn't go well with opening real windows.
public interface IWindowService
{
void ShowWindow( SomeParameter parameter );
}
If you inject that into your view model, you can still test it (verify that ShowWindow is called).
Third, remove calls to new and replace them with calls to injected factories.
var viewModel = _viewModelFactory.Create( "some parameter" );
is a lot nicer than
var viewModel = new ViewModel( "some parameter", new ADependency( new OtherDependency(), new SomeService() );
not to speak of the difficulties if singletons are involved.

A WPF/Prism window calling CommonOpenFileDialog (select directory) loses focus after selection

I have a WPF (Prism) application that needs "select directory" functionality. The main window's menu opens a child "Settings" window through notorious Prism InteractionRequest. That "Settings" window has a button to open a "Select Directory" window.
Since the standard FolderBrowserDialog dialog is so ugly, I tried to use a CommonOpenFileDialog from Windows API Code Pack.
I use a service from Orc.Controls to wrap the dialog:
using Microsoft.WindowsAPICodePack.Dialogs;
public class MicrosoftApiSelectDirectoryService : ISelectDirectoryService
{
public bool DetermineDirectory()
{
var browserDialog = new CommonOpenFileDialog();
browserDialog.IsFolderPicker = true;
browserDialog.Title = Title;
browserDialog.InitialDirectory = InitialDirectory;
if (browserDialog.ShowDialog() == CommonFileDialogResult.Ok)
{
DirectoryName = browserDialog.FileName;
return true;
}
DirectoryName = string.Empty;
return false;
}
// ...
}
From my view model, I call _selectDirectoryService.DetermineDirectory():
public DelegateCommand SelectDirectoryCommand { get; private set; }
private void SelectDirectory()
{
if (_selectDirectoryService.DetermineDirectory())
{
var dir = _selectDirectoryService.DirectoryName;
// ...
}
}
The problem is that after selecting a directory in a CommonOpenFileDialog, the "Settings" window loses focus (and for some reason actually hides behind the maximized main window). By contrast, the FolderBrowserDialog returns focus to the "Settings" window.
So, basically I need either a better "select directory" implementation or I need to find a way to focus the "Settings" window again without severe violation of MVVM pattern. Any ideas would be appreciated.
You have to set the parent/owner of the Window to prevent it from suddenly popping behind it.

MVVM Modal Dialog using Service Locator

I am developing a WPF application that follows MVVM pattern. To display modal dialogs, I am trying to follow the way the following articles suggests.
http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx
But in these kind of articles, What I have observed, that ShowDialog method of DialogService interface is called from MainWindowViewModel.
Situation in my application is slightly different.
MainWindow.xaml contains a user control say ChildView that contains a button Add.
MainWindowViewModel contains another ViewModel say ChildVM that is bind with ChildView.
ChildVM contains AddCommand, and I need to display Modal Dialog when AddExecute method
corresponding to AddCommand is called.
How can I accomplish that?
Edited Code
private Window FindOwnerWindow(object viewModel)
{
FrameworkElement view = null;
// Windows and UserControls are registered as view.
// So all the active windows and userControls are contained in views
foreach (FrameworkElement viewIterator in views)
{
// Check whether the view is an Window
// If the view is an window and dataContext of the window, matches
// with the viewModel, then set view = viewIterator
Window viewWindow = viewIterator as Window;
if (null != viewWindow)
{
if (true == ReferenceEquals(viewWindow.DataContext, viewModel))
{
view = viewWindow;
break;
}
}
else
{
// Check whether the view is an UserControl
// If the view is an UserControl and Content of the userControl, matches
// with the viewModel, then set view = userControl
// In case the view is an user control, then find the Window that contains the
// user control and set it as owner
System.Windows.Controls.UserControl userControl = viewIterator as System.Windows.Controls.UserControl;
if (null != userControl)
{
if (true == ReferenceEquals(userControl.Content, viewModel))
{
view = userControl;
break;
}
}
}
}
if (view == null)
{
throw new ArgumentException("Viewmodel is not referenced by any registered View.");
}
// Get owner window
Window owner = view as Window;
if (owner == null)
{
owner = Window.GetWindow(view);
}
// Make sure owner window was found
if (owner == null)
{
throw new InvalidOperationException("View is not contained within a Window.");
}
return owner;
}
Ok, if I get you right, you want to open the modal dialog not from the MainWindowViewModel, but from a different ChildViewModel?
Have a look at the constructors of the MainWindowViewModel of the CodeProject article that you have linked:
The ViewModel has a constructor with the following signature:
public MainWindowViewModel(
IDialogService dialogService,
IPersonService personService,
Func<IOpenFileDialog> openFileDialogFactory)
This means that for construction you need the service which shows the modal dialogs, another service (personService), which doesn't matter here and a factory for the specific dialog to open files, openFileDialogFactory.
In order to use the service, which is the core part of the article, a simple ServiceLocator is implemented and a default constructor is defined, which uses the ServiceLocator to get instances of the services that the ViewModel needs:
public MainWindowViewModel()
: this(
ServiceLocator.Resolve<IDialogService>(),
ServiceLocator.Resolve<IPersonService>(),
() => ServiceLocator.Resolve<IOpenFileDialog>())
{}
This is possible, because ServiceLocator is static. Alternatively, you could set the local field for the services in the constructor, using the ServiceLocator. The above approach is better, because it allows you to set the services yourself, if you don't want to use the ServiceLocator.
You can do exactly the same in your own ChildViewModel.
public ChildViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
}
Create an default constructor, which calls the above constructor with the service instance resolved from the ServiceLocator:
public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {}
And now you can use the service from anywhere in your ChildViewModel like this:
_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog);
In order to find the owner window of your view, which is not a view itself, you need to modify the FindOwnerWindow method of the DialogService to find the parent window of the view, rather than expecting a Window as the view itself. You can use the VisualTreeHelper to do so:
private Window FindOwnerWindow(object viewModel)
{
var view = views.SingleOrDefault(v => ReferenceEquals(v.DataContext, viewModel));
if (view == null)
{
throw new ArgumentException("Viewmodel is not referenced by any registered View.");
}
DependencyObject owner = view;
// Iterate through parents until a window is found,
// if the view is not a window itself
while (!(owner is Window))
{
owner = VisualTreeHelper.GetParent(owner);
if (owner == null)
throw new Exception("No window found owning the view.");
}
// Make sure owner window was found
if (owner == null)
{
throw new InvalidOperationException("View is not contained within a Window.");
}
return (Window) owner;
}
You still need to register the UserControl though, setting the attached property on the UserControl:
<UserControl x:Class="ChildView"
...
Service:DialogService.IsRegisteredView="True">
...
</UserControl>
As far as I can tell, this works.
Additional information:
To accomplish the same thing, I use the PRISM framework, which comes with a lot of functionality for exactly this kind of decoupling, Inversion of Control (IoC) and dependency injection (DI). Maybe it is worth it to have a look at it for you, too.
Hope this helps!
Edited to consider the comment.
See if you like this idea... I'm using Castle Windsor and Prism, so your mileage may vary, but the concepts should be the same with another MVVM and IoC.
You start with your MainViewModel.cs that wants to open a modal dialog
var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.ShowDialog();
but of course it's not honoring what you set in MainView.xaml
WindowStartupLocation="CenterOwner"
Drat!
But wait, couldn't ServiceLocator just give me the MainView?
var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.Owner = ServiceLocator.Current.GetInstance<MainView>(); // sure, why not?
view.ShowDialog();
This line throws an exception with my IoC configuration, since my IoC registers views to have a "transient lifetime". In Castle Windsor, this means each request is provided a brand new instance, and I need the MainView instance itself, not a new one that hasn't been shown.
But by simply changing the registration from every view being "transient"
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.LifestyleTransient());
to be slightly more discriminating by using the fluent Unless() and If()
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.Unless(type => type == typeof(MainView))
.LifestyleTransient()); // all as before but MainView.
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.If(type => type == typeof(MainView))
.LifestyleSingleton()); // set MainView to singleton!
the MainView provided is the one we wanted!
HTH

Opening a new dialog using WPF with MVVM

I am currently using MVVM (Light) to build an application with WPF. However, in a few cases I must open a new dialog (also WPF) when the user clicks a button. However, this is being a tough fight.
Here is how I am doing it:
private void _ShowItemDialog(Item item)
{
var itemVM = new ItemViewModel();
itemVM.CurrentItem = item ?? new Item();
itemVM.Load();
var itemView = new View.ItemView() { DataContext = itemVM };
if (itemView.ShowDialog() == true)
{
if (item == null)
{
itemList.Add(itemVM.CurrentItem);
}
}
itemVM.Cleanup();
}
And the itemView XAML there is no binding to the DataContext, otherwise two different instances of the ViewModel would be created.
Inside the Window tag. To have the result at ShowDialog, I use the DialogCloser code:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
In the ItemView, this is declared inside Window tag as follows:
my:DialogCloser.DialogResult="{Binding DialogResult}"
And when the dialog is closed, the closing event sets DialogResult to true or false.
This works perfectly for the first time the screen is opened, but it is not possible to open the dialog again after it is closed.
I would like to know if you have any better ideas for opening the dialog, and why this code does not work.
Thanks!
EDIT:
I have already fixed the code. What I need to do is create a new ViewModel and attach it to the DataContext every time the dialog is opened. Moreover, I had to remove the DataContext binding from XAML. Please check the code changes above.
With these changes I have found out that it is not possible to use the ViewModel from ViewModelLocator because it is a "singleton" and not a new instance at each new window. Therefore, the DialogResult held the last value and if I tried to change its value back to null (as it is when the ViewModel is initialized) an exception is thrown. Do you have any clues of why this happens? It would be very good for me to use the ViewModel from ViewModelLocator, since it would keep the same strategy throughout the system.
Thank you!
I do that by implementing static XxxxInteraction classes that have methods called for example NewUser(); That methods opens the Dialogs and do some work. In my ViewModel I call the XxxxInteraction classes via commands.
The efforts of that way of implementing is, that you can easely modify the methods in the static Interaction classes for using UnitTests.
public static class UserInteractions
{
public static User NewUser()
{
var userDialog = new NewUserDialog();
If(userDialog.ShowDialog() != true) return null;
var user = new User();
user.Name = userDialog.Name;
user.Age = userDialog.Age;
return user;
}
}
public class MyViewModel
{
...
public void NewUserCommandExecute()
{
var newUser = UserInteractions.NewUser();
if(newUser == null) return;
//Do some with new created user
}
}
NewUserDialog is a normal Window that is bound to a ViewModel too.
I think this is a good way of implementing dialogs for the mvvm pattern.
i've done this a while ago, i use a dialog service and call this service in my viewmodel. take a look.
EDIT: btw, thats all you have to do in your viewmodel
var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

ShowDialog() behind the parent window

I am using ShowDialog() with WindowStyle = WindowStyle.SingleBorderWindow; to open a modal window in my WPF (MVVM) application, but it lets me navigate to parent window using the Windows taskbar (Windows 7).
I've found an answer here: WPF and ShowDialog() but it isn't suitable for me because I don't need an "always on top" tool window.
Thanks in advance
Try setting the Owner property of the dialog. That should work.
Window dialog = new Window();
dialog.Owner = mainWindow;
dialog.ShowDialog();
Edit:
I had a similar problem using this with MVVM. You can solve this by using delegates.
public class MainWindowViewModel
{
public delegate void ShowDialogDelegate(string message);
public ShowDialogDelegate ShowDialogCallback;
public void Action()
{
// here you want to show the dialog
ShowDialogDelegate callback = ShowDialogCallback;
if(callback != null)
{
callback("Message");
}
}
}
public class MainWindow
{
public MainWindow()
{
// initialize the ViewModel
MainWindowViewModel viewModel = new MainWindowViewModel();
viewModel.ShowDialogCallback += ShowDialog;
DataContext = viewModel;
}
private void ShowDialog(string message)
{
// show the dialog
}
}
I had this problem but as the Window was being opened from a view model I didn't have a reference to the current window. To get round it I used this code:
var myWindow = new MyWindowType();
myWindow.Owner = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
You can use: myWindow.Owner = Application.Current.MainWindow;
However, this method causes problems if you have three windows open like this:
MainWindow
|
-----> ChildWindow1
|
-----> ChildWindow2
Then setting ChildWindow2.Owner = Application.Current.MainWindow will set the owner of the window to be its grandparent window, not parent window.
When the parent window makes (and shows) the child window, that is where you need to set the owner.
public partial class MainWindow : Window
{
private void openChild()
{
ChildWindow child = new ChildWindow ();
child.Owner = this; // "this" is the parent
child.ShowDialog();
}
}
Aditionally, if you don't want an extra taskbar for all the children... then
<Window x:Class="ChildWindow"
ShowInTaskbar="False" >
</Window>
Much of the reason for the MVVM pattern is so that your interaction logic can be unit tested. For this reason, you should never directly open a window from the ViewModel, or you'll have dialogs popping up in the middle of your unit tests.
Instead, you should raise an event that the View will handle and open a dialog for you. For example, see this article on Interaction Requests: https://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx#sec12
The problem seems to be related to Window.Owner, and indeed if you judge by previous knowledge that you might have of the Win32 API and WinForms, a missing owner would be the typical cause of such a problem, but as many have pointed out, in the case of WPF that's not it. Microsoft keeps changing things to keep things interesting.
In WPF you can have a dialog with a specific owner and you can still have the dialog appear in the taskbar. Because why not. And that's the default behavior. Because why not. Their rationale is that modal dialogs are not kosher anymore, so you should not be using them; you should be using modeless dialogs, which make sense to show as separate taskbar icons, and in any case the user can then decide whether they want to see different app windows as separate icons, or whether they want to see them grouped.
So, they are trying to enforce this policy with complete disregard to anyone who might want to go against their guidelines and create a modal dialog. So, they force you to explicitly state that you do not want a taskbar icon to appear for your dialog.
To fix this problem, do the following in the constructor of your view class:
ShowInTaskbar = false;
(This may happen right after InitializeComponent();
This is equivalent to Xcalibur37's answer, though the way I figure things, since WPF forces you to have both a .cs file and a .xaml file, you might as well put things that are unlikely to change in the .cs file.
Add "ShowInTaskbar" and set it to false.
Even if this post is a bit old, I hope it is OK that I post my solution.
All the above results are known to me and did not exactly yield the desired result.
I am doing it for the other googlers :)
Lets say f2 is your window that you want to display on top of f1 :
f2.Owner = Window.GetWindow(this);
f2.ShowDialog();
That's it , I promise it will not disappear !
HTH
Guy

Resources