1

What's the right approach to open a child window (for example, to modify a selected item on the main window) keeping MVVM in mind?

Here's what I have: MainWindow.xaml (and in MainWindow.xaml.cs it assigns MainVM as its own DataContext)

I would also like to have: ChildWindow.xaml and barebones ChildWindow.xaml.cs with ChildVM behind controls.

So, now:

  1. How can I popup ChildWindow and pass some object Data to its ChildVM?
  2. Get the result (true/false) and result data (some complex object) back to MainVM?
  3. As a bonus, can changes in Data be observed by MainVM while they are being worked on by ChildVM?

Here's what I tried - it doesn't solve everything, but is this even the right direction?

  • For (2), I created a subclass of Window, called DialogWindow, which has 3 DependencyProperties: Data (for input data), ResultData (for output data) and ResultValue (for a bool result).
  • ResultData and ResultValue are both set by the ChildVM of DialogWindow using Binding, and when ResultValue is set, the DialogWindow closes.
  • At the moment, the ChildWindow is launched (for all intents and purposes) from MainWindow.xaml.cs - kinda bad. I can then pass some input data, like so:

    ChildDialogWindow w = new ChildDialogWindow();

    w.Data = myDataObj;

So, now I need to have a property Data on ChildVM, and set in ChildDialogWindow.xaml.cs. Again, making .xaml.cs thicker.

I thought that maybe a better approach that avoids MainWindow.xaml.cs would be some kind of DialogService which is passed to MainVM as a dependency. But then, how can I pass values to the ChildVM?

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • 1
    [Maybe have a look at this?](http://stackoverflow.com/a/16994523/1834662) – Viv Jul 20 '13 at 19:37
  • @Viv, this is not bad, except it uses a framework, which I hoped to avoid. I guess maybe I could lift the code for the Messenger (unless it relies on a bunch of other framework stuff, like SimpleIoC. – New Dev Jul 20 '13 at 20:15
  • Why is using a framework to be avoided? .net is a framework. we could write native C code and avoid .net all-together :) If your intention is to "learn" it's functionality, you can always get it's source and see how it's implemented and do it yourself. `SimpleIoC` is just a very simple DI container. If your looking for extensive capabilities you'd be looking at Unity and sorts. I'm just not a fan of re-inventing the wheel just for the sake of "not taking a dependency". – Viv Jul 20 '13 at 20:54
  • Saying that there are cases whr you only need 5% of a library's features, In those cases yeh it'd make more sense to just do it yourself and take guide from the library's implementation, but something like MVVM Light, I just don't see what the wasted bit's of that are cos you almost use all of it's features all over the app. – Viv Jul 20 '13 at 20:55
  • I didn't say "to be avoided"... I said, "I hoped to avoid" :) – New Dev Jul 20 '13 at 21:00

2 Answers2

1

Try this. Make a DialogService.cs

public class DialogService
{
    public void Show(FrameworkElement view, ChildViewModel ChildVM)
    {
         Window window = new Window();
         window.Content = view;
         window.DataContext = ChildVM;

         // For closing this dialog using MVVM
         ChildVM.RequestClose += delegate
         {
            window.Close();
         };

         window.Show();
    }
}

Now in ChildVm class, add this

public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
                _closeCommand = new RelayCommand(param => this.OnRequestClose());

            return _closeCommand;
        }
    }

    public event EventHandler RequestClose;

    void OnRequestClose()
    {
        EventHandler handler = this.RequestClose;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

Now, this the way to launch this

public void OpenChildDailog()
    {
    DialogService service = new DialogService();
    ChildViewModel childVM = new ChildViewModel();
    childVM.Data = ; // Assign whatever you want
    childVM.ResultData = ; 

    service.Show(new ChildView(), childVM);

    // Now get the values when the child dailog get closed

    var retVal = childVM.ResultValue;

}
UsmanAzam
  • 539
  • 4
  • 15
  • thanks. Is the ChildView a UserControl? I have ChildWindow.xaml/.xaml.cs... Are you forgoing this in favor of a UserControl? Also... in your example, service.Show() would return immediately, so childVM.ResultValue would not be set. – New Dev Jul 20 '13 at 23:04
  • Yes in this scenario, the ChildView should be a usercontrol. The service.Show() won't return immediately. It will open the window and remain open until the user close it, then it will return to next line which is `var retVal = childVM.ResultValue;`. Make sure you provide the logic for setting the ResultValue in ChildViewModel, so that it get filled when returned from dialog closing. – UsmanAzam Jul 20 '13 at 23:27
  • 1
    [Window.Show()](http://msdn.microsoft.com/en-us/library/system.windows.window.show.aspx) returns immediately... But it should work, at the very least, with `ShowDialog()`... Thanks – New Dev Jul 20 '13 at 23:48
  • I still have concerns about this approach - it now requires me to know the View and its ViewModel in other ViewModels. Also it places extra requirements on the ViewModel, like having to subclass ChildVM. I'll wait if there are other suggestions - if not, I'll mark this as answer. Thanks again, though. – New Dev Jul 21 '13 at 00:33
  • actually, the more I thought and read about it, it seems that if I wanted to completely decouple ViewModels I'd need somehow to tie the View with its ViewModel via something like a ViewModelLocator, and then do some sort of property injection to pass the parameter... In other words, too complicated for me for now... so your answer looks better every minute – New Dev Jul 21 '13 at 04:15
0

I'm using the ICommand helper "RelayCommand," and pushing an IntPtr datatype to the new ViewModel (or use any other object.) Lots of cookie cutter stuff.

Main View:

<Button Command="{Binding DataContext.ShowObjectInfoCommand}" CommandParameter="{Binding ObjectOffset}" Content="{Binding Name}"/>

MainViewModel:

private RelayCommand _showObjectInfoCommand;
public RelayCommand ShowObjectInfoCommand { get { return _showObjectInfoCommand ?? (_showObjectInfoCommand = new RelayCommand(ExeShowObjectInfoCommand)); } set { } } //Draw Specific Item Table
void ExeShowObjectInfoCommand(object parameter)
{
    ViewObjectInfo objInfo = new ViewObjectInfo();
    IObjectOffsetParameter viewModel = objInfo.DataContext as IObjectOffsetParameter;
    viewModel.ObjectOffset = (IntPtr)parameter;
    objInfo.Show();
}

New ViewModel + interface:

interface IObjectOffsetParameter
{
    IntPtr ObjectOffset { get; set; }
}

class ViewModelObjectInfo : ViewModelBase, IObjectOffsetParameter
{
    public ViewModelObjectInfo()
    {
    }

    private IntPtr _objectOffset; //Entity Offset
    public IntPtr ObjectOffset
    {
        get { return _objectOffset; }
        set { if (_objectOffset != value) { _objectOffset = value; RaisePropertyChanged("Offset"); } }
    }
}

New View code-behind:

InitializeComponent();
ViewModelObjectInfo viewModel = new ViewModelObjectInfo();
this.DataContext = viewModel;

New View xaml:

<TextBlock Text="{Binding ObjectOffset}"/>
Logan Klenner
  • 415
  • 2
  • 7
  • 15