Richard
Richard

Reputation: 509

Adding to XAML WPF from ViewModel (MVVM)

first attempt at MVVM and WPF (steep learning curve). In my ViewModel I want to run the following code to add a "layoutDocument" which is an AvalonDock layout into my Mainform UI.

ViewModel class:

LayoutDocument layoutDocument = new LayoutDocument { Title = "Plan Layout" };

Window mainWindow = Application.Current.Windows.OfType<Window>().Where(x => x.Name == "MainWindow").FirstOrDefault();
if (mainWindow != null)
{
    mainWindow.mainPanel.Children.Add(layoutDocument);
}

The above code gives me the following error:

"'Window' does not contain definition for 'mainPanel' and no extension method for 'mainPanel'".

Note in my XAML below that "LayoutDocumentPane" does contain a name "mainPanel".

I have tried adding the above code directly into my MainForm View Class (excluding the Application.Current.Windows.OfType and If statement bit) and just including the: mainPanel.Children.Add(layoutDocument); And it works fine (a new layout is created in my MainForm when I click the button).

However, as I want to stickto MVVM this is not a suitable solution.

How can I add "layoutDocument" to MainWindow from ViewModel? Thanks in advance.

An extract of my XAML looks like this:

<Window x:Class="LiveExplorer.MainWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:LiveExplorer"
             xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
             xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
             xmlns:s="clr-namespace:System;assembly=mscorlib"
             xmlns:vm="clr-namespace:WpfApp1.ViewModel">

<Grid> etc etc etc here---

                <xcad:LayoutDocumentPaneGroup>
                    <xcad:LayoutDocumentPane x:Name="mainPanel">
                        <xcad:LayoutDocument ContentId="document1" Title="Document 1" >
                            <Button Content="Document 1 Content" HorizontalAlignment="Center" VerticalAlignment="Center" 
                                    Command="{Binding NewPlanCommand, Source={StaticResource viewModel}}" 
                                    />
                        </xcad:LayoutDocument>
                        <xcad:LayoutDocument ContentId="document2" Title="Document 2">
                            <TextBox Text="Document 2 Content" AcceptsReturn="True"/>
                        </xcad:LayoutDocument>
                    </xcad:LayoutDocumentPane>
                </xcad:LayoutDocumentPaneGroup >

EDIT:

Whilst the accepted answer does not answer the question in terms of MMVM, it does correct the coding error.

Upvotes: 0

Views: 2632

Answers (2)

mm8
mm8

Reputation: 169200

This is not related to MVVM but to be able access the mainPanel you need to cast the returned Window to a MainWindow:

MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
{
    mainWindow.mainPanel.Children.Add(layoutDocument);
}

A view model shouldn't access any window directly though. This breaks the MVVM pattern.

Upvotes: 0

Thierry
Thierry

Reputation: 6458

What you've tried to implement does not follow the MVVM pattern. You need to take care of 3 things to get started:

  • ViewModels
  • Initialize the ViewModel binded to the window
  • Binding ViewModel to the UI in XAML

ViewModels:

Create a viewmodel that will be binded to your MainWindow and create an observable collection inside that MainWindowViewModel that contains a list of object that will contain data that can be used in the UI:

public ObservableCollection<LayoutDocumentViewModel> LayoutDocument {get;set;}

Make sure that both the MainWindowViewModel and the LayoutDocumentViewModel inherits from INotifyPropertyChanged(Implement Property Change Notification) or if you use MVVMLight (or similar) from ViewModelBase.

The LayoutDocumentViewModel is just a ViewModel that will be used to store information about your layout document and that can be binded to the UI.

public LayoutDocumentViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    [NotifyPropertyChangedInvocator]

    protected virtual void OnPropertyChanged([CallerMemberName] 
        string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string _name;

    public string Name
    {
      get { return _name; }
      set
      {
          _name = value;
          // Call OnPropertyChanged whenever the property is updated
          OnPropertyChanged("Name");
      }
    }

}

I would strongly recommend that you use MVVMLight (or similar) or put the INotifyPropertyChange code into a base class i.e. ViewModelBase for example.

For simplicity sake in this example, I'm initializing the observable collection and creating a couple of document layouts objects directly in the MainWindowViewModel but you'll need to research this further and find out where it is appropriate for you to initialize and/or create these.

public MainPageViewModel() { DocumentLayouts = new ObservableCollection(); DocumentLayouts.Add(new DocumentLayout {Name="Layout1"}); DocumentLayouts.Add(new DocumentLayout {Name="Layout2"}); }

The above takes care of creating your MainWindowViewModel and layout documents.

Initializing MainViewModel (and binded to the MainWindow.xaml). Note this is a quick and dirty way to get you started and you should really look into IoC containers.

<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>

Finally, bind your ViewModel & UI

XAML:

<Grid>
    <ItemsControl ItemsSource="{Binding LayoutDocuments}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Name}"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

Note: Just replace the Label by your LayoutDocument control and bind it to the relevant element properties you have declared in LayoutDocumentViewModel.

Hope this helps get you started.

Upvotes: 3

Related Questions