ZackDeRose ZackDeRose - 2 months ago 10
C# Question

User Control as components. No access to Parent Window

I am learning WPF and the MVVM pattern, and I may be implementing things incorrectly.

I want to build out components for my app's windows using User Control objects, where components could be used across Windows. For an example I have a NewUnitUserControl that I intend to use as the only component for a dialog window, and also as a component for the MainWindow.

NewUnitUserControl.xaml:

<UserControl x:Class="Sample.Views.UserControls.NewUnitUserControl"
...
xmlns:local="clr-namespace:Sample.Views.UserControls">

<Grid>
<StackPanel Orientation="Vertical">
<Border>
<StackPanel Orientation="Horizontal">
<Label>Name:</Label>
<TextBox Text="{Binding Unit.Name}" Width="136"/>
</StackPanel>
</Border>
<Border HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Border>
<Button Command="{Binding CreateUnitCommand}">Create</Button>
</Border>
<Border>
<Button Command="{Binding CancelCommand}">Cancel</Button>
</Border>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</UserControl>


NewUnitUserControl.xaml.cs:

namespace Sample.Views.UserControls
{
public partial class NewUnitUserControl : UserControl
{
public NewUnitUserControl()
{
InitializeComponent();
NewUnitViewModel nuvm = new NewUnitViewModel();
DataContext = nuvm;
if (nuvm.CloseAction == null)
{
var window = Window.GetWindow(this); // window evaluates to null
// after this line.
nuvm.CloseAction = new Action(window.Close);
}
}
}
}


NewUnitViewModel.cs

namespace Sample.ViewModels
{
internal class NewUnitViewModel : INotifyPropertyChanged
{
//Properties
private Unit _unit;
public Unit Unit
{
get { return _unit; }
set { _unit = value; OnPropertyChanged("Unit"); }
}

public Action CloseAction { get; set; }

private ICommand _createUnitCommand;
public ICommand CreateUnitCommand
{
get
{
if (_unitUpdateCommand == null)
_unitUpdateCommand = new RelayCommand(param => CreateUnit(), param => true);
return _unitUpdateCommand;
}
}

private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if(_cancelCommand == null)
_cancelCommand = new RelayCommand(param => Cancel(), param => true);
return _cancelCommand;
}
}

//Constructor
public CreateUnitViewModel()
{
_unit = new Models.Unit();
}

//Methods
public void CreateUnit()
{
Debug.Assert(false, String.Format("{0} was created.", Unit.Name));
}

public void Cancel()
{
this.CloseAction();
}

#region INotifyProperty Members

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

#endregion
}
}


NewUnitDialogWindow.xaml:

<Window x:Class="Sample.Views.NewUnitWindow"
...
xmlns:local="clr-namespace:Sample.Views"
xmlns:controls="clr-namespace:Sample.Views.UserControls"
>
<controls:NewUnitUserControl />
</Window>


NewUnitDialogWindow.xaml.cs

namespace Sample.Views
{
public partial class NewUnitWindow : Window
{
public NewUnitWindow()
{
InitializeComponent();
}
}
}


Full Source: GitHub

The problem I'm immediately facing is that with my implementation, I'm unable to access the User Control's parent window using the solution here (see the comment in NewUnitUserControl.xaml.cs). I expect the root of my issues is that I'm not understanding the MVVM pattern correctly.

Answer

I think the problem is that the user control doesn't know who the parent window is yet. Try moving to the Loaded event:

private Window _parentWindow = null;
public NewUnitUserControl()
{
    InitializeComponent();
    Loaded += (s, e) => 
    {
        _parentWindow = Window.GetWindow(this);
        /// whatever you are going to do with parent window
    }
}

You could also send a message from the button's command action using something like IEventAggregator from the Prism toolkit or IMessenger (I think that is what it is called) from MvvmToolkit.