How to display user control within the main window in WPF using MVVM

13,648

Solution 1

Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.

In main windo xaml

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

    <Window.Resources>
        <DataTemplate DataType="{x:Type home:HomeViewModel}">
            <home:HomeView />
        </DataTemplate>

        <DataTemplate DataType="{x:Type other:OtherViewModel}">
            <other:OtherView />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid x:Name="Navigation">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="HomeView"
                        Content="Home"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="home" />
                <Button x:Name="Menu"
                        Content="OtherView"
                        Margin="5"
                        Command="{Binding NavigationCommand}"
                        CommandParameter="Other" />
            </StackPanel>
        </Grid>
        <Grid x:Name="MainContent"
              Grid.Row="1">
            <ContentControl Content="{Binding CurrentViewModel}" />
        </Grid>

    </Grid>

MainWindowViewModel can look something like this.

public class MainWindowViewModel : INotifyPropertyChanged
    {
        private OtherViewModel otherVM;
        private HomeViewModel homeVM;

        public DelegateCommand<string> NavigationCommand { get; private set; }

        public MainWindowViewModel()
        {
            otherVM = new OtherViewModel();
            homeVM = new HomeViewModel();

            // Setting default: homeViewModela.
            CurrentViewModel = homeVM;

            NavigationCommand = new DelegateCommand<string>(OnNavigate);
        }

        private void OnNavigate(string navPath)
        {
            switch (navPath)
            {
                case "other":
                    CurrentViewModel = otherVM;
                    break;
                case "home":
                    CurrentViewModel = homeVM;
                    break;
            }
        }

        private object _currentViewModel;
        public object CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (_currentViewModel != value)
                {
                    _currentViewModel = value;
                    OnPropertyChanged();
                }
            }
        }


        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
          PropertyChanged(this, new propertyChangedEventArgs(propertyName));
        }
        #endregion
    }

Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.

EDIT: This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.

in your MainViewViewModel make a bool property IsVisible

private bool _isVisible;
        public bool IsVisible
        {
            get { return _isVisible; }
            set
            {
                _isVisible = value;
                OnPropertyChanged();
            }
        }

in your MainView.Resources set key to BooleanToVisibilityConverter something like:

 <BooleanToVisibilityConverter x:Key="Show"/>

and your grid that you want to show/hide set visibility:

<Grid x:Name="SomeGridName"
              Grid.Row="1"
              Grid.Colum="1"
              Visibility="{Binding IsVisible,Converter={StaticResource Show}}">

And finally set that IsVisible property to some ToggleButton, just to switch between true/false

 <ToggleButton IsChecked="{Binding IsVisible}"
                              Content="ShowGrid" />

This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.

Solution 2

Inside your Window xaml:

 <ContentPresenter Content="{Binding Control}"></ContentPresenter>

In this way, your ViewModel must contain a Control property.

Add your ViewModel to DataContext of Window. (For example in window constructor, this.Datacontext = new ViewModel();)

Or another way with interfaces:

 public interface IWindowView
 {
        IUserControlKeeper ViewModel { get; set; }
 }

 public interface IUserControlKeeper 
 {
        UserControl Control { get; set; }
 }


 public partial class YourWindow : IViewWindow
 {
        public YourWindow()
        {
            InitializeComponent();
        }

        public IUserControlKeeper ViewModel
        {
            get
            {
                return (IUserControlKeeper)DataContext;
            }

            set
            {
                DataContext = value;
            }
        }
  }

(In this way, initialize your window where you want to use. Service?)

private IViewWindow _window;
private IViewWindow Window  //or public...
{
  get{
       if(_window==null)
       {
           _window = new YourWindow();
           _window.ViewModel = new YourViewModel();
       }
       return _window;
     }
}

Open your window with one of your UserControls:

  void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
  {
        if(ControlView is UserControl)
        {       //with interface: Window.ViewModel.Control = ...
               (Window.DataContext as YourViewModel).Control = (UserControl)ControlView;

               if (ControlViewModel != null)
                   (Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;

               if (ShowAsDialog) //with interface use cast to call the show...
                   Window.ShowDialog();
               else
                   Window.Show();

         }
  }
Share:
13,648
Junius
Author by

Junius

A self-proclaimed software developer who earns for a living by grinding code everyday.

Updated on June 05, 2022

Comments

  • Junius
    Junius almost 2 years

    I have a WPF application and just embarked in learning MVVM pattern.

    My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.

    This is the code of MainWindow.xaml

    <Window x:Class="SmartPole1080.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
        xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
        xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
        xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
        xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
        xmlns:view1="clr-namespace:SmartPole1080.View"
        xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
        Title="Smart Pole" 
        WindowStartupLocation="CenterScreen"
        Name="mainWindow"
        behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
    <Canvas Background="DarkGray">
        <!--Main Grid-->
        <Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
              HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <StackPanel Background="Black">                
                <Grid Background="#253341">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="5"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="5"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="5"/>
                        <ColumnDefinition Width="264"/>
                    </Grid.ColumnDefinitions>
    
                    <Grid Grid.Row="1" Grid.Column="1">
                        <Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
                                Command="{Binding ShowEmergencyPanel}">
                            <Image Source="{StaticResource EmergencyImg}" />
                        </Button>
                    </Grid>
    
                    <!--Emergency Window Dialog-->
                    <Grid Name="EmergencyPanel">
                        <view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
                    </Grid>
                </Grid>
            </StackPanel>
        </Grid>
        <!--Main Grid-->
    </Canvas>
    

    This is the other window (user control - EmergencyInfo.xaml)

    <UserControl x:Class="SmartPole1080.View.EmergencyInfo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:SmartPole1080.View"
             mc:Ignorable="d" 
             d:DesignHeight="1920" d:DesignWidth="1050"
             x:Name="EmergencyInfoPanel">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
        <Border Grid.Row="0" BorderBrush="White" Background="White">
            <Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red" 
                    HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
                    Command="{Binding HideEmergencyPanel}">
                close X
            </Button>
        </Border>
        <Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
    </Grid>
    

    I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.

    Any help is greatly appreciated.